EasyExcel 下拉扩展方案(支持字典 & 数据库字段)

目标:
在现有 ExcelUtils(static 工具类)基础上
扩展支持:

  • ✅ 原 @ExplicitConstraint
  • ✅ 类实现方式下拉
  • ✅ 类似若依的 dictType 查字典
  • ✅ 指定 table + column 查数据库字段
  • ✅ 不破坏原结构

一、整体架构

原有逻辑:

@ExplicitConstraint → resolveExplicitConstraint → String[]

升级为:

1️⃣ ExplicitConstraint
2️⃣ DynamicConstraint
3️⃣ 统一返回 String[]
4️⃣ 写入 explicitListConstraintMap
5️⃣ SheetWriteHandler 生成下拉

二、新增注解:@DynamicConstraint

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicConstraint {

    /**
     * 字典类型(优先)
     */
    String dictType() default "";

    /**
     * 表名
     */
    String table() default "";

    /**
     * 字段名
     */
    String column() default "";
}

三、动态数据解析器(Spring Bean)

@Component
public class DynamicConstraintResolver {

    @Autowired
    private DictMapper dictMapper;

    @Autowired
    private CommonQueryMapper commonQueryMapper;

    public String[] resolve(DynamicConstraint constraint) {

        if (constraint == null) {
            return null;
        }

        // 1️⃣ 字典查询
        if (!constraint.dictType().isEmpty()) {
            List<String> list =
                    dictMapper.selectDictByType(constraint.dictType());
            return list.toArray(new String[0]);
        }

        // 2️⃣ 表字段查询
        if (!constraint.table().isEmpty()
                && !constraint.column().isEmpty()) {

            List<String> list =
                    commonQueryMapper.selectColumnData(
                            constraint.table(),
                            constraint.column()
                    );
            return list.toArray(new String[0]);
        }

        return null;
    }
}

四、SpringContextHolder(解决 static 注入问题)

因为 ExcelUtils 是 static 工具类,不能 @Autowired

@Component
public class SpringContextHolder implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }

    public static <T> T getBean(Class<T> clazz) {
        return context.getBean(clazz);
    }
}

五、修改 ExcelUtils.writeTemplate 核心逻辑

原代码:

ExplicitConstraint explicitConstraint = field.getAnnotation(ExplicitConstraint.class);
String[] explicitArray = resolveExplicitConstraint(explicitConstraint);

升级为 👇

Field[] declaredFields = zclass.getDeclaredFields();

for (int i = 0; i < declaredFields.length; i++) {

    Field field = declaredFields[i];
    String[] explicitArray = null;

    // 1️⃣ 解析 ExplicitConstraint
    ExplicitConstraint explicitConstraint =
            field.getAnnotation(ExplicitConstraint.class);

    if (explicitConstraint != null) {
        explicitArray = resolveExplicitConstraint(explicitConstraint);
    }

    // 2️⃣ 解析 DynamicConstraint
    if (explicitArray == null) {

        DynamicConstraint dynamicConstraint =
                field.getAnnotation(DynamicConstraint.class);

        if (dynamicConstraint != null) {

            DynamicConstraintResolver resolver =
                    SpringContextHolder.getBean(DynamicConstraintResolver.class);

            explicitArray = resolver.resolve(dynamicConstraint);
        }
    }

    // 3️⃣ 放入下拉集合
    if (explicitArray != null && explicitArray.length > 0) {
        explicitListConstraintMap.put(i, explicitArray);
    }
}

六、DTO 使用方式

1️⃣ 固定枚举

@ExplicitConstraint(source = {"是", "否"})
private String enable;

2️⃣ 字典表方式(类似若依)

@DynamicConstraint(dictType = "device_status")
private String status;

3️⃣ 查任意表字段

@DynamicConstraint(
    table = "device_info",
    column = "device_name"
)
private String deviceName;

七、Mapper 示例

字典查询

@Select("""
    select dict_label
    from sys_dict_data
    where dict_type = #{dictType}
""")
List<String> selectDictByType(String dictType);

表字段查询(⚠ 必须做白名单校验)

@Select("""
    <script>
    select distinct ${column}
    from ${table}
    where ${column} is not null
    </script>
""")
List<String> selectColumnData(
        @Param("table") String table,
        @Param("column") String column
);

⚠ 生产环境必须做白名单验证,防 SQL 注入。


八、执行流程

当调用:

ExcelUtils.writeTemplate(...)

流程如下:

遍历字段
    ↓
找 ExplicitConstraint
    ↓
找 DynamicConstraint
    ↓
统一得到 String[]
    ↓
放入 Map<列索引, 下拉数组>
    ↓
SheetWriteHandler.afterSheetCreate
    ↓
生成 DataValidation

九、注意事项(生产必须知道)

1️⃣ Excel 255 字符限制

Excel 下拉字符串总长度 ≤ 255

如果:

  • 字典数据 > 20 条
  • 或字符串较长

会报错。

解决方案:

使用隐藏 Sheet + 公式引用


2️⃣ 必须做表字段白名单

防止:

@DynamicConstraint(table = "user;drop table")

建议:

private static final Set<String> TABLE_WHITE_LIST =
        Set.of("device_info", "user_info");

3️⃣ 建议加缓存

避免每次导出都查数据库。

推荐 key:

excel:dict:device_status
excel:table:device_info:device_name

十、升级后的能力对比

功能 支持情况
原 ExplicitConstraint
类方式下拉
dictType 字典
任意表字段
保持 static 工具类
类似若依写法

十一、最终效果总结

你现在这套结构已经升级为:

✔ 通用下拉扩展框架
✔ 可扩展
✔ 兼容旧版本
✔ 结构清晰
✔ 可平滑升级为企业级版本

posted @ 2026-02-25 00:10  槑孒  阅读(18)  评论(0)    收藏  举报