最近项目中有5个导入模块,不想复制粘贴,加上最近对注解和反射有点想用的的冲动,写了个粗略的动态导入:
PS:以下内容过长,容易引起舒适度不爽,请做好心理准备
一、需求分析:
0、导入的数据列头是中文,所以需要用反射 + 注解进行对应
1、基础字段,如user 的 name 、age;
2、外键关联字段,如属于哪个部门 ,dept_id(导入的是中文名称,需要将对应的id查询出来)
3、外键内关联字段 , 如部门属于哪个事业部,business_unit_id, 这个是和 2相关的,当然还可能出现第3个互相关联字段,假设有权限表,其根据部门来设置的,后面代码有会有具体讲解(此处是最麻烦的,注解配置项会很多,达到了11个)
4、验证功能,如user 的身份证号、手机号、各种信息长度等
5、外联字段可能需要同时导入id和name
6、将每一行,出错的列都统计处理,并返回
二、外键关联字段实现思路:
1、对于基础字段来说,动态导入很简单,只需要有 @FiledMappingAnnotation(cnName="表头") 就可以了,当然需要加入限制,如下:
@FiledMappingAnnotation(cnName = "密码", validate = RegexConst.NO_EMPTY_STR,advice = "必填,长度6-32位且不能有空格",length = 32)
2、以下着重对外键关联字段说明思路
① 如果只是单独的外键关联,不与其他字段发生关系,则需要配置该字段关联的表的serviceImpl来查询数据,达到根据中文名称获取id,以下是一个例子:
@FiledMappingAnnotation(cnName = "职务", pkName = "name", pkCode = "id", actionFiled = "0", beanName = "hospitalQuarterDictServiceImpl",advice = "非必填,必须为已有职务才能使用",length = 100)
后面注解中详细讲解每一个的含义
② 如果需要与其他字段发生关联关系,则需要更加复杂的实现,需要将字段进行分级处理,高等级的字段,实现 2,低等级的字段通过高等级的字段查询自己的值,如:
@FiledMappingAnnotation(cnName = "医院名称", pkName = "name", pkCode = "id",
actionFiled = "0",validate = RegexConst.NOT_NULL,beanName = "hospitalServiceImpl",
fieldLevel = "height",advice = "必填,且必须为已有医院",length = 50)
private Long hospitalId; ---医院是高等级字段
@FiledMappingAnnotation(cnName = "科室名称",contingencyName = "name",contingencyCode = "id",
actionFiled = "0",validate = RegexConst.NOT_NULL, beanName = "hospitalDeptServiceImpl"
,fieldLevel = "low",heightField = "hospitalId",correlationField = "hospitalId",
advice = "必填,且必须为已有科室",length = 50)
private Integer deptId; --- 科室是低等级字段
PS:此处的高低等级针对导入时的一种实现策略,可能中间还有更加复杂的,高、中、低三等,甚至存在 高、中、中、低等。
三、代码实现:
1、注解类
package com.ih.common.util.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 导入时,用于映射中英文字段,包括当前table表的外联表字段值 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface FiledMappingAnnotation { /** * 是否name 和 id一起导入 * @return */ String moreoverName() default ""; /** * 列的中文名称 / String cnName(); /** * 外联表的name字段 -- 导入的外联表字段在其他表的数据库(实体)名称 如 张三 关联user表 name ,如费用、关联Fee表 fee * @return */ String pkName() default ""; /** * 外联表的code字段 -- 导入的外联表名称对应的 需要插入当前表中的 id或者其他属性 如 id,code * @return */ String pkCode() default ""; /** * mid/low 查询自关联表的字段名称 --表中存在内关联字段的时候,中、低等级配置的,与高、中等级在关联表中的字段, * 如: user 属于医院下的科室,此时科室为低,医院为高,其关联表为科室表,科室的名称关联的字段是 name * @return */ String contingencyName() default ""; /** * mid/low 查询自关联表的id * @return */ String contingencyCode() default ""; /** * 外联表 字段作用范围 * -1 -> 自己基础字段 * 0 -> 关联字段 * @return */ String actionFiled() default "-1"; /** * 校验规则,正则表达式 * @return */ String validate() default ""; /** * 用于是否启用、性别之类的映射关系 * @return */ FiledMappingEnum[] state() default {}; /** * 关联字段-只能为string * 是将多关联分级处理了 * 见下面fieldLevel * @return */ String correlationField() default ""; /** * 描述关联字段的等级高级 * height -- 名称必须唯一的 * mid -- 仅当该节点有父子关系的时候配置 * low * 如医院 -> 科室 -> 人员 * height - mid - low * 如 院区<- 医院 -> 科室 * low - height - low * @return */ String fieldLevel() default ""; /** * 外联表对象service bean * @return */ String beanName() default ""; /** * height/mid 等级在中间表的外键 与 contingencyName contingencyCode在同一张表中,用于高等级查询低等级 * @return */ String heightField() default ""; /** * 限制长度 * @return */ long length() default 0L; /** * 验证出错的信息提示 * @return */ String message() default "数据格式错误"; /** * 导入错误的建议 * @return */ String advice() default "修改数据"; }
2、枚举类
package com.ih.common.util.annotation; public enum FiledMappingEnum { MALE("男","0"),FAMALE("女","1"),ENABLE("启用",1),DISABLE("停用",0),NONE("none",null),BUILDAUTHORITY("是",1),BUILDAUTHORITYNO("否",0),MANAGER("是",1),MANAGERNO("否",0); private String name; private Object value; FiledMappingEnum(String name,Object value) { this.name = name; this.value = value; } public Object getValue() { return value; } public String getName() { return name; } }
3、正则表达式常量类(大部分都是网上直接摘抄的)
package com.ih.common.util.annotation; public class RegexConst { /** * 校验非必填属性不填值和填值,用@Length限制 */ public static final String EMPTY_OR_NOT = "^$|^[\\s\\S]*(\\s*\\S+)([\\s\\S]*)$"; /** * 登录账号,密码等不允许有空格 */ public static final String NO_EMPTY_STR = "^\\S{0,32}$"; /** * 不能为空,可以有空格在任意地方,可以用@NotNull @NotEmpty @NotBlank 替换 * (?=^.{0,30}$)(^([\s\S]*(\s*\S+)([\s\S]*))$) //可以加长度限制,没有@Length灵活,重复太多 */ public static final String NOT_NULL = "^[\\s\\S]*(\\s*\\S+)([\\s\\S]*)$"; /** * 如英文名字之类的,开头、结尾不允许为空,中间可以为空 */ public static final String BEGIN_NOT_NULL = "^[\\S]+(\\s*\\S+)*([\\S]*)$"; public static final String CAN_NULL_AND_BEGIN_NOT_NULL = "^$|^[\\S]+(\\s*\\S+)*([\\S]*)$"; /** * 比率相关 可以为空 不超过100 */ public static final String RATE = "^$|^0$|^100$|^(([1-9][0-9])|([1-9]))(\\.[0-9]{1,2})?$"; /** * 速率 */ public static final String VELOCITY = "^(0|[1-9][0-9]{0,8})(\\.[0-9]{1,2})?$"; /** * 速率 可以为空 */ public static final String VELOCITY_CAN_NO = "^(0|[1-9][0-9]{0,8})(\\.[0-9]{1,2})?$"; /** * 不超过366天 */ public static final String YEAR = "^([0-9]|[1-9][0-9]|[1-2][0-9]{2}|3[0-5][0-9]|36[0-6])$"; /** * 中文名称 */ public static final String CHINES_ENAME = "^[\\u0391-\\uFFE5]+$"; /** * 非中文-验证的时候去除英文提示信息 */ public static final String NOT_CHINES_ENAME = "^[^\\u0391-\\uFFE5]*[^\\u0391-\\uFFE5]+?"; /** * 性别 */ public static final String SEX = "^[男女]$"; /** * 电话号码 */ public static final String PHONE_NUM = "^1[3|4|5|6|7|8|9][0-9]\\d{8}$"; /** * 可以为空的电话 */ public static final String CONTACT_PHONE_NUM = "^$|^1[3|4|5|6|7|8|9][0-9]\\d{8}$"; /** * 邮箱 */ public static final String EMAIL = "^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\\.[a-zA-Z0-9]{2,6}$"; /** * 价格 */ public static final String PRICE = "^(0|[1-9][0-9]{0,8})(\\.[0-9]{1,2})?$"; /** * 15或者18位身份证号 */ public static final String ID_CARD = "^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{2}[0-9]$||^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$"; /** * 出生日期 */ public static final String BIRTH_DAY = "^(([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))$|^((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[13579][26])00))-02-29)$"; }
4、上下文对象
package com.ih.common.util.annotation; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class SpringContextUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext){ SpringContextUtil.applicationContext = applicationContext; } public static ApplicationContext getApplicationContext(){ return applicationContext; } public static Object getBean(String name) throws BeansException { return applicationContext.getBean(name); } }
5、导入类
package com.ih.common.util.util; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.ih.common.util.annotation.FiledMappingAnnotation; import com.ih.common.util.annotation.FiledMappingEnum; import com.ih.common.util.annotation.RegexConst; import com.ih.common.util.annotation.SpringContextUtil; import com.ih.common.util.base.UploadErrorMessage; import org.apache.commons.lang.StringUtils; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import org.springframework.web.multipart.MultipartFile; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.*; /** * 通过反射动态解析excel */ @Component public class ImportExcelUtils<T> { /** * 存放注解数据 */ private Map<String, Map<String, Object>> annotationMap; /** * 存放外键关联的数据 key为beanName,value为table的数据 */ private Map<String, List<Map<String, Object>>> dataMap; /** * 存放Class注解解析的数据,并和表格比较是否全部存在 */ private List<Map<String, Object>> recordList; /** * 存放未通过的数据 */ private List<UploadErrorMessage> allErrorList; /** * 存放验证通过的数据的索引 */ private List<Integer> dataRownumList; /** * 存放验证通过的数据 */ private List<T> entityList; /** * 存放所有的数据,因为需要验证重复的 */ private List<T> allDataList; /** * 存放所有的数据的索引,备用 */ private List<Integer> allDataRowNumList; Integer integer; /** * 存放无外联映射的数据索引 */ private Map<String, Object> heighLevelFieldValueMap; private Map<String, Object> midLevelFieldValueMap; public List<T> parseExcel(MultipartFile file,Class clazz) throws Exception { initStoreDataStructer(); String fileName = file.getOriginalFilename(); boolean isExcel2003 = true; if (fileName.matches("^.+\\.(?i)(xlsx)$")) { isExcel2003 = false; } Workbook wb = null; if (isExcel2003) { wb = new HSSFWorkbook(file.getInputStream()); } else { wb = new XSSFWorkbook(file.getInputStream()); } return parseExcel(clazz,wb,true); } /** * 初始化一些存储数据结构,每一个导入的结构必须是最新的 */ private void initStoreDataStructer() { allErrorList = new ArrayList<>(); dataRownumList = new ArrayList<>(); heighLevelFieldValueMap = new HashMap<>(); midLevelFieldValueMap = new HashMap<>(); entityList = new ArrayList<>(); allDataList = new ArrayList<>(); allDataRowNumList = new ArrayList<>(); integer = 0; } public List<T> parseExcel(Class clazz, Workbook workbook,boolean isInit) throws Exception { if(!isInit){ initStoreDataStructer(); } Sheet sheet = workbook.getSheetAt(0); Row row = sheet.getRow(0); if (row == null) { return entityList; } //获取列总数 int lastCellNum = row.getPhysicalNumberOfCells(); getAnnotationMsg(clazz); //存储header是否在表格中 getRecordList(lastCellNum, row); int lastRowNum = sheet.getLastRowNum(); List<Map<String, Object>> tempStoreList; Set<String> fieldLevelSet = new HashSet<>(); for (int i = 1; i <= lastRowNum; i++) { T object = (T) clazz.newInstance(); Row currentRow = sheet.getRow(i); if (isAllRowEmpty(currentRow, row)) { continue; } tempStoreList = new ArrayList<>(); for (int j = 0; j < recordList.size(); j++) { Map<String, Object> objectMap = recordList.get(j); if ((Boolean) objectMap.get("isExist")) { Map<String,Object> map = new HashMap<>(); map.put("rowNum",i + 1); Cell cell = currentRow.getCell(j); List<Map<String, Object>> data = dataMap.get(objectMap.get("beanName")); //先对字段进行校验,校验不通过,则没有必要继续往下执行 boolean validateCondition = validateCellDataByValidateCondition(objectMap, cell, i + 1,data); if(!validateCondition){ integer++; if(j < recordList.size() - 1){ continue; }else if (j == recordList.size() - 1){ setSelfCorrelationFieldValue(tempStoreList, object, fieldLevelSet); } }else { //需要判断fieldLevel 如果不为null,表示是内关联的字段,暂时存起来,当循环的length为 recordList.size() - 1时执行 Object fieldLevel = objectMap.get("fieldLevel"); if (fieldLevel != null && ("mid".equals(fieldLevel) || "low".equals(fieldLevel))) { fieldLevelSet.add(fieldLevel.toString()); objectMap.put("cell", cell); objectMap.put("index", i + 1); tempStoreList.add(objectMap); } else { setData(cell, objectMap, data, object,integer,i + 1); } if (j == recordList.size() - 1) { setSelfCorrelationFieldValue(tempStoreList, object, fieldLevelSet); } } }else if (j == recordList.size() - 1){ setSelfCorrelationFieldValue(tempStoreList, object, fieldLevelSet); } } allDataList.add(object); allDataRowNumList.add(i + 1); if(integer == 0){ entityList.add(object); dataRownumList.add(i + 1); } } return entityList; } /** * 通过配置的正则表达式校验 Cell数据是否满足条件 * 如果校验不通过,则直接将信息放到最大的异常list里面 * @param objectMap * @param cell * @param rowNum * @param data */ private boolean validateCellDataByValidateCondition(Map<String, Object> objectMap, Cell cell,Integer rowNum,List<Map<String,Object>> data) { Object cnName = objectMap.get("cnName"); String simpleName = getSimpleName(objectMap); UploadErrorMessage uploadErrorMessage = new UploadErrorMessage(rowNum,cnName.toString(),null,objectMap.get("advice").toString()); if("Double,BigDecimal,Long,Integer,Date".contains(simpleName)){ //获取值之前先判断类型是否匹配 boolean typeMatches = typeMatches(cell, simpleName); if(!typeMatches){ uploadErrorMessage.setErrorInfo("不支持的数据类型"); allErrorList.add(uploadErrorMessage); return false; } } Object value = getValueByFieldType(cell, simpleName); boolean validateData = validateData(objectMap, value,data); if(!validateData){ uploadErrorMessage.setErrorInfo("数据验证失败"); allErrorList.add(uploadErrorMessage); return false; } return true; } private Object validateDataPkValueIsExist(List<Map<String,Object>> data,String pkName,Object values) { if(values == null){ return values; } Object id = null; for (int k = 0; k < data.size(); k++) { //外联表字段映射 其实就是name列的名字 if (values.equals(data.get(k).get(pkName))) { Object obj = data.get(k).get("id"); if (obj != null && obj instanceof Integer) { id = Integer.parseInt(data.get(k).get("id").toString()); } else if (obj != null && obj instanceof Long) { id = Long.parseLong(data.get(k).get("id").toString()); } break; } } return id; } /** * 处理自关联字段的值 * * @param tempStoreList * @return */ private void setSelfCorrelationFieldValue(List<Map<String, Object>> tempStoreList, T object, Set<String> fieldLevelSet) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException { //判断有几个等级 if (fieldLevelSet.size() == 1) { setHeightOrMidValue(tempStoreList, "low", heighLevelFieldValueMap, object); } if (fieldLevelSet.size() == 2) { setHeightOrMidValue(tempStoreList, "mid", heighLevelFieldValueMap, object); setHeightOrMidValue(tempStoreList, "low", midLevelFieldValueMap, object); } } private void setHeightOrMidValue(List<Map<String, Object>> tempStoreList, String level, Map<String, Object> fieldValueMap, T instance) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { for (int i = 0; i < tempStoreList.size(); i++) { Map<String, Object> map = tempStoreList.get(i); Object contingencyName = map.get("contingencyName"); Object contingencyCode = map.get("contingencyCode"); Object heightField = map.get("heightField"); Object correlationField = map.get("correlationField"); Cell cell = (Cell) map.get("cell"); Field field = (Field) map.get("field"); String simpleName = getSimpleName(map); Object values = getValueByFieldType(cell, simpleName); List<Map<String, Object>> data = dataMap.get(map.get("beanName")); //如果是查询mid的值,需要将关联的字段拿出来作为条件判断 if (level.equals(map.get("fieldLevel"))) { for (int j = 0; j < data.size(); j++) { Map<String, Object> dataTempMap = data.get(j); Object selfNameValue = dataTempMap.get(contingencyName); Object heightFieldValue = dataTempMap.get(heightField); Object heightValue = fieldValueMap.get(correlationField); //查出来的数据库数据中 根据名称获取的数据不能为空且关联字段的数据不能为空,最后根据cell值和关联字段自己的值进行比较 if (selfNameValue != null && heightFieldValue != null && selfNameValue.toString().equals(values) && heightFieldValue.equals(heightValue)) { field.setAccessible(true); //满足情况下都需要设置值了 Object contingencyCodeValue = dataTempMap.get(contingencyCode); if (contingencyCodeValue instanceof Integer) { field.set(instance, Integer.parseInt(contingencyCodeValue.toString())); } else if (contingencyCodeValue instanceof Long) { field.set(instance, Long.parseLong(contingencyCodeValue.toString())); } if ("mid".equals(level)) { midLevelFieldValueMap.put(field.getName(), contingencyCodeValue); } break; } else if (j == data.size() - 1) { //找不到,就放到错误list UploadErrorMessage uploadErrorMessage = new UploadErrorMessage(Integer.parseInt(map.get("index").toString()),map.get("cnName").toString(),"找不到数据",map.get("advice").toString()); allErrorList.add(uploadErrorMessage); } } } } } /** * 验证excel是否全部为空 * * @param row 当前行 * @param firstRow 第一行标题行 * @return */ private boolean isAllRowEmpty(Row row, Row firstRow) { if (row == null) { return true; } int count = 0; //单元格数量 int rowCount = firstRow.getLastCellNum() - firstRow.getFirstCellNum(); //判断多少个单元格为空 for (int c = 0; c < rowCount; c++) { Cell cell = row.getCell(c); if (cell == null || cell.getCellType() == Cell.CELL_TYPE_BLANK || StringUtils.isEmpty((cell + "").trim())) { count += 1; } } if (count == rowCount) { return true; } return false; } /** * 根据中文header获取对应的配置信息 * 并且标识字段是否在实体类中存在 */ private void getRecordList(int lastCellNum, Row row) { recordList = new ArrayList<>(); for (int i = 0; i < lastCellNum; i++) { String cellValue = row.getCell(i).getStringCellValue(); Map<String, Object> filedProperty = annotationMap.get(cellValue); if (filedProperty == null) { filedProperty = new HashMap<>(); filedProperty.put("isExist", false); } else { filedProperty.put("isExist", true); } recordList.add(filedProperty); } } /** * 获取注解数据 */ private void getAnnotationMsg(Class clazz) throws InvocationTargetException, IllegalAccessException, NoSuchFieldException { annotationMap = new HashMap<>(); dataMap = new HashMap<>(); Map<String, Field> tempFieldMap = new HashMap<>(); Field[] declaredFields = clazz.getDeclaredFields(); for (Field field : declaredFields) { tempFieldMap.put(field.getName(), field); } //将需要映射的字段注解全部加载出来 //根据依赖外联表的serviceBean将数据查询出来 for (int i = 0; i < declaredFields.length; i++) { FiledMappingAnnotation annotation = declaredFields[i].getAnnotation(FiledMappingAnnotation.class); if (annotation == null) { continue; } String cnName = annotation.cnName(); String pkName = annotation.pkName(); String pkCode = annotation.pkCode(); String validate = annotation.validate(); String actionFiled = annotation.actionFiled(); String beanName = annotation.beanName(); FiledMappingEnum[] state = annotation.state(); String fieldLevel = annotation.fieldLevel(); String contingencyCode = annotation.contingencyCode(); String contingencyName = annotation.contingencyName(); String correlationField = annotation.correlationField(); String heightField = annotation.heightField(); String message = annotation.message(); String advice = annotation.advice(); long length = annotation.length(); Map<String, Object> tempMap = new HashMap<>(); if (StringUtils.isNotEmpty(fieldLevel)) { tempMap.put("fieldLevel", fieldLevel); tempMap.put("contingencyCode", contingencyCode); tempMap.put("contingencyName", contingencyName); tempMap.put("correlationField", correlationField); tempMap.put("heightField", heightField); } String moreoverName = annotation.moreoverName(); if(StringUtils.isNotEmpty(moreoverName)){ Field field = clazz.getDeclaredField(moreoverName); tempMap.put("moreoverName",field); } tempMap.put("cnName", cnName); tempMap.put("pkName", pkName); tempMap.put("pkCode", pkCode); tempMap.put("field", declaredFields[i]); tempMap.put("actionFiled", actionFiled); tempMap.put("validate", validate); tempMap.put("beanName", beanName); tempMap.put("state", state); tempMap.put("message", message); tempMap.put("length", length); tempMap.put("advice", advice); getTableData(beanName); annotationMap.put(cnName, tempMap); } } /** * Ps : 此处是根据关联表配置的beanName通过反射查询数据,此处是用的MybatisPlus的selectMaps,如果没有该方法,可以自己写一个查询关联表所有数据的方法,如果需要通用,最好在baseServiceImpl里面统一定义 * */ private void getTableData(String beanName) throws InvocationTargetException, IllegalAccessException { if (!dataMap.containsKey(beanName) && StringUtils.isNotEmpty(beanName)) { Object bean = SpringContextUtil.getBean(beanName); Class<?> dependenceClass = bean.getClass(); Method[] methods = dependenceClass.getMethods(); Method method1 = null; for (Method method : methods) { if ("selectMaps".equals(method.getName())) { method1 = method; } } method1.setAccessible(true); EntityWrapper entityWrapper = new EntityWrapper(); List<Map<String, Object>> list = (List<Map<String, Object>>) method1.invoke(bean, entityWrapper); dataMap.put(beanName, list); } } /** * 对数据进行一系列的判断 * 包括类型是否匹配,是否为空,是否满足正则表达式,状态类(性别、是否启用)是否满足等 * @param objectMap * @param value * @return */ private boolean validateData(Map<String, Object> objectMap, Object value,List<Map<String,Object>> data) { String validate = objectMap.get("validate").toString(); String pkName = objectMap.get("pkName").toString(); long length = Long.parseLong(objectMap.get("length").toString()); if(CollectionUtils.isEmpty(data)){ //这是非关联字段 if (value == null && StringUtils.isEmpty(validate)) { //此处说明不校验,数据正确 return true; } else if (value == null && StringUtils.isNotEmpty(validate)) { //此处说明需要校验,将值设置为空字符串,测试是否可以为空串 String str; if(RegexConst.NO_EMPTY_STR.equals(validate)){ str = " "; }else { str = ""; } return str.matches(validate); } //如果校验规则不为空,并且数据不满足,就中断当前行的操作 if (StringUtils.isNotEmpty(validate) && (!value.toString().trim().matches(validate) || value.toString().length() > length)) { return false; } //如果是state 需要判断数据是否在可选范围内 FiledMappingEnum[] stateArr = (FiledMappingEnum[]) objectMap.get("state"); if(stateArr != null && stateArr.length > 0){ Object stateTypeMatches = validateStateTypeMatches(objectMap, value); if(stateTypeMatches == null){ return false; } } }else if(!CollectionUtils.isEmpty(data)){ //关联字段 if(StringUtils.isNotEmpty(validate) && StringUtils.isNotEmpty(pkName)){ return validateDataPkValueIsExist(data,pkName,value) != null; }else if(StringUtils.isEmpty(validate) && value != null && ( value != null && StringUtils.isNotEmpty(value.toString()))){ return validateDataPkValueIsExist(data,pkName,value) != null; } } return true; } /** * 经过上面的验证后,value到此不会为空 * @param objectMap * @param value */ private Object validateStateTypeMatches(Map<String, Object> objectMap,Object value) { Object stateValue = null; FiledMappingEnum[] stateArr = (FiledMappingEnum[]) objectMap.get("state"); if(stateArr != null && stateArr.length > 0){ for(FiledMappingEnum filedMappingEnum : stateArr){ if(filedMappingEnum.getName().equals(value)){ stateValue = filedMappingEnum.getValue(); break; } } } return stateValue; } /** * 如果字段是bean本身的数据,直接用本身的字段类型,如果字段是关联的其他表或者表格中数据与实体数据类型不符的, * 如id,将类型定位string, * 如果是日期,但是bean中是string ,转为Date,用于获取表格中数据 * 如state 将类型转为string * @param objectMap 字段注解数据 * @return */ private String getSimpleName(Map<String, Object> objectMap) { Field field = (Field) objectMap.get("field"); String actionFiled = objectMap.get("actionFiled").toString(); FiledMappingEnum[] state = (FiledMappingEnum[]) objectMap.get("state"); //获取字段类型 String simpleName = field.getType().getSimpleName(); simpleName = "-1".equals(actionFiled) ? simpleName : "String"; if (RegexConst.BIRTH_DAY.equals(objectMap.get("validate").toString())) { simpleName = "Date"; }else if(state != null && state.length > 0){ simpleName = "String"; } return simpleName; } //只负责设置数据就可以,前面已经做了数据类型校验和正则匹配 private void setData(Cell cell, Map<String, Object> objectMap, List<Map<String, Object>> data, T object,Integer integer,Integer rowNum){ try{ String simpleName = getSimpleName(objectMap); Object values = getValueByFieldType(cell, simpleName); //查询数据,根据作用字段获取值 String pkName = objectMap.get("pkName").toString(); Field field = (Field) objectMap.get("field"); String actionFiled = objectMap.get("actionFiled").toString(); field.setAccessible(true); switch (actionFiled) { case "-1"://是自己的字段,只需要判断数据类型 Object typeMatches = validateStateTypeMatches(objectMap, values); if(typeMatches != null){ values = typeMatches; } field.set(object, values); break; case "0": //作用于id.根据cell值获取id Object id = validateDataPkValueIsExist(data, pkName, values); if(id != null){ field.set(object, id); heighLevelFieldValueMap.put(field.getName(), id); if(objectMap.get("moreoverName") != null){ Field fieldMoreoverName = (Field)objectMap.get("moreoverName"); fieldMoreoverName.setAccessible(true); fieldMoreoverName.set(object, values); } break; } } }catch (Exception e){ integer++; UploadErrorMessage uploadErrorMessage = new UploadErrorMessage(rowNum,objectMap.get("cnName").toString(),"数据设置异常",objectMap.get("advice").toString()); allErrorList.add(uploadErrorMessage); e.printStackTrace(); } } /** * 对于数字类型的数据需要做类型匹配,放置出现转换异常,以error方式返回告知用户 / private boolean typeMatches(Cell cell, String simpleName) { if(cell == null){ return true; } int cellType = cell.getCellType(); switch (cellType){ case Cell.CELL_TYPE_NUMERIC: if(!"Double,BigDecimal,Long,Integer,Date".contains(simpleName)){ return false; } break; case Cell.CELL_TYPE_STRING: if(!"String".equals(simpleName)){ return false; } break; } return true; } /** * 获取cell值,如果数据与定义的类型不符,出现异常,捕获并设置为空, * 原因:后续有正则的validate,如果是必填数据,必定有相应的validate规则,如果非必填数据,错误了,直接设置为null * @param cell * @param simpleName * @return */ private Object getValueByFieldType(Cell cell, String simpleName) { Object value = null; if (cell == null) { return null; } try { switch (simpleName) { case "String": cell.setCellType(Cell.CELL_TYPE_STRING); value = cell.getStringCellValue(); break; case "BigDecimal": cell.setCellType(Cell.CELL_TYPE_NUMERIC); value = new BigDecimal(cell.getNumericCellValue() + ""); break; case "Long": cell.setCellType(Cell.CELL_TYPE_NUMERIC); Double cellValue = cell.getNumericCellValue(); value = cellValue.longValue(); break; case "Integer": cell.setCellType(Cell.CELL_TYPE_NUMERIC); Double numericCellValue = cell.getNumericCellValue(); value = numericCellValue.intValue(); break; case "Double": cell.setCellType(Cell.CELL_TYPE_NUMERIC); value = cell.getNumericCellValue(); break; case "Date": Date dateCellValue = cell.getDateCellValue(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); value = simpleDateFormat.format(dateCellValue); break; } } catch (Exception e) { value = null; } return value; } public List<Integer> getDataRownumList() { return this.dataRownumList; } public List<UploadErrorMessage> getFinalException() { return this.allErrorList; } public List<Integer> getAllDataRowNumList() { return allDataRowNumList; } public List<T> getAllDataList() { return allDataList; } }
6、实体配置(较全,基本覆盖了上述的配置)
package com.ih.entity;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.ih.common.util.annotation.FiledMappingAnnotation;
import com.ih.common.util.annotation.FiledMappingEnum;
import com.ih.common.util.annotation.RegexConst;
import org.hibernate.validator.constraints.Length;
import org.springframework.beans.BeanUtils;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
import java.util.List;
@TableName("hospital_user")
public class HospitalUser extends Model<HospitalUser> {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 医院代码
*/
@TableField("hospital_id")
@FiledMappingAnnotation(cnName = "医院名称", pkName = "name", pkCode = "id",
actionFiled = "0",validate = RegexConst.NOT_NULL,beanName = "hospitalServiceImpl",
fieldLevel = "height",advice = "必填,且必须为已有医院",length = 50)
@JsonSerialize(using = ToStringSerializer.class)
private Long hospitalId;
/**
* 医院name
*/
private String hospitalName;
/**
* 科室name
*/
private String deptName;
/**
* 科室代码
*/
@TableField("dept_id")
@FiledMappingAnnotation(cnName = "科室名称",contingencyName = "name",contingencyCode = "id",
actionFiled = "0",validate = RegexConst.NOT_NULL, beanName = "hospitalDeptServiceImpl"
,fieldLevel = "low",heightField = "hospitalId",correlationField = "hospitalId",
advice = "必填,且必须为已有科室",length = 50)
private Integer deptId;
/**
* 人员代码
*/
@TableField("user_code")
private String userCode;
/**
* 姓名
*/
@FiledMappingAnnotation(cnName = "姓名", validate = RegexConst.BEGIN_NOT_NULL,advice = "必填,且长度不能超过20位",length = 20)
@Length(max = 20,message = "姓名必填,且长度不能超过20位")
@Pattern(regexp = RegexConst.BEGIN_NOT_NULL,message = "姓名必填,且长度不能超过20位")
private String name;
/**
* 性别
*/
@FiledMappingAnnotation(cnName = "性别", validate = RegexConst.SEX, length = 2, state = {FiledMappingEnum.MALE,FiledMappingEnum.FAMALE},advice = "必填,可选男、女")
private String sex;
/**
* 身份证号
*/
@TableField("id_card")
@FiledMappingAnnotation(cnName = "身份证号", validate = RegexConst.ID_CARD,advice = "必填,输入15或者18位身份证号",length = 20)
@Pattern(regexp = RegexConst.ID_CARD,message = "请输入有效的15、18位身份证")
private String idCard;
/**
* 登录账号
*/
@FiledMappingAnnotation(cnName = "登录账号", validate = RegexConst.NO_EMPTY_STR,advice = "必填,长度不能超过50位且不能有空格",length = 50)
@Length(max = 50,message = "请输入50位以内登录账号")
@Pattern(regexp = RegexConst.NO_EMPTY_STR,message = "请输入有效的50位以内账号")
private String account;
/**
* 密码
*/
@FiledMappingAnnotation(cnName = "密码", validate = RegexConst.NO_EMPTY_STR,advice = "必填,长度6-32位且不能有空格",length = 32)
@Length(max = 32,min = 6,message = "请输入6-32位密码")
@Pattern(regexp = RegexConst.NO_EMPTY_STR,message = "请输入有效的6-32位密码")
private String password = "123456";
* 职称
*/
@FiledMappingAnnotation(cnName = "职称", pkName = "name", pkCode = "id",moreoverName = "jobTitleName", actionFiled = "0", beanName = "hospitalJobTitleDictServiceImpl",advice = "非必填,必须为已有职称才能使用",length = 100)
@TableField("job_title_id")
private Integer jobTitleId;
/**
* 职称名称 前端展示
*/
private String jobTitleName;
/**
* 学历
*/
@FiledMappingAnnotation(cnName = "学历", pkName = "name", pkCode = "id", actionFiled = "0", beanName = "hospitalEductionDictServiceImpl",advice = "非必填,必须为已有学历才能使用",length = 100)
@TableField("education_id")
private Integer educationId;
/**
* 手机号
*/
@TableField("phone_number")
@FiledMappingAnnotation(cnName = "电话号码", validate = RegexConst.PHONE_NUM,advice = "必填,输入正确的11位电话号码",length = 11)
@Pattern(regexp = RegexConst.PHONE_NUM,message = "请输入有效的电话号码")
private String phoneNumber;
/**
* 邮件地址
*/
@FiledMappingAnnotation(cnName = "邮件地址", validate = RegexConst.EMAIL,advice = "必填,长度不能超过50位",length = 50)
@Length(max = 50,message = "请输入50位以内邮件地址")
@Pattern(regexp = RegexConst.EMAIL,message = "请输入50位以内有效的邮件地址")
private String email;
/**
* 擅长
*/
@FiledMappingAnnotation(cnName = "擅长", validate = RegexConst.EMPTY_OR_NOT,advice = "长度不能超过100,且输入值不能全为空格",length = 100)
@Length(max = 100,message = "请输入100位以内擅长信息")
@Pattern(regexp = RegexConst.EMPTY_OR_NOT,message = "请输入100位以内有效擅长信息")
private String adept;
/**
* 简介
*/
@FiledMappingAnnotation(cnName = "简介", validate = RegexConst.EMPTY_OR_NOT,advice = "长度不能超过200,且输入值不能全为空格",length = 200)
@Length(max = 200,message = "请输入200位以内简介信息")
@Pattern(regexp = RegexConst.EMPTY_OR_NOT,message = "请输入200位以内简介信息")
private String intro;
/**
* 是否启用
*/
@FiledMappingAnnotation(cnName = "是否启用", state = {FiledMappingEnum.DISABLE,FiledMappingEnum.ENABLE},advice = "必填,可选项启用、停用",length =3)
private Integer state;
/**
* 备注
*/
@FiledMappingAnnotation(cnName = "备注", validate = RegexConst.EMPTY_OR_NOT,length = 100,advice = "长度不能超过100,且输入值不能全为空格")
@Length(max = 100,message = "请输入100位以内备注")
@Pattern(regexp = RegexConst.EMPTY_OR_NOT,message = "请输入100位以内有效备注")
private String remark;
/**
* 出生日期
*/
@FiledMappingAnnotation(cnName = "出生日期", validate = RegexConst.BIRTH_DAY,length = 20,advice = "必填,请输入2018/12/20格式的日期")
@Pattern(regexp = RegexConst.BIRTH_DAY,message = "请输入2018/12/20格式的日期")
private String birthday;
/**
* 工作类别
*/
@TableField("job_type_id")
@FiledMappingAnnotation(cnName = "工作类别", pkName = "dictName", pkCode = "id", actionFiled = "0",
beanName = "dictServiceImpl",length = 50,advice = "非必填,必须为已有工作类别才能使用")
private Integer jobTypeId;
/**
* 城市名称
*/
@FiledMappingAnnotation(cnName = "城市名称", validate = RegexConst.EMPTY_OR_NOT)
private String cityName;
}