在java后端,使用ftl模版将数据导出到自定义pdf文件中的基本操作流程如下
其实操作并不复杂,关键在于FTL模版的创建和转换成PDF文件这两步操作上。
ftl,即freemarker template language,Freemarker其实是一种比较简单的网页展示技术,说白了就是网页模板和数据模型的结合体。据个人理解,Freemarker大致的工作方式是,网页模板里面嵌入了数据模型中的数据、Freemarker自定义流程控制语言、Freemarker自定义的操作函数等等,在装载网页的时候,Freemarker模板自动从数据模型中提取数据,并解释整个网页为我们熟知的HTML页面。
创建ftl文件的一个方便快捷的玩法是,先创建一个html文件,调整好样式布局后,修改文件后缀为ftl即可。贴出我的ftl文件代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"></meta>
<title>学校网上报名新生个人信息表(<#if level == 1>小学<#else>初中</#if>)</title>
<style>
td {
padding: 4pt 0;
}
div{
margin: 0;
padding: 0;
}
h2{
margin: 0;
padding: 0;
margin-bottom: 8pt;
}
p{
margin: 0;
padding: 0;
}
</style>
</head>
<body style="font-family: SimSun;">
<div style="width: 517pt;margin: auto;text-align: center;color: #000000">
<h2 style="text-align: center">学校网上报名新生个人信息表(<#if level == 1>小学<#else>初中</#if>)</h2>
<div>
<table width="100%" border="1" cellpadding="0" cellspacing="0">
<tr>
<td style="width: 64pt">学生姓名</td>
<td style="width: 65pt">${data.studentName!''}</td>
<td style="width: 64pt">性别</td>
<td style="width: 60pt">${data.studentSex!''}</td>
<td style="width: 66pt">出生年月日</td>
<td style="width: 70pt">${data.studentBirth!''}</td>
<td style="width: 64pt">民族</td>
<td style="width: 64pt">${data.studentNation!''}</td>
</tr>
<#if level == 1>
<tr>
<td colspan="2">身份证号码</td>
<td colspan="3">${data.studentIdcardNo!''}</td>
<td colspan="2">健康状况</td>
<td>${data.health!''}</td>
</tr>
</#if>
<#if level == 2>
<tr>
<td>身份证号码</td>
<td colspan="2">${data.studentIdcardNo!''}</td>
<td>毕业小学</td>
<td colspan="2">${data.byxx}</td>
<td>健康状况</td>
<td>${data.health!''}</td>
</tr>
</#if>
<tr>
<td rowspan="4"><p>少</p><p>年</p><p>儿</p><p>童</p><p>户</p><p>籍</p><p>居</p><p>住</p><p>情</p>况</td>
<td>户籍所属派出所</td>
<td colspan="2"><#if data.isBlshj == 1>${data.policeStationName!''}<#else>${data.policeStation!''}</#if></td>
<td>户籍属地</td>
<td colspan="3">${data.hjTownName!''}${data.hjVillageName!''}</td>
</tr>
<tr>
<td>户籍地址</td>
<td colspan="6">${data.hjAddress!''}</td>
</tr>
<tr>
<td>拟申请就读学校辖区内的房产地址</td>
<td colspan="6">${data.fcTownName!''}${data.fcVillageName!''}${data.fcGroupName}</td>
</tr>
<tr>
<td>房产证持有人姓名</td>
<td>${data.fczHolder!''}</td>
<td>房产证、不动产权证书或购房合同编号</td>
<td>${data.fcNo!''}</td>
<td>少年儿童与房产证、不动产权证书或购房合同持有人的关系</td>
<td colspan="2">${data.relation!''}</td>
</tr>
<tr>
<td rowspan="3"><p>家</p><p>庭</p><p>情</p>况</td>
<td>父</td>
<td>姓名</td>
<td colspan="2">${data.fatherName!''}</td>
<td>联系电话</td>
<td colspan="2">${data.fatherPhone!''}</td>
</tr>
<tr>
<td>母</td>
<td>姓名</td>
<td colspan="2">${data.motherName!''}</td>
<td>联系电话</td>
<td colspan="2">${data.motherPhone!''}</td>
</tr>
<tr>
<td colspan="2"><p>法定监护人</p>(孤儿填写)</td>
<td colspan="2">${data.fdjhrName!''}</td>
<td>联系电话</td>
<td colspan="2">${data.fdjhrPhone!''}</td>
</tr>
<tr>
<td colspan="8"></td>
</tr>
<tr>
<td colspan="3">拟申请就读学校</td>
<td colspan="5">${data.applySchoolName!''}</td>
</tr>
<tr>
<td colspan="8" style="text-align: left;padding-left: 6pt">
<p>申请理由(在○内打√):</p>
<p><#if data.type == 'A'>√<#else>○</#if> A.少年儿童具有就读学校服务范围内户籍。</p>
<p><#if data.type == 'B'>√<#else>○</#if> B.政策性照顾生。</p>
<p><#if data.type == 'C'>√<#else>○</#if> C.法定监护人房产在拟申请就读学校辖区内。</p>
<p><#if data.type == 'D'>√<#else>○</#if> D.法定监护人城区无独立房产,户籍不在城区,但具备进城务工随迁子女入学的条件。</p>
</td>
</tr>
<tr>
<td>入学类型审定</td>
<td colspan="3"></td>
<td>证明材料学校审核人签名</td>
<td colspan="3"></td>
</tr>
<tr>
<td>家长签名</td>
<td colspan="3"></td>
<td>网上信息核定人签名</td>
<td colspan="3"></td>
</tr>
</table>
<div style="text-align: left;margin-top: 8pt">
<p style="font-weight: bolder">备注:</p>
<p style="text-indent: 2em">1.此表信息均为真实信息。凡是发现家长提供入学虚假证件等报名材料的,一经查实,其子女一律取消城区义务教育学校就读资格,并进一步追究相关人员的责任。</p>
<p style="text-indent: 2em">2.学生需提交房产证(或不动产权证书)、户口簿、小学毕业证(就读初中需要提交)等原件、复印件到拟申请就读学校,由学校现场审验,审核人须在复印件上签名。</p>
<p style="text-indent: 2em">3.2023年城区<#if level == 1>小学<#else>初中</#if>入学报名均用此表。</p>
<p style="text-indent: 2em">4.凡是报读D类少年儿童必须具备进城务工人员随迁子女入学细则所需证件。</p>
</div>
</div>
</div>
</body>
</html>
上述代码创建了一个报名信息的表格文件,使用Freemarker渲染数据。有一点需要特别注意,即ftl文件要求所有标签成对闭合,因为默认创建的html文件的meta表情是这样:
<meta charset="UTF-8">
我们需要手动修改为这样:
<meta charset="UTF-8"></meta>
否则模版无法正常渲染,后端会报错。
ftl模版文件准备好之后,我们需要在后端处理获取模版文件,查询模版需要的数据,并渲染生成一个HTML文件,示例代码如下:
public String createApplyPdf(String applyId, Integer level) throws AppException,Exception {
// 获取或创建一个模版
Configuration configuration = new Configuration();
configuration.setDirectoryForTemplateLoading(new File(resourcePath + File.separator + "temp"));
configuration.setDefaultEncoding("UTF-8");
Template template = configuration.getTemplate("apply.ftl");
String fileName = (level == 1 ? "P" : "M") + ToolUtil.md5(String.valueOf(applyId));
String html = resourcePath + File.separator + "temp" + File.separator + fileName + ".html";
//设置文件输入流编码
OutputStream os = new FileOutputStream(html);
OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8);
// 将页面中要展示的数据放入一个map中
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("level", level);
if(level == 1) {
PrimarySchoolApply primarySchoolApply = primarySchoolApplyService.info(applyId);
map.put("data", primarySchoolApply);
}else if(level == 2) {
MiddleSchoolApply middleSchoolApply = middleSchoolApplyService.info(applyId);
map.put("data", middleSchoolApply);
}
//将map中的数据输入到模板文件中
template.process(map, osw);
osw.close();
//html转成PDF文档
String pdf = resourcePath + File.separator + "temp" + File.separator + fileName + ".pdf";
ToolUtil.convertPdf(html, pdf, fontPath);
return File.separator + "temp" + File.separator + fileName + ".pdf";
}
需要引入freemarker的依赖,在pom.xml文件中添加:
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
上面的代码末尾调用了将html文件转成pdf文件的方法,该操作我们可以使用ITextRenderer的方法,在pom.xml中引入依赖
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.1.5</version>
</dependency>
转换pdf文件的代码比较简单
public static void convertPdf(String inputPath, String outPath, String fontPath){
try {
OutputStream os = new FileOutputStream(outPath);
ITextRenderer renderer = new ITextRenderer();
ITextFontResolver fontResolver = renderer.getFontResolver();
//启动中文支持
fontResolver.addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
String url = new File(inputPath).toURI().toURL().toString();
renderer.setDocument(url);
renderer.layout();
renderer.createPDF(os);
os.close();
}catch (Exception e){
e.printStackTrace();
}
}
注意需要启动中文支持,否则中文会乱码。生成的PDF效果图下图所示