Spring - Boot Thymeleaf生成转为PDF并保存
一,需要引入的依赖包
<!-- HTML转pdf -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>font-asian</artifactId>
<version>7.2.0</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>commons</artifactId>
<version>7.2.0</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>kernel</artifactId>
<version>7.2.0</version>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.20</version>
</dependency>
<!-- 这个地方需要特别注意 itextpdf 的包除了html2pdf以外 其他的包必须统一版本 否则会出现依赖冲突问题 导致执行程序出错 -->
创建thymeleaf页面
这个地方就不多说了 可以去百度看下thymeleaf的基本语法
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<title>测试</title>
<style>
.bill-print-root {
height: 100%;
overflow: auto;
}
.bill-print-page {
background-color: #fff;
min-height: 100%;
overflow: auto;
overflow-x: hidden;
font-size: 12px;
padding: 0 10px;
}
.bill-name {
text-align: center;
font-size: 24px;
padding-top: 20px;
}
.el-button {
float: right;
}
.area {
margin-top: 10px;
border: 2px solid #b5b5b5!important;
border-collapse: collapse;
border-spacing: 0;
}
thead {
font-weight: 700;
}
.head {
margin-top: 5px;
border: 2px solid #b5b5b5!important;
border-collapse: collapse;
border-spacing: 0;
}
th {
padding: 10px 5px;
border: 1px solid #b5b5b5;
vertical-align: middle;
text-align: center!important;
}
td {
padding: 10px 5px;
border: 1px solid #b5b5b5;
vertical-align: middle;
text-align: center!important;
}
.spanText {
word-break: break-all;
}
</style>
</head>
<body>
<div class="bill-print-root">
<div>
<div>
<div class="bill-print-page">
<div style="margin-bottom: 50px;">
<h1 class="bill-name" th:text="${printInfoBO.title}"></h1>
<div class="page">
<table width="100%" class="head">
<thead>
<tr>
<th colspan="6"><span>测试</span></th>
</tr>
</thead>
<tbody>
<tr th:each="fieldList:${headList}">
<th>
<span th:text="${fieldList.get(0).colName}"></span>
</th>
<td>
<span th:if="${printInfoBO.data.XXXXX}"
th:text="${printInfoBO.data.XXXX}"></span>
</td>
<th th:if="${fieldList.size()%3 ne 1}">
<span th:text="${printInfoBO.data.XXXX}"></span>
</th>
<td th:if="${fieldList.size()%3 ne 1}">
<span th:if="${printInfoBO.data.XXXX}"
th:text="${printInfoBO.data.XXXX}"></span>
</td>
<th th:if="${fieldList.size()%3 eq 0}">
<span th:text="${fieldList.get(2).colName}"></span>
</th>
<td th:if="${fieldList.size()%3 eq 0}">
<span th:if="${printInfoBO.data.XXXX}"
th:text="${printInfoBO.data.XXXX}"></span>
</td>
</tr></tbody>
</table>
<div class="body">
<div>
<table width="100%" class="area" th:each="bodyData:${printInfoBO.body}">
<thead>
<tr>
<td th:colspan="${bodyData.fieldList.size()}" th:text="${printInfoBO.data.XXXX}"></td>
</tr>
<tr>
<td th:each="field:${bodyData.fieldList}"><span th:text="${field.colName}"></span></td>
</tr>
</thead>
<tbody>
<tr th:each="${printInfoBO.data.XXXX}">
<td th:each="field:${bodyData.fieldList}">
<span class="spanText" th:if="${data.get(field.colCode)}"
th:text="${data.get(field.colCode).get('LABEL')}"></span>
</td>
</tr>
</tbody>
</table>
</div>
<div>
<table width="100%" class="area">
<thead>
<tr>
<td colspan="5">审批历史</td>
</tr>
<tr>
<td width="20%"><span>审批环节</span></td>
<td width="20%"><span>审批人</span></td>
<td width="20%"><span>审批状态</span></td>
<td width="20%"><span>审批意见</span></td>
<td width="20%"><span>审批时间</span></td>
</tr>
</thead>
<tbody>
<tr th:each="${printInfoBO.data.XXXX}">
<td><span th:text="${printInfoBO.data.XXXX}"></span></td>
<td><span th:text="${printInfoBO.data.XXXX}"></span></td>
<td><span th:text="${printInfoBO.data.XXXX}"></span></td>
<td><span th:text="${printInfoBO.data.XXXX}"></span></td>
<td><span th:text="${printInfoBO.data.XXXX}"></span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
三,传入参数生成页面转为String格式的Html原生代码
package com.stream.ecs.bill.listener.normal.complateEnd;
import com.stream.bill.manager.IBillFillManager;
import com.stream.core.client.FeignClientUtil;
import com.stream.core.util.BeanUtil;
import com.stream.core.util.ListUtil;
import com.stream.core.util.StringUtil;
import com.stream.ecs.bill.event.normal.EcsBillComplateEndEvent;
import com.stream.ecs.bill.util.ITextHtmlPdfUtil;
import com.stream.pub.bo.bill.print.QueryBillPrintInfoBO;
import com.stream.pub.bo.fill.AttachmentMergeBO;
import com.stream.pub.bo.fill.FileAttachment;
import com.stream.pub.bo.fill.FileAttachmentMergeReqBO;
import com.stream.pub.client.file.IFileAttachmentClient;
import com.stream.pub.client.storage.IStorageClient;
import com.stream.pub.enums.file.FileBusinessTypeEnum;
import org.apache.http.entity.ContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
* @author x
*/
@Component
public class CreatePDFOnCompleteEndEventListener {
@Autowired
private IXXXXXXXXManager XXXXXXXManager;
@Autowired
private SpringTemplateEngine templateEngine;
/**
* 生成单据PDF保存到镜像服务器
* @param event
*/
@Override
public void onApplicationEvent() {
MultipartFile fileResult;
/* 获取模板信息 */
List<QueryBillPrintInfoBO<Map<String, Object>>> queryBillPrintInfoBOS = billFillManager.queryBillPrintInfo(event.getBillInfo().getModuleCode(), mainIds, event.getUserInfo());
QueryBillPrintInfoBO<Map<String, Object>> printInfoBO = queryBillPrintInfoBOS.get(0);
/* 保存进map */
Map<String, Object> params = new HashMap();
params.put("printInfoBO", printInfoBO);
params.put("headList", ListUtil.split(printInfoBO.getHead().getFieldList(),3));
/* 创建上下文对象 */
Context context = new Context();
context.setVariables(params);
/* 构建生成html页面 返回字符串内容 */
String process = templateEngine.process("test.html", context);
System.out.println(process);
/* 生成pdf文件并获取字节数组 */
try {
byte[] bytes = ITextHtmlPdfUtil.createPdf(process);
/* 转换为MultipartFile文件 */
InputStream inputStream = new ByteArrayInputStream(bytes);
fileResult = new MockMultipartFile(event.getBillMain().getBillNo() + "-打印件.pdf",event.getBillMain().getBillNo() + "-打印件.pdf" ,ContentType.WILDCARD.toString(),inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
List<MultipartFile> fileList = new ArrayList<>();
fileList.add(fileResult);
/* 存储 */
List<AttachmentMergeBO> mergeBOS = FeignClientUtil.target(IStorageClient.class).batchUpload(fileList);
/* 存储附件信息 */
}
}
四,这是HTML生成PDF的核心工具类
package com.stream.ecs.bill.util;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider;
import com.itextpdf.io.font.FontProgram;
import com.itextpdf.io.font.FontProgramFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.font.FontProvider;
import com.itextpdf.styledxmlparser.css.media.MediaDeviceDescription;
import com.itextpdf.styledxmlparser.css.media.MediaType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* jzc
*/
@Slf4j
public class ITextHtmlPdfUtil {
/**
* html路径
*/
public static final String ANALYSE_HTML = "/html/pdfPage.html";
/**
* 中文支持插件在resource下的位置
*/
public static final String FONT = "/fonts/fzsjt.TTF";
/**
* PDF扩展名
*/
public static final String PDF_EXTENSION = ".pdf";
public static final String ANALYSE_REPORT = "前缀";
/**
* pdf html 的路径
*/
public static final String TEMPLATE_PATH = "/template";
public static byte[] createPdf(String htmlString) throws IOException {
PageSize pageSize = new PageSize(1200, 2000);
return createPdf(TEMPLATE_PATH, htmlString, pageSize);
}
// html 转 pdf 并返回字节数组
public static byte[] createPdf(String resourceTemplateUri, String html, PageSize pageSize) throws IOException {
//设置pdf的尺寸
ByteArrayOutputStream outputStream = null;
InputStream fontInputStream = null;
try {
outputStream = new ByteArrayOutputStream();
PdfWriter writer = new PdfWriter(outputStream);
PdfDocument pdf = new PdfDocument(writer);
pdf.setTagged();
pdf.setDefaultPageSize(pageSize);
ConverterProperties properties = new ConverterProperties();
//pdf宽高
properties.setBaseUri(resourceTemplateUri);
MediaDeviceDescription mediaDeviceDescription = new MediaDeviceDescription(MediaType.SCREEN);
mediaDeviceDescription.setWidth(pageSize.getWidth());
properties.setMediaDeviceDescription(mediaDeviceDescription);
//中文支持
/** 这个地方特别注意 必须要在Resource项目目录下传入字体格式包 可以去搜下载 很好找 */
FontProvider fontProvider = new DefaultFontProvider();
fontInputStream = ITextHtmlPdfUtil.class.getResourceAsStream(FONT);
FontProgram fontProgram = FontProgramFactory.createFont(IOUtils.toByteArray(fontInputStream));
fontProvider.addFont(fontProgram);
properties.setFontProvider(fontProvider);
InputStream inputStreamRoute = new ByteArrayInputStream(html.getBytes());
HtmlConverter.convertToPdf(inputStreamRoute, pdf, properties);
return outputStream.toByteArray();
} finally {
IOUtils.closeQuietly(outputStream);
IOUtils.closeQuietly(fontInputStream);
}
}
}
这是结构图