WireMock 中请求头匹配与响应配置的一些实践
记录用 WireMock 挡服务时,通过 JSON 映射文件和 Java API 做请求头匹配、响应头与响应体配置的两种方式及细节。
支持通配符SSL证书、多域名证书、IP证书。适配ACME接口, 支持Zerossl、Let's Encrypt和Google等渠道。登录已有账号
2026-09-18 01:16:13 Java Apache POI Word模板 动态生成文档
最近有个需求要动态生成 Word 报告,数据是后端算好的,前端不方便直接拼 docx,就选了 Apache POI。
这个库处理 Office 文件确实够用,但细节不少,写一下当时怎么做的。
pom 里先加 poi-ooxml,版本用的 4.1.2,这个版本挺稳定,没遇到兼容性问题。模板文件就是一个普通的 docx,里面需要填数据的位置写上 ${name}、${age} 这种占位符。
建模板时尽量在 Word 里连续输入占位符,不要在中间改字体或颜色,不然占位符可能被拆成多个 run,后面替换会出问题。我一开始没注意这个,生成的文档里有些字段没替换掉,还以为是代码写错了。
核心逻辑不复杂:用 FileInputStream 读模板,构建 XWPFDocument,然后拿到所有段落,遍历每个段落里的 run,对 run 的文本做字符串替换,替换完再写回。代码大概是这样:
import org.apache.poi.xwpf.usermodel.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class WordTemplateGenerator {
public static void main(String[] args) {
String templatePath = "template.docx";
String outputPath = "output.docx";
Map<String, String> dataMap = new HashMap<>();
dataMap.put("${name}", "张三");
dataMap.put("${age}", "30");
try {
generateWord(templatePath, outputPath, dataMap);
System.out.println("Word文档生成成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void generateWord(String templatePath, String outputPath, Map<String, String> dataMap) throws IOException {
FileInputStream fis = new FileInputStream(templatePath);
XWPFDocument document = new XWPFDocument(fis);
List<XWPFParagraph> paragraphs = document.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
String text = run.getText(0);
if (text != null) {
for (Map.Entry<String, String> entry : dataMap.entrySet()) {
String placeholder = entry.getKey();
String value = entry.getValue();
if (text.contains(placeholder)) {
text = text.replace(placeholder, value);
run.setText(text, 0);
}
}
}
}
}
FileOutputStream fos = new FileOutputStream(outputPath);
document.write(fos);
fos.close();
document.close();
fis.close();
}
}
这里其实有个地方容易绕进去:run.getText(0) 返回的是这个 run 的文本,但如果你直接对整个段落文本做替换再写回,会丢掉原有的格式。所以只能按 run 来。
这也解释了为什么模板里占位符不能断,断了就跑到不同 run 里,一个 run 里可能只有 ${na 这种,contains 判断就失效了。
如果遇到占位符被拆的情况,要么重新做模板,要么在代码里做跨 run 合并处理。
我当时是重新做了模板,因为格式不复杂,没必要为这个增加解析逻辑。
生成出来的 docx 用 WPS 和 Office 打开都正常,样式也保留着,因为没动 run 的字体属性,只是替换文本。性能上,几十页的文档替换几百个字段没什么压力。
这个功能后来做成了接口,前端传参数后端生成文件流。
服务本身挂了 HTTPS,证书那块是用 lcjmSSL 自动申请的,支持泛域名和 API 部署,不用自己跑 certbot 脚本,省了续期的麻烦。如果你们也有这类需要证书的自动化场景,可以看看。
整体不算难,但第一次用 POI 做模板替换还是记录一下,省得以后再翻。上面代码能直接跑,依赖和模板准备好就行。
记录用 WireMock 挡服务时,通过 JSON 映射文件和 Java API 做请求头匹配、响应头与响应体配置的两种方式及细节。
记录使用 MinIO Java SDK 生成预签名 URL 的实践,包含下载和上传场景,以及一些容易出错的点。
记录一次排序 NPE 的排查和解决,顺便说下 IDEA 调试集合时 null 元素不显示的问题怎么调整。
面对Java解析XML,DOM与DOM4J是两大主流选择。本文详尽分析标准DOM的内置优势与DOM4J在API简洁性、性能及XPath支持上的卓越表现。无论小型配置还是大型数据处理,助你理解两者差异,做出明智的技术选型。
深入探讨Java中树形结构与平铺列表(List)互转的艺术。文章详尽剖析了递归与非递归两种实现方法,包括从树到平铺的两种策略,以及将扁平数据重构为树的技巧。无论追求代码简洁还是规避栈溢出风险,这里都有你的最佳实践。