最近在开发一个项目管理系统的周报模块时,遇到了一个典型需求:需要将数据库中的任务数据自动生成格式规范的Word周报,其中相同项目或负责人的行需要自动合并单元格。经过技术选型对比,最终选择了poi-tl这个基于Apache POI的Word模板引擎。它不仅解决了传统POI API操作Word时代码冗长的问题,更通过创新的模板+策略模式,让复杂表格合并变得异常简单。
在SpringBoot项目中引入poi-tl前,需要特别注意版本兼容性。以下是推荐配置:
xml复制<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.0</version>
</dependency>
<!-- 显式声明POI版本避免冲突 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
提示:实际项目中遇到过因POI版本冲突导致的NoSuchMethodError异常,建议通过
mvn dependency:tree命令检查依赖树。
在resources目录下创建模板文件时,建议采用以下结构:
code复制src/main/resources
└── templates
└── weekly_report.docx
模板设计要点:
{{variable}}作为占位符推荐模板内容示例:
markdown复制| 项目名称 | 任务内容 | 负责人 | 进度 |
|----------|----------|--------|------|
{{#oneTable}}
| {{project}} | {{task}} | {{owner}} | {{progress}} |
{{/oneTable}}
针对周报场景,我们设计了三层数据结构:
java复制@Data
public class WeeklyReportData {
private List<ReportRow> rows; // 明细数据
private List<MergeRule> mergeRules; // 合并规则
private Integer[] mergeColumns; // 需合并的列索引
}
@Data
public class ReportRow {
private String project;
private String task;
private String owner;
private String progress;
}
@Data
public class MergeRule {
private String fieldValue; // 匹配字段值
private int mergeSize; // 合并行数
}
继承DynamicTableRenderPolicy实现智能合并:
java复制public class SmartMergePolicy extends DynamicTableRenderPolicy {
@Override
public void render(XWPFTable table, Object data) throws Exception {
WeeklyReportData reportData = (WeeklyReportData) data;
// 插入数据行
int insertPos = 1; // 保留表头
for (ReportRow row : reportData.getRows()) {
XWPFTableRow newRow = table.insertNewTableRow(insertPos++);
// 创建单元格并填充数据...
}
// 执行合并
for (Integer colIndex : reportData.getMergeColumns()) {
applyMerge(table, colIndex, reportData.getMergeRules());
}
}
private void applyMerge(XWPFTable table, int colIndex,
List<MergeRule> rules) {
// 实现智能合并算法...
}
}
合并算法关键点:
TableTools.mergeCellsVertically()建议采用工厂模式管理模板配置:
java复制@Component
public class TemplateConfigFactory {
private final Configure defaultConfig;
public TemplateConfigFactory() {
this.defaultConfig = Configure.builder()
.useSpringEL(false)
.bind("reportTable", new SmartMergePolicy())
.build();
}
public XWPFTemplate createTemplate(InputStream templateStream,
Map<String, Object> data) {
return XWPFTemplate.compile(templateStream, defaultConfig)
.render(data);
}
}
服务层实现流式导出:
java复制@Service
@RequiredArgsConstructor
public class ReportExportService {
private final TemplateConfigFactory configFactory;
public void exportWeeklyReport(HttpServletResponse response,
WeeklyReportData data) throws IOException {
try (InputStream template = getClass()
.getResourceAsStream("/templates/weekly_report.docx");
XWPFTemplate doc = configFactory.createTemplate(template,
Map.of("reportTable", data))) {
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.setHeader("Content-Disposition",
"attachment; filename=weekly_report.docx");
doc.write(response.getOutputStream());
}
}
}
通过列表渲染实现动态表格:
java复制List<WeeklyReportData> multiTables = getMultiTableData();
Map<String, Object> data = new HashMap<>();
data.put("tables", multiTables);
// 模板对应语法
{{#tables}}
=== {{tableTitle}} ===
{{#reportTable}}
| {{project}} | ... |
{{/reportTable}}
{{/tables}}
当数据量超过500行时,建议:
java复制int batchSize = 200;
List<List<ReportRow>> batches = ListUtils.partition(data.getRows(), batchSize);
for (List<ReportRow> batch : batches) {
WeeklyReportData batchData = new WeeklyReportData(batch, ...);
// 生成分片文档...
}
java复制Configure.builder()
.setGarbageCollector(new SimpleGarbageCollector(100))
.build();
通过RenderPolicy实现复杂样式:
java复制new AbstractRenderPolicy() {
@Override
public void render(RenderContext context) {
XWPFRun run = context.getRun();
run.setColor("FF0000");
run.setBold(true);
// 其他样式设置...
}
}
在实际项目中,我们通过这套方案成功处理了单周超过1000条任务的周报生成需求,导出时间控制在3秒以内。关键点在于合理设置合并策略的匹配算法,以及采用分批次渲染机制。对于特别复杂的合并逻辑,建议先在单元测试中验证合并规则的正确性。