热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java生成pdf文件或jpg图片

在一些业务场景中,需要生成pdf文件或者jpg图片,有时候还需要带上水印。我们可以事先用freemarker定义

在一些业务场景中,需要生成pdf文件或者jpg图片,有时候还需要带上水印。我们可以事先用freemarker定义好html模板,然后把模板转换成pdfjpg文件。

同时freemarker模板还支持变量的定义,在使用时可以填充具体的业务数据。

1、Maven导包

org.springframework.bootspring-boot-starter-parent2.1.4.RELEASEorg.springframeworkspring-context-supportorg.freemarkerfreemarkercom.itextpdfitextpdf5.5.12com.itextpdfitext-asian5.2.0com.itextpdf.toolxmlworker5.5.12org.apache.pdfboxpdfbox2.0.5

2、接口定义

2.1、请求

@Data
public class GeneratePdfReq {
    /**
     * 生成pdf文件的绝对路径
     */
    @NotBlank(message = "生成pdf文件的绝对路径不能为空")
    @Pattern(regexp = "^.*(\.pdf|\.jpg)$", message = "生成的文件必须以.pdf或.jpg结尾")
    private String absolutePath;
    /**
     * 使用html模板的绝对路径
     */
    @NotBlank(message = "使用的模板路径不能为空")
    private String templateName;
    /**
     * 渲染模板的业务数据
     */
    private Object dataModel;
    /**
     * 水印信息
     */
    private WaterMarkInfo waterMarkInfo;
    /**
     * pdf文件的宽,默认A4
     */
    private float width = 595;
    /**
     * pdf文件的高,默认A4
     */
    private float height = 842;
}

2.2、水印

@Data
public class WaterMarkInfo {
    /**
     * 如果为null设置水印时会报错
     */
    private String waterMark = "";
    /**
     * 水印透明度,值越小透明度越高
     */
    private float opacity = 0.2F;
    /**
     * 水印字体,如果乱码设置为本地宋体字体:fonts/simsun.ttc,1
     */
    private String fOntName= "STSong-Light";
    /**
     * 水印编码格式,如果乱码设置为:BaseFont.IDENTITY_H
     */
    private String encoding = "UniGB-UCS2-H";
    /**
     * 字体大小
     */
    private float fOntSize= 24;
    /**
     * 横坐标在页面宽度的百分比,左下角为原点
     */
    private float x = 50;
    /**
     * 纵坐标在页面高度的百分比,左下角为原点
     */
    private float y = 40;
    /**
     * 水印旋转角度
     */
    private float rotation = 45;
}

2.3、响应

@Data
public class GeneratePdfResp {
    /**
     * 生成pdf的绝对路径
     */
    private String absolutePath;
}

3、应用代码

3.1、渲染freemarker模板获取html网页

@Service("freeMarkerService")
@Slf4j
public class FreeMarkerServiceImpl implements FreeMarkerService {
    @Autowired
    private FreeMarkerConfigurer freeMarkerConfigurer;

    /**
     * 渲染html后获取整个页面内容
     *
     * @param templatePath 模板路径
     * @param dataModel    业务数据,一般以map形式传入
     * @return
     */
    @Override
    public String getHtml(String templatePath, Object dataModel) {
        log.info("开始将模板{}渲染为html,业务数据{}", templatePath, JSONUtil.toJsonPrettyStr(dataModel));
        Configuration cfg = freeMarkerConfigurer.getConfiguration();
        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); // freemaker异常时仍旧抛出,统一异常处理
        cfg.setClassicCompatible(true);// 不需要对null值预处理,否则需要在模板取值时判断是否存在,不然报错
        StringWriter stringWriter = new StringWriter();
        try {
            // 设置模板所在目录,绝对路径方式,不打进jar包
//            cfg.setDirectoryForTemplateLoading(new File(templatePath).getParentFile());
//            Template temp = cfg.getTemplate(new File(templatePath).getName());

            // 相对路径设置模板所在目录,模板打进jar包,默认就是resources目录下的/templates目录。
            cfg.setClassForTemplateLoading(this.getClass(), "/templates");
            Template temp = cfg.getTemplate(templatePath);
            temp.process(dataModel, stringWriter);
        } catch (Exception e) {
            log.error(PdfErrorCode.PDF_TEMPLATE_RENDER_FAIL.getDesc(), e);
            throw new PdfBizException(PdfErrorCode.PDF_TEMPLATE_RENDER_FAIL);
        }
        return stringWriter.toString();
    }
}

