java使用poi-tl模版+vform自定义表单生成word,使用LibreOffice导出为pdf,批量下载为压缩文件

java使用poi-tl模版+vform自定义表单生成word,使用LibreOffice导出为pdf,批量下载为压缩文件。

java 复制代码
    @Override
    public void exportMeetingAll(Long id, HttpServletResponse response) {
        TbProjectStartHold tbProjectStartHold = selectTbProjectStartHoldById(id, null);
        TbProjectStart tbProjectStart = tbProjectStartMapper.selectTbProjectStartById(id);
        //查询伦理的项目名称
        ProjectInitiation initiation = projectInitiationService.findById(tbProjectStartHold.getProjectInitiationId(), null);
        String fileStr=initiation.getAcceptanceNumber().replace(" ", "")+ "_启动会文件_" + DateUtils.dateTimeNow()+ ".zip";
        // 设置响应头
        response.setContentType("application/zip");
        String fileName = URLEncoder.encode(fileStr, StandardCharsets.UTF_8);
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);


        // 创建ZIP输出流
        try (ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(response.getOutputStream());
             BufferedOutputStream bos = new BufferedOutputStream(zipOut)) {
            zipOut.setEncoding("UTF-8"); // 设置编码
            // 导出启动会预约确认单
            projectStartService.exportAppointmentForZip(tbProjectStart,initiation,zipOut);
            //导出会议议程
            exportMeetingAgendaPdfForZip(tbProjectStartHold,initiation,zipOut);
            //导出启动承诺函
            exportMeetingStartForZip(tbProjectStartHold,initiation,zipOut);
            //导出会议记录
            exportMeetingRecordForZip(tbProjectStartHold,initiation,zipOut);
            //导出会议照片
            List<SysFileData> meetiongPhoto=tbProjectStartHold.getMeetingPhoto();
            if(meetiongPhoto!=null){
                Map<String,String> map=new HashMap<>();
                meetiongPhoto.forEach(e->{
                    map.put(e.getFileUrl(),"会议照片/"+e.getFileName());
                });
                ImageExport.batchAddImagesToZip(zipOut,map,resourceLoader);
            }

            // 刷新流,确保所有数据写入
            bos.flush();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("打包下载失败:" + e.getMessage());
        }
    }
java 复制代码
  @Override
    public void exportMeetingStartForZip(TbProjectStartHold tbProjectStartHold,ProjectInitiation initiation, ZipArchiveOutputStream zipOut) {
        Map<String, Object> data=new HashMap<>();
        data.put("projectName",Optional.ofNullable(tbProjectStartHold.getProjectName()).orElse(""));
        data.put("meetingDate","");
        if(tbProjectStartHold.getMeetingDate()!=null){
            data.put("meetingDate",DateUtils.parseDateToStr("yyyy-MM-dd",tbProjectStartHold.getMeetingDate()));
        }
        //模板选择
        String url = "classpath:templates/start_chengnuohan.docx";
        String fileName=initiation.getAcceptanceNumber().replace(" ", "")+ "_项目启动承诺函_" + DateUtils.dateTimeNow();
        //响应返回 pdf 文件
        WordToPdf.generatePdfForZip(zipOut,url,data,resourceLoader,libreoffice,fileName + ".pdf");
    }
