项目中需要生成PDF和Word文件的报告,文件中包含图片和表格。基于Java的解决方案有Freemarker模板引擎,是通过XML文件将填入的内容放上\${}占位符。这种方式对于简单的文本是没问题,但如果占位符中有字符跟\${}冲突,就比较难处理了。

iReport是一款可视化报表设计工具,看软件界面跟Qt的风格有几分相似,内置丰富的图表,能够创建复杂的报表。相比XML模板的方式,更加灵活和稳定。

  • 主界面

本文以一个实际的导出PDF报告案例来讲解。

一、ireport可视化布局

1,插入图片

从组件面板拖动Image到中间的页面Designer区域。

在右下角的Image属性列表中,设置Image Expression为$F{netImage}。

不过,首先得在Fields定义好变量:netImage,netImage可以在Java工程中传值传过来,也可以是静态图片或url。

Image Expression支持的三种方式:

  1. $F{netImage};
  2. “D://net.png”
  3. https://dn-abc.qbox.me/logo.png

生成的pdf效果:

2,插入表格

为了便于扩展,表格放到Subreport中;在组件面板中新建一个Subreport,进入subreport设计表格;

这里有几个要点:

a, 中文字符的显示:

需要在属性中设置

1
2
3
Pdf Font: STSong-Light
Pdf Embedded: 勾选
Pdf Encoding: UniGB-UCS2-H(Chinese Simplified)

b, 数字的格式化

由于Java传过来的值在ireport中默认都是String,在Expression Class中设置为java.lang.Double也没有用。
需要在Text Field Expression中将字符串转成Double,然后在格式化,Pattern中设置“###0.0000”,表示保留4为小数。

c, 数字百分比的显示

1
2
Text Field Expression: ($F{val}.equals("") ? "--" : new Double($F{val}))
Pattern:#,##0.00%

如果val为空,则用显示–。按百分比格式化,保留2位小数。
效果如下:

3,生成编译文件

ireport源文件是jrxml格式,点击下图中的锤子,编译后生成jasper文件。

二、Java项目配置

将生成的jasper文件导入到java工程

1,生成图片

项目中的方案是从echarts中截图,将截图保存到本地文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 调用js
* 产生图片
*/
function genPic(callback){
var data = "pic="+encodeURIComponent(mainChart.getDataURL(
{
type:"png",
pixelRatio:1,
excludeComponents:['toolbox', 'dataZoom']
}));
var xmlhttp;
if (window.XMLHttpRequest) { // code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp = new XMLHttpRequest();
} else { // code for IE6, IE5
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.open("POST",ctx+"/productReport/genNetPic?fundId="+$('#netfundId').val(),true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
// 回调生成结果
callback(true);
} else if (xmlhttp.status!=200){
callback(false);
}
}
xmlhttp.send(data);
}

Controller中的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RequestMapping(value = "genNetPic", method = RequestMethod.POST)
@ResponseBody
public BaseResponse genNetPic(HttpServletRequest request){
logger.info("开始生成图片");
String pic = request.getParameter("pic");
String fundId = request.getParameter("fundId");
BaseResponse baseResponse = null;
try{
String[] url = pic.split(",");
String u = url[1];
byte[] b = new BASE64Decoder().decodeBuffer(u);
// 创建图片生成的路径:日期+产品id
Date curDate = new Date();
StringBuilder filePath = new StringBuilder("");
filePath.append(upload_path).append(DateUtil.formatDate(curDate, "yyyy-MM-dd")).append("/").append(fundId);
FileUtil.createDictionary(filePath.toString());
// 创建图片文件
OutputStream out = new FileOutputStream(new File(filePath.toString()+"/net.png"));
out.write(b);
out.flush();
out.close();
baseResponse = new BaseResponse(true);
logger.info("完成生成图片");
} catch(Exception e){
logger.error("生成图片失败,失败原因:{}",e.getMessage());
baseResponse = new BaseResponse(false, "图片生成失败");
}
return baseResponse;
}

2,构建报告参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 构建报表的参数
* @return
*/
private FundReportDto getReportParams(String fundId, String fundName, Date curDate){
String dateStr = DateUtil.formatDate(curDate, "yyyy-MM-dd");
//
FundReportDto fundReportDto = new FundReportDto();
// 得到图片路径
StringBuilder netImage = new StringBuilder(jasper_chart_path);
netImage.append(dateStr).append("/").append(fundId).append("/").append("net.png");
fundReportDto.setNetImage(netImage.toString());
...
return fundReportDto;
}

返回参数用对象FundReportDto封装,FundReportDto是可序列化的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class FundReportDto implements Serializable{
/**
*
*/
private static final long serialVersionUID = -3023588547102105811L;
/**
* 统计日期
*/
private String statisticDate;
/*
* 图片
*/
private String netImage;
...
}

3,生成报告

报告支持pdf和word两种格式,生成步骤是先将报告保存在本地文件夹,然后调用浏览器下载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
* 一建生成报告
*/
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.export.JRRtfExporter;
import net.sf.jasperreports.engine.util.JRLoader;
@RequestMapping(value = "exportReport", method = RequestMethod.GET)
public void exportReport(String fundId, String fundName, String fileFormat, HttpServletResponse response){
Date curDate = new Date();
String dateStr = DateUtil.formatDate(curDate, "yyyy-MM-dd");
// 文件路径
StringBuilder filePath = new StringBuilder("");
filePath.append(upload_path).append(dateStr).append("/").append(fundId);
FileUtil.createDictionary(filePath.toString());
// 文件名
StringBuilder fileName = new StringBuilder("");
fileName.append(fundName).append("评价报告_").append(dateStr).append(".").append(fileFormat);
// 文件全路径
StringBuilder fullName = new StringBuilder("");
fullName.append(filePath).append("/").append(fileName);
// 加载报表对象
InputStream is = null;
JasperReport jasperReport = null;
JasperPrint jasperPrint = null;
is = ProductDetailController.class.getClassLoader().getResourceAsStream(analysis_jasper_path);
try{
// 响应头部信息设置
response.setContentType("text/plain");
response.setHeader("Content-Disposition", "attachment; filename="+FileUtil.getAttachName(fileName.toString()));
//
jasperReport = (JasperReport) JRLoader.loadObject(is);
Map<String,Object> params = new HashMap<String,Object>();
params.put("SUBREPORT_DIR", getClassPath(subreport_dir));
params.put("fundName", fundName);
params.put("reportTitle", fundName+"报告");
//
List<FundReportDto> fundReportDtos = new ArrayList<FundReportDto>();
FundReportDto fundReportDto = getReportParams(fundId, fundName,curDate);
fundReportDtos.add(fundReportDto);
params.put("statisticDate", fundReportDto.getStatisticDate());
//
jasperPrint = JasperFillManager.fillReport(jasperReport, params, new JRBeanCollectionDataSource(fundReportDtos));
if (fileFormat.equals("pdf")) {
JasperExportManager.exportReportToPdfFile(jasperPrint, fullName.toString());
}else{
JRRtfExporter docReport = new JRRtfExporter();
docReport.setParameter(JRExporterParameter.OUTPUT_FILE_NAME,fullName.toString());
docReport.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
docReport.exportReport();
}
FileUtil.download(fullName.toString(), response.getOutputStream());
} catch(Exception e){
e.printStackTrace();
logger.error("生成报告失败,失败原因:{}",e.getMessage());
} finally{
try{
if (is!=null) is.close();
}catch(Exception e){
logger.error("关闭文件输入流异常:{}",e.getMessage());
}
}
}

其中analysis_jasper_path为jasper文件的路径。
这样,一份完整的pdf或word报告就制作出来了。