概述
由于业务的需要,录入很多信息,而这些信息中绝大多数都是字典code,所以就会有很多数据字典的定义。
而前台数据的获取基本上都是下拉列表的选择。在保存或编辑的时候,返回到前端只要字典code就可以,但是在查看的时候,需要将字典翻译同时返回用于展示。
基于这样的前提下,受Hibernate Validator校验方式的启示,通过自定义注解的方式来实现。
这样做有一个非常大的优势:可拓展性强,以后新增字典项或字典类型直接加入字典仓库,然后在相关字段上声明就可以了。
实现的功能
由于公司项目的特殊性,存在老的字典码和新字典码映射转换;所以除了字典code的翻译,还有字典code的转换。
除了一对一的字典code的翻译,还有一种是上下级的翻译,比如省市区
- 一般字典code的翻译
- 上下级字典code的翻译
- 字典code的转换(主要是新老系统的兼容对接)
具体实现
话不多说,来看示例:
- 首先在DTO中添加自定义的注解
@Data
public class PersonDTO {
/** 证件类型(字典) */
private String custCertificateType;
/** 学历(字典) */
private String custEducation;
/** 学位(字典) */
private String custDegree;
/** 单位所在省份(字典) */
private String companyProvince;
/** 单位所在城市(字典) */
private String companyCity;
/** 单位所在区/县(字典) */
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;
/** 单位所在区/县(名称) */
@Dict(value = DICT_SYSTEM_AREA, isChild = true, parentField = "companyCity")
private String companyDistrictName;
}
自定义注解Dict
的定义如下:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Dict {
/** 数据字典code类型; 如果查找二级编码,则该值存储二级编码类型前缀,比如AR表示区域 */
String value() default "";
/** 存储编号的字段名称 如果是添加了Name后缀,则可省略*/
String codeName() default "";
/** 是否是子元素,如果是true,则 parentField不得为空*/
boolean isChild() default false;
/** 子元素的上级code字段名 */
String parentField() default "";
/** 当转换后的值为null的时候,是否进行赋值;默认为false,表示不覆盖 */
boolean whetherSetIfNull() default false;
/** 是否是转换编码;也就是说默认不转换code,而是翻译成name */
boolean whetherConvertCode() default false;
}
- 其次是在接口输出list结果前调用service方法解析@Dict注解
使用注解的方式自定义解析,比较灵活。哪里需要使用,哪里调用方法解析就可以。
@Autowired private DictService dictService;
// 这里person从数据库查询出来的对象
PersonDTO person = ……
// 数据字典翻译
dictService.translationDictCode(person);
// translationDictCode 方法的实现
@Override
public void translationDictCode(Object obj) {
DictUtil.translationDictCode(obj, this::queryDictMapData, false);
}
// 其中DictUtil.translationDictCode方的定义如下:
/**
* 将DTO对象(对象中含有有Dict注解的对象)的字典码转换为字典值名称(注:通过Dict注解标识码值的字段)
*
* @param obj 处理的对象
* @param convertMapFun 字典类型和值的集合转换为名称
* @param whetherConvertCode 是否转换编码
*/
public static void translationDictCode(final Object obj, Function<Map<String, String>, Map<String, String>> convertMapFun,
boolean whetherConvertCode)
// 这里的queryDictMapData定义如下:做为translationDictCode中的入参convertMapFun,它是接收一个map,然后返回另一个map,其中param是根据解析DTO上的Dict注解构造出来的查询参数。
/**
* 查询字典码对应的name
*/
private Map<String, String> queryDictMapData(Map<String, String> param) {
// 调用接口根据入参param查询数据字典翻译出来的name值
// 一般数据字典放在redis缓存中,统一一个地方处理;这里可以根据这种入参去缓存中查询,然后封装返回需要格式的数据
// 主要就是解析入参,然后构建特定格式的返回值,比如:{"xx:certificateType=1": "身份证", "xx:certificateType=2": "护照"}
return ……;
}
对queryDictMapData方法的入参和返回值的解读:
param 示例:{"relationship": "1,2", "AR:1001": "", ……}
其中key是字典type,value是要查询的字典值,有多个则用逗号分隔;
relationship对应一个字典项列表,比如[{"1": "朋友"}, {"2", "同事"}, {"3", "亲属"}……]
而value说明要查询字典code 1和2对应的翻译,那么返回结果如下:
因为字典type可能会有冒号字符,这里用=做链接符
{"relationship=1": "朋友", "relationship=2": "同事"}
-
DictUtil
的部分方法
首先从方法translationDictCode
一步步来剖析:
其实一开始是没有参数whetherConvertCode,后来为了兼容一种情况,就是先字典code的转换(老系统的code转换为新系统的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<>();
// 解析需要转换的字典码(递归处理)
parseFieldCodeValue(obj, fieldKeyMap, keyCodeMap, whetherConvertCode);
// 字典值转换(具体转换的方法)
Map<String, String> convertedMap = convertMapFun.apply(keyCodeMap);
// 回填字典码对应的值(递归处理)
backFillCodeName(obj, fieldKeyMap, convertedMap, whetherConvertCode);
}
除了对Object进行翻译,直接对List<Object>也是可以的,方法如下:
/**
* 将集合中所有对象的字典码转换为字典值 (注:通过Dict注解标识码值)
*
* @param list 处理的集合
* @param convertMapFun 字典类型和值的集合转换为名称
* @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<>();
// 构造填充映射关系
list.forEach(obj -> fillDictCodeConvertMap(obj, fieldKeyMap, keyCodeMap));
// 字典值转换
Map<String, String> convertedMap = convertMapFun.apply(keyCodeMap);
// 给field赋值
list.forEach(obj -> setObjAllFieldValue(obj, fieldKeyMap, convertedMap));
}
完整的示例
- 测试类和运行结果
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("转换之前");
personList.forEach(DictServiceTest::printJson);
// 字典翻译转换
dictService.translationListCodeName(personList);
System.out.println("转换之后");
personList.forEach(DictServiceTest::printJson);
}
private static void printJson(Object obj) {
try {
System.out.println(objectMapper.writeValueAsString(obj));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
// 运行结果:
// 转换之前
// {"custCertificateType":"1"}
// {"custCertificateType":"2","companyProvince":"10001","companyCity":"1000101"}
// 转换之后
// {"custCertificateType":"1","custCertificateTypeName":"身份证"}
// {"custCertificateType":"2","companyProvince":"10001","companyCity":"1000101","custCertificateTypeName":"护照","companyProvinceName":"河北省","companyCityName":"邯郸市"}
- DTO和常量类
/**
* 数据字典相关常量
*
* @Author liam
*/
public interface DictConstant {
/** “=”等号*/
String SIGN_EQUAL = "=";
/** “:”冒号 */
String SIGN_COLON = ":";
/** “,”逗号*/
String SIGN_COMMA = ",";
/**XX系统接口的数据字典前缀*/
String DICT_SYSTEM_XX = "XX";
/**省市区数据字典前缀*/
String DICT_SYSTEM_AREA = "AR";
/** XX系统数据字典 */
String DICT_XX_COLON = DICT_SYSTEM_XX + SIGN_COLON;
/** 省市区数据字典 */
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";
/**省市区数据字典标识: 省*/
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;
/** 单位所在区/县(字典) */
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;
/** 单位所在区/县(名称) */
@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);
}
/**
* 查询字典码对应的name
*/
private Map<String, String> queryDictMapData(Map<String, String> typeCodeMap) {
// 调用接口根据入参param查询数据字典翻译出来的name值
// 一般数据字典放在redis缓存中,统一一个地方处理;这里可以根据这种入参去缓存中查询,然后封装返回需要格式的数据
Map<String, String> result = new HashMap<>();
if (typeCodeMap.isEmpty()) {
return result;
}
// 获取测试数据
Map<String, List<DictDTO>> testData = getTestData();
// 解析请求参数,构造返回结果
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是否相同,如果找到结束循环,取出结果
*
* @param dictCode : 字典code
* @param hashKey : 字典码
* @param list : 字典集合
* @param result : 最终结果集
*/
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;
}
}
}
/**
* 获取测试数据
*/
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();
}
// 假如数据字典是如下列表
/** 证件列表 */
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", "秦皇岛市")
);
/** 临时测试数据 */
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", "浦东新区")
.build();
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DictDTO {
/** 字典码 */
private String code;
/** 字典名称 */
private String name;
}
- 工具类DictUtil,数据字典转换的核心
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.*;
/**
* 数据字典工具类
*
* @Author libing
*/
@Slf4j
public class DictUtil {
/**
* Name后缀,默认的字典名称字段命名规则
*/
private static final String NAME_SUFFIX = "Name";
/**
* 将DTO对象(对象中含有有Dict注解的对象)的字典码转换为字典值名称(注:通过Dict注解标识码值的字段)
*
* @param obj 处理的对象
* @param convertMapFun 字典类型和值的集合转换为名称
* @param whetherConvertCode 是否转换编码
*/
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<>();
// 解析需要转换的字典码(递归处理)
parseFieldCodeValue(obj, fieldKeyMap, keyCodeMap, whetherConvertCode);
// 字典值转换(具体转换的方法)
Map<String, String> convertedMap = convertMapFun.apply(keyCodeMap);
/* 这里 keyCodeMap 、 fieldKeyMap 、 convertedMap 三者之间的关系
* keyCodeMap 是字典type与字典code值的关系
* 比如: {"xx:certificateType": "1,2"}
* convertedMap 外部方法处理的结果,字典翻译的结果
* 比如: {"xx:certificateType=1": "身份证", "xx:certificateType=2": "护照"}
* fieldKeyMap 是字典code所对应的字段与转换结果的关系
* 比如: {"1:custCertificateType": "xx:certificateType=1", "2:custCertificateType": "xx:certificateType=2"}
*/
// 回填字典码对应的值(递归处理)
backFillCodeName(obj, fieldKeyMap, convertedMap, whetherConvertCode);
}
/**
* 将集合中所有对象的字典码转换为字典值 (注:通过Dict注解标识码值)
*
* @param list 处理的集合
* @param convertMapFun 字典类型和值的集合转换为名称
* @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<>();
// 构造填充映射关系
list.forEach(obj -> fillDictCodeConvertMap(obj, fieldKeyMap, keyCodeMap));
// 字典值转换
Map<String, String> convertedMap = convertMapFun.apply(keyCodeMap);
// 给field赋值
list.forEach(obj -> setObjAllFieldValue(obj, fieldKeyMap, convertedMap));
}
/**
* 通过对Object的分析,构造填充2种转换关系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));
}
/**
* 解析需要转换的字典码(递归处理)
*
* @param obj : 取字段的对象
* @param fieldKeyMap : (dictCode+字段名称)和接口查询返回的字典key的映射
* @param keyCodeMap : 用于接口查询的字典key和code的映射
* @param whetherConvertCode: 是否转换编码
* @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();
// 构造填充映射关系
Arrays.stream(declaredFields).forEach(field ->
parseFieldObj(field, obj, whetherConvertCode,
fieldObj -> parseFieldCodeValue(fieldObj, fieldKeyMap, keyCodeMap, whetherConvertCode), // 递归处理
// 解析注解字段信息
() -> parseDictAnnotation(obj, field, fieldKeyMap, keyCodeMap)
)
);
}
/**
* 回填字典码对应的值(递归处理)
*
* @param obj : 回填字段的对象
* @param fieldKeyMap : (dictCode+字段名称)和接口查询返回的字典key的映射
* @param convertedMap : 字典值转换后的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对象,对基本数据类型和复杂类型做不同的处理
*
* @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直接跳过
}
// 需要数据字典转换的属性:有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,继续递归处理
if (Collection.class.isAssignableFrom(cls)) {
// 如果是list-map结果,则这里返回null
Class generic = getGeneric(obj.getClass(), field.getName());
if (null != generic && notInFilterClass(generic)) {
// 循环递归处理
((Collection) fieldValue).forEach(recursiveFunc::accept);
}
}
// 非基本数据类型
else if (notInFilterClass(cls)) {
recursiveFunc.accept(fieldValue);
}
}
);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
/**
* 解析含有注解Dict的field信息,并将其充到2种转换关系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();
// 根据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);
}
// 属性名对应的 字典码和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);
}
// 属性名对应的 字典码和code
fieldKeyMap.put(fieldName, dictType + SIGN_EQUAL + code);
}
}
}
/**
* 给一个对象所有的含有Dict注解的字段赋值
*
* @param obj : 取字段的对象
* @param fieldKeyMap : (dictCode+字段名称)和接口查询返回的字典key的映射
* @param convertedMap : 字典值转换后的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设置值
*
* @param obj : 对象
* @param field : 字段
* @param fieldKeyMap : 字段值映射关系
* @param convertedMap :转换码关系
* @author liam
*/
private static void setFieldValue(Object obj, Field field, Map<String, String> fieldKeyMap,
Map<String, String> convertedMap) {
// 根据字段code和字段名称构建组合名称
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); // 转换后的值
try {
field.setAccessible(true);
// 当转换后的值为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);
}
});
}
/**
* 获取对象里指定属性的值,并转化为字符串
*
* @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 "";
}
/**
* 根据Dict的codeName属性或者字段名称,获取字段值 <br/>
* <pre>
* 注意:
* 如果当前字段没有以Name结尾,那就取当前字段的值;也就是根据当前字段的值转换。
* </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结尾,进行截取;否则取当前字段名称
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唯一区分
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();
// 如果是泛型参数的类型
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;
}
/**
* 判断不在过滤类(常用基本数据类型)中
*/
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);
}
/**
* 函数式接口:类似freemarker中的<#nested>处理
*/
@FunctionalInterface
public interface NestedFunction {
/**
* 无参无返回值的方法运行
*/
void run();
}
}