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();
}
最终结果
如果有大神知道如何正确实现自动换行,还请不吝赐教。
//获取模板文件
private static Template getTemplate(String templateName) {
Template template = null;
try {
Configuration cfg=new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
cfg.setClassForTemplateLoading(FreeMarkerUtil.class, "/templates");
template = cfg.getTemplate(templateName);
} catch (TemplateNotFoundException e) {
logger.error("模板未找到", e);
} catch (IOException e) {
logger.error("文件读取异常", e);
}
return template;
}