使用 Word 模板导出带替换符、动态表格和二维码的文档
引言
在企业应用开发中,生成定制化的文档(如出库单、发票等)是常见需求。传统的解决方案可能是手动填写 Word 文档或使用复杂的报表工具。然而,通过编程方式利用 Word 模板结合动态数据生成文档,可以显著提高效率。本文将介绍如何以 Word 文档为模板,结合 Java 和 Apache POI 实现带替换符的动态内容填充、代码生成表格以及添加二维码的功能,并提供完整的代码示例。
技术背景
工具与依赖
- Apache POI :用于操作 Word 文档(
.docx
),支持读取模板、替换占位符和动态创建内容。 - ZXing:用于生成二维码,增强文档的可追溯性。
- Java:作为开发语言,结合上述库实现自动化。
应用场景
假设我们需要生成出库单,包含基础信息(如订单号、日期)、动态明细表格和右上角的二维码。
实现步骤
准备 Word 模板
-
创建一个 Word 文档(例如
OutboundTemplate.docx
)作为模板。 -
在模板中添加基础信息部分,使用
[key]
格式的占位符,例如:出库主题 [orderCode] 出库日期 [outboundDate] 客户名称 [customerName] 仓库编号 [warehouseCode] 制单人 [creator] 审核人 [auditor] 制单时间 [createTime] 出库单据id [outboundOrderId] 审核人: [reviewer]
-
明细信息部分无需预定义表格,代码将动态生成。
-
预留右上角空间用于二维码(可选,可通过段落调整位置)。
Word模版:
依赖配置
在 pom.xml
中添加以下依赖:
xml
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
代码实现
数据模型
文件:OutboundDetail.java
定义出库明细的模型类,用于存储每条明细数据。
java
package org.dealpdf.model;
// 出库明细数据模型
public class OutboundDetail {
private String orderId; // 订单号
private String detailId; // 明细编号
private String productName; // 产品名称
private String unit; // 单位
private double quantity; // 数量
private double unitPrice; // 单价
// 默认构造函数
public OutboundDetail() {}
// 参数化构造函数
public OutboundDetail(String orderId, String detailId, String productName, String unit,
double quantity, double unitPrice) {
this.orderId = orderId;
this.detailId = detailId;
this.productName = productName;
this.unit = unit;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
// getter 和 setter 方法
public String getOrderId() { return orderId; }
public void setOrderId(String orderId) { this.orderId = orderId; }
public String getDetailId() { return detailId; }
public void setDetailId(String detailId) { this.detailId = detailId; }
public String getProductName() { return productName; }
public void setProductName(String productName) { this.productName = productName; }
public String getUnit() { return unit; }
public void setUnit(String unit) { this.unit = unit; }
public double getQuantity() { return quantity; }
public void setQuantity(double quantity) { this.quantity = quantity; }
public double getUnitPrice() { return unitPrice; }
public void setUnitPrice(double unitPrice) { this.unitPrice = unitPrice; }
}
工具类
文件:WordUtil.java
包含替换占位符、生成表格和添加二维码的工具方法。
java
package org.dealpdf.utils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.dealpdf.model.OutboundDetail;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
// Word 文档操作工具类
public class WordUtil {
/**
* 替换文档中的占位符 [key] 为对应值
* @param doc Word 文档对象
* @param data 包含键值对的 Map,键为占位符名称,值为替换内容
*/
public static void replacePlaceholders(XWPFDocument doc, Map<String, String> data) {
// 处理段落中的占位符
for (XWPFParagraph paragraph : doc.getParagraphs()) {
String text = paragraph.getText();
if (text != null && !text.isEmpty()) {
System.out.println("Paragraph text: " + text); // 调试输出
for (Map.Entry<String, String> entry : data.entrySet()) {
String key = "[" + entry.getKey() + "]";
if (text.contains(key)) {
for (XWPFRun run : paragraph.getRuns()) {
String runText = run.getText(0);
if (runText != null && runText.contains(key)) {
run.setText(runText.replace(key, entry.getValue()), 0);
}
}
}
}
}
}
// 处理表格中的占位符
for (XWPFTable table : doc.getTables()) {
for (XWPFTableRow row : table.getRows()) {
for (XWPFTableCell cell : row.getTableCells()) {
for (XWPFParagraph paragraph : cell.getParagraphs()) {
String text = paragraph.getText();
if (text != null && !text.isEmpty()) {
System.out.println("Cell text: " + text); // 调试输出
for (Map.Entry<String, String> entry : data.entrySet()) {
String key = "[" + entry.getKey() + "]";
if (text.contains(key)) {
for (XWPFRun run : paragraph.getRuns()) {
String runText = run.getText(0);
if (runText != null && runText.contains(key)) {
run.setText(runText.replace(key, entry.getValue()), 0);
}
}
}
}
}
}
}
}
}
}
/**
* 动态生成出库明细表格
* @param doc Word 文档对象
* @param details 出库明细数据列表
*/
public static void addTable(XWPFDocument doc, List<OutboundDetail> details) {
XWPFTable table = doc.createTable(); // 创建新表格
XWPFTableRow headerRow = table.getRow(0);
// 确保表头行有 7 列
while (headerRow.getTableCells().size() < 7) {
headerRow.createCell();
}
String[] headers = {"序号", "订单号", "订单明细号", "产品名称", "单位", "数量", "单价"};
for (int i = 0; i < headers.length; i++) {
headerRow.getCell(i).setText(headers[i]);
}
// 填充数据行
for (int i = 0; i < details.size(); i++) {
XWPFTableRow row = table.createRow();
OutboundDetail detail = details.get(i);
row.getCell(0).setText(String.valueOf(i + 1));
row.getCell(1).setText(detail.getOrderId());
row.getCell(2).setText(detail.getDetailId());
row.getCell(3).setText(detail.getProductName());
row.getCell(4).setText(detail.getUnit());
row.getCell(5).setText(String.valueOf(detail.getQuantity()));
row.getCell(6).setText(String.format("%.2f", detail.getUnitPrice()));
}
}
/**
* 在文档右上角添加二维码
* @param doc Word 文档对象
* @param qrContent 二维码内容
* @throws IOException 输入输出异常
* @throws WriterException ZXing 编码异常
* @throws InvalidFormatException 文档格式异常
*/
public static void addQRCode(XWPFDocument doc, String qrContent) throws IOException, WriterException, InvalidFormatException {
int size = 100; // 二维码大小(像素)
BitMatrix bitMatrix = new MultiFormatWriter().encode(qrContent, BarcodeFormat.QR_CODE, size, size);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix, "png", baos);
byte[] qrImageBytes = baos.toByteArray();
// 将图片数据添加到文档
String pictureId = doc.addPictureData(qrImageBytes, XWPFDocument.PICTURE_TYPE_PNG);
// 创建右对齐段落并插入图片
XWPFParagraph qrParagraph = doc.createParagraph();
qrParagraph.setAlignment(ParagraphAlignment.RIGHT);
XWPFRun qrRun = qrParagraph.createRun();
qrRun.addPicture(
new ByteArrayInputStream(qrImageBytes),
XWPFDocument.PICTURE_TYPE_PNG,
"qrcode.png",
Units.toEMU(size),
Units.toEMU(size)
);
}
}
主生成类
文件:OutboundDocumentGenerator.java
控制文档生成流程。
java
package org.dealpdf.sample;
import com.google.zxing.WriterException;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.dealpdf.model.OutboundDetail;
import org.dealpdf.utils.WordUtil;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// 文档生成主类
public class OutboundDocumentGenerator {
public static void main(String[] args) {
try {
generateWord(); // 执行 Word 文档生成
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 生成 Word 文档
* @throws IOException 输入输出异常
* @throws WriterException ZXing 编码异常
* @throws InvalidFormatException 文档格式异常
*/
private static void generateWord() throws IOException, WriterException, InvalidFormatException {
String templatePath = "src/main/resources/OutboundTemplate.docx"; // 模板文件路径
String wordOutputPath = "outbound_byWord.docx"; // 输出 Word 文件路径
try (FileInputStream fis = new FileInputStream(templatePath);
XWPFDocument doc = new XWPFDocument(fis);
FileOutputStream fosWord = new FileOutputStream(wordOutputPath)) {
// 填充基础信息
Map<String, String> data = getBaseInfoFromApi();
System.out.println("Data to replace: " + data); // 调试输出
WordUtil.replacePlaceholders(doc, data);
data.put("reviewer", "王五"); // 确保 reviewer 有值
// 填充表格
List<OutboundDetail> details = generateSampleDetails();
WordUtil.addTable(doc, details);
// 添加二维码
String qrContent = "OrderID: " + data.get("orderCode") + "\nDate: " + data.get("outboundDate");
WordUtil.addQRCode(doc, qrContent);
// 保存 Word 文档
doc.write(fosWord);
}
}
/**
* 从 API 获取基础信息
* @return 包含基础信息的 Map
*/
private static Map<String, String> getBaseInfoFromApi() {
Map<String, String> data = new HashMap<>();
data.put("orderCode", "BF-03-05-1");
data.put("outboundDate", "2025-07-11");
data.put("customerName", "某某公司");
data.put("warehouseCode", "CK_7525080416537318");
data.put("creator", "张三");
data.put("auditor", "李四");
data.put("createTime", "2025-07-11 01:00");
data.put("outboundOrderId", "13500135001");
return data;
}
/**
* 生成样例出库明细数据
* @return 出库明细列表
*/
private static List<OutboundDetail> generateSampleDetails() {
List<OutboundDetail> details = new ArrayList<>();
details.add(new OutboundDetail("BF-03-05-1", "no1", "34CrNiMo6", "原材料", 6.60, 100.00));
details.add(new OutboundDetail("BF-03-05-2", "no2", "35CrMo", "半成品", 5.5, 120.00));
details.add(new OutboundDetail("BF-03-05-3", "no3", "Q235", "成品", 10.0, 80.00));
return details;
}
}
运行与测试
- 将
OutboundTemplate.docx
放入src/main/resources
目录。 - 运行
OutboundDocumentGenerator.main()
。 - 检查生成的
outbound_byWord.docx
:- 基础信息(如
[orderCode]
替换为BF-03-05-1
)是否正确。 - 动态表格是否包含明细数据。
- 右上角是否显示二维码。
- 基础信息(如
导出结果:
待优化

模板优化
- 使用书签替代
[key]
占位符,提高替换精度。 - 预留右上角空白区域,确保二维码位置美观。
PDF 导出
-
使用
itext7
将 Word 转换为 PDF,例如:javaimport com.itextpdf.html2pdf.HtmlConverter; // 将 XWPFDocument 转换为 HTML 后生成 PDF
样式调整
-
为表格添加边框和标题样式,提升可读性:
javaheaderRow.getCell(i).setColor("CCCCCC"); // 设置表头背景色
总结
通过以上步骤,我们成功实现了以 Word 模板为基础,动态生成带替换符、表格和二维码的文档。这一方法适用于出库单、发票等场景,结合 Apache POI 和 ZXing 提供了灵活性和扩展性。未来可进一步优化 PDF 导出和样式控制,以满足更多需求。
如果您有其他需求(如多语言支持或复杂布局),欢迎留言讨论!
代码地址:Gitee