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>
相关推荐
Daniel 大东1 小时前
BugJson因为json格式问题OOM怎么办
java·安全
Theodore_10225 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸6 小时前
01-spring security认证笔记
java·笔记·spring
世间万物皆对象6 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
没书读了7 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
小二·7 小时前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic7 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
懒洋洋大魔王7 小时前
RocketMQ的使⽤
java·rocketmq·java-rocketmq
武子康7 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
qq_17448285758 小时前
springboot基于微信小程序的旧衣回收系统的设计与实现
spring boot·后端·微信小程序