springboot导出pdf,解决中文问题
引入包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>html2pdf</artifactId> <version>5.0.5</version> </dependency>
去windows目录下 获取字体 C:\Windows\Fonts simsunb.ttf、或者下载.ttc
@GetMapping("/download")
@ApiOperation("下载")
public void download(AlgorithmRouteDownloadDto downloadDto, HttpServletResponse response) {
try (OutputStream os = response.getOutputStream()) {
response.reset();
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition", "attachment;filename=user_pdf_" + System.currentTimeMillis() + ".pdf");
//获取指定的路由定义
List<AlgorithmRouteDownloadVo> routeApis = this.algorithmRouteMapper.selectJoinList(AlgorithmRouteDownloadVo.class, queryWrapper);
HashMap<String, Object> mapData = Maps.newHashMap();
mapData.put("routeApis", routeApis);
String templateContent = HtmlUtils.getTemplateContent("index.ftl", mapData);
ByteArrayOutputStream byteArrayOutputStream = HtmlUtils.html2Pdf(templateContent);
os.write(byteArrayOutputStream.toByteArray());
} catch (Exception e) {
log.error("error occurs when downloading file",e);
}
}
package com.ev.edge.utils;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.layout.font.FontProvider;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
public class HtmlUtils {
/**
* @return
* @throws Exception
*/
public static String getFileDirectory(String fileName) {
ClassLoader classLoader = HtmlUtils.class.getClassLoader();
URL resource = classLoader.getResource(fileName);
try {
return Objects.requireNonNull(resource).toURI().getPath();
} catch (URISyntaxException e) {
log.error("获取模板文件夹失败,{}", e);
}
return null;
}
/**
* 获取模板内容
*
* @param templateName 模板文件名
* @param paramMap 模板参数
* @return
* @throws Exception
*/
public static String getTemplateContent(String templateName, Map<String, Object> paramMap) throws Exception {
Configuration config = SpringUtil.getBean(FreeMarkerConfigurer.class).getConfiguration();
config.setDefaultEncoding("UTF-8");
config.setEncoding(Locale.CHINA, "UTF-8");
Template template = config.getTemplate(templateName, "UTF-8");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, paramMap);
}
/**
* HTML 转 PDF
*
* @param content html内容
* @param outPath 输出pdf路径
* @return 是否创建成功
*/
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) {
e.printStackTrace();
log.error("生成模板内容失败,{}", e);
return false;
}
return true;
}
/**
* HTML 转 PDF
*
* @param content html内容
* @return PDF字节数组
*/
public static ByteArrayOutputStream html2Pdf(String content) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
ConverterProperties converterProperties = new ConverterProperties();
converterProperties.setCharset("UTF-8");
FontProvider fontProvider = new FontProvider();
PdfFont fftFont = PdfFontFactory.createFont("fonts/simsunb.ttf", PdfEncodings.IDENTITY_H);
PdfFont ttcFont = PdfFontFactory.createTtcFont("fonts/simsun.ttc", 1, PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED, true);
boolean ttcFontResult = fontProvider.addFont(ttcFont.getFontProgram());
boolean fftFontResult = fontProvider.addFont(fftFont.getFontProgram());
log.info("html2Pdf add fount ttf:{} ttc:{}", fftFontResult, ttcFontResult);
fontProvider.addSystemFonts();
converterProperties.setFontProvider(fontProvider);
HtmlConverter.convertToPdf(content, outputStream, converterProperties);
} catch (Exception e) {
e.printStackTrace();
log.error("生成 PDF 失败,{}", e);
}
return outputStream;
}
public static List<Map<String, Object>> convertJsonToListMap(String json) {
if (StringUtils.isBlank(json)) {
return Collections.emptyList();
}
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将 JSON 字符串转换为 List<Map<String, Object>> 类型
List<Map<String, Object>> list = objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(List.class, Map.class));
// 将 List<Map<String, Object>> 转换为 List<Map<String, Object>>,并排除 'id' 和 'mapping' 字段
return list.stream()
.map(map -> filterMap(map))
.collect(Collectors.toList());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static Map<String, Object> filterMap(Map<String, Object> map) {
return map.entrySet().stream()
.filter(entry -> !"id".equals(entry.getKey()) && !"mapping".equals(entry.getKey()))
.collect(Collectors.toMap(
Map.Entry::getKey, // 保持 key 不变
entry -> {
if (entry.getValue() instanceof List) {
// 处理 children 字段
@SuppressWarnings("unchecked")
List<Map<String, Object>> childrenList = (List<Map<String, Object>>) entry.getValue();
return childrenList.stream()
.map(child -> filterMap(child))
.collect(Collectors.toList());
} else if (entry.getValue() instanceof Map) {
// 递归处理嵌套的 Map
return filterMap((Map<String, Object>) entry.getValue());
} else {
// 其他 value 转为 String
return entry.getValue().toString();
}
}
));
}
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title></title>
<style>
.main{
width: 60%;
margin: 0 auto;
padding-bottom: 30px;
}
table {
width: 100%;
font-size: 14px;
margin:10px auto;
border-collapse: collapse;
}
th, td {
border: 1px solid #000;
padding: 2px 5px;
text-align: center;
}
th {
font-weight: bold;
}
.thFirst{
width: 30%;
}
.sizeWeight{
font-weight: bold;
}
</style>
</head>
<body>
<#list routeApis as routeApi>
<div class="main">
<h3>${routeApi?index+1}. <#if routeApi.algoName??> ${routeApi.algoName} <#else> ${routeApi.remark!""} </#if></h3>
<p><span class="sizeWeight">路由地址:</span>${routeApi.routePath}</p>
<p><span class="sizeWeight">请求方式:</span>${routeApi.routeMethod}</p>
<p><span class="sizeWeight">描述:</span> <#if routeApi.algoName??> ${routeApi.algoName} <#else> ${routeApi.remark!""} </#if> </p>
<h4>请求参数:</h4>
<table>
<thead>
<tr>
<th class="thFirst">参数</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<#if routeApi.reqParamList??>
<#list routeApi.reqParamList as reqParam>
<#list reqParam?keys as key>
<#assign value = reqParam[key]>
<#if key != "children">
<tr>
<td>${key}</td>
<td>${value!}</td>
</tr>
</#if>
<#if key == "children">
<#if value??>
<#list value as childValue>
<#list childValue?keys as key1>
<#assign value1 = childValue[key1]>
<tr>
<td>-${key1}</td>
<td>${value1}</td>
</tr>
</#list>
</#list>
</#if>
</#if>
</#list>
</#list>
<#else>
<tr>
<td colspan="2">无请求参数</td>
</tr>
</#if>
</tbody>
</table>
<h4>返回参数:</h4>
<table>
<thead>
<tr>
<th class="thFirst">参数</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<#if routeApi.resParamList??>
<#list routeApi.resParamList as resParam>
<#list resParam?keys as key>
<#assign value = resParam[key]>
<#if key != "children">
<tr>
<td>${key}</td>
<td>${value!}</td>
</tr>
</#if>
<#if key == "children">
<#if value??>
<#list value as childValue>
<#list childValue?keys as key1>
<#assign value1 = childValue[key1]>
<tr>
<td>-${key1}</td>
<td>${value1}</td>
</tr>
</#list>
</#list>
</#if>
</#if>
</#list>
</#list>
<#else>
<tr>
<td colspan="2">无请求参数</td>
</tr>
</#if>
</tbody>
</table>
<h4>响应码:</h4>
<table>
<thead>
<tr>
<th class="thFirst">响应码</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<#if routeApi.resCodeList??>
<#list routeApi.resCodeList as resCode>
<#list resCode?keys as key>
<#assign value = resCode[key]>
<#if key != "children">
<tr>
<td>${key}</td>
<td>${value!}</td>
</tr>
</#if>
<#if key == "children">
<#if value??>
<#list value as childValue>
<#list childValue?keys as key1>
<#assign value1 = childValue[key1]>
<tr>
<td>-${key1}</td>
<td>${value1}</td>
</tr>
</#list>
</#list>
</#if>
</#if>
</#list>
</#list>
<#else>
<tr>
<td colspan="2">无响应码</td>
</tr>
</#if>
</tbody>
</table>
</div>
</#list>
</body>
</html>