1558 字
8 分钟
在 Spring Boot 中集成 JasperReports

JasperReports 官网:https://community.jaspersoft.com/.
导入依赖 pom.xml
:
<!-- JasperReports 核心库 --><dependency> <groupId>net.sf.jasperreports</groupId> <artifactId>jasperreports</artifactId> <version>7.0.1</version> <exclusions> <exclusion> <groupId>com.lowagie</groupId> <artifactId>itext</artifactId> </exclusion> </exclusions></dependency><!-- JasperReports PDF 扩展库 --><dependency> <groupId>net.sf.jasperreports</groupId> <artifactId>jasperreports-pdf</artifactId> <version>7.0.1</version> <!-- 与 JasperReports 核心版本保持一致 --></dependency><!-- JasperReports 字体扩展库,用于解决中文不能识别的问题 --><dependency> <groupId>net.sf.jasperreports</groupId> <artifactId>jasperreports-fonts</artifactId> <version>7.0.1</version></dependency>
基础设施
接口:
import net.sf.jasperreports.engine.JRDataSource;import net.sf.jasperreports.engine.JRException;import org.springframework.http.ResponseEntity;import java.sql.SQLException;import java.util.Map;
public interface JasperService { /** * 输出 PDF 二进制流响应 * @param fileName 输出文件名 * @param parameters 渲染参数 * @param dataSource 自定义渲染数据源,传递 null 表示使用数据库的数据源 * @return 二进制流 * @throws JRException Jasper 操作异常 * @throws SQLException 数据库异常 */ ResponseEntity<byte[]> PDF(String fileName, Map<String, Object> parameters, JRDataSource dataSource) throws JRException, SQLException;}
实现:
import cn.hutool.core.lang.UUID;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import net.sf.jasperreports.engine.*;import net.sf.jasperreports.engine.util.JRLoader;import org.springframework.http.*;import org.springframework.stereotype.Service;import org.springframework.util.ObjectUtils;import javax.sql.DataSource;import java.io.InputStream;import java.nio.charset.StandardCharsets;import java.sql.SQLException;import java.util.Map;import java.util.concurrent.TimeUnit;
@Slf4j@Service@RequiredArgsConstructorpublic class JasperServiceImpl implements JasperService { // 注入项目默认数据源,需在配置文件 application.properties 中进行配置 private final DataSource systemDataSource;
@Override public ResponseEntity<byte[]> PDF(String fileName, Map<String, Object> parameters, JRDataSource dataSource) throws JRException, SQLException { JasperPrint jasperPrint = FillData(fileName, parameters, dataSource); // 生成 PDF 二进制字节 byte[] bytes = JasperExportManager.exportReportToPdf(jasperPrint); // 创建一个新的 HTTP 头集合对象,用于设置响应头信息 HttpHeaders header = new HttpHeaders(); // 指定响应内容类型为 PDF(application/pdf) header.setContentType(MediaType.APPLICATION_PDF); // 设置缓存控制头 // maxAge():即 max-age=600,表示资源可以被缓存 10 分钟 // mustRevalidate():即 must-revalidate,表示资源过期后必须重新验证 header.setCacheControl(CacheControl .maxAge(10, TimeUnit.MINUTES) .mustRevalidate()); // 设置内容处置 header.setContentDisposition( // inline:指示浏览器应尝试在页面内显示PDF(而不是下载) ContentDisposition.builder("inline") // 设置导出文件名 .filename("report_" + UUID.fastUUID() + ".pdf") .build() ); // 构建响应实体,创建 HTTP 200 OK 响应 return ResponseEntity.ok() // 添加上面设置的所有头信息 .headers(header) // 设置响应体为 PDF 文件的字节数组 .body(bytes); }
private JasperPrint FillData(String fileName, Map<String, Object> parameters, JRDataSource dataSource) throws JRException, SQLException { // 获取 Jasper 模板文件 InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName); if (ObjectUtils.isEmpty(inputStream)) log.error("Error loading file {}", fileName);
// 合并请求参数和系统参数 JasperReport jasperReport = (JasperReport) JRLoader.loadObject(inputStream);
JasperPrint jasperPrint; if (!ObjectUtils.isEmpty(dataSource)) // 使用自定义的数据源进行数据渲染 jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, dataSource); else // 使用项目配置数据源,.jrxml 中可以根据 SQL 从数据库中拉取数据并渲染 jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, systemDataSource.getConnection()); return jasperPrint; }}
配置文件
在 resources
目录下创建字体目录 fonts
。
fonts/fonts.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 注册字体,根元素 <fontFamilies> 包含所有字体定义 --><fontFamilies> <!-- <fontFamily> 定义了一个名为"华文宋体"的字体族,.jrxml 中引用的字体名称 --> <fontFamily name="华文宋体"> <!-- 字体文件路径,需放在 /resources 目录下 --> <!-- 指定了四种字体样式的文件路径(都是同一个 TTF 文件) --> <!-- 虽然华文宋体可能没有真正的粗体或斜体变体,但这里都指向同一文件 --> <normal>fonts/STSONG.TTF</normal> <bold>fonts/STSONG.TTF</bold> <italic>fonts/STSONG.TTF</italic> <boldItalic>fonts/STSONG.TTF</boldItalic> <!-- pdfEncoding:指定PDF编码为 Identity-H (Unicode 水平书写) --> <pdfEncoding>Identity-H</pdfEncoding> <!-- pdfEmbedded:设置为 true 表示将字体嵌入 PDF 文件中 --> <pdfEmbedded>true</pdfEmbedded> <!-- 定义 HTML 和 XHTML 导出时的字体回退链 --> <exportFonts> <!-- 如果"华文宋体"不可用,会依次尝试 Arial、Helvetica 和 sans-serif 字体 --> <export key="net.sf.jasperreports.html">'华文宋体', Arial, Helvetica, sans-serif</export> <export key="net.sf.jasperreports.xhtml">'华文宋体', Arial, Helvetica, sans-serif</export> </exportFonts> </fontFamily></fontFamilies>
注册字体
创建配置文件,必须定义为 jasperreports.properties
:
# 强制 JasperReports 忽略缺失的字体(避免 JRFontNotFoundException)# JasperReports 遇到未安装的字体时不会报错,而是使用默认字体替换net.sf.jasperreports.awt.ignore.missing.font=true
创建配置文件,必须定义为 jasperreports_extension.properties
:
# 注册字体扩展工厂net.sf.jasperreports.extension.registry.factory.simple.font.families=net.sf.jasperreports.engine.fonts.SimpleFontExtensionsRegistryFactory# 指定字体定义文件路径(需在 classpath 下),fonts/fonts.xml:字体注册文件net.sf.jasperreports.extension.simple.font.families.lobstertwo=fonts/fonts.xml
注意:以上两个配置文件不能合成一个配置文件!!
创建 PDF 模板
xxx.jrxml
:
<?xml version="1.0" encoding="UTF-8"?><jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="lq_item" language="java" pageWidth="842" pageHeight="595" orientation="Landscape" columnWidth="802" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="d231d38f-0c4c-456f-8a9a-6418352f4484" whenNoDataType="AllSectionsNoDetail" isIgnorePagination="false">
<!-- 若是需要显示中文,则必须配置字体文件!! --> <!-- fontName:设置字体样式,若系统中找不到对应字体会报错!! --> <!-- 这里使用自行导入的字体,字体引用名称为:华文宋体 --> <style name="Title" fontName="华文宋体" fontSize="30" isBold="true" hTextAlign="Center" vTextAlign="Middle"/> <style name="SubTitle" fontName="华文宋体" fontSize="16" hTextAlign="Center" vTextAlign="Middle"/> <style name="ColumnHeader" fontName="华文宋体" fontSize="14" isBold="true" hTextAlign="Center" vTextAlign="Middle"/> <style name="Detail" fontName="华文宋体" fontSize="14" hTextAlign="Center" vTextAlign="Middle"/>
<!-- 设置普通参数 --> <parameter name="title" class="java.lang.String"/> <parameter name="time" class="java.lang.String"/> <!-- 设置列表参数,需要循环渲染的参数 --> <field name="id" class="java.lang.Integer"/> <field name="account" class="java.lang.String"/> <field name="name" class="java.lang.String"/> <field name="memo" class="java.lang.String"/> <field name="ghInfo" class="java.lang.String"/>
<background> <band splitType="Stretch"/> </background>
<title> <band height="100"> <textField> <reportElement x="0" y="10" width="802" height="40" style="Title"/> <!-- $P{xxx}:对应上面设置的 <parameter> 普通参数 --> <textFieldExpression><![CDATA[$P{title}]]></textFieldExpression> </textField> <textField> <reportElement x="0" y="60" width="802" height="30" style="SubTitle"/> <textFieldExpression><![CDATA["时间:" + $P{time}]]></textFieldExpression> </textField> </band> </title>
<pageHeader> <band height="0"/> </pageHeader>
<columnHeader> <band height="40"> <staticText> <reportElement x="17" y="0" width="121" height="40" style="ColumnHeader"/> <box> <topPen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/> <leftPen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/> <bottomPen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/> <rightPen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/> </box> <text><![CDATA[序号]]></text> </staticText> <staticText> <reportElement x="138" y="0" width="121" height="40" style="ColumnHeader"/> <box> <pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/> </box> <text><![CDATA[账号/联系电话]]></text> </staticText> <staticText> <reportElement x="259" y="0" width="121" height="40" style="ColumnHeader"/> <box> <pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/> </box> <text><![CDATA[姓名]]></text> </staticText> <staticText> <reportElement x="380" y="0" width="151" height="40" style="ColumnHeader"/> <box> <pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/> </box> <text><![CDATA[人员备注]]></text> </staticText> <staticText> <reportElement x="531" y="0" width="240" height="40" style="ColumnHeader"/> <box> <pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/> </box> <text><![CDATA[衣物信息]]></text> </staticText> </band> </columnHeader>
<detail> <band height="30"> <textField> <reportElement x="17" y="0" width="121" height="30" style="Detail"/> <box> <pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/> </box> <!-- $F{xxx}:对应上面设置的 <field> 列表参数 --> <textFieldExpression><![CDATA[$F{id}]]></textFieldExpression> </textField> <textField> <reportElement x="138" y="0" width="121" height="30" style="Detail"/> <box> <pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/> </box> <textFieldExpression><![CDATA[$F{account}]]></textFieldExpression> </textField> <textField> <reportElement x="259" y="0" width="121" height="30" style="Detail"/> <box> <pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/> </box> <textFieldExpression><![CDATA[$F{name}]]></textFieldExpression> </textField> <textField> <reportElement x="380" y="0" width="151" height="30" style="Detail"/> <box> <pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/> </box> <textFieldExpression><![CDATA[$F{memo}]]></textFieldExpression> </textField> <textField> <reportElement x="531" y="0" width="240" height="30" style="Detail"/> <box> <pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/> </box> <textFieldExpression><![CDATA[$F{ghInfo}]]></textFieldExpression> </textField> </band> </detail>
<columnFooter> <band height="0"/> </columnFooter>
<pageFooter> <band height="0"> </band> </pageFooter>
<summary> <band height="0"/> </summary></jasperReport>
将 .jrxml
文件复制到 Jaspersoft Studio 中并编译成 .jasper
文件,然后将 .jasper
文件拖入到 resources
目录中。
Resources 目录层级结构
渲染数据并导出
import lombok.RequiredArgsConstructor;import net.sf.jasperreports.engine.JRException;import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.sql.SQLException;import java.util.Arrays;import java.util.HashMap;import java.util.List;import java.util.Map;
@RestController@RequiredArgsConstructor@RequestMapping("/jasper")public class JasperController { private final JasperService jasperService;
@GetMapping("/pdf") public ResponseEntity<byte[]> PDF() throws JRException, SQLException { // 模板文件相对位置,需要放在 resources 目录下!! String template = "jasper/xxx.jasper"; // 填充普通参数 $P{xxx} Map<String, Object> parameters = new HashMap<>(); parameters.put("title", "Test Table"); parameters.put("time", "2999-10-12"); // 填充元素/列表参数 $F{xxx} List<Map<String, Object>> data = Arrays.asList( new HashMap<>() {{ put("id", 1); put("account", "001"); put("name", "name1"); put("memo", "memo1"); put("ghInfo", "info1"); }}, new HashMap<>() {{ put("id", 2); put("account", "002"); put("name", "name2"); put("memo", "memo2"); put("ghInfo", "info2"); }} ); // 将自定义数据源转换成 Jasper 支持的类型 JRBeanCollectionDataSource dataSource = new JRBeanCollectionDataSource(data); return jasperService.PDF(template, parameters, dataSource); }}