SpringBoot项目实战:用EasyPoi + Docx4j搞定Word模板转PDF(含图片和字体乱码解决方案)
·
SpringBoot实战:基于EasyPoi与Docx4j的Word模板转PDF全流程解决方案
在企业级应用开发中,文档自动化生成是高频需求场景。合同、报告等标准化文档往往需要根据业务数据动态生成,并最终以PDF格式交付。本文将深入剖析SpringBoot环境下整合EasyPoi与Docx4j实现Word模板到PDF转换的完整技术方案,重点解决中文乱码、图片嵌入、依赖冲突等典型问题。
1. 技术选型与架构设计
文档自动化生成通常涉及三个核心环节:模板设计、数据填充和格式转换。在Java生态中,Apache POI是处理Office文档的事实标准,但其原生API较为底层。我们的技术栈组合如下:
- EasyPoi 4.3+ :基于POI封装的模板引擎,支持类似Freemarker的表达式语法
- Docx4j 6.1+ :专业Word文档处理库,提供高质量的PDF导出能力
- SpringBoot 2.7+ :简化依赖管理和配置
典型系统架构如下图所示(伪代码表示处理流程):
// 数据处理层
Map<String, Object> params = prepareBusinessData();
// 文档生成层
XWPFDocument doc = EasyPoi.processTemplate(templatePath, params);
// 格式转换层
PDFConverter.convert(doc, pdfOutputPath);
2. 环境配置与依赖管理
2.1 Maven依赖关键配置
核心依赖需注意版本兼容性,特别要处理常见的SLF4J冲突:
<dependencies>
<!-- EasyPoi核心 -->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>4.3.0</version>
</dependency>
<!-- Docx4j转换模块 -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>6.1.0</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
2.2 资源文件保护配置
必须防止Maven对DOCX模板的压缩处理,在pom.xml中添加:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>docx</nonFilteredFileExtension>
<!-- 其他需要保留原格式的文件类型 -->
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
</build>
3. Word模板开发规范
3.1 模板语法示例
EasyPoi支持多种模板表达式:
{{company.name}} // 简单变量
{{?list = employeeList}} // 列表迭代
{{!image:logo}} // 图片嵌入
3.2 图片处理最佳实践
图片注入需使用ImageEntity对象:
ImageEntity logo = new ImageEntity();
logo.setUrl("classpath:/static/logo.png");
logo.setWidth(100);
logo.setHeight(50);
params.put("companyLogo", logo);
注意:网络图片需先下载到本地临时目录,直接使用URL会导致转换失败
4. 核心工具类实现
4.1 PDF转换关键代码
字体映射是解决中文乱码的核心:
public void convertDocxToPdf(String docxPath, String pdfPath) throws Exception {
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new File(docxPath));
// 字体映射配置
IdentityPlusMapper fontMapper = new IdentityPlusMapper();
fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
// 添加更多中文字体...
wordMLPackage.setFontMapper(fontMapper);
// PDF输出配置
OutputStream os = new FileOutputStream(pdfPath);
FOSettings foSettings = Docx4J.createFOSettings();
foSettings.setWmlPackage(wordMLPackage);
Docx4J.toFO(foSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL);
}
4.2 SpringBoot集成方案
建议将转换器声明为Spring Bean:
@Component
public class DocumentGenerator {
@Value("${doc.template.dir:classpath:/templates}")
private Resource templateDir;
public void generateContract(PdfRequest request) {
// 模板路径解析
String templatePath = resolveTemplate(request.getTemplateType());
// 数据准备
Map<String, Object> params = buildTemplateParams(request);
// 生成PDF
String pdfPath = pdfConverter.convert(templatePath, params);
// 文件下载或存储逻辑...
}
}
5. 生产环境问题排查
5.1 常见错误与解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 中文显示为方框 | 字体未正确映射 | 检查PhysicalFonts.get()返回值 |
| 图片缺失 | URL协议不支持 | 使用本地文件路径或byte[] |
| 转换耗时过长 | 复杂样式处理 | 优化模板复杂度 |
| 内存溢出 | 大文档处理 | 增加JVM内存或分页处理 |
5.2 性能优化建议
-
模板设计原则 :
- 避免嵌套过深的表格结构
- 减少动态内容区块数量
- 固定图片尺寸减少计算
-
缓存策略 :
@Cacheable(value = "documentCache", key = "#templateName") public byte[] generateCachedDocument(String templateName, Map<String, Object> params) { // 生成逻辑 } -
异步处理方案 :
@Async public CompletableFuture<String> asyncGenerate(PdfRequest request) { String result = syncGenerate(request); return CompletableFuture.completedFuture(result); }
6. 高级应用场景
6.1 动态表格生成
结合集合数据生成动态表格:
List<Map<String, Object>> items = new ArrayList<>();
// 添加行数据...
params.put("itemList", items);
模板中使用:
{{?list = itemList}}
{{#.name}} | {{#.price}} | {{#.quantity}}
{{/list}}
6.2 多文档合并
使用PDFBox实现合并:
PDDocument mergedDoc = new PDDocument();
for (String pdfPath : pdfPaths) {
PDDocument doc = PDDocument.load(new File(pdfPath));
for (PDPage page : doc.getPages()) {
mergedDoc.addPage(page);
}
}
mergedDoc.save(outputPath);
7. 安全与异常处理
7.1 输入验证机制
public void validateTemplateParams(Map<String, Object> params) {
Assert.notNull(params, "模板参数不能为空");
Assert.isTrue(params.containsKey("title"), "必须包含title字段");
// 更多业务规则校验...
}
7.2 事务性文件处理
确保临时文件清理:
try {
// 生成临时文件
} finally {
Files.deleteIfExists(Paths.get(tempFilePath));
}
在实际项目部署中,我们发现Linux服务器往往缺少Windows字体库。解决方案是在Dockerfile中安装字体包:
RUN apt-get update && \
apt-get install -y fonts-wqy-zenhei fonts-wqy-microhei
更多推荐

所有评论(0)