一、需求說明:
根據業務需要,需要在服務器端生成可動態配置的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