3.2、将html网页转pdf,并添加水印

@Service("pdfService")
@Slf4j
public class PdfServiceImpl implements PdfService {
    public static final String FONT_PATH = "fonts/simsun.ttc,1";

    @Autowired
    private WaterMarkerService waterMarkerService;

    /**
     * html页面内容转pdf,并给每页附上水印
     *
     * @param html          html页面内容
     * @param width         pdf的宽
     * @param height        pdf的高
     * @param waterMarkInfo 水印信息
     * @return
     */
    @Override
    public byte[] html2Pdf(String html, float width, float height, WaterMarkInfo waterMarkInfo) {
        log.info("=================开始将html转换为pdf=================");
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        this.html2Pdf(html, width, height, out);
        byte[] bytes = out.toByteArray();
        // 设置水印
        if (waterMarkInfo != null) {
            bytes = waterMarkerService.addWaterMarker(bytes, waterMarkInfo);
        }
        return bytes;
    }

    /**
     * html转pdf
     *
     * @param html   html页面内容
     * @param width  pdf的宽
     * @param height pdf的高
     * @param out    输出流,pdf文件用此流输出,需要pdf文档关闭后流中才会有数据
     */
    @Override
    @SneakyThrows
    public void html2Pdf(String html, float width, float height, OutputStream out) {
        @Cleanup Document document = new Document(new RectangleReadOnly(width, height)); // 默认A4纵向
        // 这里需要关闭document才能让生成的pdf字节数据刷到输出流中
        PdfWriter writer = PdfWriter.getInstance(document, out); // 关闭可能导致生成的pdf显示异常(Chrome)
        document.open();
        // 设置字体,这里统一用simsun.ttc即宋体
        XMLWorkerFontProvider asianFOntProvider= new XMLWorkerFontProvider() {
            @Override
            public Font getFont(String fontname, String encoding, boolean embedded, float size, int style, BaseColor color, boolean cached) {
                Font font;
                try {
                    fOnt= new Font(BaseFont.createFont(FONT_PATH, BaseFont.IDENTITY_H, BaseFont.EMBEDDED));
                } catch (Exception e) {
                    log.error(PdfErrorCode.SET_PDF_FONT_FAIL.getDesc(), e);
                    throw new PdfBizException(PdfErrorCode.SET_PDF_FONT_FAIL);
                }
                font.setStyle(style);
                font.setColor(color);
                if (size > 0) {
                    font.setSize(size);
                }
                return font;
            }
        };

        // 生成pdf
        try {
            XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(html.getBytes("UTF-8")), null, Charset.forName("UTF-8"), asianFontProvider);

            // 如果系统已经装有simsun.ttc字体,则不需要单独设置字体也不需要itext-asian jar包
//            XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(html.getBytes("UTF-8")), null, Charset.forName("UTF-8"));
        } catch (RuntimeWorkerException e) {
            log.error(PdfErrorCode.HTML_CONVERT2PDF_FAIL.getDesc(), e);
            throw new PdfBizException(PdfErrorCode.HTML_CONVERT2PDF_FAIL);
        }
    }
}

添加水印实现类

@Service("waterMarkerService")
@Slf4j
public class WaterMarkerServiceImpl implements WaterMarkerService {

    /**
     * 给pdf文件每页添加水印
     *
     * @param source        pdf文件的字节数组形式
     * @param waterMarkInfo 水印信息
     * @return
     */
    @Override
    public byte[] addWaterMarker(byte[] source, WaterMarkInfo waterMarkInfo) {
        log.info("开始设置水印数据{}", JSONUtil.toJsonPrettyStr(waterMarkInfo));
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        this.addWaterMarker(source, waterMarkInfo, out);
        return out.toByteArray();
    }

