在企业日常运营中,文档处理是个绕不开的活儿。我见过太多团队被Word模板渲染、PDF导出这些问题折腾得够呛。比如财务部门每月要生成几百份报表,人力部门要批量制作员工档案,这些场景下如果手动操作,不仅效率低下,还容易出错。
传统方案通常有两种:一种是直接用Apache POI操作Word,这种方式灵活但代码量巨大;另一种是使用Word模板引擎,比如Freemarker,但处理复杂表格和图片时又显得力不从心。直到我遇到了poi-tl(POI Template Lite),这个基于Apache POI的Word模板引擎彻底改变了我们的开发体验。
poi-tl最大的特点是采用"模板+数据"的模式。你可以先用Word设计好模板,用{{}}标记占位符,然后在代码中填充数据。我特别喜欢它的这几个特性:
但光有Word生成还不够,很多场景需要PDF格式。这时就轮到Aspose登场了。这个商业库的转换质量堪称完美,但免费版会有水印。不过别担心,后面我会分享如何通过合法授权解决这个问题。
先来看项目依赖配置。poi-tl需要配合特定版本的POI使用,版本不匹配会导致各种奇怪错误。以下是我的Gradle配置(Maven用户自行转换):
groovy复制// 核心依赖
implementation 'com.deepoove:poi-tl:1.10.5'
implementation "org.apache.poi:poi:4.1.2"
implementation "org.apache.poi:poi-ooxml:4.1.2"
implementation group: 'org.apache.poi', name: 'ooxml-schemas', version: '1.4'
// PDF转换相关
implementation 'com.aspose:aspose-words:21.12'
注意:Aspose的JAR需要从官网下载后手动安装到本地仓库。这里有个小技巧——可以申请30天试用license,到期后换个邮箱再申请,完全合法合规。
创建一个简单的Word模板(template.docx):
对应的Java代码:
java复制XWPFTemplate template = XWPFTemplate.compile("template.docx")
.render(Collections.singletonMap("title", "Hello poi-tl!"));
template.writeToFile("output.docx");
就是这么简单!但实际项目中我们往往需要处理更复杂的场景。比如最近我做的一个项目,需要动态生成包含合并单元格、嵌套图片的表格,下面我会详细讲解。
处理表格是文档生成中最棘手的部分。poi-tl提供了强大的表格操作API,看这个例子:
java复制// 定义表格样式
TableStyle style = new TableStyle();
style.setWidth(8000); // 总宽度
style.setColWidths(new int[]{1000, 3000, 4000}); // 列宽
// 创建表头
RowRenderData header = Rows.of("ID", "Name", "Description")
.textColor("FFFFFF")
.bgColor("4472C4")
.center()
.create();
// 添加数据行
List<RowRenderData> rows = new ArrayList<>();
rows.add(Rows.create("1", "Item A", "First product"));
rows.add(Rows.create("2", "Item B", "Second product"));
// 设置合并规则
MergeCellRule rule = MergeCellRule.builder()
.map(Grid.of(1, 0), Grid.of(2, 0)) // 合并第2-3行第1列
.build();
// 组装表格
TableRenderData table = new TableRenderData(style, header, rows);
table.setMergeRule(rule);
// 放入数据模型
Map<String, Object> data = new HashMap<>();
data.put("product_table", table);
对应的模板中只需要放置{{product_table}}占位符即可。这种方式的优势在于:
图片处理同样简单。假设我们要在文档中展示产品图片:
java复制// 本地图片
data.put("product_image", Pictures.ofLocal("product.jpg").size(300, 200).create());
// 网络图片(注意处理异常)
data.put("logo", Pictures.ofUrl("https://example.com/logo.png").size(150, 150).create());
// 图片列表循环
List<PictureRenderData> gallery = new ArrayList<>();
gallery.add(Pictures.ofLocal("img1.jpg").create());
gallery.add(Pictures.ofLocal("img2.jpg").create());
data.put("gallery", gallery);
模板中使用区块对语法处理循环:
code复制{{#gallery}}
{{@#this}}
{{/gallery}}
Aspose转换PDF的质量无可挑剔,但免费版会添加水印。合法解决方案有两种:
这里分享如何加载授权文件:
java复制public class PdfConverter {
private static final String LICENSE_PATH = "/license/license.xml";
public static void initLicense() throws Exception {
try (InputStream is = PdfConverter.class.getResourceAsStream(LICENSE_PATH)) {
License license = new License();
license.setLicense(is);
}
}
public static void convertToPdf(InputStream wordInput, OutputStream pdfOutput) throws Exception {
Document doc = new Document(wordInput);
doc.save(pdfOutput, SaveFormat.PDF);
}
}
直接使用文件IO会影响性能,特别是在Web环境中。推荐使用内存流:
java复制public void exportPdf(HttpServletResponse response) throws Exception {
// 1. 生成Word到内存
ByteArrayOutputStream wordOutput = new ByteArrayOutputStream();
XWPFTemplate template = ...; // 模板渲染
template.write(wordOutput);
// 2. 转换为PDF
response.setContentType("application/pdf");
try (InputStream wordInput = new ByteArrayInputStream(wordOutput.toByteArray());
OutputStream pdfOutput = response.getOutputStream()) {
PdfConverter.convertToPdf(wordInput, pdfOutput);
}
}
这种方式避免了临时文件,内存占用也更可控。实测处理10MB的Word文档,堆内存峰值不超过200MB。
在团队协作中,模板管理是个挑战。我们的解决方案是:
处理大批量文档时,这些技巧很管用:
这些坑我都踩过,希望你能避开:
有次线上事故让我记忆犹新——因为没有处理网络图片加载超时,导致批量任务卡死。现在我的代码都会设置超时:
java复制PictureRenderData pic = Pictures.ofUrl(url)
.size(width, height)
.timeout(3000) // 3秒超时
.create();
文档处理看似简单,但要打造稳定高效的企业级解决方案,需要在这些细节上下功夫。经过多个项目的打磨,我们现在的文档生成服务已经能够稳定处理日均10万+的文档请求,平均耗时控制在200ms以内。