注解实现字典值转换
1 背景
项目中对数据字典值、机构名称、创建人和更新人等编码值的转换方式一般通过关联特定表查询对应的name,
这样的查询效率低且对于不熟悉业务的新员工很不友好,因此想要开发通过注解实现编码值转换的功能
2 设计思路
通过在需要转换的字段上添加注解标识需要转换的字段,再通过fastjson的序列化完成对特定字段的处理
3 实现方式
通过继承WebMvcConfigurer进行统一的http请求信息管理,使用FastJsonHttpMessageConverter作为http消息转换类,
添加自定义的序列化类,完成注解字段的转换
maven依赖
<!-- fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.5</version>
</dependency>
3.1 注解
3.1.1 注解结构
注解@CodeValue如下所示
public @interface CodeValue {
/*** 编码类型 */
CodeType type() default CodeType.DICT;
/** 字典类型编码*/
DictType dictType() default DictType.DEFAULT;
/** 默认值*/
String defaultValue() default "";
/**
* @Description
* 扩展字段名,作为描述编码值的name字段,
* 不填时默认为编码字段加Name后缀
*/
String fieldName() default "";
}
type : 类型是CodeType ,代表的是编码值的类型,默认是字典类型
public enum CodeType {
DICT,
ORG,
USER;
}
dictType:类型是DictType,当CodeValue是DICT字典类型时,该字段表示的是字典类型的编码值,如性别SEX
public enum DictType {
DEFAULT("","空"),
PROVINCE("province","省份"),
SEX("sex","性别"),
IS_START("is_start","是否启动");
/** 字典类型编码 */
private String code;
/*** 描述*/
private String description;
}
defaultValue: 类型是String,该字段表示的是当注解的字段值为空时的默认值
3.1.2 使用方法
注解直接在字段上使用,如下所示
public class BussinessDTO implements Serializable {
// 转换字典类型不需要指定type字段,指定字典类型的枚举即可
@CodeValue(dictType = DictType.SEX)
private String sex;
// 转换字典类型必须有默认值时,可以将默认值填写到defaultValue字段上
@CodeValue(dictType = DictType.PROVINCE, defaultValue = "BEI_JING")
private String province;
// 转换用户id字段时,type字段使用USER枚举类
@CodeValue(type = CodeType.USER)
private String createUser;
// 转换机构id字段时,type字段使用ORG枚举类,通过fieldName属性指定转换后的字段名
@CodeValue(type = CodeType.ORG,fieldName="orgName")
private String orgId;
}
3.1.3 返回样例
{
"sex": "1",
"sexName": "男",
"province": "GUANG_DONG",
"provinceName": "广东省",
"createUser": "u123i1231oi23123i12o31",
"createUserName": "张三",
"orgId": "asdasfasfasfsafas",
"orgName": "财政部"
}
3.2 实现过程
继承WebMvcConfigurer
@Configuration
public class WebMvcConfigurer implements WebMvcConfigurer {
private static final String DATA_FORMAT = "yyyy-MM-dd HH:mm:ss";
@Override
public void configureMessageConverters(List<?>> converters) {
converters.add(fastJsonHttpMessageConverter());
}
@Bean
public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
converter.setFastJsonConfig(defaultFastJsonConfig());
converter.setDefaultCharset(StandardCharsets.UTF_8);
converter.setSupportedMediaTypes(defaultMediaTypes());
return converter;
}
// 默认fastjson配置
private FastJsonConfig defaultFastJsonConfig() {
FastJsonConfig fastJsonConfig = new FastJsonConfig();
//日期格式转换
fastJsonConfig.setDateFormat(DATA_FORMAT);
fastJsonConfig.setSerializerFeatures(
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.WriteMapNullValue,
SerializerFeature.WriteNullBooleanAsFalse,
SerializerFeature.WriteNullListAsEmpty,
SerializerFeature.WriteNullStringAsEmpty
);
// 设置自定义的序列化配置类
FastJsonSerializeConfig fastJsonSerializeConfig = new FastJsonSerializeConfig();
fastJsonConfig.setSerializeConfig(fastJsonSerializeConfig);
return fastJsonConfig;
}
// 默认媒体类型列表
private List defaultMediaTypes() {
...
}
}
自定义序列化配置类FastJsonSerializeConfig
public class FastJsonSerializeConfig extends SerializeConfig {
@Override
public ObjectSerializer getObjectWriter(Class<?> clazz) {
ObjectSerializer writer = super.get(clazz);
if (writer != null) {
return writer;
}
Map codeValueAnnotationFields = ReflectUtil.listAnnotationCode v a lueField(clazz);
if (!CollectionUtils.isEmpty(codeValueAnnotationFields)) {
// 包含自定义字典转换注解,返回自定义序列化类
writer = new CodeValueSerializer(clazz);
}
if (writer == null) {
return super.getObjectWriter(clazz);
}
put(clazz, writer);
return writer;
}
}
ReflectUtil
public class ReflectUtil {
/**
* 注解字段集合
* 以类为key,
*/
private static final Map<?>, Map> annotationFieldMap = new ConcurrentHashMap<>();
/**
* 根据类获取带{Link @CodeValue}注解的字段列表,以字段名为key
*
* @param clazz 类型
*/
public static Map listAnnotationCodevalueField(Class<?> clazz) {
if (clazz == null) {
return null;
}
// 存在直接返回
if (annotationFieldMap.containsKey(clazz)) {
return annotationFieldMap.getOrDefault(clazz, null);
}
Map fieldList = getFieldMap(clazz);
annotationFieldMap.put(clazz, fieldList);
return fieldList;
}
/**
* @Description 获取字段集合, 以字段名为key
*/
public static Map getFieldMap(Class<?> clazz) {
Map fieldMap = new HashMap<>();
Field[] _fields = clazz.getDeclaredFields();
for (Field field : _fields) {
// 静态修饰的字段忽略
if (Modifier.isFinal(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
continue;
}
if (field.isAnnotationPresent(CodeValue.class)) {
fieldMap.put(field.getName(), field);
}
}
Class<?> superClass = clazz.getSuperclass();
if (Objects.nonNull(superClass)) {
fieldMap.putAll(getFieldMap(superClass));
}
return fieldMap;
}
}
自定义序列化类CodeValueSerializer
public class CodeValueSerializer extends JavaBeanSerializer {
private static final String SUFFIX_VALUE = "Name";
private static final char JSON_FIELD_SEPARATOR = ',';
public CodeValueSerializer(Class<?> beanType) {
super(beanType);
}
/**
* @Description 重写应用方法,添加字段填充方法
*/
@Override
public boolean apply(JSONSerializer jsonBeanDeser, Object object, String key, Object propertyValue) {
try {
addFieldHandler(jsonBeanDeser, object, key, propertyValue);
} catch (IOException e) {
log.error("编码值翻译失败:{}", e);
}
return super.apply(jsonBeanDeser, object, key, propertyValue);
}
/**
* @Description 新增字段After过滤器
*/
public void addFieldHandler(JSONSerializer serializer, Object object, String key, Object propertyValue) throws IOException {
if (object == null) {
return;
}
Map fieldMap = ReflectUtil.listAnnotationCodeValueField(object.getClass());
if (CollectionUtils.isEmpty(fieldMap) || fieldMap.get(key) == null) {
return;
}
CodeValue codeValue = fieldMap.get(key).getAnnotation(Code v a lue.class);
String fillFieldName = getFillFieldName(codeValue, key);
String fillFieldValue = getFillFieldValue(codeValue,propertyValue);
Field Field = ClassUtil.getDeclaredField(object.getClass(), fillFieldName);
if (Field != null) {
// 有相同的字段名,重新赋值
BeanUtil.setFieldValue(object, fillFieldName, fillFieldValue);
return;
}
if (isFirstField(serializer, key)) {
// 如果是首字符进行特殊处理
serializer.out.writeFieldValue(' ', fillFieldName, fillFieldValue);
// 在填充字段fieldName:FieldValue后,补充逗号',' 防止逗号丢失
serializer.out.write(JSON_FIELD_SEPARATOR);
return;
}
// 不是首字母,直接写键值对
serializer.out.writeFieldValue(JSON_FIELD_SEPARATOR, fillFieldName, fillFieldValue);
}
/**
* @Description 是否是首字符
*/
public boolean isFirstField(JSONSerializer serializer, String key) {
FieldSerializer[] getters;
if (serializer.out.isSortField()) {
getters = this.sortedGetters;
} else {
getters = this.getters;
}
FieldSerializer fieldSerializer = getters[0];
String startFiledName = fieldSerializer.fieldInfo.name;
if (key.equals(startFiledName)) {
return true;
}
return false;
}
/**
* @Description 获取编码名称字段的字段名
*/
public String getFillFieldName(CodeValue codeValue, String fieldName) {
String newFieldName = fieldName + SUFFIX_VALUE;
String defaultFieldName = codeValue.fieldName();
if (!StringUtils.isEmpty(defaultFieldName)) {
newFieldName = defaultFieldName;
}
return newFieldName;
}
public String getFillFieldValue(CodeValue codeValue, Object propertyValue) {
String fieldValue = "";
CodeType codeType = codeValue.type();
DictType dictType = codeValue.dictType();
String typeCode = dictType.getCode();
// 如果是空字段枚举类,返回空字符串,防止忘记赋值字典类型
if (CodeType.DICT.equals(codeType) && DictType.DEFAULT.equals(dictType)) {
return "";
}
String defaultValue = codeValue.value();
String value = propertyValue == null ? defaultValue : String.valueOf(propertyValue);
fieldValue = CodeValueTranslateUtil.getName(codeType, value, typeCode);
if (fieldValue == null) {
fieldValue = "";
}
return fieldValue;
}
}
CodeValueTranslateUtil
class CodeValueTranslateUtil {
// 编码值转换方法
public static getName(String codeType,String value,String typeCode){
CacheManager cacheManager = SpringContextHolder.getBean(CacheManager.class);
if(CacheManager==null){
return "";
}
return CacheManager.getName(codeType, value, typeCode);
}
CacheManager的使用方式见redisson + CacheManager缓存管理
本文介绍了一种通过注解实现数据字典值转换的方法,旨在提高查询效率和提升新员工对业务的理解。文章详细阐述了设计思路,包括在需要转换的字段上添加注解,并利用Fastjson进行序列化处理。此外,还提供了注解结构、使用方法及实现过程的详细步骤。
649

被折叠的 条评论
为什么被折叠?



