这是进公司接受的第二个项目,在我将项目改造成springBoot项目时竟然发现同事写的导入方法竟然高达上前行,而且其他导入方法行数都是大几百行往上。难怪我下班后他还经常加班 = . =。这个整个的流程其实就是:
1、扫描@ExcelChecker 注解标记的类。
2、注册到ExcelCkeckerFactory中。
3、ExcelUtils拿到检测类list,检测并将通过的加入到返回list
下面是人员信息的导入
这个人员信息的导入方法高达1100多行,还有一些其他导入方法差不多类似。方法中含有大量if else以及Cell取值没有封装,导致大量重复代码的出现,后期十分难以维护,我看到这些代码头都大了。我不能让这些代码出现在我的项目中(我经过领导同意将这个项目重新搭建,改为了springboot单体服务项目),所以对这些令人头痛的代码进行了改造,下面是我改造的步骤。
1.定义注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ExcelCheckerAutoConfigureRegistrar.class})
public @interface ExcelCheckerScan {
@AliasFor("basePackages") String[] value() default {};
@AliasFor("value") String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcelChecker {
String prefix() default "";
String[] field();
int order();
}
1、@ExcelCheckerScan 注解用来扫描包下面包含@ExcelChecker 注解的类,上面的@Import注解功能就是和Spring XML 里面 的 一样. @Import注解是用来导入配置类或者一些需要前置加载的类。
2、@ExcelChecker是用来标注检测类。
定义 ImportBeanDefinitionRegistrar动态注册bean
public class ExcelCheckerAutoConfigureRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
private ResourceLoader resourceLoader;
private Environment environment;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
MetaBeanDefinitionScanner scanner =
new MetaBeanDefinitionScanner(registry, true,this.environment, this.resourceLoader);
Set<String> packagesToScan = this.getPackagesToScan(importingClassMetadata);
scanner.scan(packagesToScan.toArray(new String[]{}));
}
private static class MetaBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
public MetaBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment, ResourceLoader resourceLoader) {
super(registry, useDefaultFilters, environment, resourceLoader);
registerFilters();
}
protected void registerFilters() {
addIncludeFilter(new AnnotationTypeFilter(ExcelChecker.class));
}
}
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
AnnotationAttributes attributes =
AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(ExcelCheckerScan.class.getName()));
String[] basePackages = attributes.getStringArray("basePackages");
Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages));
for (Class clz : basePackageClasses) {
packagesToScan.add(ClassUtils.getPackageName(clz));
}
if (packagesToScan.isEmpty()) {
packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName()));
}
return packagesToScan;
}
}
简介
- ImportBeanDefinitionRegistrar类只能通过其他类@Import的方式来加载,通常是启动类或配置类。
- 使用@Import,如果括号中的类是ImportBeanDefinitionRegistrar的实现类,则会调用接口方法,将其中要注册的类注册成bean。
- 实现该接口的类拥有注册bean的能力。
@ExcelCheckerScan注解配合ExcelCheckerAutoConfigureRegistrar类会将所有包下包含@ExcelChecker 注解的类注册到Spring容器之中。
定义ExcelCkeckerFactory
@Component
public class ExcelCkeckerFactory implements ApplicationListener, ApplicationContextAware {
private ApplicationContext applicationContext;
//检测类MAP <属性名,检测类List> 因为同一个属性可以有多个检测
private Map<String, List<BaseExcelCellChecker>> map = new ConcurrentHashMap();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
//这里从Spring容器中取出所有包含@ExcelChecker的检测类。 上面的ExcelCheckerAutoConfigureRegistrar已经将所有检测类注册到容器中,这里直接取出来就好了。
Map<String,Object> beanWhithAnnotation = applicationContext.getBeansWithAnnotation(ExcelChecker.class);
TreeMap<Integer,List<BaseExcelCellChecker>> orderMap = new TreeMap<Integer,List<BaseExcelCellChecker>>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
beanWhithAnnotation.forEach((k,v)->{
/**只有BaseExcelCellChecker的子类才会加入到检测类Map之中,并且会根据@ExcelChecker
的order值进行排序,排序的作用是检测类存在检测循序,比如检测字段为空是第一个检测的。*/
if(BaseExcelCellChecker.class.isAssignableFrom(v.getClass())) {
Integer order = v.getClass().getDeclaredAnnotation(ExcelChecker.class).order();
if(orderMap.get(order) == null){
orderMap.put(order,new ArrayList<>());
}
orderMap.get(order).add((BaseExcelCellChecker) v);
}
});
orderMap.forEach((k,v)->{
v.forEach(excelCellChain->{
/**这里拿到排序后的Map后注册到当前工厂的检测类Map中,先拿到prefix前缀,因为可能不同实体类的相同字段
的检测方法不同,例如 A类的 a字段只需要判空,B类的 a字段可能需要判断数据库是否存在,所以通过前缀区分,
自定了前缀的 key值会变成 prefix:属性名 这种格式。*/
String prefix = excelCellChain.getClass().getDeclaredAnnotation(ExcelChecker.class).prefix();
for (String s : excelCellChain.getClass().getDeclaredAnnotation(ExcelChecker.class).field()) {
register((prefix.isEmpty()?prefix:prefix+":")+s,excelCellChain);
}
});
});
}
public void register(String field, BaseExcelCellChecker excelCellChain){
List<BaseExcelCellChecker> baseExcelCellCheckers = map.get(field);
if(baseExcelCellCheckers == null){
map.put(field,new ArrayList<>());
}
map.get(field).add(excelCellChain);
}
public List<BaseExcelCellChecker> getChceker(String field){
return map.get(field);
}
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
}
}
工厂会将上面的注册至Spring的ExcelChecker 类取出来以field为key以ExcelChecker 类为value注册到工厂类中定义的Map当中,在这个过程中会根据order值进行排序,order越大越先执行。排序的原因是有些字段需要判空以及判断数据库是否存在该字段,如果不进行排序的话可能会先执行数据库判断操作,但是如果字段为空的话,会导致判断空指针。Map的value定义为List的原因是同个字段可能存在多个判断。
BaseExcelCellChecker
@NoArgsConstructor
@AllArgsConstructor
public abstract class BaseExcelCellChecker {
private Map setMap = new LinkedHashMap(0);
private ThreadLocal<Map> local = new ThreadLocal<>();
protected void setLocalValue(String key,Object value){
if(local.get() == null){
local.set(new LinkedHashMap(0));
}
local.get().put(key,value);
}
//获取缓存值
protected Object getLocalValue(String key){
if(local.get() == null){
local.set(new LinkedHashMap(0));
return null;
}
return local.get().get(key);
}
//给对象设置值
public BaseExcelCellChecker setBeanValue(String field,Object xValue){
if(field != null && !field.isEmpty()){
setMap.put(field,xValue);
}
return this;
}
public Map getSetMap(){
return this.setMap;
}
public boolean check(MetaObject original, Object obj,String fieldName, Integer row, Integer col, StringBuffer sb){
return doCheck(original,obj,fieldName,row,col,sb);
}
//校验方法
protected abstract boolean doCheck(MetaObject original,Object obj, String fieldName, Integer row,Integer col,StringBuffer sb);
public abstract Object transfer(MetaObject original,Object xValue,String fieldName);
}
基础的检测类,只有继承这个类并且包含@ExcelChecker注解的类才会被注册到ExcelCkeckerFactory工厂的检测类Map当中。下面为多个检测类的其中两个。
NotNullChecker
@ExcelChecker(field = {"staffCode","staffName","sex","staffType","birthday","mobile","salaryType","positionName",
"attendanceCycle","departmentName","entryCompanyTime","idCard","socreTitle"},order = 999)
public class NotNullChecker extends BaseExcelCellChecker {
/**
original:这个 MetaObject 是 mybatis的工具类 是通过反射将对象的所有信息封装成属性,并且提供取值,
设置值等一系列方法。
obj:读取的cell值
fieldName:属性名
row:读取的Cell行
col:读取的Cell列
sb:错误信息对象
*/
@Override
protected boolean doCheck(MetaObject original, Object obj, String fieldName,Integer row, Integer col, StringBuffer sb){
if(Objects.isNull(obj) || obj.toString().isEmpty()){
sb.append("第"+row+"行,第"+(col)+"列不能为空!");
return false;
}
return true;
}
/**
这个方法是用来转换的,比如读取的cell值为是 存到数据库时 1代表是 就在这个方法进行转换,
无需转换的话传来什么直接返回就好了。
*/
@Override
public Object transfer(MetaObject original,Object xValue,String fieldName) {
return xValue;
}
}
DepartmentChecker
@ExcelChecker(field = {"departmentName","sectionName","attachName","groupName"},order = 1)
public class DepartmentChecker extends BaseExcelCellChecker {
@Autowired
HrDepartmentDao hrDepartmentDao;
Map<String,String> fcMap = new HashMap(){{
put("departmentName","sectionName");
put("sectionName","attachName");
put("attachName","groupName");
}};
Map<String,String> cMap = new HashMap(){{
put("departmentName","departmentId");
put("sectionName","sectionId");
put("attachName","attachId");
put("groupName","groupId");
}};
@Override
protected boolean doCheck(MetaObject original, Object obj,String fieldName, Integer row,
Integer col, StringBuffer sb){
HrDepartment localValue = (HrDepartment)getLocalValue(fieldName+row);
if(Objects.nonNull(obj) && obj!="") {
JSONObject jsonObject = new JSONObject() {{
put("departmentName", obj);
}};
if (Objects.nonNull(localValue)) {
jsonObject.put("parentId", localValue.getDepartmentId());
}
HrDepartment department = hrDepartmentDao.selectOneByParam(jsonObject);
if (Objects.isNull(department)) {
sb.append("第" + row + "行,第" + col + "列部门不存在!");
return false;
}
setLocalValue(fcMap.get(fieldName)+row, department);
setLocalValue(fieldName, department);
}
return true;
}
@Override
public Object transfer(MetaObject original,Object xValue,String fieldName) {
if(Objects.nonNull(xValue) && xValue!="") {
HrDepartment localValue = (HrDepartment) getLocalValue(fieldName);
if (Objects.nonNull(localValue)) {
setBeanValue(cMap.get(fieldName), localValue.getDepartmentId());
}
}else{
setBeanValue(cMap.get(fieldName), 0);
}
return ExcelUtils.transferDefaultValueForNull(xValue,xValue.getClass());
}
}
这是多个checker中的两个,doCheck为检测方法,参数中的MetaObject 是mybatis的一个工具类,其中还包含了MetaClass对象,它通过反射将对象的各种信息保存起来,对外提供一系列的操作对象的方法,使用起来十分方便,感兴趣的小伙伴可以去看一下。transfer是转换数据的方法,例如excel中数据是 ‘是’ , ‘否’这种,存到数据库中是需要转换成 1 , 0。
Excel导入headr
/**
key 对应的cell的列
value 对应的属性名
*/
public final static LinkedHashMap<Integer, String> salaryLevelImportHeader = new LinkedHashMap<Integer, String>() {{
put(0,"region");
put(1,"positionType");
put(2,"rank");
put(3,"level");
put(4,"functionSalary");
put(5,"positionAllowance");
put(6,"housingAllowance");
put(7,"remark");
}};
ExcelUtils
@Component
public class ExcelUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
private static ConcurrentHashMap<String,MetaObject> metaMap = new ConcurrentHashMap<>(0);
private static ConcurrentHashMap<String,Field> fieldMap = new ConcurrentHashMap<>(0);
public static<T> List<T> transferToList(MultipartFile multipartFile,
LinkedHashMap<Integer,String> header,
Class<T> clazz, Integer start,StringBuffer sb) {
return transferToList(multipartFile,header,clazz,start,sb,"yyyy-MM-dd HH:mm:ss");
}
public static<T> List<T> transferToList(MultipartFile multipartFile,
LinkedHashMap<Integer,String> header,
Class<T> clazz,Integer start, StringBuffer sb,String dataFormart) {
/**
拿到Spring 中的ExcelCkeckerFactory 工厂
*/
ExcelCkeckerFactory excelCkeckerFactory = applicationContext.getBean(ExcelCkeckerFactory.class);
List<T> list = new ArrayList<>(0);
Integer failCount = 0;
Integer successCount = 0;
try(InputStream in = multipartFile.getInputStream()){
XSSFWorkbook sheets = new XSSFWorkbook(in);
XSSFSheet xssfSheet = sheets.getSheetAt(0);
if(xssfSheet != null){
int lastRowNum = xssfSheet.getLastRowNum();
for (int i = start - 1; i <= lastRowNum; i++) {
XSSFRow row = xssfSheet.getRow(i);
//如果一行有一个数据错误了那么整条数据都无需在添加到List当中
//但是需要继续判断后续数据是否有误添加至错误信息
boolean flag = true;
//getXValue 为读取cell封装的一个方法。
if(row != null && getXValue(row.getCell(0),String.class,dataFormart) != null){
/**DefaultObjectFactory 是mybatis提供的一个调用对象构造函数创建对象的。clazz为用户传的需要读取
Excel后转换的类型。
*/
T t = (T) new DefaultObjectFactory().create(clazz);
//MetaObject 上面注解已经解释过了
MetaObject metaObject = SystemMetaObject.forObject(t);
int lastCellNum = row.getLastCellNum();
//遍历cell
for (int j = 0; j < lastCellNum; j++) {
//从Import Header取出列对应的属性名
String fieldName = header.get(j);
if(fieldName == null || fieldName.isEmpty()){
continue;
}
/**
如果取出来的属性名包含 : 说明是含有前缀的,需要去掉前缀,注意,这里是拿到对象的属性名,
后续需要拿到这个属性名通过MetaObject 给对象设置值得,取检测类的时候并不是拿fieldName
这个变量取的。
*/
if(fieldName.indexOf(":") != -1){
fieldName = fieldName.substring(fieldName.indexOf(":")+1);
}
/**
这里拿到属性对应的所有检测类
*/
List<BaseExcelCellChecker> checkerList = excelCkeckerFactory.getChceker(header.get(j));
//如果检测类 List 不为空,表示这个字段需要进行检测
if(checkerList != null){
//便利检测类,这里是根据order排序后的检测类 list order值越大越先进行检测。
for (BaseExcelCellChecker baseExcelCellChecker : checkerList) {
//调用检测类的检测方法
if(baseExcelCellChecker.check(metaObject,getXValue(row.getCell(j),metaObject.getSetterType(fieldName),dataFormart),
fieldName,i+1,j+1,sb)){
//flag标识excel这一行数据是否已经出现过一次cell检测失败,如果只要一个cell检测失败,这一行就没必要继续检测了。
if(flag && metaObject.getSetterType(fieldName) != null){
metaObject.setValue(fieldName,
baseExcelCellChecker.transfer(metaObject,getXValue(row.getCell(j),
metaObject.getSetterType(fieldName),dataFormart),fieldName));
Map setMap = baseExcelCellChecker.getSetMap();
//额外的值设置,也就是检测类中setBeanValue()方法设置的map中的值。
if(setMap != null && setMap.size() > 0){
String finalField = fieldName;
setMap.forEach((k, v)->{
metaObject.setValue(k.toString(),transferDefaultValueForNull(v,metaObject.getSetterType(finalField)));
});
}
}
}else{
flag = false;
break;
}
}
}else{
if(flag && metaObject.getSetterType(fieldName) != null) {
Object xValue = getXValue(row.getCell(j), metaObject.getSetterType(fieldName),dataFormart);
metaObject.setValue(fieldName, transferDefaultValueForNull(xValue,metaObject.getSetterType(fieldName)));
}
}
}
if(flag){
successCount++;
list.add(t);
}else{
failCount++;
sb.append("<br/>");
}
}
}
sb.insert(0,"一共"+(successCount + failCount)+"条数据,其中<span style='color:#409eff'>"+successCount+"</span>条导入成功," +
"<span style='color:red'>"+failCount+"</span>条导入失败。" +(failCount > 0?"失败原因如下:":"")+"<br/>");
}else{
sb.append("请检查excle是否填写正确");
}
}catch (Exception e){
throw new BaseException("解析excle错误", ResultCode.EXCEPTION);
}
return list;
}
public static Object transferDefaultValueForNull(Object obj,Class type){
if(obj == null || obj.toString().isEmpty()){
if (String.class == type) {
return "";
} else if (int.class == type || Integer.class == type || short.class == type || Short.class == type
|| byte.class == type || Byte.class == type) {
return 0;
}
else if (float.class == type || Float.class == type) {
return 0F;
}
else if (double.class == type || Double.class == type) {
return 0D;
}
else if (long.class == type || Long.class == type) {
return 0L;
}
else if (boolean.class == type
|| Boolean.class == type) {
return true;
}
return new Object();
}
return obj;
}
private static Object getXValue(Cell cell,Class clazz,String dataFormart){
if (cell == null) {
return "";
}
if(Cell.CELL_TYPE_NUMERIC == cell.getCellType() && HSSFDateUtil.isCellDateFormatted(cell)){
if(!String.class.isAssignableFrom(clazz)){
return cell.getDateCellValue().getTime();
}else if(Data.class.isAssignableFrom(clazz)){
return cell.getDateCellValue();
}else {
return new SimpleDateFormat(dataFormart).format(cell.getDateCellValue());
}
}
String string = "";
if(cell.getCellType()==Cell.CELL_TYPE_FORMULA){
try {
string = String.valueOf(cell.getNumericCellValue());
} catch (IllegalStateException e) {
string = String.valueOf(cell.getRichStringCellValue());
}
}else{
cell.setCellType(Cell.CELL_TYPE_STRING);
string = cell.getRichStringCellValue().getString();
}
if(string.isEmpty() && clazz != String.class){
string = "0";
}
if (null != cell) {
switch (clazz.getName()) {
case "java.lang.String":
return string;
case "java.lang.Integer":
case "int":
return Integer.parseInt(string);
case "java.lang.Long":
case "long":
return Long.parseLong(string);
case "java.lang.Short":
case "short":
return Short.parseShort(string);
case "java.lang.Double":
case "double":
return Double.parseDouble(string);
case "java.lang.Float":
case "float":
return Float.parseFloat(string);
default:
return new Object();
}
} else {
return new Object();
}
}
private static void setCellValue(Cell cell, Object obj) {
if (obj == null) {
return;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if (obj instanceof String) {
cell.setCellValue((String) obj);
} else if (obj instanceof Date) {
Date date = (Date) obj;
if (date != null) {
cell.setCellValue(sdf.format(date));
}
} else if (obj instanceof Calendar) {
Calendar calendar = (Calendar) obj;
if (calendar != null) {
cell.setCellValue(sdf.format(calendar.getTime()));
}
} else if (obj instanceof Timestamp) {
Timestamp timestamp = (Timestamp) obj;
if (timestamp != null) {
cell.setCellValue(sdf.format(new Date(timestamp.getTime())));
}
} else if (obj instanceof Double) {
cell.setCellValue((Double) obj);
} else {
cell.setCellValue(obj.toString());
}
}
/**
这下面的就没必要看的,与导入无关,是导出部分,配合自己封装的metaResponse,metaFile使用的。
*/
private static Field getFiled(String key,MetaObject metaObject,Class clazz){
try{
if(fieldMap.containsKey(key)){
return fieldMap.get(key);
}
PropertyTokenizer prop = new PropertyTokenizer(key);
if (prop.hasNext()) {
if(!metaMap.containsKey(prop.getIndexedName())){
Object temp = new DefaultObjectFactory().create(metaObject.getGetterType(prop.getIndexedName()));
metaMap.put(prop.getIndexedName(),SystemMetaObject.forObject(temp));
}
MetaObject metaValue = metaMap.get(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
return null;
} else {
return getFiled(prop.getChildren(),metaValue,metaObject.getGetterType(prop.getIndexedName()));
}
}else {
Field declaredField = clazz.getDeclaredField(key);
declaredField.setAccessible(true);
if(!fieldMap.containsKey(key)){
fieldMap.put(key,declaredField);
}
return declaredField;
}
}catch (Exception e){
return null;
}
}
public static Workbook exportXlsx(Map<String,List> maps,Map<String,LinkedHashMap<String, String>> headMap) {
Workbook workbook = new XSSFWorkbook();
Set<String> strings = maps.keySet();
for (String sheetName : strings) {
exportXlsx(workbook,sheetName,maps.get(sheetName),headMap.get(sheetName));
}
return workbook;
}
/**
* 导出数据
*
* @param headMap
* @param dataList
*/
public static Workbook exportXlsx(String sheetName, List dataList ,LinkedHashMap<String, String> headMap) {
Workbook workbook = new XSSFWorkbook();
return exportXlsx(workbook,sheetName,dataList,headMap);
}
public static Workbook exportXlsx(Workbook workbook,String sheetName, List dataList ,LinkedHashMap<String, String> headMap) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
Sheet sheet = workbook.createSheet(sheetName);
int rowIndex = 0, columnIndex = 0;
Set<String> keys = headMap.keySet();
//表头
Row row = sheet.createRow(rowIndex++);
for (String key : keys) {
Cell cell = row.createCell(columnIndex++);
cell.setCellValue(headMap.get(key));
}
//内容
if (dataList != null && !dataList.isEmpty()) {
for (Object o : dataList) {
columnIndex = 0;
row = sheet.createRow(rowIndex++);
MetaObject metaObject = SystemMetaObject.forObject(o);
for (String key : keys) {
Cell cell = row.createCell(columnIndex++);
try{
Field declaredField = getFiled(key,metaObject,o.getClass());
ExcelDateFormart declaredAnnotation = declaredField.getDeclaredAnnotation(ExcelDateFormart.class);
if(declaredAnnotation != null){
try {
if(Objects.isNull(metaObject.getValue(key))){
setCellValue(cell, "");
}else{
simpleDateFormat.applyPattern(declaredAnnotation.pattern());
if(declaredField.getType() == Date.class){
setCellValue(cell, simpleDateFormat.format(metaObject.getValue(key)));
}else{
if((Long)metaObject.getValue(key) <= 0){
setCellValue(cell, "");
}else{
setCellValue(cell, simpleDateFormat.format(new Date((Long) metaObject.getValue(key))));
}
}
}
}catch (Exception e){
setCellValue(cell, metaObject.getValue(key));
}
}else{
setCellValue(cell, metaObject.getValue(key));
}
}catch (Exception e){
setCellValue(cell, "");
}
}
}
}
return workbook;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
这个工具类解释起来比较麻烦,导入方法最主要的时transferToList方法,在读取excel根据Excel Header常量给定的Map对应的key值也就是excel的列数从ExcelCkeckerFactory拿到对应的的ExcelChecker调用check方法检测,检车通过则根据Excel Header常量给定的Map对应的value值以及MetaObject给对象设置值并添加到List中最终返回List,如果校验不通过则将错误信息添加至errorMsg,在这里可以解释ExcelChecker prefix的作用,不同实体类的相同字段检测方法可能不同,每个实体都有Excel Header,其中key值就可以设置 前置:字段这种,这样就可以做到不同实体类相同字段通过前缀拿到不同的检测类。拿到List后就可以进行业务逻辑操作了,通过重构将检测方法封装成不同的类,方便拓展以及不容易出错并且可以复用。重构后使用只需要编写 ExcelChecker类以及Excel Header常量即可。
使用