SpringBoot通过FreeMarker生成pdf中遇到的难点记录
一、Configuration加载ftl模板
1.如果模板是放在本地盘符中
String templateName = "abc.ftl";
Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
cfg.setDefaultEncoding("UTF-8");
cfg.setDirectoryForTemplateLoading(new File("E:/"));
Template template = cfg.getTemplate(templateName);
2如果模板是放在Springboot项目的resource路径下
String templateName = "abc.ftl";
Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
cfg.setDefaultEncoding("UTF-8");
cfg.setClassForTemplateLoading(this.getClass(), "/");
Template template = cfg.getTemplate(templateName);
3如果生成了模板可以使用字符流接收然后转换成文件流,不需要生成到本地
private StringWriter htmHandler(String templateName,
Map<String, Object> paramMap) throws Exception {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
// Configuration cfg = new Configuration();
cfg.setDefaultEncoding("UTF-8");
// 获取模板的路径
System.out.println("==================获取模板路径=======================");
// cfg.setClassForTemplateLoading(this.getClass(), "/");
cfg.setDirectoryForTemplateLoading(new File("E:/"));
System.out.println(cfg.getTemplateLoader().toString());
System.out.println("===================获取模板路径成功=======================");
//设置模板
Template template = cfg.getTemplate(templateName);
StringWriter out = new StringWriter();
template.process(paramMap, out);
return out;
}
二、ftl文件生成pdf,导入项目中的图片
如果直接在src中写死一个本地路径也是可以读取显示的,不过无法做到动态赋值的效果
如果使用动态赋值的方式,java中的代码只能通过uri的方式,这里使用com.itextpdf.text.Image类来接收一个url并转成uri路径
![](https://img-blog.csdnimg.cn/img_convert/f3b93b6838297c8b44e81e4af47b317f.png)
URL url = Thread.currentThread().getContextClassLoader().getResource("baidu.jpg");
Image image = Image.getInstance(url);
params.put("image", image.getUrl().toURI());
三、遇到一个需求是打印pdf时表格分页截断后需要在第二页有表头,这个纯css就可以实现。
关键样式
// 固定页面大小
@page {
margin-left: 7mm;
margin-right: 7mm;
margin-bottom: 20mm;
margin-top: 13mm;
}
表格中需要有<table><thead></thead><tbody></tbody></table>结构
![](https://img-blog.csdnimg.cn/img_convert/77b2361fa1ed2d54cfa5200b77a722d2.png)
表格样式
.srv-table{
page-break-inside:auto;
-fs-table-paginate:paginate;
border-spacing:0;
word-break:break-strict;
}
.srv-table-tr{
page-break-inside:avoid;
page-break-after:auto;
}
效果
![](https://img-blog.csdnimg.cn/img_convert/d0f2292c59a5afb544650f84cb3609b9.png)
![](https://img-blog.csdnimg.cn/img_convert/e402e9cbfd7937b653bfb6b10d67a733.png)
完整代码提供
<!DOCTYPE html>
<html lang="zh-CN" style="width: 210mm;height: 297mm;background: white;font-size: 10px;font-family: simsun">
<head>
<style type="text/css">
/*解决html转pdf文件中文不显示的问题*/
body {
font-family: SimSun;
font-weight: bold;
font-size: 11px;
}
/*设定纸张大小*/
/* A4纸 */
/* @page{size:210mm*297mm} */
/*@page{size: 210mm 297mm}*/
/*@page{size: 210mm 297mm}*/
table {
width: 100%;
margin-top: 4mm;
}
img {
background: #fff;
box-shadow: none;
}
@page :first {
margin-top: 0mm;
}
@page {
margin-left: 7mm;
margin-right: 7mm;
margin-bottom: 20mm;
margin-top: 13mm;
}
.srv-table{
page-break-inside:auto;
-fs-table-paginate:paginate;
border-spacing:0;
word-break:break-strict;
}
.srv-table-tr{
page-break-inside:avoid;
page-break-after:auto;
}
</style>
</head>
<body>
<div style="margin-left:90mm;width: 107mm;height: 26mm;">
图片 <img style="width: 26mm;height: 26mm;margin-left:-1mm" src="${image!}"/>
</div>
<div style="width: 190mm;">
<div style="background:gray;width: 10px;height: 10px; border-bottom-right-radius:10;display: inline-block;"></div>
<div style="display: inline-block;">明细</div>
<table class="srv-table" style="margin-top: 0mm;">
<thead>
<tr class="srv-table-tr">
<td>NO</td>
<td>编码</td>
<td>名称</td>
<td style="margin: 0 100px;">类型</td>
<td>作业组</td>
<td>价格</td>
</tr>
</thead>
<tbody>
<#list srvList as arr>
<tr>
<td>${arr_index + 1}</td>
<td>${arr.srvCode!}</td>
<td>${arr.srvName!?html}</td>
<td style="margin: 0 0 0 101px;">${arr.srvTypeName!}</td>
<td>${arr.workByCode!}</td>
<td>¥${(arr.settlType! == 2) ? string(0, arr.sellPrice!?string(",##0.00"))}</td>
<#if (arr.msuiteCode??&&arr.msuiteCode!='') || (arr.selfDealerSetMealCode??&&arr.selfDealerSetMealCode!='')>
<td>
${(arr.msuiteCode! == '')?string(arr.selfDealerSetMealCode!, arr.msuiteCode!)}
</td>
</#if>
</tr>
</#list>
</tbody>
</table>
</div>
</body>
</html>
package com.pdf.gtmcafterpdf.pdf;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.BaseFont;
import com.pdf.gtmcafterpdf.Templates.SrvList;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.xhtmlrenderer.pdf.ITextRenderer;
import javax.xml.bind.DatatypeConverter;
import java.io.*;
import java.net.URL;
import java.util.*;
public class BlogPdf {
public static void main(String[] args) throws Exception {
String templateName ="blog.ftl";
Map<String, Object> params = new HashMap<>(16);
URL url = Thread.currentThread().getContextClassLoader().getResource("baidu.jpg");
Image image = Image.getInstance(url);
params.put("image", image.getUrl().toURI());
Scanner scanner = new Scanner(System.in);
System.out.println("请输入项目数量:");
if (scanner.hasNextLine()){
Integer srvNum = Integer.parseInt(scanner.nextLine());
System.out.println("目前项目数量:" + srvNum);
params.put("srvList", getSrvList(srvNum));
}else{
params.put("srvList", getSrvList(0));
}
StringWriter stringWriter = new BlogPdf().htmHandler(templateName, params);
new BlogPdf().blogPdfHandler(stringWriter);
}
public static List<SrvList> getSrvList(int num){
List<SrvList> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
SrvList srvList = new SrvList();
srvList.setSrvCode("100000"+i);
srvList.setSrvName("作业项目");
srvList.setSrvTypeName("A");
srvList.setSettlType(2);
srvList.setSellPrice(100.00);
srvList.setSelfDealerSetMealCode("2001");
list.add(srvList);
}
return list;
}
private StringWriter htmHandler(String templateName,
Map<String, Object> paramMap) throws Exception {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
// Configuration cfg = new Configuration();
cfg.setDefaultEncoding("UTF-8");
// 获取模板的路径
System.out.println("==================获取模板路径=======================");
// cfg.setClassForTemplateLoading(this.getClass(), "/");
cfg.setDirectoryForTemplateLoading(new File("E:/"));
System.out.println(cfg.getTemplateLoader().toString());
System.out.println("===================获取模板路径成功=======================");
//设置模板
Template template = cfg.getTemplate(templateName);
StringWriter out = new StringWriter();
template.process(paramMap, out);
return out;
}
private void blogPdfHandler(StringWriter writer)throws Exception {
StringBuffer buffer = writer.getBuffer();
ByteArrayOutputStream os = new ByteArrayOutputStream();
ITextRenderer renderer = new ITextRenderer();
System.out.println(buffer.toString());
renderer.setDocumentFromString(buffer.toString());
org.xhtmlrenderer.pdf.ITextFontResolver fontResolver = renderer
.getFontResolver();
try {
System.out.println("====================================获取字体路径================================");
String path = Thread.currentThread().getContextClassLoader().getResource("simsun.ttf").toString();
System.out.println("=====================================获取字体路径成功"+path+"===========================================");
fontResolver.addFont(path, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
e.printStackTrace();
}
renderer.layout();
try {
renderer.createPDF(os);
} catch (Exception e) {
e.printStackTrace();
}
os.close();
// 将文件流转成base64返回前端
String binary = printBase64Binary(os);
System.out.println(binary);
// String savePath = "E:\\";
String savePath = "E:/";
String fileName = "blog.pdf";
System.out.println("文件保存路径=>{}"+savePath+fileName);
base64ToFile(binary, fileName, savePath);
}
// 文件转成base64
public String printBase64Binary(ByteArrayOutputStream os){
byte[] bytes = os.toByteArray();
String strPDF = DatatypeConverter.printBase64Binary(bytes);
return strPDF;
}
// base64转成pdf
public void base64ToFile(String base64, String fileName, String savePath) {
File file = null;
//创建文件目录
String filePath = savePath;
File dir = new File(filePath);
if (!dir.exists() && !dir.isDirectory()) {
dir.mkdirs();
}
BufferedOutputStream bos = null;
java.io.FileOutputStream fos = null;
try {
byte[] bytes = Base64.getDecoder().decode(base64);
file=new File(filePath + fileName);
fos = new java.io.FileOutputStream(file);
bos = new BufferedOutputStream(fos);
bos.write(bytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}