最近有个需求,客户在提交订单的时候,自动生成一个pdf文件,方便后期的回溯数据。记录一下,如果有什么问题,请指正。
一、添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- itext7html转pdf -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.8</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!-- 中文字体支持 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>font-asian</artifactId>
<version>7.1.13</version>
</dependency>
二、模板创建
在resources文件夹下创建模板文件,如template.ftl (使用FreeMarker制作模板,模板需根据业务自行设置)
关于模板中的一些特定语法,请自行参考官方文档,地址如下:
Apache FreeMarker Manual
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8"></meta>
<style>
@page {
@top-center { content: element(header) }
}
@page {
@bottom-center { content: element(footer) }
}
.apply {
margin: 0 auto;
padding: 0 30px;
}
.title {
margin-top: 40px ;
text-align: center;
font-weight: bold;
//字体需要和后台对应上
font-family: SimSun;
font-weight: bold;
font-size: 20px;
color: #333333;
letter-spacing: 0;
}
.table {
border-collapse: collapse;
width: 100%;
margin-top: 30px;
font-family: SimSun;
font-size: 14px;
color: #111111;
letter-spacing: 0.54px;
}
.label {
background-color: #E6E6E6;
width: 20%;
}
.normaltd {
padding: 10px 0;
}
.maxtd {
height: 250px;
}
.value {
width: 30%;
padding-left: 10px;
}
tr {
page-break-inside: avoid;
page-break-after: auto;
}
.form-row {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
}
.form-group {
flex: 1;
margin-right: 10px;
}
</style>
</head>
<body style="font-family: SimSun">
<div class="apply">
<p class="title">订单详情</p>
<div class="form-row">
<div class="form-group">客户姓名:${clientName}</div>
<div class="form-group">客户电话:${clientPhone}</div>
<div class="form-group">客户Email:${clientEmail}</div>
</div>
<table border="1" cellspacing="0" class="table">
<#if type == "z">
<tr>
<th class="label normaltd" align="center">客户姓名</th>
<th class="label normaltd" align="center">客户电话</th>
<th class="label normaltd" align="center">客户Email</th>
</tr>
<#list ordersItems as item>
<tr>
<td class="label normaltd" align="center"><#if item.clientName??> ${item.clientName} <#else> -</#if></td>
<td class="label normaltd" align="center"><#if item.clientPhone??> ${item.clientPhone} <#else> -</#if></td>
<td class="label normaltd" align="center"><#if item.clientEmail??> ${item.clientEmail} <#else> -</#if></td>
</tr>
</#list>
</#if>
</table>
</div>
</body>
</html>
创建完模板之后,在Java代码中获取模板的内容,并将值传入到模板中,最终返回字符串,当然,如果不想用模板,也可以自行拼接,自行拼接很麻烦,而且很容易混乱。
HtmlGenerator.java文件
import java.io.*;
import java.util.Map;
import freemarker.template.*;
public class HtmlGenerator {
public static String generate(String template, Map<String, Object> variables) throws IOException, TemplateException, IOException {
Configuration config = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
// 指定FreeMarker模板文件的位置
config.setClassForTemplateLoading(HtmlGenerator.class, "/template");
config.setDefaultEncoding("UTF-8");
//获取模板文件
Template tp = config.getTemplate(template);
StringWriter stringWriter = new StringWriter();
BufferedWriter writer = new BufferedWriter(stringWriter);
tp.setEncoding("UTF-8");
//把map数据写入
tp.process(variables, writer);
String htmlStr = stringWriter.toString();
writer.flush();
writer.close();
return htmlStr;
}
}
三、html转pdf
Service层
代码中的各种参数、字体、输出路径请根据实际情况修改。
/**
* 将Html转换成PDF
* @param orderPicResult
* @param ordersItems
* @param type
* @throws Exception
*/
private String createPDFByHtml(OrderPicResult orderPicResult, List<OrdersItem> ordersItems, String type, String orderId) throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("ordersItems", ordersItems);
map.put("type", type);
Class<? extends OrderPicResult> aClass = orderPicResult.getClass();
Field[] fields = aClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
field.setAccessible(true);
Object value = field.get(orderPicResult);
ApiModelProperty annotation = field.getAnnotation(ApiModelProperty.class);
if (ObjectUtil.isEmpty(value)) {
value = "-";
}
if ("付款账期".equals(annotation.value())) {
value = ClientPaymentTypeEnum.getName(value.toString());
}
map.put(field.getName(), value);
}
String outputFile = "d:/info.pdf";// 输出 PDF 文件的路径
Document document = new Document();
try {
String htmlstr = HtmlGenerator.generate("orderInfo.ftl", map);
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(outputFile));
document.open();
XMLWorkerHelper.getInstance().parseXHtml(writer, document,
new ByteArrayInputStream(htmlstr.getBytes("UTF-8")),
null,
Charset.forName("UTF-8"),
new FontProviderUtil());
document.close();
} catch (DocumentException | IOException e) {
e.printStackTrace();
}
}
四、存在问题及解决方法
在转成pdf时会存在一个问题:中文不显示,如果遇到可以重写 XMLWorkerFontProvider中的getFont方法,亲测有效。
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Font;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
public class FontProviderUtil extends XMLWorkerFontProvider {
/**
* 重写getFont,解决不显示中文的问题
* @param fontname
* @param encoding
* @param embedded
* @param size
* @param style
* @param color
* @return
*/
@Override
public Font getFont(final String fontname, final String encoding,final boolean embedded, final float size, final int style,final BaseColor color) {
BaseFont bf = null;
try {
bf = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
e.printStackTrace();
}
Font font = new Font(bf, size, style, color);
font.setColor(color);
return font;
}
}