1. 程式人生 > >反射 + 註解實現動態匯入功能,單表匯入與有關聯的外檢表,以及物件內表關聯

反射 + 註解實現動態匯入功能,單表匯入與有關聯的外檢表,以及物件內表關聯

最近專案中有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;

}