如何优雅的转义Java接口中的字典值

众所周知,后端存储一些灵活可变的参数时,都会以字典的形式入库,常见的业务中,下拉框,地区联动等数据,都是这样存储的。前端

存时一时爽啊,可是用户是看不懂字典code的,因此取的时候须要把字典值再转换回来。这样取得时候就增长了工做量,常见过的方案基本有:单独前端缓存维护一个字典文件,根据code在前端进行转换。还有设计冗余字段,或者直接在sql暴力join一波。java

“vt啊,xx项目代码,关于字典code的sql关联太多表了,你看看能不能改为从缓存取,优化一下”,随着PM的一句呼叫,立刻打开xx项目检查主要的几个接口查看sql写法,果真相似这样的风格...sql

没错了,笔者遇到的状况就是 直接在sql暴力join一波的场景 闲话少说,既然收到优化任务,先干为敬,首先 暂时不要着急改目前稳定的代码,因此我新增了一个 单表查询字典值 没有进行字典转换的测试接口,返回结果以下:

{
	"name": "vt",  
	"age": 18,
	"areaCode": "20005",  // 地区code
	"cityCode": "201",  // 城市code
	"provinceCode": "03"  //省code
}
复制代码

咱们先从这个测试接口来分析,思考怎样使用缓存来获取label,以及让开发者,简便的进行转换。笔者的思路是,若是想要作到对代码低入侵,而且修改少的话,须要从共通处理入手。接口从后台返回到前端,是会通过json序列化的,因此我打算从序列化层入手。json

大体就像上图,我打算在【一顿操做】这个层面,再加上个人又一顿操做,让出方向的vo不新增字段,便可再返回一个对应的label。

笔者环境是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处理生成
}
复制代码

cityCodeLabelareaCodeLabelprovinceCodeLabel并无在实体中定义,可是接口中已经成功的增长上了,看来笔者的实现方式也算是靠谱。 这样的方案只须要开发时,在出方向Vo加个注解就搞定了,同时也保留了原字段,仍是很方便的,Aop或者Filter等应该也是能够实现相似效果,有更好的方案你们也能够告诉笔者,互相交流。 最后别忘了,咱们是依靠缓存来优化本次的逻辑,是要进行缓存预热的操做的哦~

~----------------------- 走过路过别错过~ 原创不易,点个赞再走吧~

相关文章
相关标签/搜索