需要的jar
<!-- freemarker,生成 html -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
<!-- 渲染 css 样式 -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.1.16</version>
</dependency>
<!-- iText,export pdf -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
资源文件处理工具类
import org.springframework.util.ResourceUtils;
import java.io.File;
import java.io.FileNotFoundException;
/**
* @Description: 项目静态资源文件工具类
* 仅可用于包含在web项目中的资源文件路径,资源文件必须放置于 web 模块下
* @Author: junqiang.lu
* @Date: 2019/1/4
*/
public class ResourceFileUtil {
/**
* 获取资源文件
*
* @param relativePath 资源文件相对路径(相对于 resources路径,路径 + 文件名)
* eg: "templates/pdf_export_demo.ftl"
* @return
* @throws FileNotFoundException
*/
public static File getFile(String relativePath) throws FileNotFoundException {
if (relativePath == null || relativePath.length() == 0) {
return null;
}
if (relativePath.startsWith("/")) {
relativePath = relativePath.substring(1);
}
File file = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX
+ relativePath);
return file;
}
/**
* 获取资源绝对路径
*
* @param relativePath 资源文件相对路径(相对于 resources路径,路径 + 文件名)
* eg: "templates/pdf_export_demo.ftl"
* @return
* @throws FileNotFoundException
*/
public static String getAbsolutePath(String relativePath) throws FileNotFoundException {
return getFile(relativePath).getAbsolutePath();
}
/**
* 获取资源父级目录
*
* @param relativePath 资源文件相对路径(相对于 resources路径,路径 + 文件名)
* eg: "templates/pdf_export_demo.ftl"
* @return
* @throws FileNotFoundException
*/
public static String getParent(String relativePath) throws FileNotFoundException {
return getFile(relativePath).getParent();
}
/**
* 获取资源文件名
*
* @param relativePath 资源文件相对路径(相对于 resources路径,路径 + 文件名)
* eg: "templates/pdf_export_demo.ftl"
* @return
* @throws FileNotFoundException
*/
public static String getFileName(String relativePath) throws FileNotFoundException {
return getFile(relativePath).getName();
}
}
PDF 生成工具类
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.BaseFont;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.*;
import java.util.Map;
/**
* @Description: pdf 导出工具类
* @Author: junqiang.lu
* @Date: 2018/12/25
*/
public class PDFUtil {
private PDFUtil(){}
private volatile static Configuration configuration;
static {
if (configuration == null) {
synchronized (PDFUtil.class) {
if (configuration == null) {
configuration = new Configuration(Configuration.VERSION_2_3_28);
}
}
}
}
/**
* freemarker 引擎渲染 html
*
* @param dataMap 传入 html 模板的 Map 数据
* @param ftlFilePath html 模板文件相对路径(相对于 resources路径,路径 + 文件名)
* eg: "templates/pdf_export_demo.ftl"
* @return
*/
public static String freemarkerRender(Map<String, Object> dataMap, String ftlFilePath) {
Writer out = new StringWriter();
configuration.setDefaultEncoding("UTF-8");
configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
try {
configuration.setDirectoryForTemplateLoading(new File(ResourceFileUtil.getParent(ftlFilePath)));
configuration.setLogTemplateExceptions(false);
configuration.setWrapUncheckedExceptions(true);
Template template = configuration.getTemplate(ResourceFileUtil.getFileName(ftlFilePath));
template.process(dataMap, out);
out.flush();
return out.toString();
} catch (IOException e) {
e.printStackTrace();
} catch (TemplateException e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 使用 iText 生成 PDF 文档
*
* @param htmlTmpStr html 模板文件字符串
* @param fontFile 所需字体文件(相对路径+文件名)
* */
public static byte[] createPDF(String htmlTmpStr, String fontFile) {
ByteArrayOutputStream outputStream = null;
byte[] result = null;
try {
outputStream = new ByteArrayOutputStream();
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(htmlTmpStr);
ITextFontResolver fontResolver = renderer.getFontResolver();
// 解决中文支持问题,需要所需字体(ttc)文件
fontResolver.addFont(ResourceFileUtil.getAbsolutePath(fontFile),BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
renderer.layout();
renderer.createPDF(outputStream);
result=outputStream.toByteArray();
if(outputStream != null) {
outputStream.flush();
outputStream.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
}
PDF 导出所需资源文件
PDF 导出需要 freemarker 的渲染模板 ftl 文件
关于 ftl 模板的一些信息: ftl 模板使用和 html 一致的标签,和 html 不同的是 ftl 模板文件可以添加独有的逻辑元算标签, 如 if-else, for 等,逻辑运算标签具体可参考 freemarker 官方文档:
https://freemarker.apache.org/docs/ref_directives.html
这是我的模板:pz.ftl
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Title</title>
<style>
@page {
size: 279mm 216mm;
}
table.pzjbt {
border-collapse:collapse;
border-color:#0094b5;
border-style:solid;
border-width:1px;
margin-bottom:3px;
margin-top:3px;
text-align: center;
}
td.pzjbt {
border-left-width:1px;
border-left-style:solid;
border-left-color:#000000;
border-top-width:1px;
border-top-style:solid;
border-top-color:#000000;
border-right-width:1px;
border-right-style:solid;
border-right-color:#000000;
border-bottom-width:1px;
border-bottom-style:solid;
border-bottom-color:#000000;
padding-top:1px;
padding-bottom:1px;
padding-left:1px;
padding-right:1px;
vertical-align:top;
text-align: center;
}
p.pzdl {
color:#000000;
font-family:NSimSun;
font-size:12px;
font-style:normal;
font-variant:normal;
font-weight:300;
line-height:1.2;
margin-bottom:0;
margin-left:0;
margin-right:0;
margin-top:0;
orphans:1;
page-break-after:auto;
page-break-before:auto;
text-align:center;
text-align-last:center;
text-decoration:none;
text-indent:0;
text-transform:none;
widows:1;
}
td.CellOverride-1 {
border-bottom-color:#199fbc;
border-bottom-style:solid;
border-bottom-width:1px;
border-left-color:#0094b5;
border-left-style:solid;
border-left-width:1px;
border-right-color:#0094b5;
border-right-style:solid;
border-right-width:1px;
border-top-color:#0094b5;
border-top-style:solid;
border-top-width:2px;
vertical-align:middle;
}
td.CellOverride-2 {
border-bottom-color:#0094b5;
border-bottom-style:solid;
border-bottom-width:1px;
border-left-color:#199fbc;
border-left-style:solid;
border-left-width:1px;
border-right-color:#191919;
border-right-style:solid;
border-right-width:0px;
border-top-color:#199fbc;
border-top-style:solid;
border-top-width:1px;
vertical-align:middle;
}
td.CellOverride-3 {
border-bottom-color:#0094b5;
border-bottom-style:solid;
border-bottom-width:1px;
border-left-color:#191919;
border-left-style:solid;
border-left-width:0px;
border-right-color:#0094b5;
border-right-style:solid;
border-right-width:1px;
border-top-color:#0094b5;
border-top-style:solid;
border-top-width:1px;
vertical-align:middle;
}
td.CellOverride-4 {
border-bottom-color:#0094b5;
border-bottom-style:solid;
border-bottom-width:1px;
border-left-color:#199fbc;
border-left-style:solid;
border-left-width:1px;
border-right-color:#191919;
border-right-style:solid;
border-right-width:0px;
border-top-color:#0094b5;
border-top-style:solid;
border-top-width:1px;
vertical-align:middle;
}
td.CellOverride-5 {
border-bottom-color:#199fbc;
border-bottom-style:solid;
border-bottom-width:1px;
border-left-color:#199fbc;
border-left-style:solid;
border-left-width:1px;
border-right-color:#191919;
border-right-style:solid;
border-right-width:0px;
border-top-color:#0094b5;
border-top-style:solid;
border-top-width:1px;
vertical-align:middle;
}
td.CellOverride-6 {
background-color:#0094b5;
border-bottom-style:solid;
border-bottom-width:0px;
border-left-style:solid;
border-left-width:0px;
border-right-color:#0094b5;
border-right-style:solid;
border-right-width:1px;
border-top-color:#199fbc;
border-top-style:solid;
border-top-width:1px;
vertical-align:middle;
}
td.CellOverride-7 {
background-color:#0094b5;
border-bottom-style:solid;
border-bottom-width:0px;
border-left-color:#0094b5;
border-left-style:solid;
border-left-width:1px;
border-right-color:#0094b5;
border-right-style:solid;
border-right-width:1px;
border-top-color:#199fbc;
border-top-style:solid;
border-top-width:1px;
vertical-align:middle;
}
td.CellOverride-8 {
background-color:#0094b5;
border-bottom-style:solid;
border-bottom-width:0px;
border-left-style:solid;
border-left-width:0px;
border-right-style:solid;
border-right-width:0px;
border-top-color:#0094b5;
border-top-style:solid;
border-top-width:1px;
vertical-align:middle;
}
p.ParaOverride-1 {
text-align:center;
text-align-last:center;
}
p.ParaOverride-2 {
text-align:left;
}
span.CharOverride-1 {
color:#191919;
font-family:NSimSun;
font-style:normal;
font-weight:normal;
}
span.CharOverride-2 {
font-family:NSimSun;
font-size:9px;
font-style:normal;
font-weight:normal;
}
span.CharOverride-3 {
color:#f5760a;
font-family:NSimSun;
font-size:9px;
font-style:normal;
font-weight:300;
}
span.CharOverride-4 {
font-family:NSimSun;
font-style:normal;
font-weight:300;
}
td._idGenCellOverride-1 {
border-right-style:solid;
border-right-width:0px;
}
col._idGenTableRowColumn-1 {
width:81px;
}
col._idGenTableRowColumn-2 {
width:35px;
}
col._idGenTableRowColumn-3 {
width:45px;
}
tr._idGenTableRowColumn-4 {
min-height:26px;
}
tr._idGenTableRowColumn-5 {
min-height:23px;
}
tr._idGenTableRowColumn-6 {
min-height:31px;
}
img._idGenObjectAttribute-1 {
height:25px;
width:57px;
}
img._idGenObjectAttribute-2 {
height:20px;
width:46px;
}
</style>
</head>
<body>
<div id="_idContainer002" style="width: 1000px;">
<table id="table001" class="pzjbt" style="width: 100%;margin-left: 20%;">
<colgroup>
<col class="_idGenTableRowColumn-1" />
<col class="_idGenTableRowColumn-2" />
<col class="_idGenTableRowColumn-3" />
<col class="_idGenTableRowColumn-1" />
<col class="_idGenTableRowColumn-1" />
</colgroup>
<tr class="pzjbt _idGenTableRowColumn-4">
<td class="pzjbt CellOverride-1" colspan="5">
<p class="pzdl ParaOverride-1"><span class="CharOverride-1">收 据</span></p>
</td>
</tr>
<tr class="pzjbt _idGenTableRowColumn-5">
<td class="pzjbt CellOverride-2" colspan="2">
<p class="pzdl ParaOverride-1"><span class="CharOverride-2">日期</span></p>
</td>
<td class="pzjbt CellOverride-3" colspan="3">
<p class="pzdl ParaOverride-1"><span class="CharOverride-3">${(createTime?string("yyyy-MM-dd HH:mm:ss"))!}</span></p>
</td>
</tr>
<tr class="pzjbt _idGenTableRowColumn-5">
<td class="pzjbt CellOverride-4" colspan="2">
<p class="pzdl ParaOverride-1"><span class="CharOverride-2">交易编号</span></p>
</td>
<td class="pzjbt CellOverride-3" colspan="3">
<p class="pzdl ParaOverride-1"><span class="CharOverride-3">${orderCode!''}</span></p>
</td>
</tr>
<tr class="pzjbt _idGenTableRowColumn-5">
<td class="pzjbt CellOverride-4" colspan="2">
<p class="pzdl ParaOverride-1"><span class="CharOverride-2">交易类型</span></p>
</td>
<td class="pzjbt CellOverride-3" colspan="3">
<p class="pzdl ParaOverride-1"><span class="CharOverride-3">捐赠</span></p>
</td>
</tr>
<tr class="pzjbt _idGenTableRowColumn-5">
<td class="pzjbt CellOverride-4" colspan="2">
<p class="pzdl ParaOverride-1"><span class="CharOverride-2">交易金额</span></p>
</td>
<td class="pzjbt CellOverride-3" colspan="3">
<p class="pzdl ParaOverride-1"><span class="CharOverride-3">${productPrice!0}</span></p>
</td>
</tr>
<tr class="pzjbt _idGenTableRowColumn-5">
<td class="pzjbt CellOverride-5" colspan="2">
<p class="pzdl ParaOverride-1"><span class="CharOverride-2">付款人</span></p>
</td>
<td class="pzjbt CellOverride-3" colspan="3">
<p class="pzdl ParaOverride-1"><span class="CharOverride-3">${userName!''}</span></p>
</td>
</tr>
</table>
</div>
</body>
</html>
html前端入口
<a href="${basePath}/exportPdf?orderId=${saleOrder.id}">下载</a>
Controller 层
@RequestMapping(value = "/exportPdf", method = {RequestMethod.POST, RequestMethod.GET},
produces = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<?> exportPdf(Long orderId){
try {
SaleOrderVo saleOrder = saleOrderExtService.getSaleOrderById(orderId);
ResponseEntity<?> responseEntity = export(saleOrder);
return responseEntity;
} catch (Exception e) {
e.printStackTrace();
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return new ResponseEntity<String>("{ \"code\" : \"404\", \"message\" : \"not found\" }",
headers, HttpStatus.NOT_FOUND);
}
/**
* PDF 文件导出
*
* @return
*/
public ResponseEntity<?> export(SaleOrderVo saleOrder) {
HttpHeaders headers = new HttpHeaders();
/**
* 数据导出(PDF 格式)
*/
Map<String, Object> dataMap = new HashMap<>(16);
dataMap.put("createTime",saleOrder.getCreateTime());
dataMap.put("orderCode",saleOrder.getOrderCode());
dataMap.put("productPrice",saleOrder.getProductPrice());
dataMap.put("userName",saleOrder.getUserName());
String htmlStr = PDFUtil.freemarkerRender(dataMap, "templates/website/pz/pz.ftl");
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("templates/website/pz/simsun.ttc");
// htmlStr = htmlStr.replaceAll("<br>", "");
//htmlStr = htmlStr.replaceAll("</br>", "");
byte[] pdfBytes = PDFUtil.createPDF(htmlStr, "templates/website/pz/simsun.ttc");
if (pdfBytes != null && pdfBytes.length > 0) {
String fileName = UUID.randomUUID() + ".pdf";
headers.setContentDispositionFormData("attachment", fileName);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity<byte[]>(pdfBytes, headers, HttpStatus.OK);
}
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return new ResponseEntity<String>("{ \"code\" : \"404\", \"message\" : \"not found\" }",
headers, HttpStatus.NOT_FOUND);
}
simsun.ttc字体文件 :链接:https://pan.baidu.com/s/1UO6uSKaL-DERaFYaqZqs8g 密码:yhdo
点击下载效果
遇到的错误
simsun.ttc is not a valid TTF file
原因:问题是Maven正在过滤字体文件并破坏它们。
pom.xml加入
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>ttf</nonFilteredFileExtension>
<nonFilteredFileExtension>woff</nonFilteredFileExtension>
<nonFilteredFileExtension>woff2</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
参考文章: http://www.mamicode.com/info-detail-2411295.html
参考文章:https://blog.csdn.net/Mrqiang9001/article/details/86241836