众所周知,后端存储一些灵活可变的参数时,都会以字典的形式入库,常见的业务中,下拉框,地区联动等数据,都是这样存储的。前端
存时一时爽啊,可是用户是看不懂字典code的,因此取的时候须要把字典值再转换回来。这样取得时候就增长了工做量,常见过的方案基本有:单独前端缓存维护一个字典文件,根据code在前端进行转换。还有设计冗余字段,或者直接在sql暴力join一波。java
“vt啊,xx项目代码,关于字典code的sql关联太多表了,你看看能不能改为从缓存取,优化一下”,随着PM的一句呼叫,立刻打开xx项目检查主要的几个接口查看sql写法,果真相似这样的风格...sql
{
"name": "vt",
"age": 18,
"areaCode": "20005", // 地区code
"cityCode": "201", // 城市code
"provinceCode": "03" //省code
}
复制代码
咱们先从这个测试接口来分析,思考怎样使用缓存来获取label,以及让开发者,简便的进行转换。笔者的思路是,若是想要作到对代码低入侵,而且修改少的话,须要从共通处理入手。接口从后台返回到前端,是会通过json序列化的,因此我打算从序列化层入手。json
笔者环境是SpringCloud,没错就是一堆小Springboot,那咱们就从Springboot默认的序列化框架Jackson入手,首先搞个配置,让咱们把咱们自定义的SerializerModifier注入进去。后端
@Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
SimpleModule simpleModule = new SimpleModule().setSerializerModifier(new DictSerializerModifier());
builder.modules(simpleModule);
return builder;
}
复制代码
既然是优化字典相关的,我就起名叫DictSerializerModifier,这个东西大体就是告诉框架,序列化修改你别干了,我干就好了, 这个DictSerializerModifier里面是什么呢,其实它也不是主要处理,以下:缓存
public class DictSerializerModifier extends Jdk8BeanSerializerModifier{
@Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
return new DictSerializer(null, (JsonSerializer<Object>) serializer);
}
}
复制代码
此处继承BeanSerializerModifier也是能够的,Jdk8BeanSerializerModifier是在java8中,对changeProperties方法有些内部优化,其实对业务影响不大。
咱们重要的是重写modifySerializer方法,告诉框架咱们要用哪一个具体的类来处理序列化内容。则我新建了DictSerializer实现来处理具体的操做。细心的同窗能够观察到,返回实例时,我将默认的Serializer传到了自定义的DictSerializer,这点是为了若是没有上文提到的【再次一顿操做】的状况,则用默认的Serializer就好了。
先不用着急弄DictSerializer,咱们先自定义个注解,方便让开发者指定,哪些字段想要进行什么转换:bash
ElementType type();
enum ElementType {
PROVINCE, CITY, AREA
}
复制代码
这样做为开发者的话,就像下面所示,只须要在字段上加上注解和想转换的类型就能够了。app
@DictAnnotation(type = AREA) // 我想把这个code,增长AREA规则的转换
private String areaCode;
@DictAnnotation(type = CITY) // 我想把这个code,增长CITY规则的转换
private String cityCode;
复制代码
对了,咱们的目标是从缓存里取,这个好说,笔者增长了一个RedisUtils.dictCodeToLabel方法,传入字段注解中的枚举类型和具体的code,就能获得label了。固然这不是重点,重点是DictSerializer是最主要的处理类,实现以下,我将细节已经写在了注释中:框架
public class DictSerializer extends JsonSerializer<Object> implements ContextualSerializer {
// 生成目标的Label字段后缀
private static final String LABEL_SUFFIX = "VtTestLabel";
// 自定义的注解中的枚举内部类: PROVINCE, CITY, AREA等
private DictAnnotation.ElementType type;
// 上文提到的默认Serializer,从构造函数传入
private JsonSerializer<Object> defSerializer;
public DictSerializer(DictAnnotation.ElementType type, JsonSerializer<Object> jsonSerializer) {
this.defSerializer = jsonSerializer;
this.type = type;
}
/**
* 建立上下文关联方法,实现ContextualSerializer便可
* 这里主要用户获取自定义注解,由于只靠serialize自己是获取不到字段的注解信息的
* */
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
// 经过BeanProperty获取自定义注解
DictAnnotation.ElementType elementType = Optional.ofNullable(property).map(b -> b.getAnnotation(DictAnnotation.class))
.map(d -> d.type()).orElse(null);
// 若是该字段没有标识该注解,elementType则为null,使用默认的Serializer进行序列化,不然才用自定义的DictSerializer
return elementType == null ? defSerializer : new DictSerializer(elementType, defSerializer);
}
/**
* 主要方法,处理增长一个label字段的操做
* */
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 字段自己的序列化操做,好比目前传入的是areaCode=10001,则先序列化areaCode:10001
defSerializer.serialize(value, gen, serializers);
// 有自定义注解的type标识,咱们就增长一个对应的label,例:areaCode则 增长一个areaCodeVtTestLabel字段
if (type != null) {
// 原字段名 例:areaCode
String fieldName = gen.getOutputContext().getCurrentName();
// 字段对应的label 如 高新园区
String codeLabel = RedisUtils.dictCodeToLabel(type, value.toString());
// 写入
gen.writeStringField(fieldName.concat(LABEL_SUFFIX), codeLabel);
}
}
}
复制代码
大功告成,让咱们调一下上面的测试接口,看一下结果:ide
{
"name": "vt",
"age": 18,
"areaCode": "20005",
"areaCodeLabel": "高新园区", // 由DictSerializer处理生成
"cityCode": "201",
"cityCodeLabel": "大连市", // 由DictSerializer处理生成
"provinceCode": "03",
"provinceCodeLabel": "辽宁省" // 由DictSerializer处理生成
}
复制代码
cityCodeLabel、areaCodeLabel和provinceCodeLabel并无在实体中定义,可是接口中已经成功的增长上了,看来笔者的实现方式也算是靠谱。 这样的方案只须要开发时,在出方向Vo加个注解就搞定了,同时也保留了原字段,仍是很方便的,Aop或者Filter等应该也是能够实现相似效果,有更好的方案你们也能够告诉笔者,互相交流。 最后别忘了,咱们是依靠缓存来优化本次的逻辑,是要进行缓存预热的操做的哦~
~----------------------- 走过路过别错过~ 原创不易,点个赞再走吧~