最近做了个关于导出PDF的功能,便写篇博客记录下。
1.准备工作。
我用的开发工具为是eclipse和TIBCO Jaspersoft studio 6.17.0
eclipse软件应该就不用说了,主要是TIBCO Jaspersoft studio 6.17.0,这个软件是用来画报表模板的
软件下载地址:点我跳转
安装过程略过,默认安装即可。
2.开始画模板。
file->new->Jasper report->Blank A4(即空白模板)->设置report名称->选择数据源(我这里是到时候通过java代码传,这里模板就直接给空即选择one Empty Record这个)->finish
模板设计界面的区域说明如下。中间的模板设计区域分别多个band,这些band都可以右键删除
Title:标题区,如果数据有多页只会在第一页打印
Page Header:页头区,多页的情况下每页都会打印
Column Header:列头区,配合下面的Detail区打印列表数据,多页的情况下每页都会打印
Detail:可以新增多个Detail区,可以组合打印出多个列表数据,多页的情况下每页都会打印
Column Footer:列表底部区,多页的情况下每页都会打印
Page Footer:页底部区,多页的情况下每页都会打印
Summary:合计区,只会在最后一页打印
这个是公司需求样式画出来的模板。具体分析下其中内容;删除了一些用不上的板块,只留下了Title,4个detail和page footer板块。其中detail1为单个数据使用text field,detail2和detail4为循环数据并嵌套了一个子报表即分为表头static text和子报表subreport,detail 3和title为纯static text。另外每个小块都用Frame套了一层,使用 frame 的好处是,可以画一个边框把某一类的元素圈起来,这里推荐使用矩形框,这样这些元素就可以作为一个整体统一操作咯。(如果用过android studio的小伙伴应该对这个也很熟悉,无非就是定义参数,绑定元素,拖拉至设计区域,设计成预期样式)
$P、$F、$V的区别: P为传参,不能为空,F为携带的数据,可以为空、V为求和;P在Parameters中定义,F在Fields中定义。
注释:定义P和F还有V的时候需要指定该字段的类型,若指定的类型和传入的值的类型不同会报错。
元素具体层级为:
注释:模块的英文部分最好选择DejaVu Sans,这个字体在springboot可以通过直接添加依赖进行使用,选择其他的字体,若服务器上没有该字体需要进行手动导入,否则会报错,虽然可以在配置文件中设置忽略该问题,但是会导致展示出来的与预期不同。
当数据没值得时候会显示null若希望将其设置为空白,在上图中Blank when null勾选即可。
主模板大概就是这样了。再准备设计子模板并绑定了。两个子模板样式为:
注释:这里把边距全部设置为零了,不然嵌入主模板会隔得很开,不美观。
$V需要在Variable中定义。
主子报表关联:
步骤一选择Subreport并托到对应的位置便会弹出Subreport窗口,按顺序选择指定的子模板即可。
步骤二,点击subreport元素,编辑详情
注释:Expression是查询子模板位置,我这边设置为动态,用P将子模板的路径传过来。data source Expression选择new net.sf.jasperreports.engine.data.JRBeanCollectionDataSource($F{sub1}) sub1是一个list集合,用于保存子模板的数据。
3.展示模板效果
大致上模板就这样就完成了,然后运行下看看样式(写入$P的传参):
模板到这里就没什么问题了,需要将jrxml文件转换为jasper文件。然后开始java端调用以及传参。
上面的是将所有的jrxml生成jasper文件,下面的是只将当前的jrxml文件生成。
4.java开发
1.添加依赖
因为我TIBCO Jaspersoft studio的版本是 6.17.0,所以我添加的依赖版本也都是6.17.0,因为学习过程中看有小伙伴说版本不一致可能会导致出问题,我这边虽然没出现,但是保险起见还是选择相同的版本,毕竟不要给自己找不自在不是。
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>6.17.0</version>
</dependency>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports-functions</artifactId>
<version>6.17.0</version>
</dependency>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports-fonts</artifactId>
<version>6.17.0</version>
</dependency>
注释:jasperreports依赖是基本的方法jar,jasperreports-functions是函数jar,比如我模板中用到的dateformate函数就需要导入该jar,不然是无法识别并报错。jasperreports-fonts这个是自带的字体也就是上面模板中DejaVu Sans字体,我写需求的时候一开始用的是Arial字体,然后本地没问题,但是放到测试环境上就不行,日志显示的就是没有Arial字体,虽然说可以在测试环境加,但是如果后续又换了一个环境,岂不是又会报错,所以不建议每个PC都手动添加字体,如果非要使用Arial字体,则需要在资源文件中做相对于的操作,后面会写到,这里就先不说了。
2.将模板放入指定位置
resource下创建jasper文件夹,再将jrxml和jasper文件放入该路径下,fonts文件夹和fonts.xml是用于添加字体,msyh.ttf为微软雅黑字体的ttf文件。jasperreport.properties文件用于设置忽略不存在字体报错的问题,jasperreport_extension.properties用于配置新添加的字体路径。
fonts.xml
<?xml version="1.0" encoding="UTF-8"?>
<fontFamilies>
<fontFamily name="是微软雅黑吖">
<normal>jasper/fonts/msyh.ttf</normal>
<bold>jasper/fonts/msyh.ttf</bold>
<italic>jasper/fonts/msyh.ttf</italic>
<boldItalic>jasper/fonts/msyh.ttf</boldItalic>
<pdfEncoding>Identity-H</pdfEncoding>
<pdfEmbedded>true</pdfEmbedded>
<exportFonts>
<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>
jasperreport.properties
net.sf.jasperreports.awt.ignore.missing.font=true
jasperreport_extension.properties
net.sf.jasperreports.extension.registry.factory.simple.font.families=net.sf.jasperreports.engine.fonts.SimpleFontExtensionsRegistryFactory
net.sf.jasperreports.extension.simple.font.families.dejavu=jasper/fonts/fonts.xml
3.编写公共方法:
public class JasperReportUtil {
static JasperPrint getJasperPrint(InputStream jasperStream, Map parameters, List<?> list) throws JRException {
JRDataSource dataSource = null;
if (null == list || list.size() == 0) {
dataSource = new JREmptyDataSource();
} else {
dataSource = new JRBeanCollectionDataSource(list);
}
JasperPrint jasperPrint = JasperFillManager.fillReport(jasperStream, parameters, dataSource);
return jasperPrint;
}
public static void exportToPdf(String jasperPath, Map parameters, List<?> list, HttpServletResponse response) throws Exception {
OutputStream outputStream = response.getOutputStream();
try {
ClassPathResource resource = new ClassPathResource(jasperPath);
response.setContentType("application/pdf");
InputStream jasperStream = resource.getInputStream();
JasperPrint jasperPrint = getJasperPrint(jasperStream, parameters, list);
JasperExportManager.exportReportToPdfStream(jasperPrint, outputStream);
} catch (Exception e) {
e.printStackTrace();
outputStream.write("读取报表异常".getBytes());
} finally {
outputStream.flush();
outputStream.close();
}
}
}```
exportToPdf这个方法的参数:1.模板路径;2.数据list;3顾名思义response,用于获取输出流写出。
4.调用方法
control层
@RequestMapping(value = “/printPdf”,method = RequestMethod.POST)
@NoAuthorized
public void printPdf(Map<String, Object> parameters,
HttpServletResponse response,Long headId) throws IOException {
Service.printPdf(parameters,response,headId);
}```
headeId为具体展示那条数据
service层
//resultList是查询数据库后返回的数据,因为涉及到公司数据库,所以就不展示具体Sql了。
List<Map> resultList = new ArrayList<map>();
List<HashMap> list = new ArrayList<>();
List<Object> list2 = new ArrayList<>();
List<HashMap> list3= new ArrayList<>();
HashMap<String, Object> item2 = new HashMap<String, Object>();
parameters.put("ReplenishmentSub1", new ClassPathResource("jasper/Replenishment_sub1.jasper").getPath());
parameters.put("ReplenishmentSub2", new ClassPathResource("jasper/Replenishment_sub2.jasper").getPath());
for (Map map : resultList) {
item2.put("CHANNEL_FROM",map.get("from")+"");
item2.put("CHANNEL_TO",map.get("to")+"");
item2.put("ADDRESS1_EN",map.get("address")+"");
item2.put("ADDRESS2_EN","");
item2.put("ADDRESS3_EN","");
item2.put("NO_OF_PACKAGE", "");
List<Map> lines = 具体查询数据同上不展示;
for(int i=0;i<lines.size();i++){
HashMap<String, Object> item = new HashMap<String, Object>();
item.put("ITEM_CODE", lines.get(i).get("ITEM")+"");
item.put("ITEM_DESC", lines.get(i).get("ITEMDESC")+"");
item.put("ITEM_QTY", new BigDecimal(lines.get(i).get("QTY")+""));
list.add(item);
list3.add(item);
}
}
item2.put("sub1", list);
item2.put("sub2", list3);
list2.add(item2);
try {
JasperReportUtil.exportToPdf("jasper/test_main.jasper",parameters,list2,response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
整体逻辑就是需要将$P通过parameters属性传入模板,查询出的数据需要装入一个List传入模板,每个$F都是Hashmap中的一个值,而子模板的所有数据也要装入一个list中,所以就有三个List,两个为子模板的数据,一个为父模板的数据,而子模板的list与父模板的数据平级即有层级关系为list2>item2>(list=list3=父模板的$F)>item;注释:item为子模板的数据map,item2为父模板的数据map,list和list3为子模板的list集合,list2为父模板的list集合。
5.测试展示:
这样就可以了。
5.总结
这篇文章是在我开发完后总结写的,所以可能有一些地方遗漏了,如果有啥问题可以留言,另外有地方错误也希望各位小伙伴指出。