# 【Java + EasyExcel 实战】动态列 + 公式备注 Excel 模板导出全流程(附完整代码)

📚 目录

  1. 前言\](#前言)

  2. 整体方案设计\](#整体方案设计)

  • 1. 表头构造\](#1-表头构造)

  • 3. 字典值转换\](#3-字典值转换)

  • 5. 列号转字母\](#5-列号转字母)

  1. 完整工具类代码\](#完整工具类代码)

  2. Controller 示例接口\](#controller-示例接口)


前言

在 **监控告警系统**、**规则引擎**、**数据计算配置** 等场景中,我们经常需要导出"规则模板 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(E2,"x1",F2),"y1",G2),"n",H2)`


整体方案设计

  1. **提取变量**
  • 使用正则匹配 `x\d+`、`y\d+` 等模式

  • 额外匹配 `n`(Z次/Z分钟等)

  1. **构造表头**
  • 固定列(从实体类注解读取)

  • 动态列(变量)

  • 备注列

  1. **构造数据行**
  • 固定字段填充

  • 动态列占位

  • 备注列写公式字符串

  1. **写出 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;

}

  1. 数据行构造
java 复制代码
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;
}
  1. 字典值转换
java 复制代码
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;
}
  1. 备注公式生成
java 复制代码
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;
}
  1. 列号转字母
java 复制代码
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();
}
  1. 导出主流程
java 复制代码
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();
    }
}

总结与优化方向

  • ✅ 动态列生成公式,变量替换即时可见

  • ✅ 支持字典映射,提升可读性

  • ✅ 列宽自适应,表头固定 + 动态可扩展

可优化点:

  1. 动态列映射缓存,保证批量导出一致性

  2. 多规则多 Sheet 导出

  3. 前端进度提示 & 异步导出

  4. 支持 Excel 模板反向导入