需要的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