    /**
     * 给pdf文件每页添加水印
     *
     * @param source        pdf文件的字节数组形式
     * @param waterMarkInfo 水印信息
     * @param out           输出流,pdf文件用此流输出,需要pdf文档关闭后流中才会有数据
     */
    @Override
    @SneakyThrows
    public void addWaterMarker(byte[] source, WaterMarkInfo waterMarkInfo, OutputStream out) {
        @Cleanup PdfReader reader = new PdfReader(source);
        // 这里需要关闭PdfStamper才能让生成的pdf字节数据刷到输出流中
        @Cleanup PdfStamper pdfStamper = new PdfStamper(reader, out);
        BaseFont fOnt= BaseFont.createFont(waterMarkInfo.getFontName(), waterMarkInfo.getEncoding(), BaseFont.EMBEDDED);
        PdfGState gs = new PdfGState();
        gs.setFillOpacity(waterMarkInfo.getOpacity());
        // 给每页pdf生成水印
        for (int i = 1; i 

3.3、整合实现

@Slf4j
@Service("generatePdfService")
public class GeneratePdfServiceImpl implements RestService {
    @Autowired
    private FreeMarkerService freeMarkerService;

    @Autowired
    private PdfService pdfService;

    @Override
    @SneakyThrows
    public GeneratePdfResp service(GeneratePdfReq generatePdfReq) {
        log.info("开始生成pdf文件,请求报文:{}", JSONUtil.toJsonPrettyStr(generatePdfReq));
        /*
        1.根据freemarker模板填充业务数据获取完整的html字符串
         */
        String html = freeMarkerService.getHtml(generatePdfReq.getTemplateName(), generatePdfReq.getDataModel());

        /*
        2.生成pdf文件(内存)
         */
        byte[] bytes = pdfService.html2Pdf(html, generatePdfReq.getWidth(), generatePdfReq.getHeight(), generatePdfReq.getWaterMarkInfo());

        /*
        3.本地保存pdf文件
         */
        File targetFile = new File(generatePdfReq.getAbsolutePath());
        // 上级目录不存在则创建
        if (!targetFile.getParentFile().exists()) {
            targetFile.getParentFile().mkdirs();
        }

        // 根据不同文件名后缀生成对应文件
        if (generatePdfReq.getAbsolutePath().endsWith("pdf")) {
            FileUtils.writeByteArrayToFile(targetFile, bytes);
        } else {
            @Cleanup PDDocument document = PDDocument.load(bytes);
            PDFRenderer renderer = new PDFRenderer(document);
            BufferedImage bufferedImage = renderer.renderImageWithDPI(0, 150);// 只打第一页,dpi越大图片越高清也越耗时
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "jpg", baos);
            FileUtils.writeByteArrayToFile(targetFile, baos.toByteArray());
        }
        log.info("文件本地保存完成,文件路径:[{}]", targetFile.getAbsolutePath());

        /*
        4.组织返回
         */
        GeneratePdfResp generatePdfResp = new GeneratePdfResp();
        generatePdfResp.setAbsolutePath(targetFile.getAbsolutePath());
        return generatePdfResp;
    }
}

3.4、controller

@Slf4j
@RestController
public class PdfController {
    @Autowired
    private RestService generatePdfService;

    @PostMapping(value = "/html2Pdf")
    public GeneratePdfResp html2Pdf(@RequestBody @Validated GeneratePdfReq req) {
        GeneratePdfResp resp = generatePdfService.service(req);
        return resp;
    }
}

4、应用

4.1、freemarker模板(html模板)



    


        body {
            font-family: SimSun
        }
    

html模板

姓名:${name}

证件号码:${cardNo}

日期:${date}

4.2、接口调用生成pdf

Java生成pdf文件或jpg图片
postman
Java生成pdf文件或jpg图片
pdf

5、说明

  1. 根据参数后缀名可以生成pdf或jpg文件,生成的pdf文件默认为A4大小,也可以通过请求参数设置大小。
  2. pdf文件会根据html模板内容大小自动分页。
  3. 如果生成图片,多页不会生成多张图片,可以把高度设置大一些,最后会生成长图。
  4. 水印每页都会自动添加。
  5. 为了提高代码的复用性和可维护性,工程内渲染html模板、生成pdf文件、添加水印都有单独的接口实现。

参考链接

  • java使用xmlWorkerHelper将html转pdf
  • Freemarker模板的使用简介
  • 使用itext5给PDF添加文字水印

代码地址

  • github:https://github.com/senlinmu1008/spring-boot/tree/master/html2pdf
  • gitee:https://gitee.com/ppbin/spring-boot/tree/master/html2pdf

推荐阅读
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 本文讨论了在Spring 3.1中,数据源未能自动连接到@Configuration类的错误原因,并提供了解决方法。作者发现了错误的原因,并在代码中手动定义了PersistenceAnnotationBeanPostProcessor。作者删除了该定义后,问题得到解决。此外,作者还指出了默认的PersistenceAnnotationBeanPostProcessor的注册方式,并提供了自定义该bean定义的方法。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
author-avatar
韭花帖_420
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有