概述
由于業(yè)務(wù)的需要丐怯,錄入很多信息,而這些信息中絕大多數(shù)都是字典code翔横,所以就會有很多數(shù)據(jù)字典的定義读跷。
而前臺數(shù)據(jù)的獲取基本上都是下拉列表的選擇。在保存或編輯的時候禾唁,返回到前端只要字典code就可以舔亭,但是在查看的時候些膨,需要將字典翻譯同時返回用于展示。
基于這樣的前提下钦铺,受Hibernate Validator校驗方式的啟示,通過自定義注解的方式來實現(xiàn)肢预。
這樣做有一個非常大的優(yōu)勢:可拓展性強矛洞,以后新增字典項或字典類型直接加入字典倉庫,然后在相關(guān)字段上聲明就可以了烫映。
實現(xiàn)的功能
由于公司項目的特殊性沼本,存在老的字典碼和新字典碼映射轉(zhuǎn)換;所以除了字典code的翻譯锭沟,還有字典code的轉(zhuǎn)換抽兆。
除了一對一的字典code的翻譯,還有一種是上下級的翻譯族淮,比如省市區(qū)
- 一般字典code的翻譯
- 上下級字典code的翻譯
- 字典code的轉(zhuǎn)換(主要是新老系統(tǒng)的兼容對接)
具體實現(xiàn)
話不多說辫红,來看示例:
- 首先在DTO中添加自定義的注解
@Data
public class PersonDTO {
/** 證件類型(字典) */
private String custCertificateType;
/** 學歷(字典) */
private String custEducation;
/** 學位(字典) */
private String custDegree;
/** 單位所在省份(字典) */
private String companyProvince;
/** 單位所在城市(字典) */
private String companyCity;
/** 單位所在區(qū)/縣(字典) */
private String companyDistrict;
/** 證件類型(名稱) */
@Dict(DICT_CERTIFICATE_TYPE)
private String custCertificateTypeName;
/** 學歷(名稱) */
@Dict(DICT_EDUCATION)
private String custEducationName;
/** 學位(名稱) */
@Dict(DICT_DEGREE)
private String custDegreeName;
/** 單位所在省份(名稱) */
@Dict(value = DICT_AR_PROVINCE)
private String companyProvinceName;
/** 單位所在城市(名稱) */
@Dict(value = DICT_SYSTEM_AREA, isChild = true, parentField = "companyProvince")
private String companyCityName;
/** 單位所在區(qū)/縣(名稱) */
@Dict(value = DICT_SYSTEM_AREA, isChild = true, parentField = "companyCity")
private String companyDistrictName;
}
自定義注解Dict
的定義如下:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Dict {
/** 數(shù)據(jù)字典code類型; 如果查找二級編碼祝辣,則該值存儲二級編碼類型前綴贴妻,比如AR表示區(qū)域 */
String value() default "";
/** 存儲編號的字段名稱 如果是添加了Name后綴,則可省略*/
String codeName() default "";
/** 是否是子元素蝙斜,如果是true名惩,則 parentField不得為空*/
boolean isChild() default false;
/** 子元素的上級code字段名 */
String parentField() default "";
/** 當轉(zhuǎn)換后的值為null的時候,是否進行賦值孕荠;默認為false娩鹉,表示不覆蓋 */
boolean whetherSetIfNull() default false;
/** 是否是轉(zhuǎn)換編碼;也就是說默認不轉(zhuǎn)換code稚伍,而是翻譯成name */
boolean whetherConvertCode() default false;
}
- 其次是在接口輸出list結(jié)果前調(diào)用service方法解析@Dict注解
使用注解的方式自定義解析弯予,比較靈活。哪里需要使用槐瑞,哪里調(diào)用方法解析就可以熙涤。
@Autowired private DictService dictService;
// 這里person從數(shù)據(jù)庫查詢出來的對象
PersonDTO person = ……
// 數(shù)據(jù)字典翻譯
dictService.translationDictCode(person);
// translationDictCode 方法的實現(xiàn)
@Override
public void translationDictCode(Object obj) {
DictUtil.translationDictCode(obj, this::queryDictMapData, false);
}
// 其中DictUtil.translationDictCode方的定義如下:
/**
* 將DTO對象(對象中含有有Dict注解的對象)的字典碼轉(zhuǎn)換為字典值名稱(注:通過Dict注解標識碼值的字段)
*
* @param obj 處理的對象
* @param convertMapFun 字典類型和值的集合轉(zhuǎn)換為名稱
* @param whetherConvertCode 是否轉(zhuǎn)換編碼
*/
public static void translationDictCode(final Object obj, Function<Map<String, String>, Map<String, String>> convertMapFun,
boolean whetherConvertCode)
// 這里的queryDictMapData定義如下:做為translationDictCode中的入?yún)onvertMapFun,它是接收一個map困檩,然后返回另一個map祠挫,其中param是根據(jù)解析DTO上的Dict注解構(gòu)造出來的查詢參數(shù)。
/**
* 查詢字典碼對應(yīng)的name
*/
private Map<String, String> queryDictMapData(Map<String, String> param) {
// 調(diào)用接口根據(jù)入?yún)aram查詢數(shù)據(jù)字典翻譯出來的name值
// 一般數(shù)據(jù)字典放在redis緩存中悼沿,統(tǒng)一一個地方處理等舔;這里可以根據(jù)這種入?yún)⑷ゾ彺嬷胁樵儯缓蠓庋b返回需要格式的數(shù)據(jù)
// 主要就是解析入?yún)⒃阒海缓髽?gòu)建特定格式的返回值慌植,比如:{"xx:certificateType=1": "身份證", "xx:certificateType=2": "護照"}
return ……;
}
對queryDictMapData方法的入?yún)⒑头祷刂档慕庾x:
param 示例:{"relationship": "1,2", "AR:1001": "", ……}
其中key是字典type甚牲,value是要查詢的字典值,有多個則用逗號分隔蝶柿;
relationship對應(yīng)一個字典項列表丈钙,比如[{"1": "朋友"}, {"2", "同事"}, {"3", "親屬"}……]
而value說明要查詢字典code 1和2對應(yīng)的翻譯,那么返回結(jié)果如下:
因為字典type可能會有冒號字符交汤,這里用=做鏈接符
{"relationship=1": "朋友", "relationship=2": "同事"}
-
DictUtil
的部分方法
首先從方法translationDictCode
一步步來剖析:
其實一開始是沒有參數(shù)whetherConvertCode雏赦,后來為了兼容一種情況,就是先字典code的轉(zhuǎn)換(老系統(tǒng)的code轉(zhuǎn)換為新系統(tǒng)的code芙扎,反之亦然)星岗,然后再進行字典翻譯
public static void translationDictCode(final Object obj, Function<Map<String, String>, Map<String, String>> convertMapFun,
boolean whetherConvertCode) {
if (Objects.isNull(obj)) {
return;
}
Map<String, String> fieldKeyMap = new HashMap<>();
Map<String, String> keyCodeMap = new HashMap<>();
// 解析需要轉(zhuǎn)換的字典碼(遞歸處理)
parseFieldCodeValue(obj, fieldKeyMap, keyCodeMap, whetherConvertCode);
// 字典值轉(zhuǎn)換(具體轉(zhuǎn)換的方法)
Map<String, String> convertedMap = convertMapFun.apply(keyCodeMap);
// 回填字典碼對應(yīng)的值(遞歸處理)
backFillCodeName(obj, fieldKeyMap, convertedMap, whetherConvertCode);
}
除了對Object進行翻譯,直接對List<Object>也是可以的戒洼,方法如下:
/**
* 將集合中所有對象的字典碼轉(zhuǎn)換為字典值 (注:通過Dict注解標識碼值)
*
* @param list 處理的集合
* @param convertMapFun 字典類型和值的集合轉(zhuǎn)換為名稱
* @author liam
*/
public static void translationListCodeName(List list, Function<Map<String, String>, Map<String, String>> convertMapFun) {
if (null == list || !list.isEmpty()) {
return;
}
Map<String, String> fieldKeyMap = new HashMap<>();
Map<String, String> keyCodeMap = new HashMap<>();
// 構(gòu)造填充映射關(guān)系
list.forEach(obj -> fillDictCodeConvertMap(obj, fieldKeyMap, keyCodeMap));
// 字典值轉(zhuǎn)換
Map<String, String> convertedMap = convertMapFun.apply(keyCodeMap);
// 給field賦值
list.forEach(obj -> setObjAllFieldValue(obj, fieldKeyMap, convertedMap));
}
完整的示例
- 測試類和運行結(jié)果
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import java.util.List;
public class DictServiceTest {
private static ObjectMapper objectMapper = new ObjectMapper();
public static void main(String[] args) {
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
DictService dictService = new DictService();
PersonDTO person1 = new PersonDTO();
person1.setCustCertificateType("1");
PersonDTO person2 = new PersonDTO();
person2.setCustCertificateType("2");
person2.setCompanyProvince("10001");
person2.setCompanyCity("1000101");
List<PersonDTO> personList = Lists.newArrayList(person1, person2);
System.out.println("轉(zhuǎn)換之前");
personList.forEach(DictServiceTest::printJson);
// 字典翻譯轉(zhuǎn)換
dictService.translationListCodeName(personList);
System.out.println("轉(zhuǎn)換之后");
personList.forEach(DictServiceTest::printJson);
}
private static void printJson(Object obj) {
try {
System.out.println(objectMapper.writeValueAsString(obj));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
// 運行結(jié)果:
// 轉(zhuǎn)換之前
// {"custCertificateType":"1"}
// {"custCertificateType":"2","companyProvince":"10001","companyCity":"1000101"}
// 轉(zhuǎn)換之后
// {"custCertificateType":"1","custCertificateTypeName":"身份證"}
// {"custCertificateType":"2","companyProvince":"10001","companyCity":"1000101","custCertificateTypeName":"護照","companyProvinceName":"河北省","companyCityName":"邯鄲市"}
- DTO和常量類
/**
* 數(shù)據(jù)字典相關(guān)常量
*
* @Author liam
*/
public interface DictConstant {
/** “=”等號*/
String SIGN_EQUAL = "=";
/** “:”冒號 */
String SIGN_COLON = ":";
/** “,”逗號*/
String SIGN_COMMA = ",";
/**XX系統(tǒng)接口的數(shù)據(jù)字典前綴*/
String DICT_SYSTEM_XX = "XX";
/**省市區(qū)數(shù)據(jù)字典前綴*/
String DICT_SYSTEM_AREA = "AR";
/** XX系統(tǒng)數(shù)據(jù)字典 */
String DICT_XX_COLON = DICT_SYSTEM_XX + SIGN_COLON;
/** 省市區(qū)數(shù)據(jù)字典 */
String DICT_AR_COLON = DICT_SYSTEM_AREA + SIGN_COLON;
/** 學歷 */
String DICT_EDUCATION = DICT_XX_COLON + "10001";
/** 學位 */
String DICT_DEGREE = DICT_XX_COLON + "10002";
/** 證件類型 */
String DICT_CERTIFICATE_TYPE = DICT_XX_COLON + "10003";
/**省市區(qū)數(shù)據(jù)字典標識: 省*/
String DICT_AR_PROVINCE = DICT_AR_COLON + "province";
}
import lombok.Data;
import static com.liam.dict.DictConstant.*;
@Data
public class PersonDTO {
/** 證件類型(字典) */
private String custCertificateType;
/** 學歷(字典) */
private String custEducation;
/** 學位(字典) */
private String custDegree;
/** 單位所在省份(字典) */
private String companyProvince;
/** 單位所在城市(字典) */
private String companyCity;
/** 單位所在區(qū)/縣(字典) */
private String companyDistrict;
/** 證件類型(名稱) */
@Dict(DICT_CERTIFICATE_TYPE)
private String custCertificateTypeName;
/** 學歷(名稱) */
@Dict(DICT_EDUCATION)
private String custEducationName;
/** 學位(名稱) */
@Dict(DICT_DEGREE)
private String custDegreeName;
/** 單位所在省份(名稱) */
@Dict(value = DICT_AR_PROVINCE)
private String companyProvinceName;
/** 單位所在城市(名稱) */
@Dict(value = DICT_SYSTEM_AREA, isChild = true, parentField = "companyProvince")
private String companyCityName;
/** 單位所在區(qū)/縣(名稱) */
@Dict(value = DICT_SYSTEM_AREA, isChild = true, parentField = "companyCity")
private String companyDistrictName;
}
- service類
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.liam.dict.DictConstant.*;
public class DictService {
public void translationDictCode(Object obj) {
DictUtil.translationDictCode(obj, this::queryDictMapData, false);
}
public void translationListCodeName(List list) {
DictUtil.translationListCodeName(list, this::queryDictMapData);
}
/**
* 查詢字典碼對應(yīng)的name
*/
private Map<String, String> queryDictMapData(Map<String, String> typeCodeMap) {
// 調(diào)用接口根據(jù)入?yún)aram查詢數(shù)據(jù)字典翻譯出來的name值
// 一般數(shù)據(jù)字典放在redis緩存中俏橘,統(tǒng)一一個地方處理;這里可以根據(jù)這種入?yún)⑷ゾ彺嬷胁樵內剑缓蠓庋b返回需要格式的數(shù)據(jù)
Map<String, String> result = new HashMap<>();
if (typeCodeMap.isEmpty()) {
return result;
}
// 獲取測試數(shù)據(jù)
Map<String, List<DictDTO>> testData = getTestData();
// 解析請求參數(shù)寥掐,構(gòu)造返回結(jié)果
typeCodeMap.forEach((key, value) -> {
// 查詢獲取字典項列表
List<DictDTO> dictList = testData.get(key);
// 需要翻譯的字典code
String dictCode = typeCodeMap.get(key);
if (dictCode.contains(SIGN_COMMA)) { // 逗號分隔
Splitter.on(SIGN_COMMA)
.trimResults()
.splitToList(dictCode)
.forEach(code -> this.findSetCodeName(code, key, dictList, result));
} else {
this.findSetCodeName(dictCode, key, dictList, result);
}
});
return result;
}
/**
* 遍歷取出所有值比較code是否相同,如果找到結(jié)束循環(huán)汉额,取出結(jié)果
*
* @param dictCode : 字典code
* @param hashKey : 字典碼
* @param list : 字典集合
* @param result : 最終結(jié)果集
*/
private void findSetCodeName(String dictCode, Object hashKey,
List<DictDTO> list, Map<String, String> result) {
for (DictDTO dto : list) {
if (dto.getCode().equals(dictCode)) {
result.put(hashKey + SIGN_EQUAL + dictCode, dto.getName());
break;
}
}
}
/**
* 獲取測試數(shù)據(jù)
*/
private Map<String, List<DictDTO>> getTestData() {
return ImmutableMap.<String, List<DictDTO>>builder()
.put(DICT_CERTIFICATE_TYPE, certificateTypeList)
.put(DICT_AR_PROVINCE, provinceList)
.put(DICT_AR_COLON + "10001", hebeiCityList)
.build();
}
// 假如數(shù)據(jù)字典是如下列表
/** 證件列表 */
private List certificateTypeList = Lists.newArrayList(
new DictDTO("1", "身份證"),
new DictDTO("2", "護照"),
new DictDTO("3", "駕駛證")
);
/** 省列表 */
private List provinceList = Lists.newArrayList(
new DictDTO("10001", "河北省"),
new DictDTO("10002", "河南省"),
new DictDTO("10003", "江蘇省")
);
/** 河北市列表 */
private List hebeiCityList = Lists.newArrayList(
new DictDTO("1000101", "邯鄲市"),
new DictDTO("1000102", "秦皇島市")
);
/** 臨時測試數(shù)據(jù) */
private Map<String, String> demoTestData = ImmutableMap.<String, String>builder()
.put(DICT_CERTIFICATE_TYPE + SIGN_EQUAL + "1", "身份證")
.put(DICT_CERTIFICATE_TYPE + SIGN_EQUAL + "2", "護照")
.put(DICT_AR_PROVINCE + SIGN_EQUAL + "10001", "上海市")
.put(DICT_AR_COLON + "10001" + SIGN_EQUAL + "1000101", "浦東新區(qū)")
.build();
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DictDTO {
/** 字典碼 */
private String code;
/** 字典名稱 */
private String name;
}
- 工具類DictUtil曹仗,數(shù)據(jù)字典轉(zhuǎn)換的核心
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanWrapperImpl;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import static com.liam.dict.DictConstant.*;
/**
* 數(shù)據(jù)字典工具類
*
* @Author libing
*/
@Slf4j
public class DictUtil {
/**
* Name后綴,默認的字典名稱字段命名規(guī)則
*/
private static final String NAME_SUFFIX = "Name";
/**
* 將DTO對象(對象中含有有Dict注解的對象)的字典碼轉(zhuǎn)換為字典值名稱(注:通過Dict注解標識碼值的字段)
*
* @param obj 處理的對象
* @param convertMapFun 字典類型和值的集合轉(zhuǎn)換為名稱
* @param whetherConvertCode 是否轉(zhuǎn)換編碼
*/
public static void translationDictCode(final Object obj, Function<Map<String, String>, Map<String, String>> convertMapFun,
boolean whetherConvertCode) {
if (Objects.isNull(obj)) {
return;
}
Map<String, String> fieldKeyMap = new HashMap<>();
Map<String, String> keyCodeMap = new HashMap<>();
// 解析需要轉(zhuǎn)換的字典碼(遞歸處理)
parseFieldCodeValue(obj, fieldKeyMap, keyCodeMap, whetherConvertCode);
// 字典值轉(zhuǎn)換(具體轉(zhuǎn)換的方法)
Map<String, String> convertedMap = convertMapFun.apply(keyCodeMap);
/* 這里 keyCodeMap 蠕搜、 fieldKeyMap 怎茫、 convertedMap 三者之間的關(guān)系
* keyCodeMap 是字典type與字典code值的關(guān)系
* 比如: {"xx:certificateType": "1,2"}
* convertedMap 外部方法處理的結(jié)果,字典翻譯的結(jié)果
* 比如: {"xx:certificateType=1": "身份證", "xx:certificateType=2": "護照"}
* fieldKeyMap 是字典code所對應(yīng)的字段與轉(zhuǎn)換結(jié)果的關(guān)系
* 比如: {"1:custCertificateType": "xx:certificateType=1", "2:custCertificateType": "xx:certificateType=2"}
*/
// 回填字典碼對應(yīng)的值(遞歸處理)
backFillCodeName(obj, fieldKeyMap, convertedMap, whetherConvertCode);
}
/**
* 將集合中所有對象的字典碼轉(zhuǎn)換為字典值 (注:通過Dict注解標識碼值)
*
* @param list 處理的集合
* @param convertMapFun 字典類型和值的集合轉(zhuǎn)換為名稱
* @author liam
*/
public static void translationListCodeName(List list, Function<Map<String, String>, Map<String, String>> convertMapFun) {
if (null == list || list.isEmpty()) {
return;
}
Map<String, String> fieldKeyMap = new HashMap<>();
Map<String, String> keyCodeMap = new HashMap<>();
// 構(gòu)造填充映射關(guān)系
list.forEach(obj -> fillDictCodeConvertMap(obj, fieldKeyMap, keyCodeMap));
// 字典值轉(zhuǎn)換
Map<String, String> convertedMap = convertMapFun.apply(keyCodeMap);
// 給field賦值
list.forEach(obj -> setObjAllFieldValue(obj, fieldKeyMap, convertedMap));
}
/**
* 通過對Object的分析妓灌,構(gòu)造填充2種轉(zhuǎn)換關(guān)系map
*
* @param obj : 取字段的對象
* @param fieldKeyMap : (dictCode+字段名稱)和接口查詢返回的字典key的映射
* @param keyCodeMap : 用于接口查詢的字典key和code的映射
* @author liam
*/
private static void fillDictCodeConvertMap(Object obj, Map<String, String> fieldKeyMap, Map<String, String> keyCodeMap) {
if (null == obj) { // 如果為空轨蛤,直接不做處理
return;
}
Arrays.stream(obj.getClass().getDeclaredFields())
.filter(field -> field.isAnnotationPresent(Dict.class))
.forEach(field -> parseDictAnnotation(obj, field, fieldKeyMap, keyCodeMap));
}
/**
* 解析需要轉(zhuǎn)換的字典碼(遞歸處理)
*
* @param obj : 取字段的對象
* @param fieldKeyMap : (dictCode+字段名稱)和接口查詢返回的字典key的映射
* @param keyCodeMap : 用于接口查詢的字典key和code的映射
* @param whetherConvertCode: 是否轉(zhuǎn)換編碼
* @author liam
*/
private static void parseFieldCodeValue(Object obj, Map<String, String> fieldKeyMap, Map<String, String> keyCodeMap,
boolean whetherConvertCode) {
if (Objects.isNull(obj)) {
return;
}
// 獲取當前對象所有的field
Field[] declaredFields = obj.getClass().getDeclaredFields();
// 構(gòu)造填充映射關(guān)系
Arrays.stream(declaredFields).forEach(field ->
parseFieldObj(field, obj, whetherConvertCode,
fieldObj -> parseFieldCodeValue(fieldObj, fieldKeyMap, keyCodeMap, whetherConvertCode), // 遞歸處理
// 解析注解字段信息
() -> parseDictAnnotation(obj, field, fieldKeyMap, keyCodeMap)
)
);
}
/**
* 回填字典碼對應(yīng)的值(遞歸處理)
*
* @param obj : 回填字段的對象
* @param fieldKeyMap : (dictCode+字段名稱)和接口查詢返回的字典key的映射
* @param convertedMap : 字典值轉(zhuǎn)換后的map
* @author liam
*/
private static void backFillCodeName(Object obj, Map<String, String> fieldKeyMap, Map<String, String> convertedMap,
boolean whetherConvertCode) {
if (Objects.isNull(obj)) {
return;
}
// 給field賦值
Arrays.stream(obj.getClass().getDeclaredFields()).forEach(field ->
parseFieldObj(field, obj, whetherConvertCode,
fieldObj -> backFillCodeName(fieldObj, fieldKeyMap, convertedMap, whetherConvertCode), // 遞歸處理
// 回填匹配的字段值
() -> setFieldValue(obj, field, fieldKeyMap, convertedMap)
)
);
}
/**
* 解析field對象,對基本數(shù)據(jù)類型和復雜類型做不同的處理
*
* @param field : 字段對象
* @param obj : 字段所屬的obj對象
* @param recursiveFunc : 遞歸處理方法
* @param parseAnnotationFunc : 核心解析有注解字段的方法
* @author liam
*/
private static void parseFieldObj(Field field, Object obj, boolean whetherConvertCode,
Consumer<Object> recursiveFunc,
NestedFunction parseAnnotationFunc) {
Class cls = field.getType();
if (Map.class.isAssignableFrom(cls)) {
return; // map直接跳過
}
// 需要數(shù)據(jù)字典轉(zhuǎn)換的屬性:有Dict注解
if (field.isAnnotationPresent(Dict.class)
// 且匹配whetherConvertCode的值
&& field.getAnnotation(Dict.class).whetherConvertCode() == whetherConvertCode) {
parseAnnotationFunc.run(); // 核心方法處理虫埂,
}
// 沒有注解的屬性判斷
else {
try {
// 獲取字段值且非空處理
field.setAccessible(true);
Optional.ofNullable(field.get(obj)).ifPresent(fieldValue -> {
// 集合類型祥山,如果泛型的類型是JavaBean,繼續(xù)遞歸處理
if (Collection.class.isAssignableFrom(cls)) {
// 如果是list-map結(jié)果掉伏,則這里返回null
Class generic = getGeneric(obj.getClass(), field.getName());
if (null != generic && notInFilterClass(generic)) {
// 循環(huán)遞歸處理
((Collection) fieldValue).forEach(recursiveFunc::accept);
}
}
// 非基本數(shù)據(jù)類型
else if (notInFilterClass(cls)) {
recursiveFunc.accept(fieldValue);
}
}
);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
/**
* 解析含有注解Dict的field信息缝呕,并將其充到2種轉(zhuǎn)換關(guān)系map中
*
* @param obj : 對象
* @param field : 字段
* @param fieldKeyMap : (獲取dictCode+字段名稱)和接口查詢返回的字典key的映射
* @param keyCodeMap : 用于接口查詢的字典key和code的映射
* @author liam
*/
private static void parseDictAnnotation(Object obj, Field field,
Map<String, String> fieldKeyMap,
Map<String, String> keyCodeMap) {
// 讀取注解信息,獲取編碼類型
Dict dict = field.getAnnotation(Dict.class);
String fieldName = field.getName();
// 根據(jù)Dict的codeName屬性或者字段名稱斧散,獲取字典編碼code
String code = getFieldValue(obj, dict, fieldName);
if (!Strings.isNullOrEmpty(code)) {
String dictType = dict.value();
// 獲取dictCode+字段名稱的組合
fieldName = getDictCodeCombineName(code, fieldName);
// 二級編碼字段
if (dict.isChild()) {
// 獲取二級字段的字典編碼值
String parentCode = getPropertyValue(obj, dict.parentField());
// 父級code
String key = dictType + SIGN_COLON + parentCode;
// 如果有相同的父級code供常,且code不重復
if (null != keyCodeMap.get(key) && !code.equals(keyCodeMap.get(key))) {
keyCodeMap.put(key, keyCodeMap.get(key) + SIGN_COMMA + code);
} else {
keyCodeMap.put(key, code);
}
// 屬性名對應(yīng)的 字典碼和code
fieldKeyMap.put(fieldName, key + SIGN_EQUAL + code);
} else {
// 有多個相同的類型,且code不重復
if (null != keyCodeMap.get(dictType) && !code.equals(keyCodeMap.get(dictType))) {
keyCodeMap.put(dictType, keyCodeMap.get(dictType) + SIGN_COMMA + code);
} else {
keyCodeMap.put(dictType, code);
}
// 屬性名對應(yīng)的 字典碼和code
fieldKeyMap.put(fieldName, dictType + SIGN_EQUAL + code);
}
}
}
/**
* 給一個對象所有的含有Dict注解的字段賦值
*
* @param obj : 取字段的對象
* @param fieldKeyMap : (dictCode+字段名稱)和接口查詢返回的字典key的映射
* @param convertedMap : 字典值轉(zhuǎn)換后的map
* @author liam
*/
private static void setObjAllFieldValue(Object obj, Map<String, String> fieldKeyMap, Map<String, String> convertedMap) {
if (null == obj) { // 如果為空鸡捐,直接不做處理
return;
}
Arrays.stream(obj.getClass().getDeclaredFields())
.filter(field -> field.isAnnotationPresent(Dict.class))
.forEach(field -> setFieldValue(obj, field, fieldKeyMap, convertedMap));
}
/**
* 給fiel設(shè)置值
*
* @param obj : 對象
* @param field : 字段
* @param fieldKeyMap : 字段值映射關(guān)系
* @param convertedMap :轉(zhuǎn)換碼關(guān)系
* @author liam
*/
private static void setFieldValue(Object obj, Field field, Map<String, String> fieldKeyMap,
Map<String, String> convertedMap) {
// 根據(jù)字段code和字段名稱構(gòu)建組合名稱
String fieldName = field.getName();
Dict dictAnnotation = field.getAnnotation(Dict.class);
String fieldValue = getFieldValue(obj, dictAnnotation, fieldName);
String combineFieldName = getDictCodeCombineName(fieldValue, fieldName);
Optional.ofNullable(fieldKeyMap.get(combineFieldName)).ifPresent(dicKey -> {
String convertedValue = convertedMap.get(dicKey); // 轉(zhuǎn)換后的值
try {
field.setAccessible(true);
// 當轉(zhuǎn)換后的值為null栈暇,判斷是否需要將null值覆蓋原有的值
if (null == convertedValue) {
if (dictAnnotation.whetherSetIfNull()) {
field.set(obj, null);
}
return;
}
field.set(obj, convertedValue);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
});
}
/**
* 獲取對象里指定屬性的值,并轉(zhuǎn)化為字符串
*
* @param obj : 對象
* @param propertyName : 對象里面的屬性名稱
* @author liam
*/
private static String getPropertyValue(Object obj, String propertyName) {
BeanWrapperImpl beanWrapper = new BeanWrapperImpl(obj);
if (beanWrapper.isReadableProperty(propertyName)) {
Object propertyValue = beanWrapper.getPropertyValue(propertyName);
if (null != propertyValue) {
return propertyValue.toString();
}
}
return "";
}
/**
* 根據(jù)Dict的codeName屬性或者字段名稱箍镜,獲取字段值 <br/>
* <pre>
* 注意:
* 如果當前字段沒有以Name結(jié)尾源祈,那就取當前字段的值煎源;也就是根據(jù)當前字段的值轉(zhuǎn)換。
* </pre>
*
* @param obj : 對象
* @param dict : 字段注解對象
* @param fieldName : 字段名稱
* @return java.lang.String
* @author liam
*/
private static String getFieldValue(Object obj, Dict dict, String fieldName) {
String codeName = dict.codeName();
if (Strings.isNullOrEmpty(codeName)) {
// 如果當前字段是Name結(jié)尾香缺,進行截仁窒;否則取當前字段名稱
int endNameIndex = fieldName.lastIndexOf(NAME_SUFFIX);
if (endNameIndex != -1) {
codeName = fieldName.substring(0, endNameIndex);
} else {
codeName = fieldName;
}
}
return getPropertyValue(obj, codeName);
}
/**
* 獲取dictCode+字段名稱的組合
*
* @param dictCode : 字段映射的字典碼值
* @param fieldName : 字段名稱
* @return dictCode:fieldName
* @author liam
*/
private static String getDictCodeCombineName(String dictCode, String fieldName) {
// 為了解決list下的fieldName相同導致fieldKeyMap的key相同图张,添加dictCode唯一區(qū)分
return dictCode + SIGN_COLON + fieldName;
}
/**
* 獲取一個類的屬性的泛型原献;如果沒有泛型,則返回null埂淮;
* P.s 如果有多個,取第一個写隶;如果有多層泛型倔撞,也返回null,比如List<Map>
*
* @param cls :
* @param property : 屬性名
* @author liam
*/
public static Class getGeneric(Class cls, String property) {
try {
Type genericType = cls.getDeclaredField(property).getGenericType();
// 如果是泛型參數(shù)的類型
if (null != genericType && genericType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) genericType;
Type type = pt.getActualTypeArguments()[0];
// 這里慕趴,type也可能是 ParameterizedType痪蝇, 直接不考慮
if (type instanceof Class) {
return (Class) type;
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return null;
}
/**
* 判斷不在過濾類(常用基本數(shù)據(jù)類型)中
*/
private static boolean notInFilterClass(Class cls) {
return !Lists.newArrayList(String.class, BigDecimal.class, Date.class,
Integer.class, int.class,
Double.class, double.class,
Boolean.class, boolean.class,
Long.class, long.class,
Character.class, char.class)
.contains(cls);
}
/**
* 函數(shù)式接口:類似freemarker中的<#nested>處理
*/
@FunctionalInterface
public interface NestedFunction {
/**
* 無參無返回值的方法運行
*/
void run();
}
}