【Java导出PDF】Aspose-words导出PDF指南:Java导出带图片模板PDF(亲测实用)

Aspose-words导出PDF指南:Java导出带图片模板PDF

大家好,好久不见啊,俺最近也是忙来忙去导致写博客越来越少,正好开发过程中遇到一个需求是需要导出PDF,还要按照模板样式导出,还要处理图片问题,这俺就抓抓脑壳了,实不相瞒,俺之前就接触过Excel导出,这一次也算自己学习提升了,俺浏览了很多资料,最终锁定了Aspose.Words,但是Aspose.Words俺发现CSDN和博客园很多博主写的不是很清晰,所以今天俺就来分享一波这个需求点使用Aspose.Words导出PDF遇到的坑及如何导出。

一 Jar包介绍

Aspose.Words是一个商业.NET类库,可以使得应用程序处理大量的文件任务。Aspose.Words支持Doc,Docx,PDF等不同格式,使用该类库可以在不使用Microsoft.Word的情况下生成、修改、转换和打印文档。在项目中使用Aspose.Words可以有以下好处。
今天我分享的是使用Aspose.Words在Java中进行PDF导出及图片的导出,maven依赖如下

        <dependency>
            <groupId>com.luhuiguo</groupId>
            <artifactId>aspose-words</artifactId>
            <version>23.1</version>
        </dependency>

二 导出步骤

我们先来看一下这个导出的空白模板
观察这个模板我们可以看到这里有realName,addr,图片1,图片2,以及巡防打卡四个地方需要处理,首先,执行看效果。

我们需要做的是 把这个模板Word先保存,然后放到我们的项目里面,比如俺这里把他放到了Resources目录下

闲话少说,上代码!!

    @ApiOperation("测试PDF导出")
    @GetMapping("/api/ffp/dict/PDFTest")
    public ApiResult<Boolean> PDFTest(HttpServletResponse response,@RequestParam Long id) throws Exception {
        FfpFileInfo fileInfo = ffpFileInfoService.getById(id);
        if (ObjectUtil.isEmpty(fileInfo)) {
            return ApiResult.error(602, "文件不存在");
        }
        String base64 = baseS3Service.downloadWithBase64(fileInfo.getFileKey());
        byte[] bytes = Base64.getDecoder().decode(base64);
        ClassPathResource  classPathResource= new ClassPathResource("/templateFile/demo.docx");
        InputStream inputStream = classPathResource.getInputStream();
        Document document=new Document(inputStream);
        Map paramsMap = new HashMap();
        paramsMap.put("realname","Spring不止春天");
        paramsMap.put("addr","四川");
        PdfUtils.replaceText(paramsMap,document);
        PdfUtils.replaceBookMarkImage( bytes,document);
        String name="测试测试";
        File tempFile = File.createTempFile(name, ".pdf");
        //插入表格
        DocumentBuilder builder = new DocumentBuilder(document);
        NodeCollection runs = document.getChildNodes(NodeType.PARAGRAPH, true);
        for (int i = 0; i < runs.getCount(); i++){
            Node node = runs.get(i);
            String text = node.getText();
            System.out.println(text);
            if (text.contains("巡防打卡")){
                builder.moveTo(node);
                Table table = builder.startTable();
                // Insert a cell
                builder.insertCell();
                table.autoFit( AutoFitBehavior.AUTO_FIT_TO_CONTENTS );
                table.setAlignment(1);
                builder.getCellFormat().setWidth(135);
                builder.write( "打卡时间" );
                builder.insertCell();
                builder.write( "日志" );
                builder.insertCell();
                builder.write( "打卡地址" );
                // End row
                builder.endRow();
                // start a next row and set its properties
                builder.getRowFormat().setHeight( 40 );
                builder.getCellFormat().setWidth(135);
                builder.insertCell();
                builder.write( "2023:09:01:" );
                builder.getCellFormat().setWrapText(true);
                builder.insertCell();
                builder.write( "进行了打卡的日志" );
                builder.getCellFormat().setWrapText(true);
                builder.insertCell();
                builder.write( "四川省成都市武侯区1111111111111111111" );
                builder.getCellFormat().setWrapText(true);
                builder.endRow();
                //第二行
                builder.insertCell();
                builder.write( "2023:09:01:" );
                builder.getCellFormat().setWrapText(true);
                builder.insertCell();
                builder.write( "进行了打卡的日志" );
                builder.getCellFormat().setWrapText(true);
                builder.insertCell();
                builder.write( "四川省成都市武侯区2222222222222222222222" );
                builder.getCellFormat().setWrapText(true);
                builder.endRow();
                // End table
                builder.endTable();

            }
        }
        document.save(new FileOutputStream(tempFile), SaveFormat.PDF);
        InputStream fis = new BufferedInputStream(new FileInputStream(tempFile));
        byte[] buffer = new byte[fis.available()];
        fis.read(buffer);
        fis.close();
        // 清空response
        response.reset();
        String filename=tempFile.getName();
        // 设置响应的头部信息
        response.setContentType("application/octet-stream;charset=UTF-8");
        String fileName = new String(filename.getBytes("gb2312"), "iso8859-1");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName);
        OutputStream ouputStream = response.getOutputStream();
        ouputStream.write(buffer);
        ouputStream.flush();
        ouputStream.close();
        tempFile.delete();
        return ApiResult.success(true);
    }

