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>
相关推荐
城管不管13 分钟前
重生——第十次面试之开源中国一面挂
java·linux·开发语言·算法·面试·职场和发展·开源
Java小白笔记17 分钟前
Windows系统免软件命令激活
java·网络·人工智能·windows·ai·ai编程
爱敲键盘的猴子26 分钟前
深入理解 Java HashMap
java·hashmap
霸道流氓气质26 分钟前
Java开发者AI编程常用MCP、Skills、Rules全景汇总
java·开发语言·ai编程
天涯醉青楼38 分钟前
Spring Boot 接口返回 Bean 时字段名“变形”的深度剖析与解决方案
java
步行cgn1 小时前
MyBatis 高级映射与延迟加载完全指南
java·tomcat·mybatis
thefool1122661 小时前
从中序与后序遍历序列构造二叉树
java
凤山老林1 小时前
从 RestTemplate 到 HttpClient 5:Spring Boot HTTP 客户端性能调优与连接池治理
spring boot·后端·http
吴声子夜歌1 小时前
Java面试题——JVM(二)
java·开发语言·jvm
CRMEB1 小时前
商城大促活动策划全流程:从定目标到复盘四阶段
java·开发语言·人工智能·ai·开源·php