一、需求说明:
根据业务需要,需要在服务器端生成可动态配置的PDF文档,方便数据可视化查看。
二、解决方案:
iText+FreeMarker+JFreeChart生成可动态配置的PDF文档
iText有很强大的PDF处理能力,但是样式和排版不好控制,直接写PDF文档,数据的动态渲染很麻烦。
FreeMarker能配置动态的html模板,正好解决了样式、动态渲染和排版问题。
JFreeChart有这方便的画图API,能画出简单的折线、柱状和饼图,基本能满足需要。
三、实现功能:
1、能动态配置PDF文档内容
2、能动态配置中文字体显示
3、设置自定义的页眉页脚信息
4、能动态生成业务图片
5、完成PDF的分页和图片的嵌入
四、主要代码结构说明:
1、component包:PDF生成的组件 对外提供的是PDFKit工具类和HeaderFooterBuilder接口,其中PDFKit负责PDF的生成,HeaderFooterBuilder负责自定义页眉页脚信息。
2、builder包:负责PDF模板之外的额外信息填写,这里主要是页眉页脚的定制。
3、chart包:JFreeChart的画图工具包,目前只有一个线形图。
4、test包:测试工具类
5、util包:FreeMarker等工具类。
五、关键代码说明:
1、模板配置
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
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> < html xmlns = "http://www.w3.org/1999/xhtml" > < head > < meta http-equiv = "Content-Type" content = "text/html; charset=UTF-8" /> < meta http-equiv = "Content-Style-Type" content = "text/css" /> < title ></ title > < style type = "text/css" > body { font-family: pingfang sc light; } .center{ text-align: center; width: 100%; } </ style > </ head > < body > <!--第一页开始--> < div class = "page" > < div class = "center" >< p >${templateName}</ p ></ div > < div >< p >iText官网:${ITEXTUrl}</ p ></ div > < div >< p >FreeMarker官网:${freeMarkerUrl}</ p ></ div > < div >< p >JFreeChart教程:${JFreeChartUrl}</ p ></ div > < div >列表值:</ div > < div > <#list scores as item> < div >< p >${item}</ p ></ div > </#list> </ div > </ div > <!--第一页结束--> <!---分页标记--> < span style = "page-break-after:always;" ></ span > <!--第二页开始--> < div class = "page" > < div >第二页开始了</ div > <!--外部链接--> < p >百度图标</ p > < div > < img src = "${imageUrl}" alt = "百度图标" width = "270" height = "129" /> </ div > <!--动态生成的图片--> < p >气温变化对比图</ p > < div > < img src = "${picUrl}" alt = "我的图片" width = "500" height = "270" /> </ div > </ div > <!--第二页结束--> </ body > </ html > |
2、获取模板内容并填充数据
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
|
/** * @description 获取模板 */ public static String getContent(String fileName,Object data){ String templatePath=getPDFTemplatePath(fileName); //根据PDF名称查找对应的模板名称 String templateFileName=getTemplateName(templatePath); String templateFilePath=getTemplatePath(templatePath); if (StringUtils.isEmpty(templatePath)){ throw new FreeMarkerException( "templatePath can not be empty!" ); } try { Configuration config = new Configuration(Configuration.VERSION_2_3_25); //FreeMarker配置 config.setDefaultEncoding( "UTF-8" ); config.setDirectoryForTemplateLoading( new File(templateFilePath)); //注意这里是模板所在文件夹,不是文件 config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); config.setLogTemplateExceptions( false ); Template template = config.getTemplate(templateFileName); //根据模板名称 获取对应模板 StringWriter writer = new StringWriter(); template.process(data, writer); //模板和数据的匹配 writer.flush(); String html = writer.toString(); return html; } catch (Exception ex){ throw new FreeMarkerException( "FreeMarkerUtil process fail" ,ex); } } |
3、导出模板到PDF文件
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
|
/** * @description 导出pdf到文件 * @param fileName 输出PDF文件名 * @param data 模板所需要的数据 * */ public String exportToFile(String fileName,Object data){ String htmlData= FreeMarkerUtil.getContent(fileName, data); //获取FreeMarker的模板数据 if (StringUtils.isEmpty(saveFilePath)){ saveFilePath=getDefaultSavePath(fileName); //设置PDF文件输出路径 } File file= new File(saveFilePath); if (!file.getParentFile().exists()){ file.getParentFile().mkdirs(); } FileOutputStream outputStream= null ; try { //设置输出路径 outputStream= new FileOutputStream(saveFilePath); //设置文档大小 Document document = new Document(PageSize.A4); //IText新建PDF文档 PdfWriter writer = PdfWriter.getInstance(document, outputStream); //设置文档和输出流的关系 //设置页眉页脚 PDFBuilder builder = new PDFBuilder(headerFooterBuilder,data); builder.setPresentFontSize( 10 ); writer.setPageEvent(builder); //输出为PDF文件 convertToPDF(writer,document,htmlData); } catch (Exception ex){ throw new PDFException( "PDF export to File fail" ,ex); } finally { IOUtils.closeQuietly(outputStream); } return saveFilePath; } |
4、测试工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public String createPDF(Object data, String fileName){ //pdf保存路径 try { //设置自定义PDF页眉页脚工具类 PDFHeaderFooter headerFooter= new PDFHeaderFooter(); PDFKit kit= new PDFKit(); kit.setHeaderFooterBuilder(headerFooter); //设置输出路径 kit.setSaveFilePath("/Users/fgm/Desktop/pdf/hello.pdf”); //设置出书路径 String saveFilePath=kit.exportToFile(fileName,data); return saveFilePath; } catch (Exception e) { log.error( "PDF生成失败{}" , ExceptionUtils.getFullStackTrace(e)); return null ; } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public static void main(String[] args) { ReportKit360 kit= new ReportKit360(); TemplateBO templateBO= new TemplateBO(); //配置模板数据 templateBO.setTemplateName( "Hello iText! Hello freemarker! Hello jFreeChart!" ); templateBO.setFreeMarkerUrl( "http://www.zheng-hang.com/chm/freemarker2_3_24/ref_directive_if.html" ); templateBO.setITEXTUrl( "http://developers.itextpdf.com/examples-itext5" ); templateBO.setJFreeChartUrl( "http://www.yiibai.com/jfreechart/jfreechart_referenced_apis.html" ); templateBO.setImageUrl( "https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png" ); List<String> scores= new ArrayList<String>(); scores.add( "90" ); scores.add( "95" ); scores.add( "98" ); templateBO.setScores(scores); List<Line> lineList=getTemperatureLineList(); TemperatureLineChart lineChart= new TemperatureLineChart(); String picUrl=lineChart.draw(lineList, 0 ); //自定义的数据画图 templateBO.setPicUrl(picUrl); String path= kit.createPDF(templateBO, "hello.pdf" ); System.out.println(path); } |
六、生成效果图:
七、项目完整代码
1、github地址:https://github.com/superad/pdf-kit
八、遇到的坑:
1、FreeMarker配置模板文件样式,在实际PDF生成过程中,可能会出现一些不一致的情形,目前解决方法,就是换种方式调整样式。
2、字体文件放在resource下,在打包时会报错,运行mvn -X compile 会看到详细错误:
这是字体文件是二进制的,而maven项目中配置了资源文件的过滤,不能识别二进制文件导致的,plugins中增加下面这个配置就好了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
< build > < resources > < resource > < directory >src/main/resources</ directory > < filtering >true</ filtering > </ resource > </ resources > <!--增加的配置,过滤ttf文件的匹配--> < plugins > < plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-resources-plugin</ artifactId > < version >2.7</ version > < configuration > < encoding >UTF-8</ encoding > < nonFilteredFileExtensions > < nonFilteredFileExtension >ttf</ nonFilteredFileExtension > </ nonFilteredFileExtensions > </ configuration > </ plugin > </ plugins > </ build > |
3、PDF分页配置:
在ftl文件中,增加分页标签: <span style="page-break-after:always;"></span>
九、 完整maven配置:
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
<!--pdf生成 itext--> < dependency > < groupId >com.itextpdf</ groupId > < artifactId >itextpdf</ artifactId > < version >5.4.2</ version > </ dependency > < dependency > < groupId >com.itextpdf.tool</ groupId > < artifactId >xmlworker</ artifactId > < version >5.4.1</ version > </ dependency > < dependency > < groupId >com.itextpdf</ groupId > < artifactId >itext-asian</ artifactId > < version >5.2.0</ version > </ dependency > < dependency > < groupId >org.xhtmlrenderer</ groupId > < artifactId >flying-saucer-pdf</ artifactId > < version >9.0.3</ version > </ dependency > <!--freemarker--> < dependency > < groupId >org.freemarker</ groupId > < artifactId >freemarker</ artifactId > < version >2.3.26-incubating</ version > </ dependency > <!--jfreechart--> < dependency > < groupId >jfreechart</ groupId > < artifactId >jfreechart</ artifactId > < version >1.0.0</ version > </ dependency > <!--log--> < dependency > < groupId >ch.qos.logback</ groupId > < artifactId >logback-core</ artifactId > < version >1.0.13</ version > </ dependency > < dependency > < groupId >ch.qos.logback</ groupId > < artifactId >logback-classic</ artifactId > < version >1.0.13</ version > </ dependency > < dependency > < groupId >ch.qos.logback</ groupId > < artifactId >logback-access</ artifactId > < version >1.0.13</ version > </ dependency > < dependency > < groupId >org.slf4j</ groupId > < artifactId >slf4j-api</ artifactId > < version >1.7.5</ version > </ dependency > < dependency > < groupId >org.slf4j</ groupId > < artifactId >log4j-over-slf4j</ artifactId > < version >1.7.21</ version > </ dependency > <!--util--> < dependency > < groupId >com.google.guava</ groupId > < artifactId >guava</ artifactId > < version >20.0</ version > </ dependency > < dependency > < groupId >org.projectlombok</ groupId > < artifactId >lombok</ artifactId > < version >1.14.8</ version > </ dependency > < dependency > < groupId >org.apache.commons</ groupId > < artifactId >commons-io</ artifactId > < version >1.3.2</ version > </ dependency > < dependency > < groupId >commons-lang</ groupId > < artifactId >commons-lang</ artifactId > < version >2.6</ version > </ dependency > <!--servlet--> < dependency > < groupId >javax.servlet</ groupId > < artifactId >servlet-api</ artifactId > < version >2.5</ version > </ dependency > |
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://segmentfault.com/a/1190000009160184