news 2025/12/26 15:57:02

基于 Apache POI 的体检报告 Word 生成实战文档

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于 Apache POI 的体检报告 Word 生成实战文档

基于 Apache POI 的体检报告 Word 生成实战文档


一 项目目标与总体设计

  • 目标:基于模板快速生成排版规范的体检报告,支持文本替换、动态表格、图片插入,并可一键导出PDF用于归档与打印。
  • 技术选型:
    • Apache POI XWPF:操作.docx模板,完成占位符替换、表格行循环、图片插入等。
    • LibreOffice headless:将.docx转换为.pdf,跨平台、稳定可靠。
  • 工程结构建议:
    • 模板:resources/templates/health_report_template.docx
    • 领域模型:体检人信息、体检项目结果、影像图片等
    • 服务:模板解析、数据填充、PDF 转换、HTTP 下载
  • 关键约束:
    • 模板必须使用.docx(XWPF 不支持直接操作.doc);占位符需保证在同一XWPFRun内,避免替换失败。

二 快速开始与最小可用示例

  • Maven 依赖(建议版本 ≥5.2.3):
<dependency><groupId>org.apache.poi</groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.3</version></dependency>
  • 最小可用流程:读取模板 → 替换占位符 → 写出文件
// 1) 读取模板try(InputStreamis=newClassPathResource("templates/health_report_template.docx").getInputStream();XWPFDocumentdoc=newXWPFDocument(is)){// 2) 简单文本替换(占位符格式:${key})Map<String,String>params=Map.of("name","张三","gender","男","age","28","examDate","2025-12-01");replaceTextInDoc(doc,params);// 3) 写出 .docxtry(FileOutputStreamout=newFileOutputStream("target/体检报告_张三.docx")){doc.write(out);}}
  • 占位符替换工具方法(核心要点:逐段遍历,逐 Run 替换,避免跨 Run 失效)
