java使用itext7实现html转pdf全代码完整示例

news2024/9/25 3:22:33

    之前项目有个需求,系统实现自己的打印功能,基本上都是前端找了个框架搞的,我呢,就是配合处理一些前端不好处理的部分,但是新一期的需求评审中,前端提出了,前端自己生成pdf在数据量大的时候会很慢,自然这时候需要我这个后端介入了。虽然期间遇到过不少问题,但最后都解决了,至少解决的这一期的需求任务,后续可能在此基础上还会扩展,到时候再一一补充到这篇博客中。

    首先,一开始也找了很多工具,都不是很合适,原因就不说了,最终敲定使用itext来实现html转pdf。

   html是前端自己生成,要注意的就是语法要合规(其中可能还有些标签属性不支持,但是这个得前端自己摸索改造,不在此处展开说明),不然很容易出现生成的pdf和html渲染的效果不一致的问题。

  项目通过maven引入依赖

<!-- itext7html转pdf  -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>html2pdf</artifactId>
    <version>3.0.2</version>
</dependency>
<!-- 中文字体支持 -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>font-asian</artifactId>
    <version>7.1.13</version>
</dependency>

下面是具体相关代码

1水印部分

import com.itextpdf.kernel.colors.WebColors;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.property.VerticalAlignment;

import java.io.IOException;

/**
 * @ClassName WaterMarkEventHandler
 * @Description 水印
 * @Author nw
 * @Date 2024/1/12 9:35
 * @Version 1.0
 */
public class WaterMarkEventHandler implements IEventHandler
{

    /**
     * 水印内容
     */
    private String waterMarkContent;

    /**
     * 一页中有几列水印
     */
    private int waterMarkX;

    /**
     * 一页中每列有多少水印
     */
    private int waterMarkY;

    public WaterMarkEventHandler(String waterMarkContent) {
        this(waterMarkContent, 5, 5);
    }

    public WaterMarkEventHandler(String waterMarkContent, int waterMarkX, int waterMarkY) {
        this.waterMarkContent = waterMarkContent;
        this.waterMarkX = waterMarkX;
        this.waterMarkY = waterMarkY;
    }

    @Override
    public void handleEvent(Event event) {

        PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;
        PdfDocument document = documentEvent.getDocument();
        PdfPage page = documentEvent.getPage();
        Rectangle pageSize = page.getPageSize();

        PdfFont pdfFont = null;
        try {
            pdfFont = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H", false);
        } catch (IOException e) {
            e.printStackTrace();
        }

        PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), document);

        Paragraph waterMark = new Paragraph(waterMarkContent).setOpacity(0.5f);
        Canvas canvas = new Canvas(pdfCanvas, pageSize)
                .setFontColor(WebColors.getRGBColor("lightgray"))
                .setFontSize(16)
                .setFont(pdfFont);

        for (int i = 0; i < waterMarkX; i++) {
            for (int j = 0; j < waterMarkY; j++) {
                canvas.showTextAligned(waterMark, (150 + i * 300), (160 + j * 150), document.getNumberOfPages(), TextAlignment.CENTER, VerticalAlignment.BOTTOM, 120);
            }
        }
        canvas.close();
    }
}

2.页码

import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.property.TextAlignment;

import java.io.IOException;

/**
 * @ClassName PageEventHandler
 * @Description 页数
 * @Author nw
 * @Date 2024/1/12 9:38
 * @Version 1.0
 */
public class PageEventHandler implements IEventHandler {
    @Override
    public void handleEvent(Event event) {
        PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;
        PdfDocument document = documentEvent.getDocument();
        PdfPage page = documentEvent.getPage();
        Rectangle pageSize = page.getPageSize();

        PdfFont pdfFont = null;
        try {
            pdfFont = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H", false);
        } catch (IOException e) {
            e.printStackTrace();
        }

        PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), document);
        Canvas canvas = new Canvas(pdfCanvas, pageSize);
        float  x = (pageSize.getLeft() + pageSize.getRight()) / 2;
        float  y = pageSize.getBottom() + 15;
        Paragraph paragraph = new Paragraph("第" + document.getPageNumber(page) + "页/共" + document.getNumberOfPages() + "页")
                .setFontSize(10)
                .setFont(pdfFont);
        canvas.showTextAligned(paragraph, x, y, TextAlignment.CENTER);
        canvas.close();
    }
}

3.工具类

import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.font.FontProvider;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * @ClassName HtmlToPdfUtils
 * @Description
 * @Author nw
 * @Date 2024/1/12 9:40
 * @Version 1.0
 */
