java实现根据Velocity批量生成pdf并合成zip压缩包

Velocity 模版操作

用的之前写好的: 传送门

其中需要新加一个转成输入流的方法

java 复制代码
    public static InputStream convertToPdf(StringWriter stringWriter) throws IOException {
        //将 HTML 转为字节流
        byte[] htmlBytes = stringWriter.toString().getBytes(StandardCharsets.UTF_8);
        //创建 PDF 输出流
        ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream();
        try (PdfWriter writer = new PdfWriter(pdfOutputStream);
             PdfDocument pdfDocument = new PdfDocument(writer)) {
            // 设置 A4 纸张和边距
            pdfDocument.setDefaultPageSize(PageSize.A4);
            pdfDocument.setTagged(); // 支持无障碍阅读
            //配置中文字体
            ConverterProperties properties = new ConverterProperties();
            FontProvider fontProvider = new FontProvider();
            fontProvider.addFont("STSongStd-Light", "UniGB-UCS2-H"); // 添加中文字体
            properties.setFontProvider(fontProvider);
            //转换 HTML 到 PDF
            try (InputStream htmlStream = new ByteArrayInputStream(htmlBytes)) {
                HtmlConverter.convertToPdf(htmlStream, pdfDocument, properties);
            }
        }
        // 返回 PDF 的输入流
        return new ByteArrayInputStream(pdfOutputStream.toByteArray());
    }

具体使用

java 复制代码
public void reportBatchDownload(ReportBatchDownload params, HttpServletResponse response) {
        List<SpeExamineInfo> infos = speExamineInfoMapper.selectListByReportBatchDownload(params);
        if (infos.isEmpty()) {
            throw new ServiceException("当前没有已完成的预约记录");
        }
        String filName = "体检报告.zip";
        // 设置请求流
        try {
            FileUtils.setAttachmentResponseHeader(response, filName);
            response.setContentType("application/zip");
            try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream(), StandardCharsets.UTF_8)) {
                for (SpeExamineInfo info : infos) {
                    VelocityContext velocityContext = getVelocityContext(info.getId(), info, null);
                    StringWriter stringWriter = VelocityUtils.genHtml(velocityContext, "vm/report.vm");
                    try (InputStream inputStream = VelocityUtils.convertToPdf(stringWriter)) {
                        // 添加ZIP条目
                        zipOut.putNextEntry(new ZipEntry(buildFilePath(info)));
                        // 写入文件内容
                        byte[] cache = new byte[8192];
                        int nRead;
                        while ((nRead = inputStream.read(cache)) != -1) {
                            zipOut.write(cache, 0, nRead);
                        }
                        zipOut.closeEntry();
                    }
                    zipOut.flush();
                }
                // 最后刷新缓冲区
                zipOut.finish();
            }
        } catch (IOException e) {
            throw new RuntimeException("生成压缩包失败", e);
        }

    }

    private String buildFilePath(SpeExamineInfo info){
        return "/" + info.getSchName() + "/" + info.getGradeName() + "/" + info.getClassName() + "/" + info.getStuName() + "_体检报告.pdf";
    }
    
// 处理vm模版变量
private VelocityContext getVelocityContext(String infoId, SpeExamineInfo speExamineInfo, String imgPrefix) {
        SpeExamineResQuery speExamineResQuery = new SpeExamineResQuery();
        speExamineResQuery.setInfoId(infoId);
        List<SpeExamineResDto> speExamineResDtos = speExamineResMapper.selectList(speExamineResQuery);
        SpeExamineType speExamineType = new SpeExamineType();
        speExamineType.setInfoId(infoId);
        List<SpeExamineType> speExamineTypes = speExamineTypeMapper.selectList(speExamineType);
        Map<String, Object> res = new HashMap<>();
        speExamineResDtos.forEach(item -> {
            if (StringUtils.isNotBlank(item.getItemResLabel())) {
                res.put(item.getItemCode() + "_label", item.getItemResLabel());
            }
            res.put(item.getItemCode(), item.getItemRes());
            res.put(item.getItemCode() + "_conclusion", item.getConclusion());
            res.put(item.getItemCode() + "_hasException", item.getHasException());
        });
        Map<String, Object> type = new HashMap<>();
        speExamineTypes.forEach(item -> {
            type.put(item.getItemType() + "_advice", item.getDocAdvice());
            type.put(item.getItemType() + "_signature", (StringUtils.isNotBlank(imgPrefix) ? imgPrefix : "" ) + item.getDocSign());
        });
        Map<String, Object> param = new HashMap<>(3);
        param.put("info", speExamineInfo);
        param.put("res", res);
        param.put("type", type);
        VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("pdf", param);
        return velocityContext;
    }

效果图


相关推荐
Seven9719 小时前
NIO的零拷贝如何实现高效数据传输?
java
架构师沉默1 天前
别又牛逼了!AI 写 Java 代码真的行吗?
java·后端·架构
后端AI实验室2 天前
我把一个生产Bug的排查过程,交给AI处理——20分钟后我关掉了它
java·ai
凉年技术2 天前
Java 实现企业微信扫码登录
java·企业微信
狂奔小菜鸡2 天前
Day41 | Java中的锁分类
java·后端·java ee
hooknum2 天前
学习记录:基于JWT简单实现登录认证功能-demo
java
程序员Terry2 天前
同事被深拷贝坑了3小时,我教他原型模式的正确打开方式
java·设计模式
NE_STOP2 天前
MyBatis-缓存与注解式开发
java
码路飞2 天前
不装 OpenClaw,我用 30 行 Python 搞了个 QQ AI 机器人
java
Re_zero2 天前
以为用了 try-with-resources 就稳了?这三个底层漏洞让TCP双向通讯直接卡死
java·后端