下面我来逐一讲解简单参数替换,图片插入,列表插入三种不同格式的PDF处理

一 简单参数替换

realName和addr两个参数就属于简单替换值去进行处理,可以看到代码中有这么一段
这个replaceText就是真正替换的方法,方法如下:


    /***
     * 进行Word文件中的参数替换,方便转为PDF进行导出
     * @author jiazl
     * @date 2023/6/15 17:54
     * @param paramMap,doc
     * @return void
     **/
    public static void replaceText(Map<String, String> paramMap, Document doc) throws Exception {
        FindReplaceOptions opt = new FindReplaceOptions();
        for (Map.Entry<String, String> entry : paramMap.entrySet()) {
            String key = String.format("{%s}", entry.getKey());
            String value = Objects.isNull(entry.getValue()) ? "" : entry.getValue();
            doc.getRange().replace(key, value, opt);
        }
        int replace = doc.getRange().replace(Pattern.compile(PARAM_MATCH), "", opt);
        if (replace > 0) {
            logger.error("未知参数:{}", JSON.toJSONString(paramMap));
        }
    }

这段代码的含义为寻找到Word中的{%s}参数,利用paramMap这个Map去进行值的替换,这个很简单不做过多赘述。

二 图片的插入

细心的同志已经发现了,我给出的这个API是带参数的,这个参数是什么呢,是一个文件ID,我们往想要插入的地方去插入图片,是需要寻找Word中的节点的,例如:
插入图片的方法为:

    /***
     * 进行Word文件中的图片插入(单处地方,单个图片)
     * @author jiazl
     * @date 2023/6/15 17:54
     * @param doc
     * @return void
     **/
    public static void replaceBookMarkImage(  byte[] bytes, Document doc) throws Exception {
        DocumentBuilder db = new DocumentBuilder(doc);
        NodeCollection runs = doc.getChildNodes(NodeType.PARAGRAPH, true);
        for (int i = 0; i < runs.getCount(); i++) {
            Node node = runs.get(i);
            String text = node.getText();
            System.out.println(text);
            if (text.contains("图片1")) {
                db.moveTo(node);
                db.insertImage(bytes, 119.4, 64);
            }
        }
    }

在模板pdf中,就是这里
当他寻找到这个节点,就会执行

                db.moveTo(node);
                db.insertImage(bytes, 119.4, 64);

这两个方法,第一个是将操作对象转移到这个节点,第二个是进行图片的插入,119.4和64分别为设置的图片大小。
(当然我这里是个Demo接口所以找节点图片1是写死的,各位大宝贝可以将想要寻找的节点作为参数传入,这样代码会更壮硕!)

