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>
相关推荐
system_error_x1 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
驭渊的小故事2 小时前
Spring AI Alibaba 实战:从maven依赖治理到声明式工具调用
java·人工智能·spring
Elastic 中国社区官方博客2 小时前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn1882 小时前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn3 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su3 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
我命由我123454 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
苜蓿muxu5 小时前
Java 字符串拼接:+、StringBuilder、StringBuffer的区别
java
摇滚侠5 小时前
《Java并发编程之美》第 4 章 Java 并发包中原子操作类原理剖析 阅读笔记 7
java·笔记·python