publicstaticvoidreplaceTextInDoc(XWPFDocumentdoc,Map<String,String>params){for(XWPFParagraphp:doc.getParagraphs()){List<XWPFRun>runs=p.getRuns();if(runs.isEmpty())continue;Stringtext=p.getText();if(text==null||!text.contains("${"))continue;// 简单策略:将整段文本一次性替换(要求占位符不被 Run 拆分)for(Map.Entry<String,String>e:params.entrySet()){Stringph="${"+e.getKey()+"}";if(text.contains(ph)){text=text.replace(ph,e.getValue()==null?"":e.getValue());}}// 写回第一个 Run,清空其余 Run,避免残留格式runs.get(0).setText(text,0);for(inti=runs.size()-1;i>0;i--){p.removeRun(i);}}}
  • 运行后将在 target 目录生成:体检报告_张三.docx。

三 核心能力实现

  • 动态表格(体检项目明细)
    • 模板中预留一个表格,约定第一行是表头,第二行是“数据模板行”(POI 会复用该行样式创建新行)。
// 体检项目明细staticclassItem{Stringproject;Stringresult;Stringunit;StringrefLow;StringrefHigh;Stringconclusion;}publicstaticvoidfillTable(XWPFDocumentdoc,List<Item>items){List<XWPFTable>tables=doc.getTables();if(tables.isEmpty())return;XWPFTabletable=tables.get(0);// 取第一个表格// 约定:第2行为模板行(索引1),从它之后插入数据行XWPFTableRowtpl=table.getRow(1);for(inti=0;i<items.size();i++){XWPFTableRowrow=table.insertNewTableRowAfter(tpl);// 复制模板行的单元格样式(浅拷贝,POI 默认行为)for(intc=0;c<tpl.getTableCells().size();c++){XWPFTableCellsrc=tpl.getCell(c);XWPFTableCelldst=row.getCell(c);if(dst==null)dst=row.addNewTableCell();// 简单文本填充(如需保留样式,可深拷贝 CTR)dst.setText(getCellText(items.get(i),c));}}// 可选:移除模板行table.removeRow(1);}privatestaticStringgetCellText(Itemit,intcol){returnswitch(col){case0->it.project;case1->it.result;case2->it.unit;case3->it.refLow;case4->it.refHigh;case5->it.conclusion;default->"";};}
  • 图片插入(体检影像、签名等)
    • 使用 POI 的 addPicture,尺寸以EMU为单位(1 英寸 =914400EMU)。
publicstaticvoidinsertImage(XWPFDocumentdoc,StringimgPath,intwidthInch,intheightInch)throwsException{try(FileInputStreamis=newFileInputStream(imgPath)){// 添加图片数据并返回索引(可选)intidx=doc.addPictureData(is,XWPFDocument.PICTURE_TYPE_PNG);XWPFParagraphp=doc.createParagraph();XWPFRunrun=p.createRun();run.addPicture(is,XWPFDocument.PICTURE_TYPE_PNG,imgPath,Units.toEMU(widthInch*914400),Units.toEMU(heightInch*914400));}}
  • 表格样式与对齐(居中、宽度)
    • 通过底层CTTbl/ CTTblPr/ CTJc设置表格居中与宽度(单位DXA,常用全宽约9000)。
importstaticorg.openxmlformats.schemas.wordprocessingml.x2006.main.STJc.*;publicstaticvoidsetTableStyle(XWPFTabletable){CTTblPrtblPr=table.getCTTbl().addNewTblPr();CTJcjc=tblPr.addNewJc();jc.setVal(STJc.CENTER);CTTblWidthwidth=tblPr.addNewTblW();width.setW(BigInteger.valueOf(9000));width.setType(STTblWidth.DXA);}
  • 模板占位符被 Run 拆分的处理
    • 现象:占位符被 Word 样式拆到多个XWPFRun,导致简单替换失效。
    • 解决思路:
      • 在 Word 中将占位符粘贴为“无格式文本”,保证整体位于同一 Run;或
      • 在代码中合并被拆分的 Run,再替换(遍历 Run,定位${},合并中间 Run 的文本后再替换)。

四 模板规范与最佳实践

  • 模板规范
    • 使用.docx;所有占位符统一为${key},避免特殊字符与 XML 冲突。
    • 表格循环:预留“表头 + 模板行”,模板行用于复制生成数据行。
    • 图片占位:预留位置段落,或在代码中指定插入点。
    • 样式与编号:尽量用 Word 样式(标题、正文、表格样式),避免手工格式影响复用。
  • 运行与资源
    • 模板放置于classpath,使用ClassPathResource读取,便于 JAR 包部署。
    • 所有流使用 try-with-resources 关闭,避免文件句柄泄漏。
  • 性能与稳定性
    • 大数据量(>1万行)建议分页生成多个文档或导出CSV/Excel附件。
    • 图片压缩后再写入,避免体积过大。
  • 可维护性
    • 将“文本替换、表格填充、图片插入”封装为独立组件,便于单元测试与复用。

五 导出 PDF 与 HTTP 下载

  • LibreOffice 转换(跨平台、稳定)
publicstaticbooleanconvertDocxToPdf(StringinDocx,StringoutDir){Stringos=System.getProperty("os.name").toLowerCase();Stringcmd;if(os.contains("win")){cmd="cmd /c start /wait soffice --headless --invisible --convert-to pdf:writer_pdf_Export "+inDocx+" --outdir "+outDir;}else{cmd="libreoffice --headless --invisible --convert-to pdf:writer_pdf_Export "+inDocx+" --outdir "+outDir;}try{Processp=Runtime.getRuntime().exec(cmd);intexit=p.waitFor();returnexit==0;}catch(Exceptione){e.printStackTrace();returnfalse;}}
  • Spring Boot 下载接口示例
@GetMapping("/report/export")publicvoidexport(HttpServletResponseresp)throwsException{// 1) 生成 .docxStringdocx="target/体检报告_张三.docx";// 生成逻辑// 2) 转 PDFStringpdf=docx.replace(".docx",".pdf");convertDocxToPdf(docx,"target");// 3) 输出 PDFresp.setContentType("application/pdf");resp.setHeader("Content-Disposition","attachment;filename="+URLEncoder.encode("体检报告.pdf","UTF-8"));Files.copy(Paths.get(pdf),resp.getOutputStream());resp.getOutputStream().flush();}
  • 提示
    • 服务器需安装LibreOffice;Windows 下建议使用安装路径下的 soffice.exe。
    • 转换是外部进程,注意超时与异常捕获。

六 常见问题与排查清单

  • 占位符未被替换
    • 检查占位符是否被样式拆分到多个XWPFRun;统一为无格式文本或合并 Run 后再替换。
  • 生成后 Word 损坏
    • 避免并发写同一文档;确保所有流关闭;POI 版本升级到稳定版(≥5.2.3)。
  • 图片不显示或变形
    • 尺寸单位使用EMU;确认图片格式与 addPicture 类型一致;必要时压缩图片。
  • 表格样式丢失
    • 通过复制模板行样式或操作底层CTTc/CTP保留格式;必要时设置表格居中与宽度。
  • Linux 转 PDF 失败
    • 检查 LibreOffice 是否安装、命令路径、权限与可用字体;查看进程退出码与日志。

七 一键运行与扩展建议

  • 一键运行步骤
    • 准备模板:resources/templates/health_report_template.docx(含name、{name}、name{gender}、age、{age}、age{examDate} 与“项目明细”表格)。
    • 运行单元测试或直接执行 main 方法生成.docx.pdf
    • 打开 PDF 校验排版、表格、图片与编码(中文)。
  • 扩展建议
    • 模板引擎化:将模板标签升级为##{foreachRows}####{foreachTable}##等,实现表格行/表格级循环与更灵活的布局。
    • 页眉页脚与页码:操作XWPFHeaderFooter与底层CTP添加页码域。
    • 电子签名/二维码:生成图片后插入页脚或指定区域。
    • 多格式导出:同时支持.docx/.pdf;大数据量导出Excel汇总。
    • 异步与缓存:报告生成放入异步任务,生成后缓存 PDF,避免重复渲染。

附 模板占位符与表格示例

  • 文本占位符示例
    • 姓名:${name}
    • 性别:${gender}
    • 年龄:${age}
    • 体检日期:${examDate}
  • 表格示例(项目明细)
    项目结果单位参考低参考高结论
    身高175cm
    体重68kg
    收缩压118mmHg90120正常
    舒张压76mmHg6080正常

将以上表格的第二行作为“模板行”,程序会复制该行生成数据行,最终移除模板行。


参考要点

  • 使用Apache POI XWPF操作.docx模板,完成文本替换、表格行循环与图片插入;模板占位符需位于同一XWPFRun内,避免替换失败。
  • 通过LibreOffice headless.docx转换为.pdf,适合服务器批量导出与归档。
  • 图片插入需使用EMU单位设置宽高;表格可通过底层CTTbl/CTJc设置居中与全宽,保证打印版式稳定。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/17 21:47:49

自动化测试维护策略:构建可持续的测试资产

随着敏捷开发和DevOps实践的普及&#xff0c;自动化测试已成为软件质量保障的核心环节。然而&#xff0c;许多团队在享受自动化测试带来的效率提升时&#xff0c;却面临着测试用例老化、维护成本飙升、ROI持续下降的困境。据统计&#xff0c;超过60%的自动化测试失败并非源于产…

作者头像 李华
网站建设 2025/12/17 21:47:23

二叉树的相关知识以及代码实现(Java)

一、二叉树的定义与基本概念二叉树是一种非线性数据结构&#xff0c;每个节点最多包含 2 个子节点&#xff08;左子节点、右子节点&#xff09;&#xff0c;核心特点&#xff1a;&#xff08;1&#xff09;每个节点的子树数量不超过 2&#xff1b;&#xff08;2&#xff09;左、…

作者头像 李华
网站建设 2025/12/17 21:46:01

pandas基础操作

文章目录 1. Series 与 DataFrame2. 数据查看与基本信息获取3. 数据选择与筛选4. 数据清洗与预处理5. 数据排序与重置索引6. 数据分组与聚合分析7. 数据合并 1. Series 与 DataFrame Series&#xff1a;一维带标签数组&#xff0c;类似于 Excel 中的单列数据 import pandas a…

作者头像 李华
网站建设 2025/12/20 7:32:16

泳池智能水管家推荐:5款高性价比设备实测解析

泳池智能水管家推荐&#xff1a;5款高性价比设备实测解析在洗浴行业竞争日益激烈的今天&#xff0c;水质管理正成为决定用户复购率的核心因素。当浴室能够实现“无呛鼻氯味、水体清澈透亮、皮肤泡后不痒、空气清新舒适”的体验时&#xff0c;其竞争力便已悄然超越传统服务模式。…

作者头像 李华
网站建设 2025/12/20 1:35:01

2025OpenTiny星光ShowTime!年度贡献者征集启动!

前言 携手共创&#xff0c;致敬不凡&#xff01; 2025年&#xff0c;OpenTiny持续在前端开源领域扎根&#xff0c;每一位开发者都是推动项目共同前行的宝贵力量。从bug修复&#xff0c;到技术探讨&#xff1b;从参与开源活动&#xff0c;到输出技术文章&#xff1b;从使用项目…

作者头像 李华