前言
在前后端分离的项目中,存在很多字段是下拉框选项的情况。 比如:性别字段
代码值value | 显示值label |
---|---|
1 | 男 |
0 | 女 |
2 | 其他 |
在数据库中,存的是代码值,前端显示的时候,需要显示值。这时候,在返回的时候,就需要做一次代码转换。 | |
(为什么不是前端自己转换?为了统一管理和方便变更。) | |
# 场景 |
- 字典字段为空,则不转换。
- 一个对象中的多个字段是字典字段。
- 对象中的子对象也有字典字段。
- 对象的子对象是集合,也存在字典字段。
正文
因为在业务开发过程中,存在非常多的这种实体,所以需要制定一个统一处理。减少业务开发的工作量。 (最终结果,减少到只需要一行注解)
升级版连接 Jackson 序列号字典字段属性(升级)
一 添加配置
项目中都有 fasterxml 的依赖吧,没有自己添加。 添加类 JacksonConfig,用于配置自定义内省器。
import com.cah.project.conf.serializer.ProjectJacksonAnnotationIntrospector;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
/**
* 功能描述: Jackson配置 <br/>
*/
@Configuration
public class JacksonConfig {
public JacksonConfig() {}
@Bean
@Primary
@ConditionalOnMissingBean({ObjectMapper.class})
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
// 定义 不为空,才能进行序列化
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 添加 自定义 项目注解内省器
objectMapper.setAnnotationIntrospector(new ProjectJacksonAnnotationIntrospector());
return objectMapper;
}
}
复制代码
二 添加项目内省器
import com.cah.project.core.annotation.Dict;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
/**
* 功能描述: 自定义项目内神器 <br/>
*/
public class ProjectJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector {
public ProjectJacksonAnnotationIntrospector() {}
@Override
public Object findSerializer(Annotated a) {
// 如果是字典注解
Dict dict = _findAnnotation(a, Dict.class);
if(dict != null) {
return new DictSerializer(dict.type(), dict.separator());
}
// 其他扩展。。。
return super.findSerializer(a);
}
}
复制代码
其中 Dict 注解类 为自定义注解。内容如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 功能描述: 字典注解 <br/>
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Dict {
/** 字典类型 */
String type();
/** 字典类型描述 */
String desc() default "";
/** 如果多个字典拼接,自定义分隔符 */
String separator() default ",";
}
复制代码
三 自定义序列化规则
import com.cah.project.core.cache.DictCacheUtil;
import com.cah.project.core.domain.bo.DictData;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
/**
* 功能描述: 字典序列化 <br/>
*/
public class DictSerializer extends StdSerializer<Object> {
/** 字典类型 */
private final String type;
/** 分隔符 */
private final String separator;
public DictSerializer(String type, String separator) {
super(Object.class);
this.type = type;
this.separator = separator;
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeObject(this.getLabel(value.toString()));
}
/**
* 功能描述: 核心逻辑 <br/>
* 将码值 转换成 显示对象
* @param code 数据内容
* @return "com.cah.project.core.domain.out.DictData"
*/
private DictData getLabel(String code) {
DictData dd = new DictData();
dd.setType(this.type);
dd.setValue(code);
if(this.separator != null && this.separator.length() > 0) {
String[] strs = code.split(this.separator);
StringBuilder sb = new StringBuilder();
for(String str : strs) {
// 从缓存中获取字典。如果不行,通过SpringUtil.getBean(); 获取服务处理
sb.append(DictCacheUtil.getLabel(this.type, str)).append(this.separator);
}
dd.setLabel(sb.substring(0, sb.length() - this.separator.length() + 1));
} else {
// 从缓存中获取字典。如果不行,通过SpringUtil.getBean(); 获取服务处理
dd.setLabel(DictCacheUtil.getLabel(this.type, code));
}
return dd;
}
}
复制代码
四 字典缓存工具
字典缓存工具,使用的是hutool工具包中的缓存。 至于缓存如何清理与同步,这是另外一个问题。也可以从redis中获取缓存。 缓存是为了加快转换速度。各自发挥就好了。
import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;
import com.cah.project.core.domain.bo.DictData;
import java.util.HashMap;
import java.util.Map;
/**
* 功能描述: 字典缓存工具 <br/>
*/
public class DictCacheUtil {
/** 字典缓存,最外层key为 字典的type,第二层缓存的key为字典的value */
protected static Cache<String, Map<String, DictData>> dictCache = CacheUtil.newLFUCache(10000);
/**
* 功能描述: 添加缓存 <br/>
*
* @param type 字典类型
* @param dd 字典对象
*/
public static void put(String type, DictData dd) {
Map<String, DictData> map = dictCache.get(type);
if(map == null) {
map = new HashMap<>();
dictCache.put(type, map);
}
map.put(dd.getValue(), dd);
}
/**
* 功能描述: 获取字典对象 <br/>
*
* @param type 字典类型
* @param value 字典码值
* @return "com.cah.project.core.domain.bo.DictData"
*/
public static DictData get(String type, String value) {
Map<String, DictData> map = dictCache.get(type);
if(map == null) {
return new DictData(type, value, value);
}
return map.getOrDefault(value, new DictData(type, value, value));
}
/**
* 功能描述: 获取显示值 <br/>
*
* @param type 字典类型
* @param value 字典码值
* @return "java.lang.String"
*/
public static String getLabel(String type, String value) {
Map<String, DictData> map = dictCache.get(type);
if(map == null) {
return value;
}
DictData dd = map.get(value);
return dd != null ? dd.getLabel() : value;
}
}
复制代码
五 测试对象
测试对象为返回的实体对象。其中字段字段都添加了 @Dict 注解
import com.cah.project.core.annotation.Dict;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class TestOutVO implements Serializable {
private String name;
@Dict(type = "STATUS_CD")
private String status;
@Dict(type = "SEX_CD")
private String sex;
private List<ChildOutVO> children;
}
复制代码
import com.cah.project.core.annotation.Dict;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChildOutVO {
private String name;
@Dict(type = "TYPE_CD")
private Integer type;
@Dict(type = "SEX_CD")
private String sex;
}
复制代码
六 测试接口
import cn.hutool.core.collection.ListUtil;
import com.cah.project.core.domain.out.CommonResult;
import com.cah.project.test.out.ChildOutVO;
import com.cah.project.test.out.TestOutVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* <p>测试 控制层 </p> <br/>
*
* @author cah
* @since 2021-10-06
*/
@Api(tags = {"测试 控制层"})
@RestController
@RequestMapping("/test")
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class TestController {
@GetMapping("getById")
@ApiOperation(value="测试获取数据")
public CommonResult<TestOutVO> getById(@RequestParam("id") String id){
TestOutVO outVO = new TestOutVO();
outVO.setName("名称");
outVO.setStatus("1");
outVO.setSex("0,1");
ChildOutVO c1 = ChildOutVO.builder().name("子1").type(2).sex("0").build();
ChildOutVO c2 = ChildOutVO.builder().name("子2").type(3).sex("1").build();
List<ChildOutVO> list = ListUtil.toList(c1, c2);
outVO.setChildren(list);
return CommonResult.data(outVO);
}
}
复制代码
七 测试结果
总结
通过以上的方式,可以解放业务开发过程中反复的字典映射操作。但是也存在一些问题。比如复杂的转换,非缓存数据转换等。 对于以上问题,可以使用 @JsonSerialize(using = XxxSerializer.class) 的方式进行处理。