public class HtmlToPdfUtils
{
    public static void html2Pdf(InputStream inputStream, String waterMark, OutputStream outputStream) throws
            IOException
    {
        PdfWriter pdfWriter = new PdfWriter(outputStream);
        PdfDocument pdfDocument = new PdfDocument(pdfWriter);
        pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE, new WaterMarkEventHandler(waterMark));
        pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE, new PageEventHandler());

        ConverterProperties properties = new ConverterProperties();
        //添加中文字体支持
        FontProvider fontProvider = new FontProvider();
        PdfFont sysFont = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H", false);
        fontProvider.addFont(sysFont.getFontProgram(), "UniGB-UCS2-H");
        properties.setFontProvider(fontProvider);

        HtmlConverter.convertToPdf(inputStream, pdfDocument, properties);

        pdfWriter.close();
        pdfDocument.close();
    }
}

4.由于电脑端和手机端的要求不一样,所以写了两个接口,电脑端直接响应了文件流,手机端直接返回oss的一个地址,但是底层的逻辑是一致的,只是后续处理稍微不一样而已,下面两个接口代码示例,供大家参考。

4.1电脑端

控制层

    /**
     * web端用
     * @param file
     * @param param
     * @param response
     * @throws Exception
     */
    @PostMapping("/exportPdfWeb")
    public void exportPdfWeb(@RequestParam("file") MultipartFile file, @RequestParam("param") String param,HttpServletResponse response) throws Exception {
        JSONObject params = null;
        if(StringUtil.isEmpty(param)){
            params = new JSONObject();
        }else{
            params = JSON.parseObject(param);
        }
        String fileNamePrefix = params.getString("fileNamePrefix");
        try{
            String fileName = fileNamePrefix + "_" + DateUtil.getTimestamp("yyyyMMddHHmmss") + StringUtil.getUUID2Long() +".pdf";
            this.resolveResponse(response, fileName);
            customPrintModelService.exportPdfWeb(file, response.getOutputStream(),params);
        } catch (Exception e) {
           log.error("exportPdfWeb生成pdf出错:",e);
            this.resetResponse(response, e);
        }
    }

    /**
     * 客户端用
     * @param file
     * @param param
     * @return
     * @throws Exception
     */
    @PostMapping("/exportPdfClient")
    @ResponseBody
    public ApiResponse exportPdfClient(@RequestParam("file") MultipartFile file, @RequestParam("param") String param){
        JSONObject params = null;
        if(StringUtil.isEmpty(param)){
            params = new JSONObject();
        }else{
            params = JSON.parseObject(param);
        }
        if(!params.containsKey("fileNamePrefix") || StringUtil.isEmpty(params.getString("fileNamePrefix"))){
            return ApiResponse.failed(ApiResponse.CODE_FAIL_DEFAULT,"模板名称不能为空");
        }
        try{
            return customPrintModelService.exportPdfClient(file,params);
        } catch (Exception e) {
            log.error("exportPdfClient生成pdf出错:",e);
            return ApiResponse.failed(ApiResponse.CODE_FAIL_DEFAULT,"获取pdf路径失败");
        }
    }

    private void resolveResponse(HttpServletResponse response, String fileName) throws UnsupportedEncodingException
    {
        response.setContentType("application/pdf");
        response.setCharacterEncoding("utf-8");
        String encodeFileName = URLEncoder.encode(fileName, "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + encodeFileName);
        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
    }

    private void resetResponse(HttpServletResponse response, Exception exception)throws IOException {
        // 重置response
        response.reset();
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        String result = "下载文件失败," + exception.getMessage();
        response.getWriter().println(result);
    }

service层
 

    @Override
    public void exportPdfWeb(MultipartFile file, OutputStream outputStream,JSONObject params) throws IOException
    {
        //水印
        String waterMarkText = "";
        if(params.containsKey("waterMarkText")){
            waterMarkText = params.getString("waterMarkText");
        }
        InputStream inputStream = file.getInputStream();
        HtmlToPdfUtils.html2Pdf(inputStream, waterMarkText, outputStream);
    }

    @Override
    public ApiResponse exportPdfClient(MultipartFile file, JSONObject param) throws IOException
    {
        String fileNamePrefix = param.getString("fileNamePrefix");
        String fileName = fileNamePrefix + "_" + DateUtil.getTimestamp("yyyyMMddHHmmss") + StringUtil.getUUID2Long() +".pdf";
        String fullPath = PropertiesUtil.getInstance("system.properties").getValue("upload.root_path") + File.separator + fileName;
        //水印
        String waterMarkText = "";
        if(param.containsKey("waterMarkText")){
            waterMarkText = param.getString("waterMarkText");
        }
        InputStream inputStream = file.getInputStream();
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(fullPath);
            HtmlToPdfUtils.html2Pdf(inputStream, waterMarkText, fileOutputStream);
        } catch (IOException e) {
            log.error("exportPdfClient生成pdf文件失败:",e);
            return ApiResponse.failed(ApiResponse.CODE_FAIL_DEFAULT,"生成pdf失败");
        }finally {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                log.error("exportPdfClient关闭文件流失败:",e);
            }
        }
        File localFile = new File(fullPath);
        String nowDate = DateUtil.DateTimeToString("yyyyMM", new Date());
        String fileKey = UserManager.getTenantId() + "/" + nowDate + "/print/" + fileName;
        boolean uploadFlag = FileStorgeHelper.syncUploadFileToOss(OssUsageEnum.USAGE_PRINT, localFile, fileKey, null);
        if (!uploadFlag) {
            log.info("上传文件到阿里云未成功");
            return ApiResponse.failed(ApiResponse.CODE_FAIL_DEFAULT,"上传文件到阿里云出错");
        }
        //上传成功 获取路径
        String fileUrl = FileStorgeHelper
                .generatePresignedUrl(OssUsageEnum.USAGE_PRINT, fileKey, 480, TimeUnit.MINUTES);
        return ApiResponse.success(fileUrl);
    }

