# 【Java + EasyExcel 实战】动态列 + 公式备注 Excel 模板导出全流程(附完整代码)
## 📚 目录
1. [前言](#前言)
2. [需求与目标](#需求与目标)
3. [整体方案设计](#整体方案设计)
4. [核心功能拆解](#核心功能拆解)
- [1. 表头构造](#1-表头构造)
- [2. 数据行构造](#2-数据行构造)
- [3. 字典值转换](#3-字典值转换)
- [4. 备注公式生成](#4-备注公式生成)
- [5. 列号转字母](#5-列号转字母)
- [6. 导出主流程](#6-导出主流程)
5. [完整工具类代码](#完整工具类代码)
6. [公式写入处理器](#公式写入处理器)
7. [Controller 示例接口](#controller-示例接口)
8. [总结与优化方向](#总结与优化方向)
---
## 前言
在 **监控告警系统**、**规则引擎**、**数据计算配置** 等场景中,我们经常需要导出“规则模板 Excel”,让用户在表格里填写参数后再回传。
这个 Excel 模板的特点是:
- 固定字段(规则编码、名称、类型等)
- 动态字段(根据规则描述自动提取的变量 x1、y1、n 等)
- 备注列(通过 Excel 公式动态替换变量值)
> ⚡ 本文将演示如何用 **EasyExcel** 实现“动态列 + 公式备注”的模板导出,最终效果是用户在 Excel 中输入变量值后,备注列会**实时更新**。
---
## 需求与目标
### 目标 Excel 结构
| 规则编码 | 规则名称 | 规则类型 | 规则等级 | 描述 | x1 | y1 | n | 备注 |
| -------- | -------- | -------- | -------- | ---- | -- | -- | - | ---- |
| R001 | 浆液循环泵告警 | 高级告警 | 1 | 当 x1 大于 y1 持续 n 分钟时告警 | 10 | 20 | 5 | 当 10 大于 20 持续 5 分钟时告警 |
其中:
- **x1、y1、n** 是根据 `描述` 自动识别出的变量
- **备注列** 是公式生成:`=SUBSTITUTE(SUBSTITUTE(SUBSTITUTE($E$2,"x1",F2),"y1",G2),"n",H2)`
---
## 整体方案设计
1. **提取变量**
- 使用正则匹配 `x\d+`、`y\d+` 等模式
- 额外匹配 `n`(Z次/Z分钟等)
2. **构造表头**
- 固定列(从实体类注解读取)
- 动态列(变量)
- 备注列
3. **构造数据行**
- 固定字段填充
- 动态列占位
- 备注列写公式字符串
4. **写出 Excel**
- 用 EasyExcel 写入
- 注册公式处理器(防止公式变成普通文本)
- 列宽自适应
---
## 核心功能拆解
### 1. 表头构造
```java
public static List<List<String>> buildDynamicHeader(Class<?> clazz, List<String> dynamicHeaders) {
List<List<String>> headers = new ArrayList<>();
// 固定列
for (Field field : clazz.getDeclaredFields()) {
ExcelProperty prop = field.getAnnotation(ExcelProperty.class);
if (prop != null && prop.value().length > 0) {
headers.add(Collections.singletonList(prop.value()[0]));
}
}
// 动态列
for (String dynamic : dynamicHeaders) {
headers.add(Collections.singletonList(dynamic));
}
// 备注列
headers.add(Collections.singletonList("备注"));
return headers;
}
2. 数据行构造
public static List<Object> buildRowFromEntity(RuleExcelTemplate.ExportTemplate rule, int dynamicColSize) {List<Object> row = new ArrayList<>();row.add(rule.getRuleCode());row.add(rule.getRuleName());row.add(convertDict(CustomConstant.ALARM_TYPE, rule.getRuleType()));row.add(rule.getRuleLevel());row.add(rule.getDescription());// 动态列占位for (int i = 0; i < dynamicColSize; i++) {row.add("");}// 备注列公式row.add(buildRemarkFormula(rule.getDescription()));return row;
}
3. 字典值转换
private static String convertDict(String dictKey, String rawValue) {List<DictModel> dictList = CustomUtils.dictModelList(dictKey);if (CollectionUtils.isEmpty(dictList)) {return rawValue;}for (DictModel item : dictList) {if (Objects.equals(item.getId(), rawValue)) {return item.getName();}}return rawValue;
}
4. 备注公式生成
public static String buildRemarkFormula(String description) {if (description == null || description.isEmpty()) {return "";}LinkedHashSet<String> variables = new LinkedHashSet<>();Matcher xyMatcher = CustomConstant.XY_PATTERN.matcher(description);while (xyMatcher.find()) {variables.add(xyMatcher.group());}if (CustomConstant.Z_PATTERN.matcher(description).find()) {variables.add("n");}String formula = "$E$2"; // 描述列int colIndex = 5; // 从F列开始for (String var : variables) {String colLetter = getExcelColLetter(colIndex++);formula = String.format("SUBSTITUTE(%s,\"%s\",%s2)", formula, var, colLetter);}return "=" + formula;
}
5. 列号转字母
public static String getExcelColLetter(int colIndex) {StringBuilder sb = new StringBuilder();while (colIndex >= 0) {sb.insert(0, (char) ('A' + (colIndex % 26)));colIndex = colIndex / 26 - 1;}return sb.toString();
}
6. 导出主流程
public static void exportRuleExcel(RuleExcelTemplate.ExportTemplate rule, List<String> dynamicHeaders, HttpServletResponse response) throws Exception {List<List<String>> headList = buildDynamicHeader(RuleExcelTemplate.ExportTemplate.class, dynamicHeaders);List<Object> dataRow = buildRowFromEntity(rule, dynamicHeaders.size());String fileName = URLEncoder.encode(rule.getRuleName() + "模板导出.xlsx", StandardCharsets.UTF_8);try (OutputStream out = getOutputStream(fileName, response)) {ExcelWriter writer = EasyExcel.write(out).registerWriteHandler(new RemarkFormulaCellHandler()).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).excelType(ExcelTypeEnum.XLSX).build();WriteSheet sheet = EasyExcel.writerSheet("规则导出").head(headList).build();writer.write(Collections.singletonList(dataRow), sheet);writer.finish();}
}
总结与优化方向
✅ 动态列生成公式,变量替换即时可见
✅ 支持字典映射,提升可读性
✅ 列宽自适应,表头固定 + 动态可扩展
可优化点:
动态列映射缓存,保证批量导出一致性
多规则多 Sheet 导出
前端进度提示 & 异步导出
支持 Excel 模板反向导入