减少你的代码量:基于AOP实现字典翻译

274 阅读3分钟

什么是字典?

开发者更加倾向于使用短小的字段类型比如 tinyint、char(1)等来存储一些类型字段,以让每个数据都能够尽可能少的占用空间。 而用户当然不买单,希望能够看到每个字段都真正含义(比如 status = 1时,其真正含义是状态进行中, status = 2时,其真正含义是状态完成) 而数据从1进行中的数据变换过程我们称之为字典翻译。

实现方案

而字典是任何后台管理系统比不可少的系统功能模块之一。 而根据架构选型的不同,其内容翻译的主要实现方案有两种:

  1. 基于后端查询时自动进行翻译(本文主要讨论内容)

在进行查询、列表时后端能够自动对字段进行翻译,把对于status字段,能够有一个statusName(存储翻译结果的名称,随你定)来说明status的含义,并展示给用户。

  1. 基于前端展示时的自动翻译

在后端返回结果后,通过status字段再次去调用后端的API或者是本地的字典缓存,来获取status字段的真实含义,然后展示给用户。

而本文的剩下内容将讨论如何基于Spring AOP来实现方案1(后端自动翻译)功能。

使用方式

  1. 添加字段用于填充翻译后的文本
  2. 添加@Dict注解,并指定翻译的字典类型
  3. 在API的返回方法中添加@DictTranslation注解
class Entity{
    ...
	// 未翻译的字典值
    private String type;

    @Dict(EVENT_TYPE)
    private String typeName;
    ...
}

class Controller{
    ...
    
    @GetMapping("/detail")
    @DictTranslation
    public ApiResult getById(@NotBlank @RequestParam("id") String id) {
        return ApiResult.ok().data(service.getInfo(id));
    }
    
    ...
}

实现结果

image.png

实现步骤

  1. 添加注解@Dict用于指定字典的元数据(字典类型和字典值)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Dict {

    /**
     * 字典类型:t_sys_dict_type中的dict_type字段.
     */
    String value();

    String codeField() default "";
}

  1. 添加注解@DictTranslation用于手动指定哪些方法会通过AOP实现自动翻译
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DictTranslation {
}

  1. 实现注解AOP切面
/**
 * @Author yaowenbin
 * @Date 2022/11/18
 */
@Aspect
@Component
@Slf4j
public class DictAspect {

    @Resource
    private SysDictDataService dictDataService;

    @Pointcut("@annotation(dictTranslation)")
    public void pointcut(DictTranslation dictTranslation) {

    }

    @Around("pointcut(dictTranslation)")
    public Object doAround(ProceedingJoinPoint pjp, DictTranslation dictTranslation) throws Throwable {
        Object proceed = pjp.proceed();

        if (! (proceed instanceof ApiResult) ) {
            return proceed;
        }

        ApiResult result = (ApiResult) proceed;
        // 翻译单个对象
        notNull(result.get(ApiResult.FIELD_DATA), val -> {
            translationDict(result.get("data"));
        });
    	// 翻译列表对象
        notNull(result.get(ApiResult.FIELD_PAGE), val -> {
            PageVo pageVo = (PageVo) result.get("page");
            List dataList = pageVo.getList();
            for (Object o : dataList) {
                translationDict(o);
            }
        });

        return proceed;
    }

    public void translationDict(Object data) {
        Field[] fields = data.getClass().getDeclaredFields();

        for (Field field : fields) {
            if (field.isAnnotationPresent(Dict.class)) {
                Dict dict = field.getAnnotation(Dict.class);

                String codeFieldName;
                if (dict.codeField().equals("")) {
                    String fieldName = field.getName();
                    codeFieldName = fieldName.substring(0, fieldName.length() - 4);
                } else {
                    codeFieldName = dict.codeField();
                }

                char[] chars = codeFieldName.toCharArray();
                chars[0] = toUpperCase(chars[0]);
                codeFieldName = String.valueOf(chars);
                String getterName = "get" + codeFieldName;

                Method codeGetter;
                try {
                    codeGetter = data.getClass().getMethod(getterName);
                } catch (NoSuchMethodException | SecurityException e) {
                    log.warn("翻译失败, {}未找到{}()方法,无法翻译该字段", data.getClass(), getterName);
                    continue;
                }
                String code;
                try {
                    code = (String) codeGetter.invoke(data);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    log.warn("翻译失败, {}调用{}()异常", data.getClass(), getterName);
                    continue;
                }

                DropdownVo dropdown = dictDataService.getDataByType(dict.value(), code);
                if (dropdown == null) {
                    log.warn("翻译失败, {}类中的{}未找到数据库中{}对应字典数据", getClass(), dict, code);

                    continue;
                }
                ReflectUtil.setFieldValue(data, field.getName(), dropdown.getLabel());

            }
        }
    }

    public <V> void notNull(V obj, Consumer<V> consumer) {
        if (obj != null) {
            consumer.accept(obj);
        }
    }
    private char toUpperCase(char c) {
        if (97 <= c && c<= 122) {
            c ^= 32;
        }
        return c;
    }

}