另外接口调用图

电脑端:

手机端

最后说一下,目前产品的需求并未对纸张尺寸有要求,比如A3,A4,B2,itext中默认使用的是A4,具体怎么设置,等有需求再说吧,今天周六加班不想写了,准备下班。希望帮助到大家。

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

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

相关文章

前端基础知识整理汇总(上)

HTML页面的生命周期 HTML页面的生命周期有以下三个重要事件&#xff1a; DOMContentLoaded —— 浏览器已经完全加载了 HTML&#xff0c;DOM 树已经构建完毕&#xff0c;但是像是 <img> 和样式表等外部资源可能并没有下载完毕。 load —— 浏览器已经加载了所有的资源&…

C语言宏定义小技巧

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、定义一年多少秒&#xff08;除闰年&#xff09;举例运行结果出现的问题原因 二、定义整型数据要避免的坑举例运行结果原因解决方法 三 、未完待续 前言 提…

商城小程序系统:数字化转型下的商机

近几年&#xff0c;电商行业不断发展&#xff0c;线上购物已经成为大众的重要选择。线上商超作为传统的商业购物模式&#xff0c;为带来更多的商机&#xff0c;也逐渐转向了线上电商模式&#xff0c;越来越多的商超企业开始搭建专属的商城小程序&#xff0c;为消费者提供方便快…

适用于动态 IT 环境的服务器流量监控软件

服务器在网络性能中起着至关重要的作用&#xff0c;这意味着保持其最佳容量至关重要。企业需要将 AI、ML 和云技术融入其 IT 中&#xff0c;从而提供充分的敏捷性、安全性和灵活性&#xff0c;在这方面&#xff0c;服务器流量监控已成为当务之急。通过定期监控通信、跟踪流量上…

《Linux C编程实战》笔记:线程同步

这一节主要是解决共享资源的处理。操作系统里也讲过互斥、锁之类的概念。 互斥锁 互斥锁通过锁机制来实现线程同步&#xff0c;同一时刻只允许一个线程执行一个关键部分的代码 一下是操作互斥锁的函数&#xff0c;均声明在pthread.h中。 pthread_mutex_init&#xff08;初始…

【2024济南生物发酵展同期会议】合成生物学背景下的发酵深层次技术论坛

2024合成生物学背景下的发酵深层次技术论坛 新技术、新资源、新机遇 反应设备.过滤分离.提取浓缩.干燥.流体机械.实验室设备.仪器仪表.废水废气 主办单位&#xff1a; 生物发酵展组委会 发酵人社区公众号 万物生物合成俱乐部 承办单位&#xff1a; 上海履济技术服务中心 …

在 WinForms 应用中使用 FtpWebRequest 进行文件操作和数据显示

在 WinForms 应用中使用 FtpWebRequest 进行文件操作和数据显示 引言 在企业级应用或桌面程序中&#xff0c;经常需要从远程服务器获取数据&#xff0c;并在用户界面上展示这些数据。本文将通过一个实际案例&#xff0c;演示如何在 Windows Forms 应用程序中使用 FtpWebReques…

openWrt将插件安装到USB外接硬盘上

问题描述&#xff1a; 陆由器的闪存空间不够&#xff0c;而陆由器有一个usb接口&#xff0c;可以外接硬盘&#xff0c;可以将插件安装在外接硬盘上&#xff0c;就再也不用担心陆由器的空间不够了&#xff1b; 解决方案&#xff1a; 查看USB目录&#xff0c;为 mnt/sdb1 利用…

Web前端-移动web开发_流式布局

文章目录 移动web开发流式布局1.0 移动端基础1.1浏览器现状1.2 手机屏幕的现状1.3常见移动端屏幕尺寸1.4移动端调试方法 2.0 视口2.1 布局视口 layout viewport2.2视觉视口 visual viewport2.3理想视口 ideal viewport&#xff08;苹果&#xff09;2.4meta标签 3.0 物理像素(手…

