文件批量提取工具:高效处理PDF/Word/Excel数据
2026/7/22 8:00:26
基于 Apache POI 的体检报告 Word 生成实战文档
一 项目目标与总体设计
二 快速开始与最小可用示例
<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);}}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);}}}三 核心能力实现
// 体检项目明细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->"";};}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));}}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 的文本后再替换)。四 模板规范与最佳实践
五 导出 PDF 与 HTTP 下载
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;}}@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();}六 常见问题与排查清单
七 一键运行与扩展建议
附 模板占位符与表格示例
| 项目 | 结果 | 单位 | 参考低 | 参考高 | 结论 |
|---|---|---|---|---|---|
| 身高 | 175 | cm | |||
| 体重 | 68 | kg | |||
| 收缩压 | 118 | mmHg | 90 | 120 | 正常 |
| 舒张压 | 76 | mmHg | 60 | 80 | 正常 |
将以上表格的第二行作为“模板行”,程序会复制该行生成数据行,最终移除模板行。
参考要点