处理字典值反显问题

项目中往往只保存了字典的key值,前端页面显示时需要展示字面值。

解决方案通常有以下几种:

  1. 前端展示时,根据字典key,调用专门的接口去查询字面值。为了性能考虑,可以将查询结果缓存到浏览器。适用于不常变化的字典值。
  2. 后端返回结果前将字典值处理好后一起返回。这里面也有几种处理方式:
    1. 查询时sql关联查询字典表。适用于字典模块与业务模块没有分离的情况。
    2. 返回结果根据字典值调用字典接口查询出字面值。适用于字典与业务分离的情况。字典接口可以根据情况做缓存。

这里简单说一下最后一种处理方式。

具体思路是利用mybatis的插件去处理每条记录中特定的字段。

定义标记注解FieldBind

该注解用来某个字段上,用来标记该字段是字典值,需要反显。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Inherited
public @interface FieldBind {

/**
* 字典类型
*/
String type();

/**
* 写字典值目标字段
*/
String target();

String handlerName() default "";
}
  • type:字典通常是分类的。一个分类下只有几个字典值。
  • target:处理后字面值保存到哪个字段。在返回的vo中多定义一个字段用来装字面值。
  • handlerName:处理器bean的名称。

定义字典处理器FieldBindHandler

FieldBindHandler接口是字典处理的抽象接口,用来查询字典值及其描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public interface FieldBindHandler {
/**
* @param type 字典类型
* @param value 字典值
* @param <T>
* @return 字典显示值
*/
default <T> T handle(String type, Object value){
return (T) value;
}

/**
* 列出所有可能的值
* @param type
* @param <T>
* @return
*/
default List<Value> listAll(String type){
return new ArrayList<>();
}

@Data
class Value{
/**
* 值
*/
private String value;
/**
* 描述
*/
private String desp;
}
}

工具类FieldBindHandlerHelper

该工具会遍历要处理对象的所有字段,识别出标记注解的字段,调用处理器FieldBindHandler获取到字面值,然后将字面值设置到目标字段中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class FieldBindHandlerHelper {

private static ExecutorService executor;

private static FieldBindHandler dictService;

static {
//hutool的SpringUtil的工具类
dictService = SpringUtil.getBean(FieldBindHandler.class);
executor = new ThreadPoolExecutor(10, 10, 10, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>(10), new ThreadPoolExecutor.CallerRunsPolicy());
}

/**
* 处理返回结果值
*
* @param result
* @return java.lang.Object
* @date 2021/9/6 11:20
*/
public static <T> T setFieldValue(Object result) {
if (result == null) {
return null;
}
if (result.getClass().isPrimitive()) {
//基本类型直接返回
return (T) result;
}
if (result instanceof Map) {
//map类型直接返回
return (T) result;
}
if (result instanceof Collection) {
for (Object obj : (Collection) result) {
setFieldValue(obj);
}
return (T) result;
}
// 此处可根据需要加上一些判断优化处理,如result为基本数据类型对象、Map时不再执行后续代码,或只处理限定类等

Field[] fs = ReflectUtil.getFields(result.getClass());
List<Future> futures = new ArrayList<>(fs.length);
for (Field f : fs) {
FieldBind dictBind = f.getAnnotation(FieldBind.class);
if (dictBind == null) {
continue;
}
// 获取@FieldBind标记字段具体值
Object value = ReflectUtil.getFieldValue(result, f);
if (value == null) {
continue;
}

futures.add(executor.submit(new Runnable() {
@Override
public void run() {
// 设置目标字段的转换值
FieldBindHandler handler = dictService;
if (StrUtil.isNotBlank(dictBind.handlerName())) {
handler = SpringUtil.getBean(dictBind.handlerName());
}
ReflectUtil.setFieldValue(result, dictBind.target(), handler.handle(dictBind.type(), value));

}
}));
}
futures.stream().peek(future -> {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
log.warn("字典转换失败", e);
}
}).collect(Collectors.toList());
return (T) result;
}
}

mybatis插件FieldInterceptor

此插件用来拦截mybatis处理resultset。在插件中会调用上面的工具类处理返回结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class FieldInterceptor implements Interceptor {

@Override
public Object intercept(Invocation invocation) throws Throwable {
return FieldBindHandlerHelper.setFieldValue(invocation.proceed());
}

@Override
public Object plugin(Object target) {
if (target instanceof ResultSetHandler) {
return Plugin.wrap(target, this);
}
return target;
}

@Override
public void setProperties(Properties properties) {

}

}

实现FieldBindHandler

在项目中只需要实现字典获取的方式即可。如果公司的项目中实典的获取方式是统一的,只需要实现一次即可。

如果实现了多个,则需要在其中一个指定@primary,并在FieldBind注解标记的字段中指定要使用的FieldBindHandler

如:

1
2
3
4
5
6
@FieldBind(type = "subLabelType", target = "labelTypeName", handlerName = "sysDicFieldBindHandler")
private String labelType;
/**
* 标签类型名称
*/
private String labelTypeName;

至此我们就处理了字典的反显问题。

附:

优化的点:

  1. 现在处理字典的时候,如果返回的是列表,是一条条数据进行处理的。同一个字典类型或同一个字典值将会查询多次,可以考虑一次处理多条记录。