使用场景
众所周知,字典转译 通常是指将某个数据集中的编码或ID转换为对应的文字或者可读性更好的格式,例如将数据库中的部门ID转换成对应的部门名称,用户数据中的性别SEX转换成对应中文如男、女等等。这样的字典数据通常由code值和name(title)值两个字段组成,在传统代码中会将其定义成常量或者枚举类的形式,这种做法是比较常用且规范的,但是随着业务不断扩张,有时会需要我们不定时的手动增加字典数据,那这种做法显然不再适用。
解决思路
按照图中的流程走,必定会涉及到性能优化的问题,这个时候不要慌!我们只需要引入缓存即可,缓存的选择有很多,可以选择使用内存缓存也可以引入第三方的redis缓存。
架构实现
- hutool
- jackson
- 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":"海南省,海口市"
}