前言
最近,接到一个模板打印pdf的任务,后来网上找了很多案例,本文做下记录。
如何开始
添加依赖包
<!-- thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--FlyingSaucer-->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.1.9</version>
</dependency>
yml配置(可选,不配置默认也可)
spring:
# thymeleaf
thymeleaf:
prefix: classpath:/templates/
check-template-location: true
suffix: .html
encoding: UTF-8
mode: HTML
cache: false
servlet:
content-type: text/html
模板文件
如下文件放置位置,文件都在我的开源项目中,文末有地址。
pdfPage模板文件:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.w3.org/1999/xhtml" layout:decorator="layout">
<head lang="en">
<title>Spring Boot Demo - PDF</title>
<style>
@page {
size: 210mm 297mm; /*设置纸张大小:A4(210mm 297mm)、A3(297mm 420mm) 横向则反过来*/
margin: 0.25in;
padding: 1em;
@bottom-center{
content:"葫芦科技 © 版权所有";
font-family: SimSun;
font-size: 12px;
color:red;
};
@top-center { content: element(header) };
@bottom-right{
content:"第" counter(page) "页 共 " counter(pages) "页";
font-family: SimSun;
font-size: 12px;
color:#000;
};
}
body{font-family: 'SimSun'}
h2{color: crimson}
#myheader{
width: 500px;
height: 22px;
border: 1px solid #000000;
}
table, th , td {
border: 1px solid grey;
border-collapse: collapse;
padding: 5px;
}
table tr:nth-child(odd) {
background-color: #f1f1f1;
}
table tr:nth-child(even) {
background-color: #ffffff;
}
#input1{
border-bottom: 1px solid #000000;
}
</style>
</head>
<!--这样配置不中文不会显示-->
<!--<body style="font-family: 宋体">-->
<body style="font-family: 'SimSun'">
<div>1.标题-中文</div>
<h2 th:text="${title}"></h2>
<div>2.按钮:按钮的边框需要写css渲染</div>
<button class="a" style="border: 1px solid #000000"> click me t-p</button>
<div id="divsub"></div>
<div>3.普通div</div>
<div id="myheader">Alice's Adventures in Wonderland</div>
<div>4.图片 绝对定位到左上角(注意:图片必须用全路径或者http://开头的路径,否则无法显示)</div>
<img th:src="${imageUrl}"/>
<div>5.普通table表格</div>
<div>
<table style="width: 700px">
<tr>
<th>姓名</th>
<th>昵称</th>
<th>年龄</th>
</tr>
<tr th:each="info : ${demoList}">
<td th:text="${info.name}"></td>
<td th:text="${info.nick}"></td>
<td th:text="${info.age}"></td>
</tr>
</table>
</div>
<div>6.input控件,边框需要写css渲染 (在模板中一般不用input,因为不存在输入操作)</div>
<div>
<label>姓名:</label>
<input id="input1" aria-label="葫芦胡" type="text" value="葫芦胡"/>
</div>
</body>
</html>
避坑:
测试的时候,模板文件中一定至少有一个:th:text="${xxx}",否则会报模板不存在
图片路径需绝对路径,如:http://localhost:8088/hu/imgs/sg.jpg
核心处理类
工具类
/**
* pdf处理工具类
*
* @author gourd.hu
* @version 1.0
*/
@Slf4j
public class PdfUtil {
/**
* 按模板和参数生成html字符串,再转换为flying-saucer识别的Document
*
* @param templateName 模板名称
* @param variables 模板参数
* @return Document
*/
private static Document generateDoc(TemplateEngine templateEngine, String templateName, Map<String, Object> variables) {
// 声明一个上下文对象,里面放入要存到模板里面的数据
final Context context = new Context();
context.setVariables(variables);
StringWriter stringWriter = new StringWriter();
try(BufferedWriter writer = new BufferedWriter(stringWriter)) {
templateEngine.process(templateName,context, writer);
writer.flush();
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
return builder.parse(new ByteArrayInputStream(stringWriter.toString().getBytes()));
}catch (Exception e){
ResponseEnum.TEMPLATE_PARSE_ERROR.assertFail(e);
}
return null;
}
/**
* 核心: 根据freemarker模板生成pdf文档
*
* @param templateEngine 配置
* @param templateName 模板名称
* @param out 输出流
* @param listVars 模板参数
* @throws Exception 模板无法找到、模板语法错误、IO异常
*/
private static void generateAll(TemplateEngine templateEngine, String templateName, OutputStream out, List<Map<String, Object>> listVars) throws Exception {
// 断言参数不为空
ResponseEnum.TEMPLATE_DATA_NULL.assertNotEmpty(listVars);
ITextRenderer renderer = new ITextRenderer();
//设置字符集(宋体),此处必须与模板中的<body style="font-family: SimSun">一致,区分大小写,不能写成汉字"宋体"
ITextFontResolver fontResolver = renderer.getFontResolver();
//避免中文为空设置系统字体
fontResolver.addFont("static/fonts/simsun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
//根据参数集个数循环调用模板,追加到同一个pdf文档中
//(注意:此处从1开始,因为第0是创建pdf,从1往后则向pdf中追加内容)
for (int i = 0; i < listVars.size(); i++) {
Document docAppend = generateDoc(templateEngine, templateName, listVars.get(i));
renderer.setDocument(docAppend, null);
//展现和输出pdf
renderer.layout();
if(i==0){
renderer.createPDF(out, false);
}else {
//写下一个pdf页面
renderer.writeNextDocument();
}
}
renderer.finishPDF(); //完成pdf写入
}
/**
* pdf下载
*
* @param templateEngine 配置
* @param templateName 模板名称
* @param listVars 模板参数集
* @param response HttpServletResponse
* @param fileName 下载文件名称
*/
public static void download(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response, String fileName) {
// 设置编码、文件ContentType类型、文件头、下载文件名
response.setCharacterEncoding("utf-8");
response.setContentType("multipart/form-data");
try {
response.setHeader("Content-Disposition", "attachment;fileName=" +
new String(fileName.getBytes("gb2312"), "ISO8859-1"));
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
}
try (ServletOutputStream out = response.getOutputStream()) {
generateAll(templateEngine, templateName, out, listVars);
out.flush();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
* pdf下载到特定位置
*
* @param templateEngine 配置
* @param templateName 模板名称
* @param listVars 模板参数集
* @param filePath 下载文件路径
*/
public static void save(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, String filePath) {
try (OutputStream out = new FileOutputStream(filePath);) {
generateAll(templateEngine, templateName, out, listVars);
out.flush();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
* pdf预览
*
* @param templateEngine 配置
* @param templateName 模板名称
* @param listVars 模板参数集
* @param response HttpServletResponse
*/
public static void preview(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response) {
try (ServletOutputStream out = response.getOutputStream()) {
generateAll(templateEngine, templateName, out, listVars);
out.flush();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
Controller
/**
* pdf预览、下载
*
* @author gourd.hu
* @version 1.0
*/
@RestController
@RequestMapping(value = "/pdf")
@Api(tags = "pdf测试API", description = "pdf功能")
public class PdfController {
@Autowired
private TemplateEngine templateEngine;
@Value("${pdf.windowsFileTempLoc}")
private String pdfWindowsPath;
@Value("${pdf.linuxFileTempLoc}")
private String pdfLinuxPath;
/**
* 文档预览、下载
*
* @author gourd.hu
* @version 1.0
*/
@RestController
@RequestMapping(value = "/document")
@Api(tags = "文档预览、下载API")
public class DocumentController {
@Autowired
private TemplateEngine templateEngine;
@Value("${pdf.windowsFileTempLoc}")
private String pdfWindowsPath;
@Value("${pdf.linuxFileTempLoc}")
private String pdfLinuxPath;
/**
* pdf预览
*
* @param response HttpServletResponse
*/
@GetMapping(value = "/pdf/preview")
@ApiOperation(value="pdf预览")
public void preview(HttpServletResponse response) {
// 构造freemarker模板引擎参数,listVars.size()个数对应pdf页数
List<Map<String,Object>> listVars = new ArrayList<>();
Map<String,Object> variables = new HashMap<>(4);
variables.put("title","测试预览PDF!");
variables.put("imageUrl",sslEnabled?"https://localhost:10001/imgs/sg.jpg":"http://localhost:10001/imgs/sg.jpg");
List<Map<String,String>> demoList = new ArrayList<>();
Map<String,String> demoMap = new HashMap<>(8);
demoMap.put("name","哈哈");
demoMap.put("nick","娃娃");
demoMap.put("age","19");
Map<String,String> demoMap2 = new HashMap<>(8);
demoMap2.put("name","天天");
demoMap2.put("nick","饭饭");
demoMap2.put("age","14");
demoList.add(demoMap);
demoList.add(demoMap2);
variables.put("demoList",demoList);
Map<String,Object> variables2 = new HashMap<>(4);
variables2.put("title","测试预览PDF2!");
listVars.add(variables);
listVars.add(variables2);
PdfUtil.preview(templateEngine,"pdfPage",listVars,response);
}
/**
* pdf下载
*
* @param response HttpServletResponse
*/
@GetMapping(value = "/pdf/download")
@ApiOperation(value="pdf下载")
public void download(HttpServletResponse response) {
List<Map<String,Object>> listVars = new ArrayList<>(1);
Map<String,Object> variables = new HashMap<>(4);
variables.put("title","测试下载PDF!");
variables.put("imageUrl",sslEnabled?"https://localhost:10001/imgs/sg.jpg":"http://localhost:10001/imgs/sg.jpg");
List<Map<String,String>> demoList = new ArrayList<>();
Map<String,String> demoMap = new HashMap<>(8);
demoMap.put("name","哈哈");
demoMap.put("nick","娃娃");
demoMap.put("age","19");
Map<String,String> demoMap2 = new HashMap<>(8);
demoMap2.put("name","天天");
demoMap2.put("nick","饭饭");
demoMap2.put("age","14");
demoList.add(demoMap);
demoList.add(demoMap2);
variables.put("demoList",demoList);
listVars.add(variables);
PdfUtil.download(templateEngine,"pdfPage",listVars,response,"测试打印.pdf");
}
/**
* pdf下载到特定位置
*
*/
@GetMapping(value = "/pdf/save")
@ApiOperation(value="pdf下载到特定位置")
public void save() {
List<Map<String,Object>> listVars = new ArrayList<>(1);
Map<String,Object> variables = new HashMap<>(4);
variables.put("title","测试下载PDF!");
variables.put("imageUrl",sslEnabled?"https://localhost:10001/imgs/sg.jpg":"http://localhost:10001/imgs/sg.jpg");
List<Map<String,String>> demoList = new ArrayList<>();
Map<String,String> demoMap = new HashMap<>(8);
demoMap.put("name","哈哈");
demoMap.put("nick","娃娃");
demoMap.put("age","19");
Map<String,String> demoMap2 = new HashMap<>(8);
demoMap2.put("name","天天");
demoMap2.put("nick","饭饭");
demoMap2.put("age","14");
demoList.add(demoMap);
demoList.add(demoMap2);
variables.put("demoList",demoList);
listVars.add(variables);
// pdf文件下载位置
String pdfPath = CommonUtil.isLinux() ? pdfLinuxPath : pdfWindowsPath;
PdfUtil.save(templateEngine,"pdfPage",listVars,pdfPath);
}
}
}
演示效果
结语
至此,pdf打印功能就完成了,如果本文有错误的地方,欢迎评论指正。
===============================================
代码均已上传至本人的开源项目
cloud-plus:https://blog.csdn.net/HXNLYW/article/details/104635673