refactor: 用直接XML预处理替换poi-tl模板引擎 — 合并Run+替换占位符在ZIP层完成,减少依赖

This commit is contained in:
agentuser
2026-06-24 17:54:03 +08:00
parent 7b78091f0d
commit 8f6c12f960
2 changed files with 47 additions and 27 deletions
@@ -11,9 +11,9 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.stereotype.Service;
import com.deepoove.poi.XWPFTemplate;
import com.gmp.mock.template.TemplateProvider;
import com.gmp.mock.template.TemplateRegistry;
@@ -38,22 +38,17 @@ public class DocxService {
}
try {
// 1. Pre-process: merge split runs
byte[] preprocessed = mergeSplitPlaceholders(provider.getTemplateResource());
// 1. Pre-process: merge split runs + replace all placeholders in XML
byte[] processed = preprocessTemplate(provider.getTemplateResource(), provider.getDataModel(), businessData);
// 2. Build data model
Map<String, Object> data = new java.util.HashMap<>(provider.getDataModel());
if (businessData != null) {
data.putAll(businessData);
}
// 2. Load as XWPFDocument for post-processing
XWPFDocument document = new XWPFDocument(new ByteArrayInputStream(processed));
provider.postProcess(document);
// 3. Render with poi-tl
// 3. Write to output
ByteArrayOutputStream baos = new ByteArrayOutputStream();
XWPFTemplate template = XWPFTemplate.compile(new ByteArrayInputStream(preprocessed));
template.render(data);
provider.postProcess(template.getXWPFDocument());
template.write(baos);
template.close();
document.write(baos);
document.close();
// 4. Cache to disk
String key = fileCache.store(baos.toByteArray());
@@ -67,13 +62,21 @@ public class DocxService {
public record CachedResult(String key, String fileName) {}
// ====== Run merging (unchanged) ======
/**
* 读取模板 docx,合并拆分占位符,替换全部 {{placeholder}},返回处理后的 docx 字节。
*/
private byte[] preprocessTemplate(InputStream templateStream, Map<String, Object> dataModel, Map<String, Object> businessData) throws IOException {
// Merge data: businessData overrides dataModel
Map<String, Object> allData = new java.util.HashMap<>(dataModel);
if (businessData != null) {
allData.putAll(businessData);
}
private byte[] mergeSplitPlaceholders(InputStream templateStream) throws IOException {
byte[] docxBytes;
try (InputStream is = templateStream) {
docxBytes = is.readAllBytes();
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(docxBytes));
ZipOutputStream zos = new ZipOutputStream(out)) {
@@ -83,7 +86,13 @@ public class DocxService {
byte[] content = zis.readAllBytes();
if ("word/document.xml".equals(entry.getName())) {
String xml = new String(content, "UTF-8");
// 1. Merge split runs (e.g. {{ year }} → {{year}})
xml = mergeRunsInXml(xml);
// 2. Replace all {{placeholders}} with actual values
for (var kv : allData.entrySet()) {
String val = kv.getValue() != null ? kv.getValue().toString() : "";
xml = xml.replace("{{" + kv.getKey() + "}}", val);
}
content = xml.getBytes("UTF-8");
}
zos.write(content);
@@ -93,6 +102,8 @@ public class DocxService {
return out.toByteArray();
}
// ====== Run merging (unchanged) ======
private String mergeRunsInXml(String xml) {
StringBuilder result = new StringBuilder();
java.util.regex.Matcher m = Pattern.compile(
@@ -91,17 +91,26 @@ public class AnnualReportProvider implements TemplateProvider {
int yearA = 2024;
int yearB = 2025;
int yearC = 2026;
return Map.of(
"title", "2025 年度工作报告",
"companyName", "示例科技有限公司",
"reportPeriod", "2025年1月 - 2025年12月",
"generatedDate", LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")),
"yearA", String.valueOf(yearA),
"yearB", String.valueOf(yearB),
"yearC", String.valueOf(yearC),
"snhj", "300",
"dwjjtz", "200"
);
Map<String, Object> model = new java.util.HashMap<>();
model.put("title", "2025 年度工作报告");
model.put("companyName", "示例科技有限公司");
model.put("reportPeriod", "2025年1月 - 2025年12月");
model.put("generatedDate", LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
model.put("yearA", String.valueOf(yearA));
model.put("yearB", String.valueOf(yearB));
model.put("yearC", String.valueOf(yearC));
model.put("snhj", "300");
model.put("dwjjtz", "200");
model.put("dwjjbl", "66.7");
model.put("xxjjtz", "30");
model.put("xxjjzb", "10.0");
model.put("jgtz", "50");
model.put("jgzb", "16.7");
model.put("kjtz", "10");
model.put("kjzb", "3.3");
model.put("xxhtz", "10");
model.put("xxhzb", "3.3");
return model;
}
@Override