freemarker + ItextRender 根據模板生成PDF檔案
阿新 • • 發佈:2019-02-16
1. 製作模板
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head lang="en"> <meta http-equiv="content-type" content="text/html;charset=utf-8"></meta> <title>軟體包隱患掃描報告</title> <style> .template_ftl { width: 100%; height: 100%; padding: 30px 25px; } .template_box { width: 600px; height: 842px; box-sizing: border-box; margin: 0 auto; } .template_ftl .template_header { width: 100%; height: 50px; box-sizing: border-box; color: #0D122B; } .template_ftl .template_header > i { display: inline-block; width: 24px; height: 28px; background: url("stack.png") no-repeat center center; } .template_ftl .template_header > h2 { display: inline-block; font-size: 17px; } .template_ftl .template_header > div { float: right; color: #B7C1CF; font-size: 9px; line-height: 50px; } .template_ftl .template_header > div > i { display: inline-block; width: 9px; height: 9px; background: url("Calendar.png") no-repeat center center; } .template_ftl .template_description { width: 100%; margin-top: 10px; } .template_ftl .template_title { width: 100%; height: 18px; line-height: 18px; color: #00AEA1; font-size: 12px; } .template_ftl .template_title > i { display: inline-block; width: 14px; height: 14px; margin-right: 10px; background: url("Layers.png") no-repeat center center; } .template_ftl .template_info { list-style: none; padding-left: 30px; } .template_ftl .template_info label { display: inline-block; width: 80px; margin-right: 30px; color: #646464; font-size: 10px; vertical-align: middle; } .template_ftl .template_info span { color: #0D122B; font-size: 10px; vertical-align: middle; } .template_ftl .template_info li { margin-bottom: 5px; } .template_ftl .template_title .shield { width: 14px; height: 14px; background: url("Shield.png") no-repeat center center; } .template_ftl .template_title .screen { width: 14px; height: 14px; background: url("Screen.png") no-repeat center center; } .template_ftl .template_network { list-style: none; padding-left: 30px; margin-top: 10px; } .template_ftl .template_network .network_level b { color: #0D122B; font-size: 12px; } .template_ftl .template_network .network_level span { color: #E74C3C; font-size: 12px; } .template_ftl .template_network .network_level p { margin: 0; color: #646464; font-size: 10px; } .template_ftl .template_network .network_content:before { content: '● '; color: #00AEA1; } .template_ftl .template_network .network_content { color: #3E4550; font-size: 10px; margin-top: 10px; } .online_info { width: 100%; } .online_info .online_list { width: 100%; box-sizing: border-box; list-style: none; padding-left: 30px; display: flex; flex-flow: row nowrap; justify-content: space-between; align-items: center; margin: 0; color:#3E4550; font-size: 10px; } .online_info .online_list:nth-child(2n){ background-color: #EFEFEF; } .online_info .online_list > td { width: 90px; line-height: 25px; text-align: center; } .online_info .online_list > td:first-child { text-align: center; } </style> </head> <body> <div class="template_ftl" style="font-family:SimSun;"> <div class="template_box"> <div class="template_header"> <i></i> <span style="font-size:24px;font-weight:1px bold;">軟體包掃描報告</span> <div> <i></i> <span> <#if (currentDate)??> ${currentDate} </#if> </span> </div> </div> <div class="template_description"> <div class="template_title"> <i></i> <span>掃描概要資訊</span> </div> <ul class="template_info"> <li> <label>掃描產品名稱</label> <span> <#if (softScanInfo.productName)??> ${softScanInfo.productName} </#if> </span> </li> <li> <label>檔案路徑</label> <span> <#if (softScanInfo.scanFilePath)??> ${softScanInfo.scanFilePath} </#if> </span> </li> <li> <label>掃描開始時間</label> <span> <#if (softScanInfo.scanBegintime)??> ${(softScanInfo.scanBegintime)?string("yyyy年MM月dd日 HH:mm:ss")} </#if> </span> </li> <li> <label>掃描結束時間</label> <span> <#if (softScanInfo.scanEndtime)??> ${(softScanInfo.scanEndtime)?string("yyyy年MM月dd日 HH:mm:ss")} </#if> </span> </li> </ul> </div> <div class="template_description"> <div class="template_title"> <i class="shield"></i> <span>軟體包安全資訊</span> </div> <ul class="template_network"> <li class="network_level"> <span style="font-size:13px;color:black;">軟體包風險等級為 </span> <#if (softScanInfo.scanResult)??> <#if (softScanInfo.scanResult == '高')> <span style="color:red;">${softScanInfo.scanResult}</span> <#elseif (softScanInfo.scanResult == '中')> <span style="color:#FF9900;">${softScanInfo.scanResult}</span> <#elseif (softScanInfo.scanResult == '低')> <span style="color:#0000FF;">${softScanInfo.scanResult}</span> <#else> <span style="color:#00cc00;">安全</span> </#if> <#else> <span style="color:#00cc00;">安全</span> </#if> <p style="margin-top:8px;">本次共掃描1個軟體包,其中包含<#if recordList?exists>${recordList?size} <#else>0</#if>款軟體</p> </li> <li class="network_content"> <span> 風險等級為<span style="color:red;">高</span>的軟體數為 <span style="color:red;"><#if (highRiskRecord.appScanResult)??>${highRiskRecord.appScanResult}</span></#if> </span> </li> <li class="network_content"> <span> 風險等級為<span style="color:#FF9900;">中</span>的軟體數為 <span style="color:#FF9900;"><#if (middleRiskRecord.appScanResult)??>${middleRiskRecord.appScanResult}</span></#if> </span> </li> <li class="network_content"> <span> 風險等級為<span style="color:#0000FF;">低</span>的軟體數為 <span style="color:#0000FF;"><#if (lowRiskRecord.appScanResult)??>${lowRiskRecord.appScanResult}</span></#if> </span> </li> <li class="network_content"> <span> 風險等級為<span style="color:#00cc00;">安全</span>的軟體數為 <span style="color:#00cc00;"><#if (noneRiskRecord.appScanResult)??>${noneRiskRecord.appScanResult}</span></#if> </span> </li> </ul> </div> <div class="template_description"> <div class="template_title"> <i class="screen"></i> <span>掃描軟體包概要資訊</span> </div> <div class="online_info" > <table class="online_list" style="word-break: break-all; word-wrap: break-word;"> <tr class="online_list" style="border-bottom: 1px solid #B7C1CF;color: #94949;"> <td >軟體名稱</td> <td style="width:120px;">版本號</td> <td style="width:150px;">軟體包名稱</td> <td style="width:40px;">安全風險</td> <td style="width:150px;">安全描述事件</td> <td style="width:30px;">通過</td> </tr> <#if recordList?exists> <#list recordList as record> <tr class="online_list" > <td> <#if (record.appName)??> ${record.appName} </#if> </td> <td style="width:120px;"> <#if (record.appVersion)??> ${record.appVersion} </#if> </td> <td style="width:150px;"> <#if (record.appFileName)??> ${record.appFileName} </#if> </td> <td style="width:30px;"> <#if (record.riskRating)??> <#if (record.riskRating == '無')> 安全 <#else> ${record.riskRating} </#if> <#else> 安全 </#if> </td> <td style="width:150px;"> <#if (record.riskDescription)??> ${record.riskDescription} <#else> 無 </#if> </td> <td style="width:20px;"> <#if (record.appScanResult)??> <#if (record.appScanResult = 1)> <i style="color: #00AEA1">√</i> <#else> <i style="color: #E74C3C">×</i> </#if> <#else> <i style="color: #00AEA1">√</i> </#if> </td> </tr> </#list> </#if> </table> </div> </div> </div> </div> </body> </html>
2. 獲取模板,並將所獲取的資料載入生成html檔案
public String getHtmlString (final Object data, final String templateDirectory, final String templateName, final String encoding) { log.info("FreeMarkerUtil!"); String html = null; StringWriter stringWriter = null; BufferedWriter writer = null; try { //建立一個負責管理FreeMarker模板的Configuration例項 final Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); //指定FreeMarker模板檔案位置 // configuration.setDirectoryForTemplateLoading(new File(templateDirectory)); //設定模板的編碼格式 configuration.setEncoding(Locale.CHINA, encoding); //獲取模板 configuration.setClassForTemplateLoading(this.getClass(), "/templates"); final Template template = configuration.getTemplate(templateName, encoding); stringWriter = new StringWriter(); writer = new BufferedWriter(stringWriter); //將資料輸出到html中 template.process(data, writer); writer.flush(); html = stringWriter.toString(); } catch (Exception e) { log.error("failed to get html String!", e); } finally { if (stringWriter !=null) { try { stringWriter.close(); } catch (IOException e) { log.error("failed to close writer!", e); } } if (writer !=null) { try { writer.close(); } catch (IOException e) { log.error("failed to close writer!", e); } } } log.info("getHtmlString() exit!"); return html; }
2. 生成PDF檔案
final File file = new File(reportPath);
try {
if (!file.exists()) {
file.createNewFile();
}
final String chineseFont = getChineseFont();//獲取字型檔案路徑
log.info("chineseFont=" + chineseFont);
createPDF(new FileOutputStream(file), chineseFont, html);
}
public void createPDF(final OutputStream outputStream, final String fontPath, final String html) { final ITextRenderer renderer = new ITextRenderer(); // 設定字型樣式 try { renderer.getFontResolver().addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); //這裡的字型必須和ftl模板中的字型一致,否則中文會丟失 // 把html程式碼傳入渲染器中 renderer.setDocumentFromString(html); /* * 解決圖片的相對路徑問題,必須在設定document後再設定圖片路徑,不然不起作用, * 如果使用絕對路徑依然有問題,可以在路徑前面加"file:/" */ // renderer.getSharedContext().setBaseURL("file:/C:/Users/HYH/Desktop/templateftp/"); renderer.getSharedContext().setBaseURL("file:" + getPictureDirctory()); log.info("images directory path: file:" + getPictureDirctory()); renderer.layout(); renderer.createPDF(outputStream, false); renderer.finishPDF(); outputStream.flush(); } catch (final Exception e) { log.error("failed to create pdf!", e); } finally { if (outputStream != null) { try { outputStream.close(); } catch (final IOException e) { log.error("failed to close outputStream!", e); } } } log.info("create pdf completely!"); }
其中由兩個地方需要注意,都是關於獲取檔案路徑的問題,由於專案部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點檔案,但是當打包後部署,總是出錯。於是參考網上文章,先將檔案讀出來到專案的臨時目錄下,然後再按正常方式載入該臨時檔案;
//獲取圖片目錄位置
private String getPictureDirctory () {
final File fileDir = new File("System.getProperty("user.dir") + "/images/"");
if (!fileDir.exists()) {
fileDir.mkdirs();
final InputStream calendarStream = getClass().getClassLoader().getResourceAsStream("images/Calendar.png");
final InputStream deleteStream = getClass().getClassLoader().getResourceAsStream("images/delete.png");
final InputStream layersStream = getClass().getClassLoader().getResourceAsStream("images/Layers.png");
final InputStream saveStream = getClass().getClassLoader().getResourceAsStream("images/save.png");
final InputStream screenStream = getClass().getClassLoader().getResourceAsStream("images/Screen.png");
final InputStream shieldStream = getClass().getClassLoader().getResourceAsStream("images/Shield.png");
final InputStream stackStream = getClass().getClassLoader().getResourceAsStream("images/stack.png");
final File calendarFile = new File(IMAGES_TEMP_PATH + "Calendar.png");
final File deleteFile = new File(IMAGES_TEMP_PATH + "delete.png");
final File layersFile = new File(IMAGES_TEMP_PATH + "Layers.png");
final File saveFile = new File(IMAGES_TEMP_PATH + "save.png");
final File screenFile = new File(IMAGES_TEMP_PATH + "Screen.png");
final File shieldFile = new File(IMAGES_TEMP_PATH + "Shield.png");
final File stackFile = new File(IMAGES_TEMP_PATH + "stack.png");
try {
FileUtils.copyInputStreamToFile(calendarStream, calendarFile);
FileUtils.copyInputStreamToFile(deleteStream, deleteFile);
FileUtils.copyInputStreamToFile(layersStream, layersFile);
FileUtils.copyInputStreamToFile(saveStream, saveFile);
FileUtils.copyInputStreamToFile(screenStream, screenFile);
FileUtils.copyInputStreamToFile(shieldStream, shieldFile);
FileUtils.copyInputStreamToFile(stackStream, stackFile);
} catch (IOException e) {
log.error("圖片檔案複製失敗!", e);
}
}
return IMAGES_TEMP_PATH;
}
/**
* 獲取中文字型位置
* @return
*/
private String getChineseFont() {
final InputStream stream = getClass().getClassLoader().getResourceAsStream("fonts/simsun.ttf");
final File fileDir = new File("System.getProperty("user.dir") + "/fonts/"");
if (!fileDir.exists()) {
fileDir.mkdirs();
}
final String fontsPath = FONTS_TEMP_PATH + "simsun.ttf";
final File targetFile = new File(fontsPath);
try {
FileUtils.copyInputStreamToFile(stream, targetFile);
} catch (final IOException e) {
log.error("字型檔案複製失敗!", e);
}
return fontsPath;
}
還有一個問題至今沒有解決,就是關於生成PDF檔案後自動換行的問題,參考了網上大多數的作法,都是修改模板樣式,生成的html確實是可以換行,但是對PDF無效。最後想出了一個比較笨也不是很合適的方法,在後臺先給它在某處先換好行,然後再生成PDF,雖然比較笨,也存在問題,但是確實解了燃眉之急。
/**
* 向字串source中每隔sep個插入一個replace
* @param source
* @param sep
* @param replace
* @return
*/
public String changeLine(final String source, final int sep, final String replace) {
if (source == null) {
return null;
}
final StringBuffer buffer = new StringBuffer(source);
for (int i = sep; i < source.length(); i+=(sep + 1)) {
buffer.insert(i, replace);
}
return buffer.toString();
}
最終結果
如果有大神知道如何正確實現自動換行,還請不吝賜教。