在之前的一篇文章中写到过使用FreeMark生成pdf的案例,但是之前的写法使用的都是静态模板,今天我们来给它升级一下
使用静态模板的地址:https://blog.csdn.net/winerpro/article/details/121189977
在实际场景中我们使用的模板不是一成不变的,如果使用的是静态模板每次更换模板就需要把项目停了,把模板更换然后重启项目,这样一来并不是很友好,所以我们是不是可以考虑把它提出来,让它动态获取!
那么问题来了,文件又怎么动态获取,一个简单的方式,我们可以把它放进数据库,然后给它设置类型,想要获取什么模板就获取什么类型的模板,在复杂一点还可以加上版本号等等,这样操作起来对日后的维护非常的赞!理论有了,实际搞起来吧!
具体怎么配置的不在多数,大家可以回去看我之前的一篇,这里着重向说一下html文件的设计,我们知道使用offic可以把一个word文件转成html的格式,但是转完之后会出现一对见都没见过的样式,而且不但你不认识,FreeMark也不认识!所以最后的方式就是自己来写!其中样式我已标注,文章的最后我回把整个源码上传!
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<style>
@page {
@bottom-left {
content: element(footer);
vertical-align: top;
padding-top: 10px;
}
@top-right {
content: element(header);
vertical-align: bottom;
padding-bottom: 10px;
}
/*页面布局*/
margin-top: 2.3cm;
margin-left: 2.3cm;
margin-right: 2.3cm;
margin-bottom: 2.3cm;
size: A4 portrait;
}
/*默认字体*/
body{
font-family: 宋体;
font-size: 14pt;
}
div.header {
position: running(header);
font-size: 14px;
}
div.footer {
display: block;
margin-top: 0.5cm;
position: running(footer);
}
#pagenumber:before {
content: counter(page);
}
#pagecount:before {
content: counter(pages);
}
div.header {
position: running(header);
font-size: 14px;
}
div.footer {
display: block;
margin-top: 0.5cm;
position: running(footer);
}
/*换页(在这一行之前分页 / 之后分页是page-break-after: always)*/
.next-page {
page-break-before: always
}
/*宋体四号字*/
.four{
font-family: 宋体;
font-size: 14pt;
}
/*宋体四号加粗字*/
.fourb{
font-family: 宋体;
font-size: 14pt;
font-weight: bold;
}
/*分页*/
.next-page {
page-break-before: always
}
/*左对齐*/
.right {
text-align: right;
}
/*右对齐*/
.left {
text-align: left;
}
/*首行缩进*/
.indent{
text-indent:2em;
}
/*居中*/
.center{
text-align: center;
}
/*默认1.5倍行高*/
p{
line-height: 150%;
}
table {
width: 100%;
border-collapse:collapse;
margin-left:0pt;border:solid;
}
th{
text-align:center;
border:solid windowtext 1.0pt;
height:26pt;
}
td{
text-align:center;
border:solid windowtext 1.0pt;
height:26pt;
}
hr {
background-color: #000000;
border: dashed #000000 0.5px;
height: 1px;
}
</style>
</head>
<page_footer footer="page">
<div class='footer'>
<div th:fragment="footer">
<!-- <hr> -->
<div class="page-count">
<span id="pagenumber"></span>
</div>
</div>
</div>
</page_footer>
<body>
<page>
<p class="center"><span class="fourb">考试成绩</span> </p>
<p> </p>
<table>
<thead>
<tr>
<td style="width: 25%"><span>姓名</span></td>
<td style="width: 25%"><span>语文</span></td>
<td style="width: 25%"><span>数学</span></td>
<td style="width: 25%"><span>英语</span></td>
</tr>
</thead>
<tbody>
<#if data?? && (data?size > 0)>
<#list data as p>
<tr>
<td><span>${p.name!""}</span></td>
<td><span>${p.language!"0"}</span></td>
<td><span>${p.math!""}</span></td>
<td><span>${p.english!""}</span></td>
</tr>
</#list>
</#if>
</tbody>
</table>
<p class="indent"><span>在这段时间中,孩子的进步比较大。这些都离不开老师的精心栽培,孩子的进步我们家长都看在眼里,作为家长我们也希望,老师可以关注孩子更多一些。</span></p>
<p class="indent"><span>另外,希望老师可以多多与我们家长沟通,这样可以及时反映孩子的不足或是在校的一些情况,这样我们也可以更加了解孩子,孩子的缺点我们也会加以指导改正。</span></p>
<p> </p>
<p> </p>
<p> </p>
<p style="margin-left: 300pt"><span>家长签字:___________</span></p>
</page>
</body>
</html>
搞完模板算是成功了一大半,因为真正费时的就是模板的绘制,要一点一点扣,真正的java代码配置差异不大,把我主要的PdfUtil贴到这里参考一下
package com.pdf.generate_pdf.common;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.io.font.FontProgram;
import com.itextpdf.io.font.FontProgramFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.font.FontProvider;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
* PDF工具类
*
* @author zzt
* @date 2021/12/8
*/
@Component
@Slf4j
public class PdfUtil {
private static Configuration configuration;
static {
synchronized (PdfUtil.class) {
if (ObjectUtils.isEmpty(configuration)) {
configuration = new Configuration(Configuration.VERSION_2_3_28);
configuration.setTemplateLoader(new StringTemplateLoader());
configuration.setDefaultEncoding("UTF-8");
}
}
}
/**
* 生成pdf工具
*
* @param bytes:模板
* @param obj:数据
* @param outPutPath:输出路径
*/
public static void generatePdfByByteArrays(byte[] bytes, Object obj, String outPutPath) {
File file = new File(outPutPath);
try (OutputStream fos = new FileOutputStream(file);
PdfWriter pdfWriter = new PdfWriter(fos);
PdfDocument pdfDocument = new PdfDocument(pdfWriter);
StringWriter templateOut = new StringWriter();
InputStreamReader inputStreamReader = new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8)) {
Template template = new Template("template", inputStreamReader, configuration);
template.process(obj, templateOut);
String html = templateOut.toString();
if (StringUtils.isEmpty(html)) {
log.error("模板存在错误!");
}
HtmlConverter.convertToPdf(html, pdfDocument, buildConvertProperties());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 构建字体
*
* @return
*/
private static ConverterProperties buildConvertProperties() {
try {
ConverterProperties converterProperties = new ConverterProperties();
File fontsDirectory = getClassPathStaticFile("static/fonts");
File[] fontFiles = fontsDirectory.listFiles();
FontProvider fontProvider = new FontProvider();
for (File file : fontFiles) {
FontProgram fontProgram = FontProgramFactory.createFont(file.getAbsolutePath(), true);
fontProvider.addFont(fontProgram);
}
converterProperties.setFontProvider(fontProvider);
converterProperties.setBaseUri(getClassPathStaticFile("static").getAbsolutePath());
return converterProperties;
} catch (Exception e) {
log.error(e.getMessage());
return null;
}
}
/**
* 获取字体
*
* @param classPath
* @return
*/
public static File getClassPathStaticFile(String classPath) {
try {
File file = null;
if (StringUtils.isEmpty(classPath)) {
file = new File(ResourceUtils.getURL("classpath:").getPath());
} else {
file = new File(ResourceUtils.getURL("classpath:" + classPath).getPath());
}
return file;
} catch (Exception e) {
log.error("获取路径错误:" + classPath, e);
return null;
}
}
}
注意:有时候我们一次性不止会生成一个模板,可能会是多个,这就导致前台响应特别的慢,这时有的小伙伴会使用线程池来提高响应效率,那么这里就该注意一下此处我们的configuration使用的是单例模式创建的,使用多线程的时候会出问题
,所以如果用多线程,请把此处的配置改为下图
这是需要注意的点,然后来测试一下结果如何,
这里把文件上传 保存到了表中然用其中一条的id来生成一个pdf
pdf默认是输出到了D盘的根目录,大家可以根据需要修改
以下是测试的源码链接,resources下有建表语句和html模板,-----提取密码:wtwt