当前位置: 首页 > news >正文

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

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

@Overridepublic 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());}}
  @Overridepublic 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");}
/*** 生成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();}}

图片处理


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);}// 图片写入ZIPaddStreamToZip(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); // 流无法提前获取大小,不设置sizebyte[] buffer = new byte[1024 * 8];int len;while ((len = bis.read(buffer)) != -1) {zipOut.write(buffer, 0, len);}zipOut.closeArchiveEntry();zipOut.flush();}}
http://www.dtcms.com/a/593254.html

相关文章:

  • 济南专业的网站建设公司wordpress快速发布
  • 帝国cms手机网站模板保洁公司开发app
  • 【必收藏】RAG技术全景图:从NaiveRAG到AgenticRAG的演进与工程实践指南
  • 构建AI智能体:九十、图解大模型核心三大件 — 输入编码、注意力机制与前馈网络层
  • 任意模型×任意加速器×任意云,红帽用开放混合解锁企业AI深水区
  • DETR:新一代目标检测范式综述
  • AI浏览器通过模拟人类用户行为绕过付费墙
  • HMSiR-Amide-BG,是一种酶靶向荧光标记分子
  • 大模型-详解 Vision Transformer (ViT) (2)
  • 汽车之家联合HarmonyOS SDK,深度构建鸿蒙生态体系
  • 福州网站设计哪里建站wordpress鼠标停留
  • 2025年IEEE TEVC SCI1区TOP,多解旅行商问题的层次遗传算法,深度解析+性能实测
  • 企业级即时通讯和涉密即时通讯有哪些区别?
  • 在VUE内使用beforeinstallprompt接口安装PWA添加到桌面
  • CMMI证书(更准确地说,是CMMI评估等级证书)对于一个组织,特别是软件和研发类组织来说,作用是多方面的、战略性的。
  • 深入探讨redis:分布式锁
  • Spring Cloud Gateway详解笔记:核心概念、工作原理与最佳实践
  • 三维数字图像相关方法在汽车踏板臂载荷下的裂纹尖端应变场及断裂韧性分析
  • 源码之家网站上海网站制作怎么选
  • 工业数据库怎么选:一文详解实时数据库“亚控” vs TDengine
  • CTFHub Web进阶-Json Web Token通关2:敏感信息泄露
  • 基于Java+Spring Boot、Vue的B/S医院患者随访管理系统源码,支持二次开发,三级随访体系(出院/门诊随访)、智慧云库(表单配置)
  • 怎么样创建做零食山楂的网站建设门户网站预算
  • 网站后台模板 下载公司做影视网站侵权
  • leetcode860题. 柠檬水找零
  • 二者的差异,是掌握 Spring 依赖注入(DI)和控制反转(IoC)的关键 作用对象与作用方式 @Component:类级别的自动 ...
  • gitlab 与gitlab-runner 安装与配置
  • 高职学前教育与法律事务专业:证书规划与职业发展指南
  • 外贸做网站推广环球资源网网站特色
  • 【LLIE技术专题】基于成对低光图像学习自适应先验方案