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(); }
最终结果
如果有大神知道如何正确实现自动换行,还请不吝赐教。