java 复制代码
    /**
     * 生成pdf 在压缩包内
     * @param zipOut
     * @param url
     * @param data
     * @param resourceLoader
     * @param libreoffice
     * @param pdfFileName
     */
    public static void generatePdfForZip(ZipArchiveOutputStream zipOut, String url, Map<String, Object> data,
                                           ResourceLoader resourceLoader, String libreoffice, String pdfFileName) {

        Configure config = Configure.builder().build();
        generatePdfForZip(config, zipOut, url, data, resourceLoader, libreoffice, pdfFileName);
    }
    
    /**
     * 生成pdf 在压缩包内
     * @param zipOut
     * @param url
     * @param data
     * @param resourceLoader
     * @param libreoffice
     * @param pdfFileName
     */
    public static void generatePdfForZip(Configure config,ZipArchiveOutputStream zipOut, String url, Map<String, Object> data,
                                         ResourceLoader resourceLoader, String libreoffice, String pdfFileName) {
        // ========== 2. 生成临时 Word 文件(关键修改:不再直接写入响应) ==========
        // 临时文件目录(跨平台兼容:Windows是C:\Users\XXX\AppData\Local\Temp,Linux是/tmp)
        System.out.println("****************");;
        System.out.println(System.getProperty("java.io.tmpdir"));
        File tempDir = new File(System.getProperty("java.io.tmpdir"));
        // 生成唯一文件名(避免并发冲突)
        String uniqueName = UUID.randomUUID().toString().replace("-", "");
        File tempWordFile = new File(tempDir, uniqueName + ".docx");
        File tempPdfFile = new File(tempDir, uniqueName + ".pdf");

        // 标记是否生成成功(用于最终清理临时文件)
        boolean generateSuccess = false;
        try {
            // 2.1 用 poi-tl 生成 Word 到临时文件
            Resource resource = resourceLoader.getResource(url);
            try (InputStream inputStream = resource.getInputStream();
                 OutputStream wordOut = new FileOutputStream(tempWordFile)) {
                XWPFTemplate template = XWPFTemplate.compile(inputStream, config).render(data);
                template.write(wordOut);
                PoitlIOUtils.closeQuietly(template); // 关闭模板资源
            }

            // ========== 3. 调用 LibreOffice 转换 Word → PDF(核心步骤) ==========
            boolean convertSuccess = convertWordToPdfByLibreOffice(libreoffice,tempWordFile, tempPdfFile);
            if (!convertSuccess || !tempPdfFile.exists() || tempPdfFile.length() == 0) {
                throw new RuntimeException("Word 转 PDF 失败,生成的 PDF 文件为空");
            }

            // 3. 创建ZIP条目(指定PDF在压缩包中的名称/路径)
            // 支持子目录格式,例如:"2024年报表/销售数据.pdf"
            ZipArchiveEntry pdfEntry = new ZipArchiveEntry(pdfFileName);
            // 设置文件大小(优化ZIP压缩效率)
            pdfEntry.setSize(tempPdfFile.length());
            zipOut.putArchiveEntry(pdfEntry);

            // 4. 将PDF文件写入ZIP条目
            try (InputStream pdfIn = new FileInputStream(tempPdfFile);
                 BufferedInputStream bis = new BufferedInputStream(pdfIn)) {
                byte[] buffer = new byte[1024 * 8];
                int len;
                while ((len = bis.read(buffer)) != -1) {
                    zipOut.write(buffer, 0, len);
                }
            }

            // 5. 关闭当前ZIP条目(必须调用,否则后续条目无法添加)
            zipOut.closeArchiveEntry();
            // 刷新ZIP流,确保数据写入(外部最终需调用zipOut.close())
            zipOut.flush();

            generateSuccess = true;
            System.out.println("PDF文件[" + pdfFileName + "]已成功添加到ZIP包");

        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("导出 PDF 失败:" + e.getMessage(), e);
        } finally {
            // ========== 5. 清理临时文件(关键:避免磁盘残留) ==========
            // 6. 清理临时文件(异步延迟删除,确保ZIP写入完成)
            boolean finalGenerateSuccess = generateSuccess;
            new Thread(() -> {
                try {
                    // 延迟3秒删除(如果生成失败,缩短延迟)
                    TimeUnit.SECONDS.sleep(finalGenerateSuccess ? 3 : 1);
                    // 静默删除,失败不抛异常(避免影响主流程)
                    FileUtils.deleteQuietly(tempWordFile);
                    FileUtils.deleteQuietly(tempPdfFile);
                    System.out.println("临时文件清理完成:" + tempWordFile.getName() + "、" + tempPdfFile.getName());
                } catch (InterruptedException ignored) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }

图片处理

java 复制代码
public class ImageExport {
    /**
     * 单个图片写入ZIP流(支持本地文件、classpath资源、网络图片)
     *
     * @param zipOut      ZIP输出流
     * @param imageSource 图片来源(支持3种格式:1.本地路径如D:/img/1.jpg 2.classpath路径如classpath:/images/2.png 3.网络URL如https://xxx.com/3.jpg)
     * @param zipImageName ZIP内图片文件名(支持子目录:如images/产品图.png)
     * @param resourceLoader Spring资源加载器(用于读取classpath图片)
     * @throws Exception 图片读取或写入异常
     */
    public static void addImageToZip(ZipArchiveOutputStream zipOut, String imageSource, String zipImageName,
                                     ResourceLoader resourceLoader) throws Exception {
        InputStream imageIn = null;
        try {
            // 适配不同图片来源
            if (imageSource.startsWith("classpath:")) {
                // 读取classpath下的图片
                Resource resource = resourceLoader.getResource(imageSource);
                imageIn = resource.getInputStream();
            } else if (imageSource.startsWith("http://") || imageSource.startsWith("https://")) {
                // 读取网络图片
                imageIn = new java.net.URL(imageSource).openStream();
            } else {
                // 读取本地文件图片
                String localPath = RuoYiConfig.getProfile();
                String downloadPath = localPath + StringUtils.substringAfter(imageSource, Constants.RESOURCE_PREFIX);
                File imageFile = new File(downloadPath);
                if (!imageFile.exists()) {
                    throw new FileNotFoundException("本地图片不存在:" + imageSource);
                }
                imageIn = new FileInputStream(imageFile);
            }

            // 图片写入ZIP
            addStreamToZip(zipOut, imageIn, zipImageName);
            System.out.println("图片已添加到ZIP:" + zipImageName);

        } finally {
            // 关闭图片输入流
            if (imageIn != null) {
                try {
                    imageIn.close();
                } catch (IOException ignored) {
                }
            }
        }
    }

    /**
     * 多张图片批量写入ZIP流
     *
     * @param zipOut      ZIP输出流
     * @param imageMap    图片映射:key=图片来源(本地/classpath/URL),value=ZIP内文件名(含路径)
     * @param resourceLoader Spring资源加载器
     * @throws Exception 批量处理异常
     */
    public static void batchAddImagesToZip(ZipArchiveOutputStream zipOut, Map<String, String> imageMap,
                                           ResourceLoader resourceLoader) throws Exception {
        for (Map.Entry<String, String> entry : imageMap.entrySet()) {
            addImageToZip(zipOut, entry.getKey(), entry.getValue(), resourceLoader);
        }
    }

    /**
     * 工具方法:将输入流写入ZIP流(用于图片流场景)
     *
     * @param zipOut      ZIP输出流
     * @param inputStream 输入流(图片/其他文件流)
     * @param zipFileName ZIP内的文件名(含路径)
     * @throws Exception 写入异常
     */
    private static void addStreamToZip(ZipArchiveOutputStream zipOut, InputStream inputStream, String zipFileName) throws Exception {
        try (BufferedInputStream bis = new BufferedInputStream(inputStream)) {
            ZipArchiveEntry entry = new ZipArchiveEntry(zipFileName);
            zipOut.putArchiveEntry(entry); // 流无法提前获取大小,不设置size

            byte[] buffer = new byte[1024 * 8];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                zipOut.write(buffer, 0, len);
            }

            zipOut.closeArchiveEntry();
            zipOut.flush();
        }
    }
相关推荐
CodeCraft Studio2 小时前
国产化Word处理控件Spire.Doc教程:使用Java将RTF文件转换为PDF的全面教程
java·pdf·word·spire.doc·rtf转pdf·文件格式转换·文档开发sdk
随便叫个啥呢2 小时前
java使用poi-tl模版+vform自定义表单生成word
java·word·poi-tl
青浅l2 小时前
vue中回显word、Excel、txt、markdown文件
vue.js·word·excel
开心星人2 小时前
Leetcode hot100 Java刷题(二)
java·算法·leetcode
喵手2 小时前
Java与Microservices架构的结合:构建可扩展、高可用的系统!
java·架构·华为云
半部论语2 小时前
MyBatis-Plus 通用 CRUD 实现原理技术文档
java·spring boot·mybatis
手握风云-2 小时前
JavaEE 进阶第六期:Spring Boot 如何重塑后端开发
java·spring boot·java-ee
Deamon Tree3 小时前
【设计题】如何实现一个线程安全的缓存?
java·spring boot·spring
毕设源码-钟学长3 小时前
【开题答辩全过程】以 基于Java的相机专卖网的设计与实现为例,包含答辩的问题和答案
java·开发语言