SpringBoot+Freemark渲染html導(dǎo)出PDF
摘要
PDF 導(dǎo)出在需要將信息紙質(zhì)化存檔時(shí)會(huì)使用到苹威。這里將介紹在 spring boot 框架下使用 Freemarker + iText 將 ftl 模板渲染成 html,然后導(dǎo)出 PDF 文件
Freemarker 官方文檔: https://freemarker.apache.org/docs/index.html
Maven依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>4.0.3</version>
</dependency>
核心代碼
@GetMapping(value = "/export-pdf")
public ResponseEntity exportPDF(TopoDeviceQueryDto queryDto) {
ResponseEntity response = null;
try {
HashMap<String, Object> mapData = Maps.newHashMap();
mapData.put("topDeviceList", topoDeviceService.findByList(queryDto));
String templateContent = HtmlUtils.getTemplateContent("TopoDevice.ftl", mapData);
response = this.getDownloadResponse(HtmlUtils.html2Pdf(templateContent), "device info.pdf");
} catch (Exception e) {
log.error("error occurs when downloading file");
response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
return response;
}
@Slf4j
public class HtmlUtils {
/**
* @return
* @throws Exception
*/
public static String getTemplateDirectory() {
ClassLoader classLoader = HtmlUtils.class.getClassLoader();
URL resource = classLoader.getResource("templates");
try {
return Objects.requireNonNull(resource).toURI().getPath();
} catch (URISyntaxException e) {
log.error("獲取模板文件夾失敗,{}", e);
}
return null;
}
/**
* 獲取模板內(nèi)容
*
* @param templateName 模板文件名
* @param paramMap 模板參數(shù)
* @return
* @throws Exception
*/
public static String getTemplateContent(String templateName, Map<String, Object> paramMap) throws Exception {
Configuration config = ApplicationContextUtil.getBean(FreeMarkerConfigurer.class).getConfiguration();
config.setDefaultEncoding("UTF-8");
Template template = config.getTemplate(templateName, "UTF-8");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, paramMap);
}
/**
* HTML 轉(zhuǎn) PDF
*
* @param content html內(nèi)容
* @param outPath 輸出pdf路徑
* @return 是否創(chuàng)建成功
*/
public static boolean html2Pdf(String content, String outPath) {
try {
ConverterProperties converterProperties = new ConverterProperties();
converterProperties.setCharset("UTF-8");
FontProvider fontProvider = new FontProvider();
fontProvider.addSystemFonts();
converterProperties.setFontProvider(fontProvider);
HtmlConverter.convertToPdf(content, new FileOutputStream(outPath), converterProperties);
} catch (Exception e) {
log.error("生成模板內(nèi)容失敗,{}", e);
return false;
}
return true;
}
/**
* HTML 轉(zhuǎn) PDF
*
* @param content html內(nèi)容
* @return PDF字節(jié)數(shù)組
*/
public static ByteArrayOutputStream html2Pdf(String content) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
ConverterProperties converterProperties = new ConverterProperties();
converterProperties.setCharset("UTF-8");
FontProvider fontProvider = new FontProvider();
fontProvider.addSystemFonts();
converterProperties.setFontProvider(fontProvider);
HtmlConverter.convertToPdf(content, outputStream, converterProperties);
} catch (Exception e) {
log.error("生成 PDF 失敗,{}", e);
}
return outputStream;
}
}
@Configuration
@AutoConfigureOrder(-1)
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
public static Object getBeanByName(String beanName) {
if (applicationContext == null) {
return null;
}
return applicationContext.getBean(beanName);
}
public static <T> T getBean(Class<T> type) {
return applicationContext.getBean(type);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextUtil.applicationContext = applicationContext;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UFT-8">
<title>網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)表</title>
<style>
.vertical-line {
height: 26px;
border-right: 2px solid #d0d0d0;
float: left;
margin-top: -10px;
margin-left: -15px;
}
.horizontal-line {
width: 15px;
border-top: 2px solid #d0d0d0;
float: left;
margin-top: 16px;
margin-left: -15px;
}
</style>
</head>
<style type="text/css">
table {
border-collapse: collapse;
margin: 0 auto;
text-align: center;
}
.table td,
.table th {
border: 1px solid #cad9ea;
color: #666;
height: 30px;
}
.table thead th {
background-color: #cce8eb;
width: 100px;
}
.table tr:nth-child(odd) {
background: #fff;
}
.table tr:nth-child(even) {
background: #f5fafa;
}
.tableB th {
border: 1px solid #cad9ea;
color: #666;
height: 30px;
}
.tableB thead th {
background-color: #e7f1ef;
width: 100px;
}
</style>
<!-- Table goes in the document BODY -->
<body>
<div>
<table width="90%" class="table">
<caption>
<h2>網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)表</h2>
</caption>
<thead>
<tr>
<th>設(shè)備名稱</th>
<th>符號(hào)</th>
<th>端口</th>
<th>地址</th>
<th>網(wǎng)絡(luò)中的單元</th>
</tr>
</thead>
<tbody>
<#if topDeviceList??>
<#list topDeviceList as top_device>
<tr>
<td><#if top_device.deviceName??>${top_device.deviceName}</#if></td>
<td><#if top_device.mark??>${top_device.mark}</#if></td>
<td><#if top_device.port??>${top_device.port}</#if></td>
<td><#if top_device.ip??>${top_device.ip}</#if></td>
<td><#if top_device.unit??>${top_device.unit}</#if></td>
</tr>
</#list>
</#if>
</tbody>
</table>
</div>
</body>
</html>
踩坑記錄
解決Spring項(xiàng)目打成Jar包后Freemarker找不到模板的問題
freemarker在jar包中無法使用類加載器獲取resourse目錄下的templates文件咖摹,出現(xiàn)的問題代碼如下:(本地測(cè)試正常絮宁,打包jar后無法獲取模板)
@GetMapping(value = "/export-pdf")
public ResponseEntity exportPDF(TopoDeviceQueryDto queryDto) {
ResponseEntity response = null;
try {
HashMap<String, Object> mapData = Maps.newHashMap();
mapData.put("topDeviceList", topoDeviceService.findByList(queryDto));
String templateContent = HtmlUtils.getTemplateContent("TopoDevice.ftl", mapData);
response = this.getDownloadResponse(HtmlUtils.html2Pdf(templateContent), "device info.pdf");
} catch (Exception e) {
log.error("error occurs when downloading file");
response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
return response;
}
public static String getTemplateDirectory() {
ClassLoader classLoader = HtmlUtils.class.getClassLoader();
URL resource = classLoader.getResource("templates");
try {
return Objects.requireNonNull(resource).toURI().getPath();
} catch (URISyntaxException e) {
log.error("獲取模板文件夾失敗,{}", e);
}
return null;
}
/**
* 獲取模板內(nèi)容
*
* @param templateDirectory 模板文件夾
* @param templateName 模板文件名
* @param paramMap 模板參數(shù)
* @return
* @throws Exception
*/
public static String getTemplateContent(String templateDirectory, String templateName, Map<String, Object> paramMap) throws Exception {
Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
try {
configuration.setDirectoryForTemplateLoading(new File(templateDirectory));
} catch (Exception e) {
System.out.println("-- exception --");
}
Writer out = new StringWriter();
Template template = configuration.getTemplate(templateName, "UTF-8");
template.process(paramMap, out);
out.flush();
out.close();
return out.toString();
}
/**
* HTML 轉(zhuǎn) PDF
*
* @param content html內(nèi)容
* @return PDF字節(jié)數(shù)組
*/
public static ByteArrayOutputStream html2Pdf(String content) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
ConverterProperties converterProperties = new ConverterProperties();
converterProperties.setCharset("UTF-8");
FontProvider fontProvider = new FontProvider();
fontProvider.addSystemFonts();
converterProperties.setFontProvider(fontProvider);
HtmlConverter.convertToPdf(content, outputStream, converterProperties);
} catch (Exception e) {
log.error("生成 PDF 失敗,{}", e);
}
return outputStream;
}
修改后的代碼(打包jar后正常獲取模板信息)
@GetMapping(value = "/export-pdf")
public ResponseEntity exportPDF(TopoDeviceQueryDto queryDto) {
ResponseEntity response = null;
try {
HashMap<String, Object> mapData = Maps.newHashMap();
mapData.put("topDeviceList", topoDeviceService.findByList(queryDto));
String templateContent = HtmlUtils.getTemplateContent("TopoDevice.ftl", mapData);
response = this.getDownloadResponse(HtmlUtils.html2Pdf(templateContent), "device info.pdf");
} catch (Exception e) {
log.error("error occurs when downloading file");
response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
return response;
}
/**
* 獲取模板內(nèi)容
*
* @param templateName 模板文件名
* @param paramMap 模板參數(shù)
* @return
* @throws Exception
*/
public static String getTemplateContent(String templateName, Map<String, Object> paramMap) throws Exception {
Configuration config = ApplicationContextUtil.getBean(FreeMarkerConfigurer.class).getConfiguration();
config.setDefaultEncoding("UTF-8");
Template template = config.getTemplate(templateName, "UTF-8");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, paramMap);
}
public static <T> T getBean(Class<T> type) {
return applicationContext.getBean(type);
}
/**
* HTML 轉(zhuǎn) PDF
*
* @param content html內(nèi)容
* @return PDF字節(jié)數(shù)組
*/
public static ByteArrayOutputStream html2Pdf(String content) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
ConverterProperties converterProperties = new ConverterProperties();
converterProperties.setCharset("UTF-8");
FontProvider fontProvider = new FontProvider();
fontProvider.addSystemFonts();
converterProperties.setFontProvider(fontProvider);
HtmlConverter.convertToPdf(content, outputStream, converterProperties);
} catch (Exception e) {
log.error("生成 PDF 失敗,{}", e);
}
return outputStream;
}
ps:.ftl模板文件放在 templates 目錄下
總結(jié):freemarker無法使用類加載器獲取jar包中的resourse目錄下的templates文件
解決辦法:注入FreeMarkerConfigurer
配置類钙皮,因?yàn)閒reemarker模板的默認(rèn)目錄就在resourse下的templates目錄下符欠,
使用freeMarkerConfigurer.getConfiguration().getTemplate("mailTemplate.ftl")
可直接獲取到對(duì)應(yīng)的模板文件
解決Freemark導(dǎo)出Pdf部署Linux亂碼問題
項(xiàng)目部署完之后發(fā)現(xiàn)Linux下個(gè)別字體丟失的問題调限,剛開始以為是編碼問題麻捻,經(jīng)排查是Linux系統(tǒng)中文字體缺失問題
第一步:拷貝Windows下系統(tǒng)的中文字體 Windows下字體的目錄是在C:\Windows\Fonts
第二步:移動(dòng)字體庫到linux系統(tǒng)下的字體庫文件夾/usr/share/fonts/下
第三步:讓linux系統(tǒng)識(shí)別新的中文字體:終端輸入:sudo fc-cache -fv