三 列表的插入

家人们啊,太难了,我浏览了很多网站,很少有人对列表插入写得清楚,因为这个Jar包是国内某大佬重新封装的(正版国外的收费),没注释,我一个一个方法试出来的,麻烦大家点个赞吧~~
对于列表的操作我们主要看这一部分代码

 //插入表格
        DocumentBuilder builder = new DocumentBuilder(document);
        NodeCollection runs = document.getChildNodes(NodeType.PARAGRAPH, true);
        for (int i = 0; i < runs.getCount(); i++){
            Node node = runs.get(i);
            String text = node.getText();
            System.out.println(text);
            if (text.contains("巡防打卡")){
                builder.moveTo(node);
                Table table = builder.startTable();
                // Insert a cell
                builder.insertCell();
                table.autoFit( AutoFitBehavior.AUTO_FIT_TO_CONTENTS );
                table.setAlignment(1);
                builder.getCellFormat().setWidth(135);
                builder.write( "打卡时间" );
                builder.insertCell();
                builder.write( "日志" );
                builder.insertCell();
                builder.write( "打卡地址" );
                // End row
                builder.endRow();
                // start a next row and set its properties
                builder.getRowFormat().setHeight( 40 );
                builder.getCellFormat().setWidth(135);
                builder.insertCell();
                builder.write( "2023:09:01:" );
                builder.getCellFormat().setWrapText(true);
                builder.insertCell();
                builder.write( "进行了打卡的日志" );
                builder.getCellFormat().setWrapText(true);
                builder.insertCell();
                builder.write( "四川省成都市武侯区1111111111111111111" );
                builder.getCellFormat().setWrapText(true);
                builder.endRow();
                //第二行
                builder.insertCell();
                builder.write( "2023:09:01:" );
                builder.getCellFormat().setWrapText(true);
                builder.insertCell();
                builder.write( "进行了打卡的日志" );
                builder.getCellFormat().setWrapText(true);
                builder.insertCell();
                builder.write( "四川省成都市武侯区2222222222222222222222" );
                builder.getCellFormat().setWrapText(true);
                builder.endRow();
                // End table
                builder.endTable();

            }
        }

这里作为Demo测试接口,写死两行数据,应用于具体业务可以利用for循环去做插入。
这里是不是很熟悉,插入列表同样是寻找到需要插入的节点,然后

builder.getCellFormat().setWrapText(true);

代码中这个方法就是让他在方格内换行的

三 总结及一些注意点

1 因为对于博主来说,插入图片,插入表格,替换模板参数已经是满足大部分开发任务了,更深层次的因为博主比较懒,所以也没有去了解。
2 这个Jar包对图片是有要求的,经过博主的一些测试。JPG,JPEG,PNG均是可以导出图片的,但是点名批评WEBP,会报错,因为我是懒狗,具体什么错也忘了。
3 秉持复制的态度,有用到博主文章中的方法有问题可以私信博主,博主看到就会回复的,放心,不是画大饼。
4 博主最后的给response赋值啊等一部分操作是无用的,Swagger是不支持导出流下载文件。用PostMan调接口会发现这么一个情况
博主努力了,解决不了后缀的问题,于是博主摆烂了给前端处理了。
5 注意代码中

        Map paramsMap = new HashMap();
        paramsMap.put("realname","Spring不止春天");
        paramsMap.put("addr","四川");

这个paramsMap要和PDF中对应,不然会报错,什么错也忘了。
6 有人会说啊,那最终效果中图片两个字咋办?真聪明,再替换一次,把图片两个字替换为空串就行了,篇幅原因,不上代码了。
7 本来我也想写的很好,但是我真的是一个懒狗,对不起各位,但是我能保证代码是能好的,是正常使用的,是OK的!有问题欢迎随时联系~

版权声明:本文为博主作者:拒绝空指针原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/weixin_49190101/article/details/131536093

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2024年1月11日
下一篇 2024年1月11日

相关推荐