From a7915177a090d054b88b59780733001934fd032c Mon Sep 17 00:00:00 2001 From: new_new_new <8454518+new-twice@user.noreply.gitee.com> Date: Wed, 24 Jun 2026 03:36:58 +0000 Subject: [PATCH] =?UTF-8?q?!53=20refactor(excel):=20=E6=8F=90=E5=8F=96Exce?= =?UTF-8?q?l=E5=B7=A5=E5=85=B7=E5=8C=85=E4=B8=BA=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=E6=A8=A1=E5=9D=97jeecg-module-tools=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=B7=A8=E9=A1=B9=E7=9B=AE=E5=A4=8D=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(excel): 提取Excel工具包为独立模块jeecg-module-tools,支持跨项目复用 * fix(excel): 校验错误详情返回前端,ColumnMeta重构为Record,避免字典错后重复报NotBlank * Merge remote-tracking branch 'origin/master' * chore: 添加easyexcel依赖 --- jeecg-boot-base-core/pom.xml | 6 + .../base/controller/JeecgController.java | 16 +- .../jeecg/common/util/excel/ColumnMeta.java | 51 --- .../common/util/excel/ExcelDataListener.java | 43 --- jeecg-boot-module/jeecg-module-tools/pom.xml | 80 ++++ .../jeecg/common/util/excel/ColumnMeta.java | 11 + .../common/util/excel/ColumnWidthHandler.java | 4 +- .../jeecg/common/util/excel/DictQuery.java | 19 + .../util/excel/ExcelAnnotationUtils.java | 353 +++++++++--------- .../util/excel/ExcelImportListener.java | 27 +- .../util/excel/annotation/ExcelColumn.java | 60 +++ jeecg-boot-module/pom.xml | 1 + .../modules/bg/xispeak/entity/BgXiSpeak.java | 85 +++-- 13 files changed, 441 insertions(+), 315 deletions(-) delete mode 100644 jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ColumnMeta.java delete mode 100644 jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ExcelDataListener.java create mode 100644 jeecg-boot-module/jeecg-module-tools/pom.xml create mode 100644 jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ColumnMeta.java rename {jeecg-boot-base-core => jeecg-boot-module/jeecg-module-tools}/src/main/java/org/jeecg/common/util/excel/ColumnWidthHandler.java (91%) create mode 100644 jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/DictQuery.java rename {jeecg-boot-base-core => jeecg-boot-module/jeecg-module-tools}/src/main/java/org/jeecg/common/util/excel/ExcelAnnotationUtils.java (62%) rename {jeecg-boot-base-core => jeecg-boot-module/jeecg-module-tools}/src/main/java/org/jeecg/common/util/excel/ExcelImportListener.java (82%) create mode 100644 jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/annotation/ExcelColumn.java diff --git a/jeecg-boot-base-core/pom.xml b/jeecg-boot-base-core/pom.xml index 94bba34..42c744c 100644 --- a/jeecg-boot-base-core/pom.xml +++ b/jeecg-boot-base-core/pom.xml @@ -44,6 +44,12 @@ org.jeecgframework.boot jeecg-boot-common + + + org.jeecgframework.boot + jeecg-module-tools + 1.0.0 + org.springframework.boot diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java index 0e42e8a..79cc33c 100644 --- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java +++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java @@ -18,7 +18,9 @@ import org.jeecgframework.poi.excel.entity.ExportParams; import org.jeecgframework.poi.excel.entity.ImportParams; import org.jeecgframework.poi.excel.entity.enmus.ExcelType; import org.jeecgframework.poi.excel.view.JeecgEntityExcelView; +import org.jeecg.common.util.excel.DictQuery; import org.jeecg.common.util.excel.ExcelAnnotationUtils; +import org.jeecgframework.dict.service.AutoPoiDictServiceI; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DuplicateKeyException; import org.springframework.web.multipart.MultipartFile; @@ -47,6 +49,8 @@ public class JeecgController> { protected S service; @Resource private JeecgBaseConfig jeecgBaseConfig; + @Autowired(required = false) + private AutoPoiDictServiceI dictService; private final int BATCH_NUM = 500; @@ -174,7 +178,12 @@ public class JeecgController> { private void writeExcel(HttpServletResponse response, Class clazz, String title, List data) throws IOException { - ExcelAnnotationUtils.write(response, clazz, title, data); + ExcelAnnotationUtils.write(response, clazz, title, data, dictQuery()); + } + + private DictQuery dictQuery() { + return dictService == null ? null + : (dictTable, dicCode, dicText) -> dictService.queryDict(dictTable, dicCode, dicText); } /** @@ -209,13 +218,14 @@ public class JeecgController> { clazz, batch -> service.saveBatch(batch), BATCH_NUM, - errors::addAll); + errors::addAll, + dictQuery()); log.info("【EasyExcel导入】导入完成, 数据行数: {}, 耗时: {}毫秒", total, System.currentTimeMillis() - start); if (!errors.isEmpty()) { for (ExcelAnnotationUtils.ValidationError err : errors) { log.error("【EasyExcel导入】校验失败, 行号: {}, 错误: {}", err.row(), err.messages()); } - return Result.error("文件导入失败: 存在" + errors.size() + "行数据校验不通过,请修改后重新导入"); + return Result.error("文件导入失败: 存在" + errors.size() + "行数据校验不通过,请修改后重新导入", errors); } return Result.ok("文件导入成功!数据行数:" + total); } catch (DuplicateKeyException e) { diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ColumnMeta.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ColumnMeta.java deleted file mode 100644 index 1163f26..0000000 --- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ColumnMeta.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.jeecg.common.util.excel; - -import java.lang.reflect.Field; - -/** - * @Excel 注解解析后的列元数据 - */ -public class ColumnMeta { - private final Field field; - private final String name; - private final double width; - private final String format; - private final String[] replace; - private final boolean multiReplace; - private final String numFormat; - private final String groupName; - private final int orderNum; - private final String suffix; - private final String databaseFormat; - private final boolean columnHidden; - - public ColumnMeta(Field field, String name, double width, String format, String[] replace, - boolean multiReplace, String numFormat, String groupName, int orderNum, - String suffix, String databaseFormat, boolean columnHidden) { - this.field = field; - this.name = name; - this.width = width; - this.format = format; - this.replace = replace; - this.multiReplace = multiReplace; - this.numFormat = numFormat; - this.groupName = groupName; - this.orderNum = orderNum; - this.suffix = suffix; - this.databaseFormat = databaseFormat; - this.columnHidden = columnHidden; - } - - public Field getField() { return field; } - public String getName() { return name; } - public double getWidth() { return width; } - public String getFormat() { return format; } - public String[] getReplace() { return replace; } - public boolean isMultiReplace() { return multiReplace; } - public String getNumFormat() { return numFormat; } - public String getGroupName() { return groupName; } - public int getOrderNum() { return orderNum; } - public String getSuffix() { return suffix; } - public String getDatabaseFormat() { return databaseFormat; } - public boolean isColumnHidden() { return columnHidden; } -} diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ExcelDataListener.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ExcelDataListener.java deleted file mode 100644 index c584f93..0000000 --- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ExcelDataListener.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.jeecg.common.util.excel; // 换成你的包名 - -import com.alibaba.excel.context.AnalysisContext; -import com.alibaba.excel.event.AnalysisEventListener; -import com.baomidou.mybatisplus.extension.service.IService; -import java.util.ArrayList; -import java.util.List; - -public class ExcelDataListener extends AnalysisEventListener { - private static final int BATCH_COUNT = 1000; - private final List cachedDataList = new ArrayList<>(BATCH_COUNT); - private final IService baseService; - private int totalCount = 0; - - public ExcelDataListener(IService baseService) { - this.baseService = baseService; - } - - @Override - public void invoke(T data, AnalysisContext context) { - cachedDataList.add(data); - totalCount++; - if (cachedDataList.size() >= BATCH_COUNT) { - saveData(); - cachedDataList.clear(); - } - } - - @Override - public void doAfterAllAnalysed(AnalysisContext context) { - if (!cachedDataList.isEmpty()) { - saveData(); - } - } - - private void saveData() { - baseService.saveBatch(cachedDataList); - } - - public int getTotalCount() { - return totalCount; - } -} \ No newline at end of file diff --git a/jeecg-boot-module/jeecg-module-tools/pom.xml b/jeecg-boot-module/jeecg-module-tools/pom.xml new file mode 100644 index 0000000..a4e216c --- /dev/null +++ b/jeecg-boot-module/jeecg-module-tools/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + + org.jeecgframework.boot + jeecg-module-tools + 1.0.0 + jar + + jeecg-module-tools + 独立 Excel 导入导出工具包 — @ExcelColumn + DictQuery + EasyExcel,零框架耦合 + + + 17 + UTF-8 + 4.0.3 + + + + + + com.alibaba + easyexcel + ${easyexcel.version} + + + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + + javax.validation + validation-api + 2.0.1.Final + + + + + org.hibernate.validator + hibernate-validator + 6.2.5.Final + + + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + + + org.projectlombok + lombok + 1.18.30 + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${java.version} + ${java.version} + + + + + diff --git a/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ColumnMeta.java b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ColumnMeta.java new file mode 100644 index 0000000..ee7f09b --- /dev/null +++ b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ColumnMeta.java @@ -0,0 +1,11 @@ +package org.jeecg.common.util.excel; + +import java.lang.reflect.Field; + +/** + * @Excel 注解解析后的列元数据 + */ +public record ColumnMeta(Field field, String name, double width, String format, String[] replace, boolean multiReplace, + String numFormat, String groupName, int orderNum, String suffix, String databaseFormat, + boolean columnHidden) { +} diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ColumnWidthHandler.java b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ColumnWidthHandler.java similarity index 91% rename from jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ColumnWidthHandler.java rename to jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ColumnWidthHandler.java index 1f92d7b..7afe173 100644 --- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ColumnWidthHandler.java +++ b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ColumnWidthHandler.java @@ -34,10 +34,10 @@ public class ColumnWidthHandler extends AbstractColumnWidthStyleStrategy { } ColumnMeta meta = columns.get(columnIndex); Sheet sheet = cell.getSheet(); - if (meta.isColumnHidden()) { + if (meta.columnHidden()) { sheet.setColumnHidden(columnIndex, true); } else { - sheet.setColumnWidth(columnIndex, (int) ((meta.getWidth() + 2) * 256)); + sheet.setColumnWidth(columnIndex, (int) ((meta.width() + 2) * 256)); } } } diff --git a/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/DictQuery.java b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/DictQuery.java new file mode 100644 index 0000000..916c36d --- /dev/null +++ b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/DictQuery.java @@ -0,0 +1,19 @@ +package org.jeecg.common.util.excel; + +/** + * 字典查询接口,由调用方实现并注入, + * 用于 Excel 导入导出时的字典翻译(如 sex=0 → 男)。 + */ +@FunctionalInterface +public interface DictQuery { + + /** + * 查询字典键值对映射 + * + * @param dictTable 字典表名(来自 @ExcelColumn.dictTable()) + * @param dicCode 字典编码字段(来自 @ExcelColumn.dicCode()) + * @param dicText 字典文本字段(来自 @ExcelColumn.dicText()) + * @return 格式为 {"text_code", ...} 的数组,如 {"男_0", "女_1"};无结果返回 null 或空数组 + */ + String[] query(String dictTable, String dicCode, String dicText); +} diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ExcelAnnotationUtils.java b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ExcelAnnotationUtils.java similarity index 62% rename from jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ExcelAnnotationUtils.java rename to jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ExcelAnnotationUtils.java index 14568d8..09526ba 100644 --- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ExcelAnnotationUtils.java +++ b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ExcelAnnotationUtils.java @@ -1,20 +1,11 @@ package org.jeecg.common.util.excel; import com.alibaba.excel.EasyExcel; -import com.alibaba.excel.context.AnalysisContext; -import com.alibaba.excel.metadata.data.ReadCellData; -import com.alibaba.excel.read.listener.ReadListener; -import com.alibaba.excel.write.handler.WriteHandler; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.jeecg.common.util.SpringContextUtils; -import org.jeecgframework.dict.service.AutoPoiDictServiceI; -import org.jeecgframework.poi.excel.annotation.Excel; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; +import org.jeecg.common.util.excel.annotation.ExcelColumn; import javax.servlet.http.HttpServletResponse; -import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import java.io.IOException; @@ -32,65 +23,70 @@ import java.util.*; import java.util.function.Consumer; /** - * @Excel 注解工具类,用于 EasyExcel 导入导出。 - * 从实体类字段上读取 @Excel 注解,构建 EasyExcel 表头和数据,无需 @ExcelProperty。 + * @ExcelColumn 注解工具类,用于 EasyExcel 导入导出。 + * 从实体类字段上读取 @ExcelColumn 注解,构建 EasyExcel 表头和数据,无需 @ExcelProperty。 + *

+ * 已解耦 jeecgframework,可独立打包复用。调用方通过 {@link DictQuery} 注入字典服务。 */ @Slf4j -@Component public final class ExcelAnnotationUtils { - private static volatile ExcelAnnotationUtils instance; - private final Validator validator; + private static volatile Validator cachedValidator; + + private ExcelAnnotationUtils() {} /** - * 校验错误信息载体:行号 + 错误消息列表(现代 Java Record,1行搞定) + * 校验错误信息载体:行号 + 错误消息列表 */ public record ValidationError(int row, List messages) {} - @Autowired - public ExcelAnnotationUtils(Validator validator) { - this.validator = validator; - ExcelAnnotationUtils.instance = this; - log.info("【Excel导入】Spring Validator 成功通过构造器桥接到静态上下文"); - } - - private ExcelAnnotationUtils(Validator validator, boolean isBackup) { - this.validator = validator; - } + // ==================== Validator ==================== + /** + * 获取 Validator 实例(懒加载缓存)。 + * 若调用方未通过 {@link #setValidator(Validator)} 注入,则自动创建纯 Java Validator。 + */ public static Validator getValidator() { - if (instance != null && instance.validator != null) { - return instance.validator; + if (cachedValidator != null) { + return cachedValidator; } - synchronized (ExcelAnnotationUtils.class) { - if (instance == null) { - log.warn("【Excel导入】未检测到已注入的 Spring 实例,正在启用原生纯 Java Validator 作为安全保底!"); - Validator tempValidator = Validation.buildDefaultValidatorFactory().getValidator(); - instance = new ExcelAnnotationUtils(tempValidator, true); - return tempValidator; + if (cachedValidator == null) { + cachedValidator = Validation.buildDefaultValidatorFactory().getValidator(); + log.info("【Excel工具】已创建默认 Validator(非 Spring 模式)"); } } - return instance.validator; + return cachedValidator; } /** - * 解析 Class 上带 @Excel 注解的字段,按 orderNum 排序返回列元数据 + * 注入自定义 Validator(如 Spring 管理的 Validator),需在首次调用前设置。 */ - public static List parseColumns(Class clazz) { + public static void setValidator(Validator validator) { + cachedValidator = validator; + } + + // ==================== 注解解析 ==================== + + /** + * 解析 Class 上带 @ExcelColumn 注解的字段,按 orderNum 排序返回列元数据 + * + * @param clazz 实体类 + * @param dictQuery 字典查询接口,可为 null(不启用字典翻译) + */ + public static List parseColumns(Class clazz, DictQuery dictQuery) { List columns = new ArrayList<>(); - AutoPoiDictServiceI dictService = getDictService(); for (Field field : getAllFields(clazz)) { - Excel excel = field.getAnnotation(Excel.class); - if (excel == null) { + ExcelColumn col = field.getAnnotation(ExcelColumn.class); + if (col == null) { continue; } field.setAccessible(true); - String[] replace = excel.replace(); - if (dictService != null && (StringUtils.isNotEmpty(excel.dictTable()) || StringUtils.isNotEmpty(excel.dicCode()))) { - String[] dictReplace = dictService.queryDict(excel.dictTable(), excel.dicCode(), excel.dicText()); + String[] replace = col.replace(); + if (dictQuery != null && (StringUtils.isNotEmpty(col.dictTable()) || StringUtils.isNotEmpty(col.dicCode()))) { + String[] dictReplace = dictQuery.query(col.dictTable(), col.dicCode(), col.dicText()); if (dictReplace != null && dictReplace.length > 0) { replace = mergeReplace(replace, dictReplace); } @@ -98,41 +94,43 @@ public final class ExcelAnnotationUtils { columns.add(new ColumnMeta( field, - excel.name(), - excel.width(), - StringUtils.isNotEmpty(excel.exportFormat()) ? excel.exportFormat() : excel.format(), + col.name(), + col.width(), + StringUtils.isNotEmpty(col.exportFormat()) ? col.exportFormat() : col.format(), replace, - excel.multiReplace(), - excel.numFormat(), - excel.groupName(), - parseOrderNum(excel.orderNum()), - excel.suffix(), - excel.databaseFormat(), - excel.isColumnHidden() + col.multiReplace(), + col.numFormat(), + col.groupName(), + parseOrderNum(col.orderNum()), + col.suffix(), + col.databaseFormat(), + col.hidden() )); } - columns.sort(Comparator.comparingInt(ColumnMeta::getOrderNum)); + columns.sort(Comparator.comparingInt(ColumnMeta::orderNum)); return columns; } + // ==================== 导出 ==================== + /** - * 构建 EasyExcel 多级表头,支持 @Excel.groupName() + * 构建 EasyExcel 多级表头,支持 @ExcelColumn.groupName() */ public static List> buildHead(List columns) { - boolean hasGroup = columns.stream().anyMatch(c -> StringUtils.isNotEmpty(c.getGroupName())); + boolean hasGroup = columns.stream().anyMatch(c -> StringUtils.isNotEmpty(c.groupName())); List> head = new ArrayList<>(); for (ColumnMeta col : columns) { - if (hasGroup && StringUtils.isNotEmpty(col.getGroupName())) { - head.add(Arrays.asList(col.getGroupName(), col.getName())); + if (hasGroup && StringUtils.isNotEmpty(col.groupName())) { + head.add(Arrays.asList(col.groupName(), col.name())); } else { - head.add(Collections.singletonList(col.getName())); + head.add(Collections.singletonList(col.name())); } } return head; } /** - * 将数据列表按 ColumnMeta 转换为 EasyExcel 的行数据,应用字典翻译、日期格式化、值替换等 + * 将数据列表按 ColumnMeta 转换为 EasyExcel 的行数据 */ public static List> convertData(List columns, List dataList) { List> rows = new ArrayList<>(dataList.size()); @@ -148,10 +146,12 @@ public final class ExcelAnnotationUtils { /** * 写出 Excel 的公共入口:设置响应头 + 构建表头 + 写数据 + * + * @param dictQuery 字典查询接口,可为 null */ public static void write(HttpServletResponse response, Class clazz, - String title, List data) throws IOException { - List columns = parseColumns(clazz); + String title, List data, DictQuery dictQuery) throws IOException { + List columns = parseColumns(clazz, dictQuery); List> head = buildHead(columns); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); @@ -161,60 +161,152 @@ public final class ExcelAnnotationUtils { EasyExcel.write(response.getOutputStream()) .head(head) - .registerWriteHandler(new ColumnWidthHandler(columns)) // 这里保持自适应宽度 + .registerWriteHandler(new ColumnWidthHandler(columns)) .sheet(title) .doWrite(convertData(columns, data)); } - // ---- 私有辅助方法 ---- + // ==================== 导入 ==================== + + /** + * 从 Excel 输入流分批导入数据,基于 @ExcelColumn 注解读取列配置。 + * + * @param dictQuery 字典查询接口,可为 null(不启用字典翻译) + */ + public static int read(InputStream inputStream, Class clazz, + Consumer> batchHandler, int batchSize, + Consumer> errorHandler, + DictQuery dictQuery) { + + List columns = parseColumns(clazz, dictQuery); + + Map nameToCol = new LinkedHashMap<>(); + Map displayToCode = new LinkedHashMap<>(); + + for (ColumnMeta col : columns) { + nameToCol.put(col.name(), col); + buildReverseReplace(col.replace(), displayToCode); + } + + ExcelImportListener listener = new ExcelImportListener<>( + clazz, batchSize, nameToCol, displayToCode, batchHandler, errorHandler + ); + + EasyExcel.read(inputStream, listener).sheet().doRead(); + + return listener.getSuccessCount(); + } + + // ==================== 内部辅助方法 ==================== + + static boolean isValidDictDisplay(String[] replace, String value) { + for (String s : replace) { + String[] arr = s.split("_"); + if (arr.length >= 2 && arr[0].equals(value)) { + return true; + } + } + return false; + } + + static void setFieldValue(ColumnMeta col, Object obj, String value, + Map displayToCode) throws Exception { + Field field = col.field(); + Class type = field.getType(); + + String resolved = displayToCode.getOrDefault(value, value); + + if (type == String.class) { + field.set(obj, resolved); + return; + } + try { + if (type == Integer.class || type == int.class) { + field.set(obj, Integer.valueOf(resolved)); + } else if (type == Long.class || type == long.class) { + field.set(obj, Long.valueOf(resolved)); + } else if (type == Double.class || type == double.class) { + field.set(obj, Double.valueOf(resolved)); + } else if (type == BigDecimal.class) { + field.set(obj, new BigDecimal(resolved)); + } else if (type == Boolean.class || type == boolean.class) { + field.set(obj, Boolean.valueOf(resolved)); + } else if (type == Date.class) { + String format = StringUtils.isNotEmpty(col.databaseFormat()) ? col.databaseFormat() : col.format(); + if (StringUtils.isNotEmpty(format)) { + field.set(obj, new SimpleDateFormat(format).parse(resolved)); + } + } else if (type == LocalDateTime.class) { + String format = col.format(); + if (StringUtils.isNotEmpty(format)) { + field.set(obj, LocalDateTime.parse(resolved, DateTimeFormatter.ofPattern(format))); + } + } else if (type == LocalDate.class) { + String format = col.format(); + if (StringUtils.isNotEmpty(format)) { + field.set(obj, LocalDate.parse(resolved, DateTimeFormatter.ofPattern(format))); + } + } else if (type == Float.class || type == float.class) { + field.set(obj, Float.valueOf(resolved)); + } else if (type == Short.class || type == short.class) { + field.set(obj, Short.valueOf(resolved)); + } else { + field.set(obj, resolved); + } + } catch (NumberFormatException e) { + log.warn("【Excel导入】字段值类型转换失败, field: {}, value: {}", field.getName(), resolved); + } + } + + // ---- 私有方法 ---- private static Object getCellValue(ColumnMeta col, Object obj) { try { - Object value = col.getField().get(obj); + Object value = col.field().get(obj); if (value == null || "".equals(value)) { return ""; } - if (StringUtils.isNotEmpty(col.getFormat())) { + if (StringUtils.isNotEmpty(col.format())) { value = formatDate(value, col); } String strValue = value.toString(); - if (col.getReplace() != null && col.getReplace().length > 0) { - strValue = col.isMultiReplace() - ? multiReplaceValue(col.getReplace(), strValue) - : replaceValue(col.getReplace(), strValue); + if (col.replace() != null && col.replace().length > 0) { + strValue = col.multiReplace() + ? multiReplaceValue(col.replace(), strValue) + : replaceValue(col.replace(), strValue); } - if (StringUtils.isNotEmpty(col.getNumFormat()) && value instanceof Number) { - strValue = new DecimalFormat(col.getNumFormat()).format(value); + if (StringUtils.isNotEmpty(col.numFormat()) && value instanceof Number) { + strValue = new DecimalFormat(col.numFormat()).format(value); } - if (StringUtils.isNotEmpty(col.getSuffix())) { - strValue = strValue + col.getSuffix(); + if (StringUtils.isNotEmpty(col.suffix())) { + strValue = strValue + col.suffix(); } return strValue; } catch (Exception e) { - log.error("获取单元格值失败, field: {}, row: {}", col.getField().getName(), obj, e); + log.error("获取单元格值失败, field: {}, row: {}", col.field().getName(), obj, e); return ""; } } private static Object formatDate(Object value, ColumnMeta col) throws Exception { if (value instanceof Date) { - return new SimpleDateFormat(col.getFormat()).format((Date) value); + return new SimpleDateFormat(col.format()).format((Date) value); } if (value instanceof LocalDateTime) { - return ((LocalDateTime) value).format(DateTimeFormatter.ofPattern(col.getFormat())); + return ((LocalDateTime) value).format(DateTimeFormatter.ofPattern(col.format())); } if (value instanceof LocalDate) { - return ((LocalDate) value).format(DateTimeFormatter.ofPattern(col.getFormat())); + return ((LocalDate) value).format(DateTimeFormatter.ofPattern(col.format())); } - if (value instanceof String && StringUtils.isNotEmpty(col.getDatabaseFormat())) { - Date parsed = new SimpleDateFormat(col.getDatabaseFormat()).parse((String) value); - return new SimpleDateFormat(col.getFormat()).format(parsed); + if (value instanceof String && StringUtils.isNotEmpty(col.databaseFormat())) { + Date parsed = new SimpleDateFormat(col.databaseFormat()).parse((String) value); + return new SimpleDateFormat(col.format()).format(parsed); } return value; } @@ -255,54 +347,6 @@ public final class ExcelAnnotationUtils { } } - private static AutoPoiDictServiceI getDictService() { - try { - return SpringContextUtils.getBean(AutoPoiDictServiceI.class); - } catch (Exception e) { - return null; - } - } - - /** - * 从 Excel 输入流分批导入数据,基于 @Excel 注解读取列配置。 - */ - public static int read(InputStream inputStream, Class clazz, - Consumer> batchHandler, int batchSize, - Consumer> errorHandler) { - - // 1. 职责一:解析注解元数据,准备基础映射表 - List columns = parseColumns(clazz); - - Map nameToCol = new LinkedHashMap<>(); - Map displayToCode = new LinkedHashMap<>(); - - for (ColumnMeta col : columns) { - nameToCol.put(col.getName(), col); - buildReverseReplace(col.getReplace(), displayToCode); - } - - // 2. 职责二:实例化专属的流式解析校验监听器 - ExcelImportListener listener = new ExcelImportListener<>( - clazz, batchSize, nameToCol, displayToCode, batchHandler, errorHandler - ); - - // 3. 职责三:交由 EasyExcel 引擎启动 - EasyExcel.read(inputStream, listener).sheet().doRead(); - - // 4. 返回结果 - return listener.getSuccessCount(); - } - - static boolean isValidDictDisplay(String[] replace, String value) { - for (String s : replace) { - String[] arr = s.split("_"); - if (arr.length >= 2 && arr[0].equals(value)) { - return true; - } - } - return false; - } - private static void buildReverseReplace(String[] replace, Map displayToCode) { if (replace == null || replace.length == 0) { return; @@ -315,55 +359,6 @@ public final class ExcelAnnotationUtils { } } - static void setFieldValue(ColumnMeta col, Object obj, String value, - Map displayToCode) throws Exception { - Field field = col.getField(); - Class type = field.getType(); - - String resolved = displayToCode.getOrDefault(value, value); - - if (type == String.class) { - field.set(obj, resolved); - return; - } - try { - if (type == Integer.class || type == int.class) { - field.set(obj, Integer.valueOf(resolved)); - } else if (type == Long.class || type == long.class) { - field.set(obj, Long.valueOf(resolved)); - } else if (type == Double.class || type == double.class) { - field.set(obj, Double.valueOf(resolved)); - } else if (type == BigDecimal.class) { - field.set(obj, new BigDecimal(resolved)); - } else if (type == Boolean.class || type == boolean.class) { - field.set(obj, Boolean.valueOf(resolved)); - } else if (type == Date.class) { - String format = StringUtils.isNotEmpty(col.getDatabaseFormat()) ? col.getDatabaseFormat() : col.getFormat(); - if (StringUtils.isNotEmpty(format)) { - field.set(obj, new SimpleDateFormat(format).parse(resolved)); - } - } else if (type == LocalDateTime.class) { - String format = col.getFormat(); - if (StringUtils.isNotEmpty(format)) { - field.set(obj, LocalDateTime.parse(resolved, DateTimeFormatter.ofPattern(format))); - } - } else if (type == LocalDate.class) { - String format = col.getFormat(); - if (StringUtils.isNotEmpty(format)) { - field.set(obj, LocalDate.parse(resolved, DateTimeFormatter.ofPattern(format))); - } - } else if (type == Float.class || type == float.class) { - field.set(obj, Float.valueOf(resolved)); - } else if (type == Short.class || type == short.class) { - field.set(obj, Short.valueOf(resolved)); - } else { - field.set(obj, resolved); - } - } catch (NumberFormatException e) { - log.warn("【Excel导入】字段值类型转换失败, field: {}, value: {}", field.getName(), resolved); - } - } - private static List getAllFields(Class clazz) { List fields = new ArrayList<>(); Class current = clazz; @@ -373,4 +368,4 @@ public final class ExcelAnnotationUtils { } return fields; } -} \ No newline at end of file +} diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ExcelImportListener.java b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ExcelImportListener.java similarity index 82% rename from jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ExcelImportListener.java rename to jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ExcelImportListener.java index 0af665e..30f348f 100644 --- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/excel/ExcelImportListener.java +++ b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ExcelImportListener.java @@ -68,8 +68,8 @@ public class ExcelImportListener implements ReadListener // 缓存 Java 属性名 -> Excel 表头列名的转换映射 for (ColumnMeta cm : columnOrder.values()) { - if (cm.getField() != null) { - fieldToColName.put(cm.getField().getName(), cm.getName()); + if (cm.field() != null) { + fieldToColName.put(cm.field().getName(), cm.name()); } } } @@ -82,22 +82,29 @@ public class ExcelImportListener implements ReadListener try { T obj = clazz.getDeclaredConstructor().newInstance(); List rowErrors = new ArrayList<>(); + // 🆕 新增:用来记录哪些 Java 属性名已经发生了字典错误 + Set dictFailedFields = new HashSet<>(); - // 1. 字典校验 + 安全转换赋值 + // 1. 赋值与字典校验 for (Map.Entry entry : data.entrySet()) { ColumnMeta col = columnOrder.get(entry.getKey()); String cellValue = entry.getValue(); if (col == null || StringUtils.isEmpty(cellValue)) continue; - String[] replace = col.getReplace(); + // 获取该列对应的 Java 属性名(字段名),例如 "gender" + String propertyName = col.field().getName(); + + String[] replace = col.replace(); if (replace != null && replace.length > 0) { if (!ExcelAnnotationUtils.isValidDictDisplay(replace, cellValue)) { - rowErrors.add("【" + col.getName() + "】的值'" + cellValue + "'不在字典范围内"); - continue; + rowErrors.add("【" + col.name() + "】的值'" + cellValue + "'不在字典范围内"); + dictFailedFields.add(propertyName); // 🆕 标记这个字段字典错漏 + continue; // 允许 continue,因为字典错的值无法安全转换 } } + // 字典通过,或者不需要字典校验的,正常赋值 ExcelAnnotationUtils.setFieldValue(col, obj, cellValue, displayToCode); } @@ -108,6 +115,12 @@ public class ExcelImportListener implements ReadListener if (!violations.isEmpty()) { for (ConstraintViolation v : violations) { String propertyName = v.getPropertyPath().toString(); + + // 🆕 核心拦截:如果这个字段刚才已经报了“字典错误”,就别再报“不能为空”了 + if (dictFailedFields.contains(propertyName)) { + continue; + } + String label = fieldToColName.getOrDefault(propertyName, propertyName); rowErrors.add("【" + label + "】" + v.getMessage()); } @@ -130,7 +143,7 @@ public class ExcelImportListener implements ReadListener } catch (Exception e) { skipCount[0]++; - log.error("【Excel导入】行数据转换发生系统异常, row: {}", rowIndex, e); + log.error("【Excel导入】行数据转换发生 system 异常, row: {}", rowIndex, e); } } diff --git a/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/annotation/ExcelColumn.java b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/annotation/ExcelColumn.java new file mode 100644 index 0000000..9222f86 --- /dev/null +++ b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/annotation/ExcelColumn.java @@ -0,0 +1,60 @@ +package org.jeecg.common.util.excel.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Excel 导入导出列注解,替代 jeecgframework 的 @Excel, + * 使 excel 工具包可独立打包复用。 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExcelColumn { + + /** 列名(表头),必填 */ + String name(); + + /** 列宽 */ + double width() default 15; + + /** 日期显示格式(如 yyyy-MM-dd),导出时格式化 Date/LocalDate/LocalDateTime */ + String format() default ""; + + /** 导出专用日期格式,优先于 format */ + String exportFormat() default ""; + + /** 值替换映射(格式:text_code),如 {"男_1", "女_0"} */ + String[] replace() default {}; + + /** 是否多值替换(逗号分隔的多个值分别替换) */ + boolean multiReplace() default false; + + /** 数字格式(DecimalFormat,如 #.##) */ + String numFormat() default ""; + + /** 分组表头(支持多级表头) */ + String groupName() default ""; + + /** 排序号(越小越靠前),字符串形式便于配置 */ + String orderNum() default ""; + + /** 单元格后缀 */ + String suffix() default ""; + + /** 数据库日期格式(导入时将字符串按此格式解析为 Date) */ + String databaseFormat() default ""; + + /** 是否隐藏该列 */ + boolean hidden() default false; + + /** 字典表名(需搭配 DictQuery 使用) */ + String dictTable() default ""; + + /** 字典编码字段 */ + String dicCode() default ""; + + /** 字典文本字段 */ + String dicText() default ""; +} diff --git a/jeecg-boot-module/pom.xml b/jeecg-boot-module/pom.xml index 09f9d60..2c00b83 100644 --- a/jeecg-boot-module/pom.xml +++ b/jeecg-boot-module/pom.xml @@ -19,6 +19,7 @@ jeecg-boot-module-wps--> jeecg-boot-module-airag jeecg-module-flow + jeecg-module-tools diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/BgXiSpeak.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/BgXiSpeak.java index 962940d..e275e67 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/BgXiSpeak.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/BgXiSpeak.java @@ -9,8 +9,9 @@ import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import org.jeecg.common.constant.ProvinceCityArea; import lombok.Data; import com.fasterxml.jackson.annotation.JsonFormat; -import org.springframework.format.annotation.DateTimeFormat; import org.jeecgframework.poi.excel.annotation.Excel; +import org.springframework.format.annotation.DateTimeFormat; +import org.jeecg.common.util.excel.annotation.ExcelColumn; import org.jeecg.common.aspect.annotation.Dict; import io.swagger.v3.oas.annotations.media.Schema; @@ -57,128 +58,152 @@ public class BgXiSpeak implements Serializable { @Schema(description = "所属部门") private java.lang.String sysOrgCode; /**父级节点*/ -// @Excel(name = "父级节点", width = 15) +// @ExcelColumn(name = "父级节点", width = 15) @Schema(description = "父级节点") private java.lang.String pid; /**是否有子节点*/ -// @Excel(name = "是否有子节点", width = 15, dicCode = "yn") +// @ExcelColumn(name = "是否有子节点", width = 15, dicCode = "yn") @Dict(dicCode = "yn") @Schema(description = "是否有子节点") private java.lang.String hasChild; /**习近平总书记重要讲话指示批示情况*/ - @Excel(name = "习近平总书记重要讲话指示批示情况", width = 15) + @Excel(name = "习近平总书记重要讲话指示批示情况", width = 15) + @ExcelColumn(name = "习近平总书记重要讲话指示批示情况", width = 15) @Schema(description = "习近平总书记重要讲话指示批示情况") private java.lang.String speakStatus; /**时间*/ - @Excel(name = "时间", width = 15, format = "yyyy-MM-dd") + @Excel(name = "时间", width = 15, format = "yyyy-MM-dd") + @ExcelColumn(name = "时间", width = 15, format = "yyyy-MM-dd") @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd") @DateTimeFormat(pattern="yyyy-MM-dd") @Schema(description = "时间") private java.util.Date time; /**企业负责同志批示情况*/ - @Excel(name = "企业负责同志批示情况", width = 15) + @Excel(name = "企业负责同志批示情况", width = 15) + @ExcelColumn(name = "企业负责同志批示情况", width = 15) @Schema(description = "企业负责同志批示情况") private java.lang.String elmComment; /**学习传达研究部署情况*/ - @Excel(name = "学习传达研究部署情况", width = 15) + @Excel(name = "学习传达研究部署情况", width = 15) + @ExcelColumn(name = "学习传达研究部署情况", width = 15) @Schema(description = "学习传达研究部署情况") private java.lang.String status; /**密级*/ - @Excel(name = "密级(请填入非秘/秘密/机密)-必填", width = 15, dicCode = "secret_level") + @Excel(name = "密级(请填入非秘/秘密/机密)-必填", width = 15, dicCode = "secret_level") + @ExcelColumn(name = "密级(请填入非秘/秘密/机密)-必填", width = 15, dicCode = "secret_level") @Dict(dicCode = "secret_level") @Schema(description = "密级") - @NotBlank + @NotBlank(message = "不能为空") private java.lang.String secretLevel; /**责任人*/ - @Excel(name = "责任人", width = 15, dictTable = "sys_user", dicText = "realname", dicCode = "username") + @Excel(name = "责任人", width = 15, dictTable = "sys_user", dicText = "realname", dicCode = "username") + @ExcelColumn(name = "责任人", width = 15, dictTable = "sys_user", dicText = "realname", dicCode = "username") @Dict(dictTable = "sys_user", dicText = "realname", dicCode = "username") @Schema(description = "责任人") private java.lang.String responsiblePerson; /**落实方案*/ - @Excel(name = "落实方案", width = 15) + @Excel(name = "落实方案", width = 15) + @ExcelColumn(name = "落实方案", width = 15) @Schema(description = "落实方案") private java.lang.String implPlan; /**牵头部门*/ - @Excel(name = "牵头部门", width = 15, dictTable = "sys_depart", dicText = "depart_name", dicCode = "id") + @Excel(name = "牵头部门", width = 15, dictTable = "sys_depart", dicText = "depart_name", dicCode = "id") + @ExcelColumn(name = "牵头部门", width = 15, dictTable = "sys_depart", dicText = "depart_name", dicCode = "id") @Dict(dictTable = "sys_depart", dicText = "depart_name", dicCode = "id") @Schema(description = "牵头部门") private java.lang.String leadDepartment; /**贯彻落实措施*/ - @Excel(name = "贯彻落实措施-必填", width = 15) + @Excel(name = "贯彻落实措施-必填", width = 15) + @ExcelColumn(name = "贯彻落实措施-必填", width = 15) @Schema(description = "贯彻落实措施") - @NotBlank + @NotBlank(message = "不能为空") private java.lang.String implMeasures; /**会议决策数*/ - @Excel(name = "会议决策数", width = 15) + @Excel(name = "会议决策数", width = 15) + @ExcelColumn(name = "会议决策数", width = 15) @Schema(description = "会议决策数") private java.lang.Integer numberOfResolutions; /**落实情况*/ - @Excel(name = "落实情况", width = 15) + @Excel(name = "落实情况", width = 15) + @ExcelColumn(name = "落实情况", width = 15) @Schema(description = "落实情况") private java.lang.String implStatus; /**落实条数*/ - @Excel(name = "落实条数", width = 15) + @Excel(name = "落实条数", width = 15) + @ExcelColumn(name = "落实条数", width = 15) @Schema(description = "落实条数") private java.lang.Integer implCount; /**已闭环数量*/ - @Excel(name = "已闭环数量", width = 15) + @Excel(name = "已闭环数量", width = 15) + @ExcelColumn(name = "已闭环数量", width = 15) @Schema(description = "已闭环数量") private java.lang.Integer closedCount; /**报告反馈情况*/ - @Excel(name = "报告反馈情况", width = 15) + @Excel(name = "报告反馈情况", width = 15) + @ExcelColumn(name = "报告反馈情况", width = 15) @Schema(description = "报告反馈情况") private java.lang.String reportFeedbackStatus; /**是否存在完成风险(0不存在,1存在)*/ - @Excel(name = "是否存在完成风险(请填入是/否)", width = 15, dicCode = "true_or_flase") + @Excel(name = "是否存在完成风险(请填入是/否)", width = 15, dicCode = "true_or_flase") + @ExcelColumn(name = "是否存在完成风险(请填入是/否)", width = 15, dicCode = "true_or_flase") @Schema(description = "是否存在完成风险(0不存在,1存在)") private java.lang.Integer isCompletionRisk; /**拖期风险应对措施*/ - @Excel(name = "拖期风险应对措施", width = 15) + @Excel(name = "拖期风险应对措施", width = 15) + @ExcelColumn(name = "拖期风险应对措施", width = 15) @Schema(description = "拖期风险应对措施") private java.lang.String delayRiskMitigation; /**完成状态(0推进中,1已完成)*/ - @Excel(name = "完成状态(请填入推进中/已完成)-必填", width = 15, dicCode = "db_status") + @Excel(name = "完成状态(请填入推进中/已完成)-必填", width = 15, dicCode = "db_status") + @ExcelColumn(name = "完成状态(请填入推进中/已完成)-必填", width = 15, dicCode = "db_status") @Schema(description = "完成状态(0推进中,1已完成)") @Dict( dicCode = "db_status") private java.lang.Integer completionStatus; /**落实部门id*/ - @Excel(name = "落实部门", width = 15, dictTable = "sys_depart", dicText = "depart_name", dicCode = "id") + @Excel(name = "落实部门", width = 15, dictTable = "sys_depart", dicText = "depart_name", dicCode = "id") + @ExcelColumn(name = "落实部门", width = 15, dictTable = "sys_depart", dicText = "depart_name", dicCode = "id") @Dict(dictTable = "sys_depart", dicText = "depart_name", dicCode = "id") @Schema(description = "落实部门id") private java.lang.String implDept; /**层深(最顶层节点层深为0)*/ - //@Excel(name = "层深(最顶层节点层深为0)", width = 15) + //@ExcelColumn(name = "层深(最顶层节点层深为0)", width = 15) @Schema(description = "层深(最顶层节点层深为0)") private java.lang.Integer treeDepth; /**同级数据排序号*/ - @Excel(name = "同级数据排序号", width = 15) + @Excel(name = "同级数据排序号", width = 15) + @ExcelColumn(name = "同级数据排序号", width = 15) @Schema(description = "同级数据排序号") private java.lang.Integer sortOrder; /**逻辑删除flag(0保留,1删除)*/ - @Excel(name = "逻辑删除flag(0保留,1删除)", width = 15) + //@Excel(name = "逻辑删除flag(0保留,1删除)", width = 15) + //@ExcelColumn(name = "逻辑删除flag(0保留,1删除)", width = 15) @Schema(description = "逻辑删除flag(0保留,1删除)") @TableLogic private java.lang.Integer delFlag; /**流程引擎状态字段*/ -// @Excel(name = "流程引擎状态字段", width = 15) +// @ExcelColumn(name = "流程引擎状态字段", width = 15) @Schema(description = "流程引擎状态字段") private java.lang.Integer bpmStatus; /**督办次数*/ - @Excel(name = "督办次数", width = 15) + @Excel(name = "督办次数", width = 15) + @ExcelColumn(name = "督办次数", width = 15) @Schema(description = "督办次数") private java.lang.Integer supervisionCount; /**dw领导userId字段*/ @Excel(name = "领导", width = 15, dictTable = "sys_user", dicText = "realname", dicCode = "username") + @ExcelColumn(name = "领导", width = 15, dictTable = "sys_user", dicText = "realname", dicCode = "username") @Dict(dictTable = "sys_user", dicText = "realname", dicCode = "username") @Schema(description = "领导userId字段") private java.lang.String sdwLeaderList; /**dw领导userId字段*/ @Excel(name = "是否需要sdw审批(请填入是/否)", width = 15,dicCode = "true_or_flase") + @ExcelColumn(name = "是否需要sdw审批(请填入是/否)", width = 15,dicCode = "true_or_flase") @Schema(description = "是否需要sdw审批") private java.lang.Integer needSdwApproval; /**dw领导userId字段*/ @Excel(name = "截止日期", width = 15) + @ExcelColumn(name = "截止日期", width = 15) @Schema(description = "截止日期") @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") // 核心:告诉Jackson怎么把String转成Date @DateTimeFormat(pattern="yyyy-MM-dd") @@ -188,12 +213,12 @@ public class BgXiSpeak implements Serializable { private DeptApproveDetailMap approveInfo; /**督办次数*/ -// @Excel(name = "发起类型", width = 15) +// @ExcelColumn(name = "发起类型", width = 15) @Schema(description = "0收集后直接发起,1收集后按照反馈时间周期性发起") private java.lang.Integer launchType; /**周期类型 1日 2周 3两周 4月 5季*/ -// @Excel(name = "周期类型", width = 15) +// @ExcelColumn(name = "周期类型", width = 15) @Schema(description = "周期类型 1日 2周 3两周 4月 5季") private java.lang.Integer intervalType;