反射 + 註解實現動態匯入功能,單表匯入與有關聯的外檢表,以及物件內表關聯
最近專案中有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_.-][email protected][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;
}