当前位置: 首页 > news >正文

基于Easy Excel的表头校验工具

ExcelHeaderValidator

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.event.AnalysisEventListener;
import lombok.extern.slf4j.Slf4j;import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;/*** Excel表头校验工具类* 用于校验上传的Excel文件表头是否与DTO中定义的表头一致** @author Ryan*/
@Slf4j
public class ExcelHeaderValidator {/*** 创建一个带表头校验的数据监听器** @param modelClass       DTO模型类* @param templateTypeName 模板类型名称(用于错误提示)* @param dataConsumer     数据消费者,处理每一行数据* @param <T>              DTO类型* @return 带表头校验的监听器*/public static <T> AnalysisEventListener<T> createValidationListener(Class<T> modelClass,String templateTypeName,Consumer<T> dataConsumer) {Map<Integer, String> expectedHeaders = getExpectedHeaders(modelClass);return new ValidationHeaderListener<>(expectedHeaders, templateTypeName, dataConsumer);}/*** 从DTO类中提取期望的表头** @param modelClass DTO模型类* @return 表头索引与表头名称的映射*/public static Map<Integer, String> getExpectedHeaders(Class<?> modelClass) {// 使用字段在类中的声明顺序作为索引,而不是annotation.index()// 因为当注解未指定index时,默认值(-1或Integer.MAX_VALUE)会导致重复key异常List<Field> fields = Arrays.stream(modelClass.getDeclaredFields()).filter(field -> field.isAnnotationPresent(ExcelProperty.class)).collect(Collectors.toList());Map<Integer, String> result = new java.util.LinkedHashMap<>();for (int i = 0; i < fields.size(); i++) {Field field = fields.get(i);ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);String headerName = annotation.value().length > 0 ? annotation.value()[0] : field.getName();result.put(i, headerName);}return result;}/*** 按顺序获取对象中每个字段的值** @param data 数据对象* @param <T>  对象类型* @return 字段索引与字段值的映射(值转换为String)*/public static <T> Map<Integer, String> getFieldValues(T data) {if (data == null) {return new java.util.LinkedHashMap<>();}List<Field> fields = Arrays.stream(data.getClass().getDeclaredFields()).collect(Collectors.toList());Map<Integer, String> result = new java.util.LinkedHashMap<>();for (int i = 0; i < fields.size(); i++) {Field field = fields.get(i);field.setAccessible(true);try {Object value = field.get(data);result.put(i, value != null ? value.toString() : "");} catch (IllegalAccessException e) {log.error("获取字段值失败: {}", field.getName(), e);result.put(i, "");}}return result;}}

ValidationHeaderListener

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.util.ConverterUtils;
import com.szyk.car.common.web.config.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;/*** <p> 导入表头校验监听器</p>* <p> Create Time: 2026/2/4 </p>** @author Ryan*/
@Slf4j
public class ValidationHeaderListener<T> extends AnalysisEventListener<T> {private final Map<Integer, String> expectedHeaders;private final String templateTypeName;private final Consumer<T> dataConsumer;private boolean headerValidated = false;public ValidationHeaderListener(Map<Integer, String> expectedHeaders, String templateTypeName, Consumer<T> dataConsumer) {this.expectedHeaders = expectedHeaders;this.templateTypeName = templateTypeName;this.dataConsumer = dataConsumer;}@Overridepublic void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {Map<Integer, String> convertHeadMap = ConverterUtils.convertToStringMap(headMap, context);// 保存实际表头,供 invoke 方法中的兜底校验使用if (!headerValidated) {if (ObjectUtil.isEmpty(convertHeadMap)) {throw new BusinessException("Excel文件表头为空");}validateHeaders(convertHeadMap, expectedHeaders, templateTypeName);headerValidated = true;}}@Overridepublic void invoke(T data, AnalysisContext context) {// 双重保障:确保在处理第一行数据前表头已校验if (!headerValidated) {// 通过readRowHolder获取当前行索引,第一行数据(除去表头)的索引为1Integer currentRowIndex = context.readRowHolder().getRowIndex();if (currentRowIndex != null && currentRowIndex == 1) {// 从data的类定义中获取期望表头Map<Integer, String> expectedHeadersFromClass = ExcelHeaderValidator.getFieldValues(data);// 获取实际表头进行校验if (ObjectUtil.isEmpty(expectedHeadersFromClass)) {throw new BusinessException("Excel文件表头为空");}validateHeaders(expectedHeadersFromClass, expectedHeaders, templateTypeName);headerValidated = true;return;}}dataConsumer.accept(data);}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {}/*** 校验表头是否匹配** @param actualHeaders    实际表头* @param expectedHeaders  期望表头* @param templateTypeName 模板类型名称* @throws IllegalArgumentException 表头不匹配时抛出异常*/private static void validateHeaders(Map<Integer, String> actualHeaders,Map<Integer, String> expectedHeaders,String templateTypeName) {List<String> errors = new ArrayList<>();// 检查缺少的列for (Map.Entry<Integer, String> entry : expectedHeaders.entrySet()) {String actualHeader = actualHeaders.get(entry.getKey());if (actualHeader == null) {errors.add(String.format("缺少第%d列【%s】", entry.getKey() + 1, entry.getValue()));} else if (!actualHeader.equals(entry.getValue())) {errors.add(String.format("第%d列应为【%s】,实际为【%s】",entry.getKey() + 1, entry.getValue(), actualHeader));}}// 检查多余的列for (Map.Entry<Integer, String> entry : actualHeaders.entrySet()) {if ("异常报文".equals(entry.getValue())){continue;}String expectedHeader = expectedHeaders.get(entry.getKey());if (expectedHeader == null) {errors.add(String.format("存在多余的第%d列【%s】", entry.getKey() + 1, entry.getValue()));}}if (!errors.isEmpty()) {String errorMsg = String.format("%s模板不正确:%s", templateTypeName, String.join(";", errors));log.error(errorMsg);throw new BusinessException(String.format("%s模板不正确!", templateTypeName));}}
}
http://www.jsqmd.com/news/345701/

相关文章:

  • 中国钴颗粒生产厂家推荐及综合分析(2026年2月) - 深度智识库
  • ESP32-P4NRW32:开创未来的高性能双核无线MCU
  • 永辉超市购物卡闲置不用?可可收助力快速盘活资产 - 可可收
  • AI与云原生技术在下一代LS-DYNA仿真资源管理的应用前瞻
  • 2026跨境电商孵化园区指南/跨境电商孵化园区横向对比+对比评测,5家优质园区干货推荐 - 品牌2025
  • 计算机毕业设计之springboot成都旅游网
  • 梳理2026年好奇游乐设备厂家口碑,十大排名出炉 - myqiye
  • kotlin和compose中使用by
  • 2026年国内体脂秤权威推荐榜单及选购指南 - 一搜百应
  • Compose 中的状态可变性体系
  • 2026深圳创业办公楼出租+租赁推荐 深圳企业孵化园区租赁优选攻略 - 品牌2025
  • 2026年【离婚财产分割律师】联系电话推荐:高效联系与避坑指南 - 十大品牌推荐
  • 2026年盘点玻璃反应釜知名品牌,南通三晶性价比高口碑好推荐 - mypinpai
  • 141.环形链表
  • 2026热量减肥法APP专业评测与推荐指南 - 一搜百应
  • 2026年库尔勒靠谱的学中餐培训学校推荐,新疆新东方烹饪学校表现佳 - 工业品牌热点
  • 大模型开发必备指南:8种主流Agents框架+MCP集成全解析,建议收藏学习
  • 2026夹套玻璃反应釜生产商推荐:十大知名品牌权威盘点 - 品牌推荐大师1
  • 2026年中国离婚财产律师联系电话推荐:专业律师联系名录 - 十大品牌推荐
  • 2026年临沂采光瓦厂家最新推荐榜:玻璃钢采光瓦、聚脂纤维采光瓦、金属收边型采光瓦、透明采光瓦、PC透明采光瓦、聚焦产品品质与服务竞争力深度剖析 - 海棠依旧大
  • 邮件营销如何提高进箱率?别再只盯着“工具” - U-Mail邮件系统
  • 2026年别墅电梯大型厂家排名,意墅电梯靠谱之选,口碑超棒 - 工业推荐榜
  • 计算机毕业设计springboot长途汽车票务管理系统 基于 SpringBoot 的省际客运联网售票平台 融合 SpringBoot 的长途汽车智慧票务运营系统
  • 2026年卡西欧品牌代理推荐,细聊卡西欧代理服务怎么联系 - 工业品牌热点
  • 2026年天津口碑好的五金店排名,未来之家五金商铺靠谱吗 - 工业设备
  • 计算机毕业设计springboot壁纸网站 基于SpringBoot的高清壁纸资源分享平台的设计与实现 SpringBoot+MySQL构建的个性化桌面壁纸管理系统
  • 计算机毕业设计hadoop+spark+hive租房推荐系统 租房可视化 大数据毕业设计(源码 +LW文档+PPT+讲解)
  • 探寻稳定的螺母直销厂家,靠谱的有哪些? - mypinpai
  • 开题报告 springboot和vue毕业生信息收集系统
  • (云剪贴板)个人主页 2026/2/5