坪山商城网站建设哪家效益快,门户网站意思,html素材库,企业vi设计调研构建灵活可扩展的FastExcel导入字段校验系统 一、背景与需求 在企业级应用开发中#xff0c;Excel文件导入是常见的功能需求。然而#xff0c;用户上传的Excel数据往往存在各种问题#xff0c;如格式错误、数据不规范、包含非法字符等。传统的手动校验方式效率低下且容易出…构建灵活可扩展的FastExcel导入字段校验系统一、背景与需求在企业级应用开发中Excel文件导入是常见的功能需求。然而用户上传的Excel数据往往存在各种问题如格式错误、数据不规范、包含非法字符等。传统的手动校验方式效率低下且容易出错因此我们需要一个统一、灵活、可扩展的Excel字段校验系统。核心需求灵活配置支持通过注解方式配置校验规则多种校验类型支持字符类型、长度、数字范围、正则表达式等多种校验精确控制能够精确控制哪些字符允许或禁止特别是Emoji表情的处理友好反馈提供详细的错误信息方便用户修改数据易用性简化使用方式减少重复代码可扩展性支持自定义校验器和分组校验行级数据校验支持行级数据校验可合并输出二、系统设计2.1 架构设计整个校验系统采用注解驱动的方式通过以下组件协同工作┌─────────────────────────────────────────────────────┐ │ Excel导入模块 │ ├─────────────────────────────────────────────────────┤ │ 监听器(Listener) → 校验工具类 → 结果收集 → 返回前端 │ └─────────────────────────────────────────────────────┘ │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ ExcelValid │ │ 校验枚举 │ │ 错误结果实体 │ │ 注解 │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘2.2 核心组件校验注解(ExcelValid)定义字段的校验规则字符类型枚举(CharacterValidationType)定义支持的字符类型校验工具类(ExcelValidationUtil)执行具体的校验逻辑结果实体封装校验结果和错误信息监听器(CustomExcelValidListener)集成到Excel解析流程中三、实现细节3.1 包引入dependency groupIdcn.idev.excel/groupId artifactIdfastexcel/artifactId version1.0.0/version /dependency dependency groupIdcom.vdurmont/groupId artifactIdemoji-java/artifactId version5.1.1/version /dependency3.2 字符类型枚举CharacterValidationType枚举定义了系统支持的所有字符类型每种类型都包含正则表达式和错误信息package com.fantaibao.enums; import lombok.Getter; import java.math.BigDecimal; import java.util.regex.Pattern; /** * 字符类型枚举 */ Getter public enum CharacterValidationType { /** * 中文字符包括中文标点 */ CHINESE(^[\\u4e00-\\u9fa5\\u3000-\\u303F\\uFF00-\\uFFEF\\u201C-\\u201F\\u3001-\\u301F]*$, 只能包含中文), /** * 中文字符和中文符号 */ CHINESE_WITH_SYMBOL(^[\\u4e00-\\u9fa5\\u3000-\\u303F\\uFF00-\\uFFEF]*$, 只能包含中文和中文符号), /** * 英文字母大小写 */ ENGLISH(^[A-Za-z]*$, 只能包含英文字母), /** * 英文字母和英文符号 */ ENGLISH_WITH_SYMBOL(^[A-Za-z\\p{P}\\p{S}]*$, 只能包含英文字母和英文符号), /** * 数字0-9 */ DIGIT(^[0-9]*$, 只能包含数字), /** * 正整数不包括0 */ POSITIVE_INTEGER(^[1-9]\\d*$, 只能为正整数), /** * 非负整数包括0 */ NON_NEGATIVE_INTEGER(^\\d$, 只能为非负整数), /** * 负整数 */ NEGATIVE_INTEGER(^-[1-9]\\d*$, 只能为负整数), /** * 整数包括正负整数和0 */ INTEGER(^-?\\d$, 只能为整数), /** * 正小数正浮点数 */ POSITIVE_DECIMAL(^[]?([0-9]*\\.)?[0-9]$, 只能为正小数), /** * 负小数负浮点数 */ NEGATIVE_DECIMAL(^-([0-9]*\\.)?[0-9]$, 只能为负小数), /** * 小数/浮点数包括正负 */ DECIMAL(^-?([0-9]*\\.)?[0-9]$, 只能为小数), /** * 英文符号常见标点符号 */ ENGLISH_SYMBOL(^[\\p{P}\\p{S}]*$, 只能包含英文符号), /** * 中文符号 */ CHINESE_SYMBOL(^[\\u3000-\\u303F\\uFF00-\\uFFEF]*$, 只能包含中文符号), /** * 特殊符号自定义的特殊字符 */ SPECIAL_SYMBOL(^[~!#$%^*()_\\-\\[\\]{}|;:\,./?]*$, 只能包含特殊符号), /** * 字母和数字 */ ENGLISH_DIGIT(^[A-Za-z0-9]*$, 只能包含字母和数字), /** * 中文字母数字 */ CHINESE_ENGLISH_DIGIT(^[\\u4e00-\\u9fa5A-Za-z0-9]*$, 只能包含中文、字母和数字), /** * 字母、数字和英文符号 */ ENGLISH_DIGIT_SYMBOL(^[A-Za-z0-9\\p{P}\\p{S}]*$, 只能包含字母、数字和英文符号), NULL(null, ); /** * 正则表达式 */ private final String pattern; /** * 错误信息 */ private final String message; CharacterValidationType(String pattern, String message) { this.pattern pattern; this.message message; } /** * 校验字符串是否符合该字符类型 */ public boolean validate(String value) { if (value null || value.isEmpty()) { // 空值不校验如果需要校验空值请配合ExcelValid的required属性 return false; } return !Pattern.matches(pattern, value); } /** * 校验是否为数字整数或小数 - 修正版 * 修正原方法逻辑反了应该返回true表示是数字 */ public static boolean isNumeric(String value) { if (value null || value.isEmpty()) { return false; } try { new BigDecimal(value); return true; } catch (NumberFormatException e) { return false; } } /** * 校验小数位数 * param value 要校验的值 * param decimalPlaces 要求的小数位数 * return 是否符合要求 */ public static boolean validateDecimalPlaces(String value, int decimalPlaces) { if (value null || value.isEmpty() || !isNumeric(value)) { return true; } BigDecimal decimal new BigDecimal(value); int scale decimal.scale(); // 如果是整数scale为0 return scale decimalPlaces; } /** * 校验数字范围 * param value 要校验的值 * param min 最小值可为null * param max 最大值可为null * return 是否在范围内 */ public static boolean validateNumberRange(String value, BigDecimal min, BigDecimal max) { if (value null || value.isEmpty() || !isNumeric(value)) { return true; } BigDecimal decimal new BigDecimal(value); if (min ! null decimal.compareTo(min) 0) { return true; } return max ! null decimal.compareTo(max) 0; } }设计要点使用正则表达式定义字符类型范围为MIXED类型特殊处理只排除Emoji而不限制其他字符提供静态方法支持数字范围、小数位数等高级校验3.3 校验注解ExcelValid注解是系统的核心配置接口支持丰富的校验选项package com.fantaibao.annotation; import java.lang.annotation.*; /** * Excel字段校验注解 * author 石头 */ Target(ElementType.FIELD) Retention(RetentionPolicy.RUNTIME) Documented public interface ExcelValid { /** * 是否跳过校验 * 默认false表示进行校验true表示跳过所有校验 */ boolean skip() default false; /** * 是否必填 */ boolean required() default false; /** * 必填时的错误提示信息 */ String requiredMessage() default 不能为空; /** * 最小长度 */ int minLength() default 0; /** * 最大长度 */ int maxLength() default Integer.MAX_VALUE; /** * 长度校验失败时的错误信息 */ String lengthMessage() default 长度不符合要求; /** * 自定义正则表达式优先级高于allowedTypes */ String regex() default ; /** * 自定义正则表达式校验失败时的错误信息 */ String regexMessage() default 格式不正确; /** * 是否为数字整数或小数 */ boolean numeric() default false; /** * 数字校验失败时的错误信息 */ String numericMessage() default 必须为数字; /** * 是否为整数 */ boolean integer() default false; /** * 整数校验失败时的错误信息 */ String integerMessage() default 必须为整数; /** * 是否为正整数大于0 */ boolean positiveInteger() default false; /** * 正整数校验失败时的错误信息 */ String positiveIntegerMessage() default 必须为正整数; /** * 是否为非负整数大于等于0 */ boolean nonNegativeInteger() default false; /** * 非负整数校验失败时的错误信息 */ String nonNegativeIntegerMessage() default 必须为非负整数; /** * 是否为负整数 */ boolean negativeInteger() default false; /** * 负整数校验失败时的错误信息 */ String negativeIntegerMessage() default 必须为负整数; /** * 是否为小数 */ boolean decimal() default false; /** * 小数位数限制仅当decimaltrue时有效-1表示不限制 */ int decimalPlaces() default -1; /** * 小数位数校验失败时的错误信息 */ String decimalMessage() default 小数位数不符合要求; /** * 最小值对于数字 */ String minValue() default ; /** * 最大值对于数字 */ String maxValue() default ; /** * 数值范围校验失败时的错误信息 */ String rangeMessage() default 数值不在允许范围内; /** * 是否包含Emoji */ boolean allowEmoji() default false; /** * Emoji校验失败时的错误信息 */ String emojiMessage() default 不能包含Emoji表情; }设计要点默认值设计合理简化使用支持多种校验类型可组合使用提供详细的错误信息配置3.4 校验工具类ExcelValidationUtil是系统的核心执行引擎负责解析注解并执行校验package com.fantaibao.utils.excelUtil; import cn.hutool.core.collection.CollUtil; import cn.idev.excel.annotation.ExcelProperty; import com.fantaibao.annotation.ExcelValid; import com.fantaibao.enums.CharacterValidationType; import com.fantaibao.vo.ExcelValidationBaseResultVo; import com.fantaibao.vo.ExcelValidationErrorVo; import com.vdurmont.emoji.EmojiParser; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.lang.reflect.Field; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.regex.Pattern; /** * Excel校验工具类 - 返回校验结果不抛出异常 */ Slf4j public class ExcelValidationUtil { /** * 校验单个对象的字段 * param obj 要校验的对象 * param rowIndex 行索引0-based * return 错误列表如果校验通过返回空列表 */ public static ListExcelValidationErrorVo validate(Object obj, int rowIndex) { ListExcelValidationErrorVo errors new ArrayList(); if (Objects.isNull(obj)) { return errors; } Class? clazz obj.getClass(); Field[] fields clazz.getDeclaredFields(); for (Field field : fields) { ExcelValid excelValid field.getAnnotation(ExcelValid.class); if (excelValid null) { continue; } // 如果设置了skiptrue则跳过该字段的所有校验 if (excelValid.skip()) { continue; } field.setAccessible(true); try { String value (String) field.get(obj); String fieldName getFieldDisplayName(field); // 执行校验 ListExcelValidationErrorVo fieldErrors doValidate(value, excelValid, fieldName, rowIndex); if (!fieldErrors.isEmpty()) { errors.addAll(fieldErrors); } } catch (IllegalAccessException e) { log.error(反射获取字段值失败, e); errors.add(ExcelValidationErrorVo.builder() // Excel行号从1开始 .rowNumber(rowIndex 1) .fieldName(field.getName()) .errorMessage(字段访问失败) .skipRow(false) .build()); } catch (ClassCastException e) { // 如果不是String类型跳过校验或根据需要处理 log.debug(字段{}不是String类型跳过校验, field.getName()); } } return errors; } /** * 获取字段的显示名称优先使用ExcelProperty注解的值 */ private static String getFieldDisplayName(Field field) { ExcelProperty excelProperty field.getAnnotation(ExcelProperty.class); if (excelProperty ! null excelProperty.value().length 0) { return excelProperty.value()[0]; } return field.getName(); } /** * 执行具体的校验逻辑 */ private static ListExcelValidationErrorVo doValidate(String value, ExcelValid excelValid, String fieldName, int rowIndex) { ListExcelValidationErrorVo errors new ArrayList(); // 1. 必填校验 if (excelValid.required() !StringUtils.hasText(value)) { errors.add(ExcelValidationErrorVo.builder() .rowNumber(rowIndex 1) .fieldName(fieldName) .originalValue(value) .errorMessage(excelValid.requiredMessage()) // 如果必填字段为空跳过该行 .skipRow(excelValid.required()) .build()); // 如果必填为空直接返回 return errors; } // 如果没有值且不是必填直接返回不进行后续校验 if (!StringUtils.hasText(value)) { return errors; } // 2. 长度校验 int length value.length(); if (length excelValid.minLength() || length excelValid.maxLength()) { errors.add(ExcelValidationErrorVo.builder() .rowNumber(rowIndex 1) .fieldName(fieldName) .originalValue(value) .errorMessage(String.format(%s要求长度%d-%d实际长度%d, excelValid.lengthMessage(), excelValid.minLength(), excelValid.maxLength(), length)) .skipRow(false) .build()); } // 3. 自定义正则校验优先级最高 if (StringUtils.hasText(excelValid.regex())) { if (!Pattern.matches(excelValid.regex(), value)) { errors.add(ExcelValidationErrorVo.builder() .rowNumber(rowIndex 1) .fieldName(fieldName).originalValue(value) .errorMessage(excelValid.regexMessage()) .skipRow(false) .build()); // 正则校验失败直接返回 return errors; } } // 4. 数字类型校验 if (excelValid.numeric() CharacterValidationType.isNumeric(value)) { errors.add(ExcelValidationErrorVo.builder() .rowNumber(rowIndex 1) .fieldName(fieldName) .originalValue(value) .errorMessage(excelValid.numericMessage()) .skipRow(false) .build()); } // 5. 整数类型校验 if (excelValid.integer() CharacterValidationType.INTEGER.validate(value)) { errors.add(ExcelValidationErrorVo.builder() .rowNumber(rowIndex 1) .fieldName(fieldName) .originalValue(value) .errorMessage(excelValid.integerMessage()) .skipRow(false) .build()); } // 6. 正整数校验新增 if (excelValid.positiveInteger() CharacterValidationType.POSITIVE_INTEGER.validate(value)) { errors.add(ExcelValidationErrorVo.builder() .rowNumber(rowIndex 1) .fieldName(fieldName) .originalValue(value) .errorMessage(excelValid.positiveIntegerMessage()) .skipRow(false) .build()); } // 7. 非负整数校验新增 if (excelValid.nonNegativeInteger() CharacterValidationType.NON_NEGATIVE_INTEGER.validate(value)) { errors.add(ExcelValidationErrorVo.builder() .rowNumber(rowIndex 1) .fieldName(fieldName) .originalValue(value) .errorMessage(excelValid.nonNegativeIntegerMessage()) .skipRow(false) .build()); } // 8. 负整数校验新增 if (excelValid.negativeInteger() CharacterValidationType.NEGATIVE_INTEGER.validate(value)) { errors.add(ExcelValidationErrorVo.builder() .rowNumber(rowIndex 1) .fieldName(fieldName) .originalValue(value) .errorMessage(excelValid.negativeIntegerMessage()) .skipRow(false) .build()); } // 9. 小数类型校验 if (excelValid.decimal() CharacterValidationType.DECIMAL.validate(value)) { errors.add(ExcelValidationErrorVo.builder() .rowNumber(rowIndex 1) .fieldName(fieldName) .originalValue(value) .errorMessage(excelValid.decimalMessage()) .skipRow(false) .build()); } else if (excelValid.decimal() excelValid.decimalPlaces() 0) { if (CharacterValidationType.validateDecimalPlaces(value, excelValid.decimalPlaces())) { errors.add(ExcelValidationErrorVo.builder() .rowNumber(rowIndex 1) .fieldName(fieldName) .originalValue(value) .errorMessage(String.format(%s最多允许%d位小数, excelValid.decimalMessage(), excelValid.decimalPlaces())) .skipRow(false) .build()); } } // 10. 数值范围校验 if (StringUtils.hasText(excelValid.minValue()) || StringUtils.hasText(excelValid.maxValue())) { BigDecimal min null; BigDecimal max null; try { if (StringUtils.hasText(excelValid.minValue())) { min new BigDecimal(excelValid.minValue()); } if (StringUtils.hasText(excelValid.maxValue())) { max new BigDecimal(excelValid.maxValue()); } if (CharacterValidationType.validateNumberRange(value, min, max)) { String rangeDesc ; if (min ! null max ! null) { rangeDesc String.format(应在%s-%s之间, min, max); } else if (min ! null) { rangeDesc String.format(应大于等于%s, min); } else if (max ! null) { rangeDesc String.format(应小于等于%s, max); } errors.add(ExcelValidationErrorVo.builder() .rowNumber(rowIndex 1) .fieldName(fieldName) .originalValue(value) .errorMessage(String.format(%s%s, excelValid.rangeMessage(), rangeDesc)) .skipRow(false) .build()); } } catch (NumberFormatException e) { log.warn(minValue或maxValue格式不正确, e); } } // 11. Emoji校验 if (excelValid.allowEmoji() isIncludedEmoji(value)) { errors.add(ExcelValidationErrorVo.builder() .rowNumber(rowIndex 1) .fieldName(fieldName) .originalValue(value) .errorMessage(excelValid.emojiMessage()) .skipRow(false) .build()); } return errors; } /** * 判断字符串是否包含Emoji字符 * * param input 待验证的字符串 * return 如果字符串包含Emoji字符则返回 true否则返回 false */ public static boolean isIncludedEmoji(String input) { if (input null || input.isEmpty()) { return false; } // 提取所有Emoji ListString emojis EmojiParser.extractEmojis(input); return CollUtil.isNotEmpty(emojis); } /** * 批量校验 * param dataList 数据列表 * param T 数据类型 * return 所有校验错误的列表 */ public static T ListExcelValidationErrorVo validateBatch(ListT dataList) { ListExcelValidationErrorVo allErrors new ArrayList(); if (dataList null || dataList.isEmpty()) { return allErrors; } for (int i 0; i dataList.size(); i) { ListExcelValidationErrorVo errors validate(dataList.get(i), i); if (!errors.isEmpty()) { allErrors.addAll(errors); } } return allErrors; } /** * 批量校验并分类数据 * param dataList 原始数据列表 * return 包含成功数据和错误信息的复合结果 */ public static T ExcelValidationBaseResultVoT validateAndClassify(ListT dataList) { ExcelValidationBaseResultVoT result new ExcelValidationBaseResultVo(); result.setTotalCount(dataList.size()); for (int i 0; i dataList.size(); i) { T data dataList.get(i); ListExcelValidationErrorVo errors validate(data, i); if (errors.isEmpty()) { result.getSuccessData().add(data); result.setSuccessCount(result.getSuccessCount() 1); } else { // 判断是否需要跳过该行比如必填字段为空 boolean skipRow errors.stream().anyMatch(ExcelValidationErrorVo::getSkipRow); if (!skipRow) { // 如果不跳过可能只记录第一个错误 result.getExcelErrorList().add(errors.get(0)); } else { // 如果跳过添加错误信息 result.getExcelErrorList().addAll(errors); } } } // 检查是否全部成功 result.setAllSuccess(result.getExcelErrorList().isEmpty()); return result; } }算法亮点校验顺序优化按照优先级执行校验失败后及时返回Emoji精确检测使用精确的码点检测避免将常见符号误判为Emoji错误收集支持一行多错但默认只返回第一个错误3.5 结果实体系统提供两种结果实体支持灵活的返回方式package com.fantaibao.vo; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; Data Builder NoArgsConstructor AllArgsConstructor public class ExcelValidationBaseResultVoT { /** * 成功导入条数 */ private Integer successCount; /** * 成功数据列表 */ private ListT successData; /** * 异常导入数据集合 */ private ListExcelValidationErrorVo excelErrorList; /** * 是否全部成功 */ private boolean allSuccess; /** * 总行数 */ private Integer totalCount; }package com.fantaibao.vo; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; Data Builder AllArgsConstructor NoArgsConstructor public class ExcelValidationErrorVo { /** * 行号Excel行号从1开始 */ private Integer rowNumber; /** * 字段名称 */ private String fieldName; /** * 原始值 */ private String originalValue; /** * 错误信息 */ private String errorMessage; /** * 是否跳过该行当一行有多个错误时可能只需要一个错误信息 */ Builder.Default private Boolean skipRow false; }3.6 Excel监听器CustomExcelValidListener将校验系统集成到Excel解析流程中package com.fantaibao.listener; import cn.hutool.core.collection.CollUtil; import cn.idev.excel.annotation.ExcelProperty; import cn.idev.excel.context.AnalysisContext; import cn.idev.excel.metadata.data.ReadCellData; import cn.idev.excel.read.listener.ReadListener; import cn.idev.excel.util.StringUtils; import com.fantaibao.utils.excelUtil.ExcelValidationUtil; import com.fantaibao.vo.ExcelValidationBaseResultVo; import com.fantaibao.vo.ExcelValidationErrorVo; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; Slf4j Getter RequiredArgsConstructor public class CustomExcelValidListenerT implements ReadListenerT { /** * 存储所有解析的数据包括成功和失败的 */ private ListT allDataList; /** * 存储成功的数据 */ private ListT successDataList; /** * 存储校验错误 */ private ListExcelValidationErrorVo errorList; /** * 最终返回结果 */ private ExcelValidationBaseResultVoT resultVo; /** * 表头字段列表 */ private ListString fields; /** * 是否启用字段校验默认启用 */ private boolean enableValidation true; /** * 是否立即校验在invoke方法中校验 */ private boolean validateImmediately true; /** * 是否严格模式表头校验失败则终止 */ private boolean strictMode true; /** * 默认构造方法 */ public CustomExcelValidListener(ClassT clazz) { this(clazz, true, true, true); } /** * 构造方法 */ public CustomExcelValidListener(ClassT clazz, boolean enableValidation, boolean validateImmediately, boolean strictMode) { this.allDataList new ArrayList(); this.successDataList new ArrayList(); this.errorList new ArrayList(); this.resultVo new ExcelValidationBaseResultVo(); this.fields getFields(clazz); this.enableValidation enableValidation; this.validateImmediately validateImmediately; this.strictMode strictMode; } /** * 根据class通过反射获取字段上ExcelProperty注解的值 */ private ListString getFields(ClassT clazz) { ListString fields CollUtil.newArrayList(); Field[] declaredFields clazz.getDeclaredFields(); for (Field field : declaredFields) { ExcelProperty annotation field.getAnnotation(ExcelProperty.class); if (annotation ! null) { String[] value annotation.value(); fields.addAll(Arrays.asList(value)); } } return fields; } Override public void invokeHead(MapInteger, ReadCellData? headMap, AnalysisContext context) { // 在这里检查表头是否正确 ListString headerErrors new ArrayList(); for (Map.EntryInteger, ReadCellData? entry : headMap.entrySet()) { String headerValue entry.getValue().getStringValue(); if (!isValidHeader(headerValue)) { String error String.format(表头%s不匹配, headerValue); headerErrors.add(error); } } // 检查是否所有必要的表头都被匹配 if (!fields.isEmpty()) { String error String.format(缺少必要的表头: %s, String.join(, , fields)); headerErrors.add(error); } // 处理表头错误 if (!headerErrors.isEmpty()) { String errorMessage String.join(; , headerErrors); if (strictMode) { throw new RuntimeException(模板错误: errorMessage); } else { // 非严格模式下记录错误但不中断 log.warn(表头校验失败: {}, errorMessage); ExcelValidationErrorVo errorVo ExcelValidationErrorVo.builder() // 表头错误行号为0 .rowNumber(0) .fieldName(表头) .errorMessage(模板错误: errorMessage) .build(); errorList.add(errorVo); } } } Override public void invoke(T data, AnalysisContext context) { int rowIndex context.readRowHolder().getRowIndex(); allDataList.add(data); if (enableValidation validateImmediately) { // 立即校验当前行 ListExcelValidationErrorVo errors ExcelValidationUtil.validate(data, rowIndex); if (errors.isEmpty()) { successDataList.add(data); } else { // 判断是否需要跳过该行比如必填字段为空 boolean skipRow errors.stream().anyMatch(ExcelValidationErrorVo::getSkipRow); if (!skipRow) { // 如果不跳过可能只记录第一个错误 errorList.add(errors.get(0)); } else { // 如果跳过添加所有错误信息 errorList.addAll(errors); } } } else { // 不进行立即校验先全部加入成功列表后续统一校验 successDataList.add(data); } } Override public void doAfterAllAnalysed(AnalysisContext context) { // 如果未启用立即校验则在所有解析完成后批量校验 if (enableValidation !validateImmediately) { // 清空之前的成功列表重新校验 successDataList.clear(); errorList.clear(); for (int i 0; i allDataList.size(); i) { T data allDataList.get(i); ListExcelValidationErrorVo errors ExcelValidationUtil.validate(data, i); if (errors.isEmpty()) { successDataList.add(data); } else { // 判断是否需要跳过该行 boolean skipRow errors.stream().anyMatch(ExcelValidationErrorVo::getSkipRow); if (!skipRow) { // 如果不跳过只记录第一个错误 errorList.add(errors.get(0)); } else { // 如果跳过添加所有错误信息 errorList.addAll(errors); } } } } // 构建返回结果 resultVo.setSuccessData(new ArrayList(successDataList)); resultVo.setSuccessCount(successDataList.size()); resultVo.setExcelErrorList(new ArrayList(errorList)); resultVo.setTotalCount(allDataList.size()); resultVo.setAllSuccess(errorList.isEmpty()); log.info(Excel解析完成sheet名: {}总行数: {}成功: {}失败: {}, context.readSheetHolder().getSheetName(), resultVo.getTotalCount(), resultVo.getSuccessCount(), errorList.size()); } /** * 获取最终结果 */ public ExcelValidationBaseResultVoT getResult() { return resultVo; } private boolean isValidHeader(String header) { if (StringUtils.isBlank(header)) { return true; } // 移除空白字符后比较 String trimmedHeader header.trim(); for (String field : fields) { if (field.equals(trimmedHeader)) { fields.remove(field); return true; } } return false; } }四、使用示例4.1 实体类配置package com.fantaibao.module.vo.appDish; import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelProperty; import cn.idev.excel.annotation.write.style.ColumnWidth; import com.fantaibao.annotation.ExcelValid; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; Data Builder ColumnWidth(25) AllArgsConstructor NoArgsConstructor ExcelIgnoreUnannotated public class DishAppRankingListVo1 { /** * 菜品名称 */ ColumnWidth(30) ExcelProperty(菜品名称) // 不需要校验 ExcelValid(skip true) private String menuName; /** * 理想销量排名 */ ColumnWidth(20) ExcelProperty(理想销量排名) ExcelValid( minValue 1, maxValue 9999, positiveInteger true ) private String salesRanking; /** * 理想实收排名 */ ColumnWidth(20) ExcelProperty(理想实收排名) ExcelValid( minValue 1, maxValue 9999, positiveInteger true ) private String turnoverRanking; /** * 理想毛利额排名 */ ColumnWidth(15) ExcelProperty(理想毛利额排名) ExcelValid( minValue 1, maxValue 9999, positiveInteger true ) private String profitRanking; }4.2 业务层使用Override public ExcelValidationBaseResultVoDishAppRankingListVo1 importExcelRankingTest(String fileUrl) { RLock lock redissonClient.getLock(String.format(DATA_ANALYSIS_STANDARD_RANKING_LOCK_KEY, UserProvider.getUser().getTenantId())); Assert.isFalse(lock.isLocked(), 当前租户标准菜品排名配置正在变更中请稍后再试); try { if (!lock.tryLock(60, -1, TimeUnit.SECONDS)) { throw new RuntimeException(无法获取分布式锁请稍后再试); } try { // 创建监听器 CustomExcelValidListenerDishAppRankingListVo1 listener new CustomExcelValidListener(DishAppRankingListVo1.class, true, true, true); // 读取Excel文件并填充菜品列表 EasyExcel.read(new URL(fileUrl).openStream(), DishAppRankingListVo1.class, listener) .sheet(1) .doRead(); // 获取结果 ExcelValidationBaseResultVoDishAppRankingListVo1 result listener.getResult(); // 如果存在成功数据可以进一步处理如保存到数据库 if (result.getSuccessCount() 0) { } // 记录错误信息 if (!result.isAllSuccess()) { logImportErrors(result); } return result; } catch (IOException e) { log.error(读取文件失败, e); return ExcelValidationBaseResultVo.DishAppRankingListVo1builder() .successCount(0) .allSuccess(false) .build(); } catch (Exception e) { // 处理表头错误等严重异常 log.error(Excel导入失败, e); return ExcelValidationBaseResultVo.DishAppRankingListVo1builder() .successCount(0) .allSuccess(false) .build(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }五、关键技术点5.1 校验优先级设计系统按照以下优先级执行校验确保校验效率和准确性必填校验失败则立即返回不进行后续校验长度校验基础校验失败不立即返回自定义正则优先级最高失败则立即返回数字类型校验按类型分别校验范围校验依赖于数字类型校验特殊字符校验检查特殊符号和Emoji字符类型校验最后执行的综合校验5.2 性能优化正则表达式预编译避免重复编译正则表达式校验短路失败后及时返回减少不必要的校验批量处理支持批量校验减少反射开销对象复用复用校验对象避免重复创建5.3 结果演示