概述
由于业务的需要,录入很多信息,而这些信息中绝大多数都是字典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
对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
除了对Object进行翻译,直接对List
/**
* 将集合中所有对象的字典码转换为字典值 (注:通过Dict注解标识码值)
*
* @param list 处理的集合
* @param convertMapFun 字典类型和值的集合转换为名称
* @author liam
*/
public static void translationListCodeName(List list, Function
完整的示例
- 测试类和运行结果
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 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 queryDictMapData(Map typeCodeMap) {
// 调用接口根据入参param查询数据字典翻译出来的name值
// 一般数据字典放在redis缓存中,统一一个地方处理;这里可以根据这种入参去缓存中查询,然后封装返回需要格式的数据
Map result = new HashMap<>();
if (typeCodeMap.isEmpty()) {
return result;
}
// 获取测试数据
Map> testData = getTestData();
// 解析请求参数,构造返回结果
typeCodeMap.forEach((key, value) -> {
// 查询获取字典项列表
List 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 list, Map result) {
for (DictDTO dto : list) {
if (dto.getCode().equals(dictCode)) {
result.put(hashKey + SIGN_EQUAL + dictCode, dto.getName());
break;
}
}
}
/**
* 获取测试数据
*/
private Map> getTestData() {
return ImmutableMap.>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 demoTestData = ImmutableMap.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