一键下班系列(一)傻瓜式开箱即用字典转译工具

620 阅读3分钟

使用场景

众所周知,字典转译 通常是指将某个数据集中的编码或ID转换为对应的文字或者可读性更好的格式,例如将数据库中的部门ID转换成对应的部门名称,用户数据中的性别SEX转换成对应中文如男、女等等。这样的字典数据通常由code值和name(title)值两个字段组成,在传统代码中会将其定义成常量或者枚举类的形式,这种做法是比较常用且规范的,但是随着业务不断扩张,有时会需要我们不定时的手动增加字典数据,那这种做法显然不再适用。

解决思路

image.png

按照图中的流程走,必定会涉及到性能优化的问题,这个时候不要慌!我们只需要引入缓存即可,缓存的选择有很多,可以选择使用内存缓存也可以引入第三方的redis缓存。

架构实现

  1. hutool
  2. jackson
  3. springboot

解决方案

有了思路再做起来就轻松很多,首先我们要创建一个针对实体类BEAN中字段属性使用的注解 @DictBind

@Target({ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@JsonSerialize(using = DictBindSerializer.class)
public @interface DictBind {
    /**
     * 所需翻译的字典类型 如 sex
     * @return
     */
    String type() default "";
    /**
     * 字典翻译成功后展示的新字段 默认翻译字典名加上Str 如翻译字段为integer类型名称为sex ==》 生成字符串String类型的sexStr字段
     * @return
     */
    String target() default "";
    /**
     * 字典翻译模式 默认查询字典表数据
     * <br> Join是连表查询
     * @return
     */
    DictBindType mode() default DictBindType.DICT;
    /**
     * 表名
     * <br>配合DictBindType.JOIN 来使用 指定需要获取的哪张表
     * @return
     */
    String tableName() default "";
    /**
     * 表字段
     * <br>配合DictBindType.JOIN 来使用 指定需要获取的哪张表的某个字段数据
     *
     * @return
     */
    String columns() default "";
    /**
     * 对象字段是否有拼接字符串符号
     *
     * @return
     */
    String joinSymbol() default "";
}

这里配合jackson序列化将字典的code转换成对应的中文,并在响应体里生成相对应的字段,可以做到不修改实体类bean的任何信息,真正做到 无入侵。 然后我们需要实现类 DictBindSerializer.class

public class DictBindSerializer extends JsonSerializer<Object> implements ContextualSerializer {

    private DictBind dictBind;

    public DictBindSerializer(){
    }

    public DictBindSerializer(DictBind db){
        this.dictBind = db;
    }

    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeObject(value);
        gen.writeFieldName(DictBindTranslator.genTargetFieldName(gen.getOutputContext().getCurrentName(),dictBind.target()));
        gen.writeObject(DictBindTranslator.me().genTargetFieldValue(dictBind,value));

    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        DictBind annotation = property.getAnnotation(DictBind.class);
        if(ObjectUtil.isNotNull(annotation)){
            this.dictBind = annotation;
            return this;
        }
        return prov.findValueSerializer(property.getType(), property);
    }


}

继续实现

public class DictBindTranslator extends AbstractDictBindOperator {
    public static DictBindTranslator me() {
        return new DictBindTranslator();
    }

    public static String genTargetFieldName(String currentName, String target) {
        return StrUtil.isBlank(target) ? StrUtil.join(StrUtil.EMPTY, currentName, DictConsts.TARGET_SUFFIX) : target;
    }

    public Object genTargetFieldValue(DictBind dictBind, Object value) {
        String result = null;
        String type = dictBind.type();

        DictBindType mode = dictBind.mode();
        /*if (StrUtil.isBlank(type)) {
            return StrUtil.EMPTY;
        }*/
        switch (mode) {
            case DICT:
                if(StrUtil.isBlank(dictBind.joinSymbol())){
                    result = getDictDataValue(type, value);
                }else {
                    String[] strings = Convert.toStr(value).split(dictBind.joinSymbol());
                    result = Arrays.stream(strings).map(i->getDictDataValue(type,i)).collect(Collectors.joining(dictBind.joinSymbol()));
                }
                break;
            case JOIN:
                if (StrUtil.isBlank(dictBind.joinSymbol())) {
                    result = getTableDataValue(dictBind.tableName(), dictBind.columns(), value);
                } else {
                    String[] strings = Convert.toStr(value).split(dictBind.joinSymbol());
                    result = Arrays.stream(strings).map(i->getTableDataValue(dictBind.tableName(),dictBind.columns(),i)).collect(Collectors.joining());
                }
                break;
        }

        return result;
    }

}
public class AbstractDictBindOperator implements IDictBindService{
    private static final String SELECT_DICT_SQL = "select dd.value from sys_dict_data dd left join sys_dict_type dt on dd.type_id = dt.id where dt.code = '{}' and dd.code = {}";
    private static final String SELECT_TABLE_SQL = "select t.{} from {} t where t.id = {}";

    /**
     * 获取字典缓存名称
     * <br>表名 + 方法名或者注解名称 + 字段filed名称或值
     * @param type 字典类型
     * @return
     */
    private static String getDictKey(String type) {
        return StrUtil.join(SymbolConstant.COLON, DictConsts.SYS_DICT_DATA,DictConsts.METHOD_NAME, type);
    }

    private static String getTableKey(String tableName,String column){
        return StrUtil.join(SymbolConstant.COLON, StrUtil.toCamelCase(tableName),DictConsts.METHOD_NAME, column);
    }

    @Override
    public String getDictDataValue(String type, Object value) {
        return getDataValue(type,null,null,value);
    }

    @Override
    public String getTableDataValue(String tableName, String column, Object value) {
        return getDataValue(null,tableName,column,value);
    }

    /**
     * 获取数据表对应的结果值
     * @param type 字典类型type
     * @param tableName 表名
     * @param column 字典field名
     * @param value 对应的值
     * @return
     */
    private String getDataValue(String type,String tableName, String column, Object value){
        boolean notBlank = StrUtil.isNotBlank(type);
        String key = notBlank ? getDictKey(type) : getTableKey(tableName, column);
        Map<String, Object> orElse = Optional.ofNullable(MappingCache.me().get(key)).orElse(new HashMap<>());
        Object o = orElse.get(Convert.toStr(value));//这里要注意put进去时key的值是string 所以这里取值不能直接传值
        if(ObjectUtil.isNotNull(o)){
            return Convert.toStr(o);
        }
        String sql = notBlank ? StrUtil.format(SELECT_DICT_SQL,type,value) : StrUtil.format(SELECT_TABLE_SQL,column, tableName, value);
        try {
            String result = DbUtils.me().queryString(sql);
            orElse.put(Convert.toStr(value),result);
            MappingCache.me().put(key,orElse);
            return result;
        } catch (SQLException e) {
            e.printStackTrace();
            return StrUtil.EMPTY;
        }
    }

}

至此,我们的字典转译插件是开发完成了,我们来看看实际使用效果吧。

public class Student {
    @DictBind(type = "sex")
    private Integer sex;
}

返回给前端的结果集中会自动添加**Str的字段转译值

{
    "sex":0,
    "sexStr":"男性"
}

对于一些需要连表查询转译的字典也可以哦

public class Student {	
	/**
     * 省市
     */
    @DictBind(mode = DictBindType.JOIN,tableName = "fd_area",columns = "name",joinSymbol = ",",target = "areaName")
    private String area;
    
}
{
    "area":"460000,460100",
    "areaName":"海南省,海口市"
}