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;
    }

效果图


相关推荐
码云数智-园园15 分钟前
C++ STL 容器怎么选?vector、list、map、unordered_map 优缺点对比
java·开发语言
en.en..21 分钟前
竖线 |:管道、按位或、掩码组合
java·开发语言
程序猿乐锅1 小时前
【计算机组成原理 | 第六章】中央处理器 CPU
java·开发语言
大模型丫丫1 小时前
如何提升单体 Spring Boot 应用的并发数?
java·spring boot·后端
Terra.K1 小时前
ThreadLocal解决一个线程里面跨层传递问题
java·开发语言·后端
泡海椒1 小时前
低代码规则开发:JQuick-Java声明式配置驱动开发落地教程
java·低代码·rxjava
weixin_440730502 小时前
socket解决多次输入问题-大数据接收问题-模块动态导入
java·开发语言·servlet
用户3126874877202 小时前
ReentrantLock 和 synchronized 到底选哪个?源码级对比与选型
java
计算机毕设定制辅导-无忧学长2 小时前
基于Spring Boot的玄幻小说个性化推荐平台设计与实现
java·vue.js·spring boot·后端·mysql·推荐算法
码路漫漫2 小时前
LRU 真的能解决日志按业务 Key 分文件的落地瓶颈吗?
java·后端