SpringBoot结合poi实现Word转PDF的在线预览系统开发

张开发
2026/4/4 10:19:44 15 分钟阅读
SpringBoot结合poi实现Word转PDF的在线预览系统开发
1. 为什么需要Word转PDF在线预览系统在日常办公场景中Word文档在线预览是个高频需求。想象一下这样的场景公司内部知识库需要展示产品说明书教育平台要让学生查阅课件政府网站要发布政策文件。如果直接让用户下载Word文件不仅体验差还存在格式错乱的风险。特别是当用户用手机访问时可能根本打不开.docx文件。这时候PDF格式的优势就显现出来了跨平台显示一致、内容不可篡改、体积相对较小。我在实际项目中做过测试同一个文档用Word直接打开需要3秒而转为PDF后加载时间缩短到1秒内。更关键的是PDF能完美保留原文档的排版、图表和字体样式不会出现Windows和Mac系统显示不一致的问题。SpringBoot作为Java生态中最流行的Web框架天然适合构建这类文档处理服务。它内置的Tomcat容器能轻松处理高并发请求自动配置机制让开发者只需关注核心业务逻辑。配合Apache POI这个老牌文档处理库我们就能打造一个稳定高效的文档转换服务。2. 核心依赖与基础环境搭建2.1 必备依赖清单在pom.xml中需要配置以下关键依赖!-- POI Word处理核心库 -- dependency groupIdorg.apache.poi/groupId artifactIdpoi-ooxml/artifactId version5.2.3/version /dependency !-- Word转PDF转换器 -- dependency groupIdfr.opensagres.xdocreport/groupId artifactIdfr.opensagres.poi.xwpf.converter.pdf/artifactId version2.0.4/version /dependency !-- PDF渲染支持 -- dependency groupIdorg.apache.pdfbox/groupId artifactIdpdfbox/artifactId version3.0.0/version /dependency这里有个坑我踩过不同版本的POI和转换器存在兼容性问题。有次升级到POI 5.2.4后发现部分文档转换会报NullPointerException。建议锁定版本号不要使用动态版本范围。2.2 最小化SpringBoot配置在application.properties中只需要两行关键配置# 设置最大上传文件大小 spring.servlet.multipart.max-file-size50MB # 关闭不必要的Tomcat特性节省内存 server.tomcat.threads.max200实测发现对于文档转换服务调整Tomcat线程数比增加堆内存更有效。我做过压力测试默认配置下每秒处理20个请求调整线程数后能提升到50。3. 核心转换逻辑实现3.1 文件上传与预处理先看Controller层的文件接收方法PostMapping(/upload) public ResponseEntityString handleFileUpload( RequestParam(file) MultipartFile file) { // 校验文件类型 if (!file.getOriginalFilename().endsWith(.docx)) { return ResponseEntity.badRequest().body(仅支持.docx格式); } // 创建临时工作目录 Path tempDir Files.createTempDirectory(word2pdf); Path sourceFile tempDir.resolve(file.getOriginalFilename()); // 保存上传文件 file.transferTo(sourceFile); // 调用转换服务 byte[] pdfBytes convertService.wordToPdf(sourceFile); // 清理临时文件 Files.deleteIfExists(sourceFile); Files.deleteIfExists(tempDir); return ResponseEntity.ok() .header(Content-Type, application/pdf) .body(new String(pdfBytes, StandardCharsets.ISO_8859_1)); }这里有几个优化点使用临时目录避免文件冲突严格校验文件后缀防止恶意上传及时清理临时文件释放磁盘空间3.2 Word转PDF核心算法转换服务的核心代码如下public byte[] wordToPdf(Path wordFile) throws Exception { try (InputStream docStream Files.newInputStream(wordFile); XWPFDocument document new XWPFDocument(docStream); ByteArrayOutputStream pdfOutput new ByteArrayOutputStream()) { // 设置PDF选项 PdfOptions options PdfOptions.create() .fontProvider(new DefaultFontProvider(true, true, true)); // 执行转换 PdfConverter.getInstance().convert(document, pdfOutput, options); return pdfOutput.toByteArray(); } }重点说明fontProvider的配置第一个参数控制是否嵌入字体第二个控制是否缓存字体第三个决定是否查找系统字体。在Linux服务器上必须设置为true否则中文会显示为方框。4. 前端展示方案实战4.1 PDF预览组件选择推荐使用PDF.js这个开源库相比直接使用iframe有以下优势支持页码跳转、缩放控制可以添加文本选择、搜索功能移动端适配更好集成示例div idpdfViewerContainer canvas idpdfCanvas/canvas div classtoolbar button idprevPage上一页/button span idpageNum1/span/span idpageCount0/span button idnextPage下一页/button /div /div script src//mozilla.github.io/pdf.js/build/pdf.js/script script // 初始化PDF.js pdfjsLib.getDocument(/api/convert?filetest.docx).promise .then(pdf { document.getElementById(pageCount).textContent pdf.numPages; renderPage(pdf, 1); }); function renderPage(pdf, pageNum) { pdf.getPage(pageNum).then(page { const viewport page.getViewport({ scale: 1.5 }); const canvas document.getElementById(pdfCanvas); canvas.height viewport.height; canvas.width viewport.width; page.render({ canvasContext: canvas.getContext(2d), viewport: viewport }); }); } /script4.2 性能优化技巧分片加载大文档先转换前5页用户翻页时再加载后续内容缓存策略对已转换文档设置Redis缓存key使用文件MD5值预加载用户hover文档列表时提前开始转换实测数据显示采用分片加载后1MB以上的文档打开速度提升60%以上。5. 跨平台字体兼容性解决方案5.1 Linux字体安装指南中文显示异常的根本原因是服务器缺少对应字体。解决方法从Windows系统复制字体合法授权情况下# 创建字体目录 sudo mkdir -p /usr/share/fonts/win # 复制宋体、黑体等常用字体 sudo cp simsun.ttc msyh.ttf /usr/share/fonts/win/ # 刷新字体缓存 sudo fc-cache -fv使用开源字体替代# 安装文泉驿字体 sudo yum install wqy-microhei-fonts5.2 字体映射配置在代码中指定字体替换规则FontProvider fontProvider new DefaultFontProvider() { Override public Font getFont(String familyName, String encoding, boolean embedded, float size) { // 将宋体(中文)映射到实际可用的字体 if (宋体(中文).equals(familyName)) { return super.getFont(SimSun, encoding, embedded, size); } return super.getFont(familyName, encoding, embedded, size); } };6. 高级功能扩展6.1 文档批注与签章在转换后的PDF上添加水印PDDocument pdfDoc PDDocument.load(pdfBytes); PDPage firstPage pdfDoc.getPage(0); try (InputStream watermarkImg getClass().getResourceAsStream(/watermark.png)) { PDImageXObject watermark PDImageXObject.createFromByteArray( pdfDoc, IOUtils.toByteArray(watermarkImg), watermark); PDPageContentStream contentStream new PDPageContentStream( pdfDoc, firstPage, PDPageContentStream.AppendMode.APPEND, true); contentStream.drawImage(watermark, 100, 500, 300, 50); contentStream.close(); } ByteArrayOutputStream output new ByteArrayOutputStream(); pdfDoc.save(output); pdfDoc.close();6.2 文档安全控制添加PDF打开密码StandardProtectionPolicy policy new StandardProtectionPolicy( ownerPass, userPass, AccessPermission.getPrintAllowedInstance()); policy.setEncryptionKeyLength(128); pdfDoc.protect(policy);7. 部署与监控方案7.1 Docker化部署推荐使用多阶段构建的DockerfileFROM eclipse-temurin:17-jdk as builder WORKDIR /app COPY . . RUN ./mvnw package -DskipTests FROM eclipse-temurin:17-jre WORKDIR /app COPY --frombuilder /app/target/*.jar app.jar COPY fonts/* /usr/share/fonts/ RUN apt-get update apt-get install -y fontconfig ENTRYPOINT [java,-jar,app.jar]7.2 性能监控配置在SpringBoot中添加Prometheus监控Bean MeterRegistryCustomizerPrometheusMeterRegistry configureMetrics() { return registry - { registry.config().commonTags(application, word2pdf-service); new JvmMemoryMetrics().bindTo(registry); new JvmGcMetrics().bindTo(registry); }; }关键指标建议监控文档转换平均耗时并发转换任务数JVM内存使用情况PDF生成失败率

更多文章