java导出pdf(使用html)

引入maven

<dependencies><!-- Thymeleaf --><dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf</artifactId><version>3.1.1.RELEASE</version> <!-- 或与 Spring Boot 匹配的版本 --></dependency><!-- Flying Saucer PDF 渲染(使用 OpenPDFXHTMLRenderer--><dependency><groupId>org.xhtmlrenderer</groupId><artifactId>flying-saucer-pdf-openpdf</artifactId><version>9.1.22</version></dependency><!-- iText 2.x (lowagie) 用于合并 PDF 页等操作 --><dependency><groupId>com.lowagie</groupId><artifactId>itext</artifactId><version>2.1.7</version></dependency><!-- Servlet API(如果你在非 Web 项目中操作 HttpServletResponse--><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency><!-- Lombok(用于注解 @Slf4j @SneakyThrows--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version><scope>provided</scope></dependency>
</dependencies>

工具类

package com.tlzf.util;import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfCopy;
import com.lowagie.text.pdf.PdfImportedPage;
import com.lowagie.text.pdf.PdfReader;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.w3c.dom.Document;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.util.List;
import java.util.Map;/*** pdf处理工具类** @author gourd.hu* @version 1.0findInstallationElevatorBlockVOById*/
@Slf4j
@Component
public class PdfUtil {private PdfUtil() {}/*** 按模板和参数生成html字符串,再转换为flying-saucer识别的Document** @param templateName freemarker模板名称x`* @param variables    freemarker模板参数* @return Document*/private static Document generateDoc(TemplateEngine templateEngine, String templateName, Map<String, Object> variables) {// 声明一个上下文对象,里面放入要存到模板里面的数据final Context context = new Context();context.setVariables(variables);StringWriter stringWriter = new StringWriter();try (BufferedWriter writer = new BufferedWriter(stringWriter)) {templateEngine.process(templateName, context, writer);writer.flush();DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();return builder.parse(new ByteArrayInputStream(stringWriter.toString().getBytes("UTF-8")));} catch (Exception e) {log.error(e.getMessage(), e);return null;}}/*** 核心: 根据freemarker模板生成pdf文档** @param templateEngine 配置* @param templateName   模板名称* @param out            输出流* @param listVars       模板参数* @throws Exception 模板无法找到、模板语法错误、IO异常*/private static void generateAll(TemplateEngine templateEngine, String templateName, OutputStream out, List<Map<String, Object>> listVars) throws Exception {if (CollectionUtils.isEmpty(listVars)) {log.warn("警告:模板参数为空!");return;}ITextRenderer renderer = new ITextRenderer();//设置字符集(宋体),此处必须与模板中的<body style="font-family: SimSun">一致,区分大小写,不能写成汉字"宋体"ITextFontResolver fontResolver = renderer.getFontResolver();//避免中文为空设置系统字体fontResolver.addFont("static/fonts/simsun.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);fontResolver.addFont("static/fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);//根据参数集个数循环调用模板,追加到同一个pdf文档中//(注意:此处从1开始,因为第0是创建pdf,从1往后则向pdf中追加内容)for (int i = 0; i < listVars.size(); i++) {Document docAppend = generateDoc(templateEngine, templateName, listVars.get(i));renderer.setDocument(docAppend, null);renderer.getSharedContext().setBaseURL(null);//展现和输出pdfrenderer.layout();if (i == 0) {renderer.createPDF(out, false);} else {//写下一个pdf页面renderer.writeNextDocument();}}renderer.finishPDF(); //完成pdf写入}private static void generateAll(TemplateEngine templateEngine, String templateName, OutputStream out, List<Map<String, Object>> listVars, String path) throws Exception {if (CollectionUtils.isEmpty(listVars)) {log.warn("警告:模板参数为空!");return;}ITextRenderer renderer = new ITextRenderer();//设置字符集(宋体),此处必须与模板中的<body style="font-family: SimSun">一致,区分大小写,不能写成汉字"宋体"ITextFontResolver fontResolver = renderer.getFontResolver();//避免中文为空设置系统字体fontResolver.addFont("static/fonts/simsun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);fontResolver.addFont("static/fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);String url = "File:///" + path + "/";log.info("导出 文件地址  = {}", url);//根据参数集个数循环调用模板,追加到同一个pdf文档中//(注意:此处从1开始,因为第0是创建pdf,从1往后则向pdf中追加内容)for (int i = 0; i < listVars.size(); i++) {Document docAppend = generateDoc(templateEngine, templateName, listVars.get(i));renderer.setDocument(docAppend, null);renderer.getSharedContext().setBaseURL(url);//展现和输出pdfrenderer.layout();if (i == 0) {renderer.createPDF(out, false);} else {//写下一个pdf页面renderer.writeNextDocument();}}renderer.finishPDF(); //完成pdf写入}/*** pdf下载** @param templateEngine 配置* @param templateName   模板名称(带后缀.ftl)* @param listVars       模板参数集* @param response       HttpServletResponse* @param fileName       下载文件名称(带文件扩展名后缀)*/public static void download(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response, String fileName) {// 设置编码、文件ContentType类型、文件头、下载文件名response.setCharacterEncoding("utf-8");response.setContentType("multipart/form-data");try {response.setHeader("Content-Disposition", "attachment;fileName=" +new String(fileName.getBytes("gb2312"), "ISO8859-1"));} catch (UnsupportedEncodingException e) {log.error(e.getMessage(), e);}try (ServletOutputStream out = response.getOutputStream()) {generateAll(templateEngine, templateName, out, listVars);out.flush();} catch (Exception e) {log.error(e.getMessage(), e);}}public static void downloadWithImg(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response, String fileName, String path) {// 设置编码、文件ContentType类型、文件头、下载文件名response.setCharacterEncoding("utf-8");response.setContentType("multipart/form-data");try {response.setHeader("Content-Disposition", "attachment;fileName=" +new String(fileName.getBytes("gb2312"), "ISO8859-1"));} catch (UnsupportedEncodingException e) {log.error(e.getMessage(), e);}try (ServletOutputStream out = response.getOutputStream()) {generateAll(templateEngine, templateName, out, listVars, path);out.flush();} catch (Exception e) {log.error(e.getMessage(), e);}}/*** pdf预览** @param templateEngine 配置* @param templateName   模板名称(带后缀.ftl)* @param listVars       模板参数集* @param response       HttpServletResponse*/public static void preview(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response) {try (ServletOutputStream out = response.getOutputStream()) {generateAll(templateEngine, templateName, out, listVars);out.flush();} catch (Exception e) {log.error(e.getMessage(), e);}}public static void previewWithImg(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response, String path) {try (ServletOutputStream out = response.getOutputStream()) {generateAll(templateEngine, templateName, out, listVars, path);out.flush();} catch (Exception e) {log.error(e.getMessage(), e);}}/*** 生成pdf 指定文件绝对路径** @param templateEngine 配置* @param templateName   模板名称(带后缀.ftl)* @param listVars       模板参数集* @param filePath       文件路径*/public static void creatPdfFile(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, String filePath) {FileOutputStream out = null;try {//新建文件File file = new File(filePath);// 创建复制路径File parent = file.getParentFile();if (!parent.exists()) {parent.mkdirs();}// 复制文件if (file.exists()) {file.createNewFile();}out = new FileOutputStream(file);generateAll(templateEngine, templateName, out, listVars);out.flush();} catch (Exception e) {log.error(e.getMessage(), e);} finally {if (null != out) {try {out.close();} catch (IOException e) {e.printStackTrace();}}}}/*** 多个PDF合并功能 要捕获错误信息,所以不用try-catch** @param files    多个PDF的路径* @param savePath 生成的新PDF绝对路径*/@SneakyThrowspublic static void mergePdfFiles(String[] files, String savePath) {if (files.length > 0) {
//            try {com.lowagie.text.Document document = new com.lowagie.text.Document(new PdfReader(files[0]).getPageSize(1));PdfCopy copy = new PdfCopy(document, new FileOutputStream(savePath));document.open();for (String file : files) {PdfReader reader = new PdfReader(file);int n = reader.getNumberOfPages();for (int j = 1; j <= n; j++) {document.newPage();PdfImportedPage page = copy.getImportedPage(reader, j);copy.addPage(page);}}document.close();
//            } catch (IOException | DocumentException e) {
//                e.printStackTrace();
//            }}}}

pdf.html 模版

多页模版

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.w3.org/1999/xhtml" layout:decorator="layout">
<head lang="en"><title></title><style>@media print {div.footer-content {display: block;font-family: SimSun;font-size: 16px;color: #000;text-align: left;margin-left: -490px;position: running(footer-content);}}@page {size: A4; /*设置纸张大小:A4(210mm 297mm)、A3(297mm 420mm) 横向则反过来*/margin: 1in;padding: 1em;}.page {page-break-after: always;font-size: 16px;font-family: SimSun;}/* 防止最后一页多空页 */.page:last-child {page-break-after: auto;}body {font-family: 'SimSun'}h1 {text-align: center;line-height: 60px;}table, th, td {border: 1px solid black;border-collapse: collapse;padding: 3px;font-size: 16px;height: 10px;}.half-cell {position: relative;}.footer {position: fixed;bottom: 10px;left: 0;width: 100%;font-size: 12px;margin-left: 10px;margin-right: 10px; /* 右边也留 */}.footer .right {text-align: right;margin-top: 30px;}</style>
</head>
<!--这样配置不中文不会显示-->
<!--<body style="font-family: 宋体">-->
<body style="font-family: 'SimSun'">
<!--第一页-->
<div class="page"><div style="width: 100%; overflow: hidden;"><!----><div style="float: left; width: 40%;"><table style="width: 100%; border-collapse: collapse;text-align: center;"><colgroup><col style="width: 50%;" /><col style="width: 50%;" /></colgroup><tr class="expertTitle"><td>**</td><td th:text="${bjbh}"></td></tr><tr class="expertTitle"><td>**</td><td th:text="${bdh}"></td></tr><tr><td>**</td><td th:text="${fbType}"></td></tr></table></div><!----><div style="float: right; width: 30%; text-align: right;"><img th:src="${photo}" alt="二维码" style="width: 100px; height: 100px;"/></div></div><h2 style="text-align: center;" data-pdf-bookmark-name="***">***</h2><div style="line-height: 20px;"><!-- 继续下面内容 --><div style="margin-top: 20px; font-size: 16px; margin-left: 10px; line-height: 1.8;"><span style="border-bottom: 1px solid #000; padding: 0 0px" th:text="${name}"></span> <span></span><p style="text-indent: 2em;">***</p></div><div><table style="text-align: center; width: 100%; border-collapse: collapse;" border="1"><colgroup><!-- 设置表格宽度 --><col style="width: 16.66%;" /><col style="width: 16.66%;" /><col style="width: 16.66%;" /><col style="width: 16.66%;" /><col style="width: 16.66%;" /><col style="width: 16.66%;" /></colgroup><tr><td>**</td><!-- 设置表格合并 --><td colspan="5" th:text="${jsdd}"></td></tr><tr style="height: 150px;"><td colspan="1">**</td><!-- 设置表格自动换行 --><td colspan="3" style="text-align: left; vertical-align: top; padding: 5px;"><span th:text="${zbdljg}"></span></td></tr></table></div></div><div class="footer" ><div>附注:</div><div>&#160;1. ****</div><div class="right"><div>******&#160;&#160;</div><div>2020&#160;&#160;</div></div></div></div><div class="page"><div style="width: 100%; overflow: hidden;"><!----><div style="float: left; width: 40%;text-align: center;"><table style="width: 100%; border-collapse: collapse;text-align: center;"><colgroup><col style="width: 50%;" /><col style="width: 50%;" /></colgroup><tr class="expertTitle"><td>**</td><td th:text="${bjbh}"></td></tr><tr class="expertTitle"><td>**</td><td th:text="${bdh}"></td></tr><tr><td>**</td><td th:text="${fbType}"></td></tr></table></div><!----><div style="float: right; width: 30%; text-align: right;"><img th:src="${photo}" alt="二维码" style="width: 100px; height: 100px;"/></div></div><h2 style="text-align: center;" data-pdf-bookmark-name="专业工程暂估价明细表">专业工程暂估价明细表</h2><table style="width: 100%; border-collapse: collapse; font-weight: normal;text-align: center;" ><colgroup><col style="width: 20%;" /><col style="width: 20%;" /><col style="width: 20%;" /><col style="width: 20%;" /><col style="width: 20%;" /></colgroup><tr><td>序号</td><td>专业工程名称</td><td>专业类别</td><td>招标主体</td><td>金额(万元)</td></tr><!-- 循环 --><tr th:each="item, iterStat : ${list}" ><td th:text="${iterStat.count}"></td><td th:text="${item.projectName}"></td><td th:text="${item.projectType}"></td><td th:text="${item.bidEntity}"></td><td th:text="${item.amount}"></td></tr></table><div class="footer"><div>附注:</div><div>1. 本中标可通过二维码在上海市建筑业官方微信验证。</div><div class="right" style=" margin-right: 30px;"><div> ***&#160;&#160;</div><div>2020&#160;&#160;</div></div></div></div></body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/91129.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/91129.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt 远程过程调用(RPC)实现方案

在分布式系统开发中&#xff0c;远程过程调用&#xff08;RPC&#xff09;是实现跨进程、跨机器通信的重要技术。Qt 作为一个强大的跨平台框架&#xff0c;提供了多种 RPC 实现方案&#xff0c;能够满足不同场景下的通信需求。本文将深入探讨 Qt 中 RPC 的各种实现方式&#xf…

攻防世界-引导-Web_php_unserialize

题目内容&#xff1a;出现一段源代码&#xff0c;分段分析第一部分如下<?php class Demo { private $file index.php;public function __construct($file) { $this->file $file; }function __destruct() { echo highlight_file($this->file, true); }function __w…

pytorch学习笔记-自定义卷积

未完结的草稿———&#xff01;大概是准备整合一下常见的层&#xff0c;整合完感觉就可以进行搭建了&#xff08;还没进行到这一步所以不太确定版&#xff09; &#xff08;ps我将在完结这一篇的时候删除上面的小字and二编一下整篇文章的结构&#xff0c;如果看到了这部分文字…

[明道云]-基础教学2-工作表字段 vs 控件:选哪种?

本文深入解析“工作表字段”与“控件”的关系与差别,并从结构、功能、使用场景和选型建议等方面进行对比。 一、基础概念厘清 ✅ 工作表字段 = 数据模型中的列 工作表字段相当于数据库表中的列,是记录每条业务对象(如订单、客户等)属性的数据项,每个字段都有明确的名称和…

C++-一篇文章入门coroutines协程

文章目录前言什么是协程协程实现原理C协程的最小例子12345协程等效代码协程传值的例子前言 最近学习了一下C协程&#xff0c;这篇文章将介绍协程的相关概念&#xff0c;以及在C中如何使用协程。 什么是协程 C中&#xff0c;协程&#xff08;coroutines&#xff09;可以理解为…

数字经济专业的就业全景指南

CDA数据分析师证书含金量高&#xff0c;适应了未来数字化经济和AI发展趋势&#xff0c;难度不高&#xff0c;行业认可度高&#xff0c;对于找工作很有帮助。一、数字经济就业热力图二、核心岗位发展路径1. 互联网数字运营岗2. 金融科技岗岗位类型技能组合证书加持5年薪资范围智…

PDF转Word免费工具!批量处理PDF压缩,合并, OCR识别, 去水印, 签名等全功能详解

大家好&#xff0c;欢迎来到程序视点&#xff01;我是你们的老朋友.小二&#xff01;前言PDF软件我发的非常多&#xff0c;但今天这款工具是大家公认最值得推荐的&#xff0c;这款软件就是PDF24PDF24几乎包含了PDF的所有功能&#xff0c;目前是更新到了最新版本&#xff01;文末…

Flutter开发实战之Widget体系与布局原理

第3章:Widget体系与布局原理 在前面两章中,我们已经搭建好了Flutter开发环境,并且了解了Dart语言的基础知识。现在是时候深入Flutter的核心——Widget体系了。如果说Dart是Flutter的语言基础,那么Widget就是Flutter的灵魂。理解Widget体系,是掌握Flutter开发的关键所在。…

C++:stack与queue的使用

stack与queue的使用一.stack与queuej基础1.stack1.1基本认识1.2示例代码代码功能解析2.queue2.1基础知识操作说明2.2示例代码代码分析 一.stack与queuej基础 1.stack 1.1基本认识以上图片展示了栈&#xff08;stack&#xff09;这种数据结构的基本操作示意。栈是一种遵循后进先…

Unity 编辑器开发 之 Excel导表工具

一个简单的Excel导表工具&#xff0c;可以用来热更数据配置工具使用&#xff1a;&#xfeff;&#xfeff;执行菜单 SDGSupporter/Excel/1.Excel2Cs 生成c#脚本。&#xfeff;&#xfeff;等待C#类编译完成&#xfeff;&#xfeff;执行菜单 SDGSupporter/Excel/2.Excel2Bytes …

【数据结构与算法】力扣 415. 字符串相加

题目描述 415. 字符串相加 给定两个字符串形式的非负整数 num1 和num2 &#xff0c;计算它们的和并同样以字符串形式返回。 你不能使用任何內建的用于处理大整数的库&#xff08;比如 BigInteger&#xff09;&#xff0c; 也不能直接将输入的字符串转换为整数形式。 示例 1…

进阶向:Manus AI与多语言手写识别

Manus AI与多语言手写识别:从零开始理解 手写识别技术作为人工智能领域的重要应用之一,近年来在智能设备、教育、金融等行业得到了广泛运用。根据市场调研机构IDC的数据显示,2022年全球手写识别市场规模已达到45亿美元,预计到2025年将突破70亿美元。其中,多语言手写识别技…

Javaweb————HTTP请求头属性讲解

❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️前面我们已经说过http请求分为三部分&#xff0c;请求行&#xff0c;请求头和请求体 请求头包含若干个属性&#xff1a;格式为属性名&#xff1a;属性值&#xff0c;这篇文章我们就来介绍一下http请求头中一些常见属性的含义 我们…

9.c语言常用算法

查找顺序查找&#xff08;线性查找&#xff09;算法思想&#xff1a;从数组的第一个元素开始&#xff0c;逐个与目标值进行比较&#xff0c;直到找到目标值或查找完整个数组。时间复杂度&#xff1a;最好情况&#xff1a;O(1)&#xff08;目标在第一个位置&#xff09;最坏情况…

AI小智源码分析——音频部分(一)

一、源码跳转这里采用了函数重载来进行代码复用&#xff0c;当需要对I2S接口的数据进行配置&#xff0c;比如左右音道切换&#xff0c;可以使用第二个构造函数&#xff0c;这里小智使用的是第一个构造函数&#xff0c;即只传递I2S相关的引脚参数&#xff08;不带slot mask&…

【GNSS原理】【LAMBDA】Chapter.12 GNSS定位算法——模糊度固定LAMBDA算法[2025年7月]

Chapter.12 GNSS定位算法——模糊度固定LAMBDA算法 作者&#xff1a;齐花Guyc(CAUC) 文章目录Chapter.12 GNSS定位算法——模糊度固定LAMBDA算法一.整周模糊度理论1.LAMBDA算法干了一件什么事情&#xff1f;2.LAMBDA算法步骤&#xff08;1&#xff09;去相关&#xff08;Z变换…

计算机毕业设计java在线二手系统的设计与实现 基于Java的在线二手交易平台开发 Java技术驱动的二手物品管理系统

计算机毕业设计java在线二手系统的设计与实现z2n189&#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。随着互联网技术的飞速发展&#xff0c;二手交易市场也逐渐从传统的线下模式转…

如何进行项目复盘?核心要点分析

进行项目复盘需要明确复盘目标、确定复盘参与人员、选择合适的复盘方法、梳理项目过程与关键节点、分析成功与失败的原因、总结经验教训并制定改进计划。其中&#xff0c;选择合适的复盘方法尤其关键&#xff0c;常见的复盘方法包括鱼骨图分析法、SWOT分析法、PDCA循环法&#…

LeetCode 923.多重三数之和

给定一个整数数组 arr &#xff0c;以及一个整数 target 作为目标值&#xff0c;返回满足 i < j < k 且 arr[i] arr[j] arr[k] target 的元组 i, j, k 的数量。 由于结果会非常大&#xff0c;请返回 109 7 的模。 示例 1&#xff1a; 输入&#xff1a;arr [1,1,2,2,…

.Net日志系统Logging-五

日志概念 日志级别 NET (Microsoft.Extensions.Logging) 中定义的 6 个标准日志级别&#xff0c;按严重性从低到高排列&#xff1a; 日志级别数值描述典型使用场景Trace0最详细的信息&#xff0c;包含敏感数据&#xff08;如请求体、密码哈希等&#xff09;。仅在开发或深度故…