kafka入门(六):日志分段(LogSegment)

日志分段&#xff08;LogSegment&#xff09; Kafka的一个 主题可以分为多个分区。 一个分区可以有一至多个副本&#xff0c;每个副本对应一个日志文件。 每个日志文件对应一个至多个日志分段&#xff08;LogSegment&#xff09;。 每个日志分段还可以细分为索引文件、日志存储…

MOOSE相关滤波跟踪算法(个人学习笔记)

MOOSE 论文标题 “Visual Object Tracking using Adaptive Correlation Filters” 原文地址 用滤波器对目标外观进行建模&#xff0c;并通过卷积操作来执行跟踪。 参考阅读&#xff1a; 目标跟踪经典算法——MOSSE&#xff08;Minimum Output Sum Square Error&#xff09…

Redis命令总结

1、启动Redis服务&#xff0c;登录Redis # 开启redis服务 redis-server redis配置文件路径例子&#xff1a; redis-server redis.windows.conf# 连接redis 【无密码】 redis-cli# 连接redis【有密码】 # 1 先连接再输入密码 redis-cli auth 密码 2、连接时输入 IP址、端口号、…

GC6153步进电机驱动芯片——低噪声、低振动,应用于摄像机,机器人等产品上

GC6153是双通道5V低压步进电机驱动器具有低噪声、低振动的特点&#xff0c;特别适用于相机的变焦和对焦系统&#xff0c;万向节&#xff0c;摇头机和其他精密&#xff0c;低噪声扫描隧道显微镜控制系统。该芯片为每个通道集成了256微步驱动器通过SPI和I2C接口&#xff0c;用户可…

【模型评估 07】过拟合与欠拟合

在模型评估与调整的过程中&#xff0c;我们往往会遇到“过拟合”或“欠拟合”的情况。如何有效地识别“过拟合”和“欠拟合”现象&#xff0c;并有针对性地进行模型调整&#xff0c;是不断改进机器学习模型的关键。特别是在实际项目中&#xff0c;采用多种方法、从多个角度降低…

USB_CH340一键下载电路

目录标题 1、CH340概述2、CH340芯片特点3、CH340系列芯片4、CH340引脚定义5、CH340传统的一键下载电路5.1、Stm32串口下载5.2、ESP32串口下载5.3、注意 6、免外围电路下载 1、CH340概述 CH340是一个USB总线的转接芯片&#xff0c;可实现USB转串口或者USB转打印口。 2、CH340芯…

高级分布式系统-第7讲 分布式系统的时钟同步

顺序的分类 在分布式系统中&#xff0c; 顺序关系主要分为以下三类&#xff1a;时间顺序&#xff1a; 事件在时间轴上发生的先后关系。 无限时刻集组成有向时间轴&#xff0c; 时间顺序是通过时刻的顺序体现的。 因果顺序&#xff1a; 如果事件e1是事件e2发生的原因&#xf…

Android Studio代码联想不区分大小写的方法

Android Studio默认的代码联想是要区分大小写的 例如Bitmap&#xff0c;输入bit后并不会有提示 为了让其不区分大小写&#xff0c;可以在 File --> Setting 中进行设置 依次选择 Editor --> General --> Code Completion &#xff0c;将 Match case取消勾选即可 这个…

半小时实现GPT纯血鸿蒙版

仅需半小时&#xff0c;即可实现纯血鸿蒙版本的ChatGPT&#xff01; 废话少说&#xff0c;先看效果图&#xff1a; 如上图所示&#xff0c;这个小Demo实现了AI智能问答。靠右加粗的文本是用户点击底部提交按钮后出现的&#xff1b;后面靠左对齐的普通文本是来自AI的回答内容。当…

Spark原理——Shuffle 过程

Shuffle 过程 Shuffle过程的组件结构 从整体视角上来看, Shuffle 发生在两个 Stage 之间, 一个 Stage 把数据计算好, 整理好, 等待另外一个 Stage 来拉取 放大视角, 会发现, 其实 Shuffle 发生在 Task 之间, 一个 Task 把数据整理好, 等待 Reducer 端的 Task 来拉取 如果更细…

【数据集处理】FFHQ如何进行人脸对齐,Aligned and cropped images at 1024×1024

什么是人脸对齐&#xff1f; 人脸对齐是一种图像处理技术&#xff0c;旨在将图像中的人脸部分对齐到一个标准位置或形状。在许多情况下&#xff0c;这通常涉及将眼睛、鼻子和嘴巴等关键点对齐到特定的位置。通过这种方式&#xff0c;所有的人脸图像可以有一个一致的方向和尺寸…