SpringBoot 字典翻译:Redis 缓存 + 注解驱动

AI 概述
为方便后台判断,项目常将状态等保存为数字或字母,但前端回显需翻译成表达值。为此采用字典转换方案:给需翻译的 entity 对象字段添加 @Dict 注解,通过拦截器拦截 Controller 返回数据,判断有注解则从字典表查询翻译值,在原对象上添加新字段。字典翻译工具类 DictTranslateUtils,将对象转 JSON 添加新字段,可返回 JSON 对象,也可返回原对象类型(需 DTO 提前加翻译字段)。示例展示了订单状态等字段通过注解自动翻译并返回的效果。
目录
文章目录隐藏
  1. 为什么需要字典转换?
  2. 代码编写

为什么需要字典转换?

在项目中经常遇到的一个字典转换问题。

在保存状态或者一些属性的时候一般保存的是典型值,比如“是和否”保存在数据库中就是“1 和 0”,保存一些状态的时候都是数字或者字母,这样做的原因就是方便后台进行一系列的判断。

但是查询的时候要回显给前端,这时候就不能使用 0 和 1 显示了,因为用户不知道是什么意思,需要翻译一下,显示成表达的值。

代码编写

常见的几种处理方式要么后台查询返回要么是前端通过接口查询。这次我们采用后台查询之后把数据返回回来。只要我们给需要的字段加上注解,查询就自动翻译,直接看代码。

过程是这样,给需要字典翻译的 entity 对象添加一个注解,当请求接口需要返回的时候会进入到拦截器里面,拦截会判断对象内容有没有加字典注解,如果有注解会从字典表里面查询对应的翻译值,然后在该对象内容上添加一个新的字段原对象字段不加以改动。

使用的 JDK 版本是 21

Result 封装的前端返回对象:

/**
 * 接口返回结果
 */
@Datapublic class Result {
    //状态码
    private Integer code;
    //状态信息
    private String message;
    //返回对象
    private Object result;
    /**
     * 无参构造
     */
    public Result() {
    }
    /**
     * 自定义返回状态码,状态信息,数据对象
     *
     * @param code 状态码
     * @param msg  状态信息
     * @param result 数据对象
     */
    public Result(int code, String msg, Object result) {
        this.code = code;
        this.message = msg;
        this.result = result;
    }
}

@Dict注解代码,主要作用在类对象的字段上。

/**
 * 字典注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dict {
    /**
     * 字典编码
     */
    String code() default "";
}

字典翻译拦截器,拦截所有 Controller 方法返回的数据,检查是否有 Dict 注解有注解就进行处理。

import com.haiou.common.result.Result;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.lang.reflect.Field;import java.util.Collection;

/**
 * 字典翻译拦截器
 *
 * @author qinxianzhong
 * @since 2025/11/13
 */
@RestControllerAdvice
public class DictResponseAdvice implements ResponseBodyAdvice<Object> {
    private final DictTranslateUtils dictTranslateUtils;
    public DictResponseAdvice(DictTranslateUtils dictTranslateUtils) {
        this.dictTranslateUtils = dictTranslateUtils;
    }
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        //如果 body 为空 直接返回
        if (body == null) {
            return body;
        }
        // 检查 body 是否包含任何带有 Dict 注解的字段
        boolean containsDictFields = containsDictAnnotatedFields(body);
        if (!containsDictFields) {
            return body;
        }

        //判断返回结果是否为 Result 对象
        if (body instanceof Result) {
            Result resultObject = (Result) body;
            //如果结果为空,直接返回
            if (resultObject.getResult() == null) {
                return body;
            }
            Object result = dictTranslateUtils.translate(resultObject.getResult());
            return new Result(result);
        } else {
            Object translateBody = dictTranslateUtils.translate(body);
            return translateBody != null ? translateBody : body;
        }
    }
    /**
     * 判断对象是否包含 Dict 注解的字段
     *
     * @param body 对象
     * @return 是否包含 Dict 注解的字段
     */
    private boolean containsDictAnnotatedFields(Object body) {
        //空值检查
        if (body == null) {
            return false;
        }
        //如果是 Result 返回对象,循环检查
        if (body instanceof Result) {
            return containsDictAnnotatedFields(((Result) body).getResult());
        }
        //如果是集合需要循环
        if (body instanceof Collection<?>) {
            for (Object obj : (Collection<?>) body) {
                if (containsDictAnnotatedFields(obj)) {
                    //找到任何符合的条件就返回
                    return true;
                }
            }
            return false;
        }
        //处理数组类型
        if (body.getClass().isArray()) {
            Object[] array = (Object[]) body;
            for (Object obj : array) {
                if (containsDictAnnotatedFields(obj)) {
                    //找到任何符合的条件就返回
                    return true;
                }
            }
        }
        //检查自身对象
        Class<?> clazz = body.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Dict.class)) {
                return true;
            }
        }
        // 如果没有 Dict 注解的字段,返回 false
        return false;
    }
}

这段代码会有一个接口返回对象判断,是否是 Result 对象,Result 对象是一个封装给前端一个统一的构造.。

字典翻译工具类:

/**
 * 字典翻译工具类

 */
@Component
public class DictTranslateUtils {
    @Resource
    private SysDictService sysDictService;
    //字典翻译缓存
    private final Map<Class<?>, List<Field>> dictFieldCache = new ConcurrentHashMap<>();
    /**
     * 字典翻译方法
     *
     * @param data 源对象数据
     * @param <T>  源对象类型
     * @return 翻译后的对象
     */
    public <T> Object translate(T data) {
        //为空直接返回
        if (data == null) {
            return data;
        }
        //检查基本类型和常见不可变类型
        if (data instanceof String || data instanceof Number ||
                data instanceof Boolean || data instanceof Character ||
                data instanceof Enum) {
            return data;
        }
        if (data instanceof Collection<?> col) {
            return translateCollection(col);
        } else {
            return translateSingle(data);
        }
    }
    /**
     * 集合翻译方法
     *
     * @param col 集合对象
     * @param <T> 集合元素类型
     * @return 翻译后的集合
     */
    public <T> Collection<T> translateCollection(Collection<T> col) {
        if (col == null || col.isEmpty()) {
            return col;  // 直接返回空集合
        }
        // 保持原始集合类型(List、Set 等)
        Collection<T> translatedCollection = col instanceof Set ?
                new LinkedHashSet<>(col.size()) : new ArrayList<>(col.size());
        // 遍历集合中的每个元素
        for (T item : col) {
            // 获取翻译后的对象
            T translatedItem = (T) translateSingle(item);
            // 添加翻译后的对象到新集合
            translatedCollection.add(translatedItem);
        }
        // 返回修改后的集合
        return translatedCollection;
    }
    /**
     * 单个对象翻译方法
     *
     * @param data 源对象数据
     * @param <T>  源对象类型
     * @return 翻译后的对象
     */
    public <T> T translateSingle(T data) {
        Class<?> clazz = data.getClass();
        // 从缓存中取出 clazz 类对应的字典字段列表
        List<Field> dictFields = dictFieldCache.computeIfAbsent(clazz, this::resolveDictFields);
        //如果没有需要翻译的字段直接返回源对象
        if (dictFields.isEmpty()) {
            return data;
        }
        // 配置 null 字段也返回
        JSONWriter.Feature[] features = {JSONWriter.Feature.WriteMapNullValue};
        String jsonString = JSON.toJSONString(data, features);
        // 将对象转换为 JSONObject
        JSONObject dataJson = JSON.parseObject(jsonString);
        for (Field field : dictFields) {
            Dict anno = field.getAnnotation(Dict.class);
            // 字典编码
            String dictCode = anno.code();
            try {
                String value = (String) field.get(data);
                //查询 Redis 缓存里面的数据。
                String translate = sysDictService.translate(dictCode, value);
                if (StrUtil.isNotBlank(translate)) {
                    // 设置 Desc 字段到 dataMap 中
                    dataJson.put(field.getName() + "Desc", translate);
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        //返回新的 JSON 对象
        return (T) dataJson;
    }
    /**
     * 获取类对象所有的 Dict 注解字段
     *
     * @param clazz 类对象
     * @return 字段列表
     */
    private List<Field> resolveDictFields(Class<?> clazz) {
        //获取类上面带 Dict 注解的字段
        return Arrays.stream(clazz.getDeclaredFields())
                // 过滤出带有 Dict 注解的字段
                .filter(field -> field.isAnnotationPresent(Dict.class))
                // 使字段可以访问(即使是 private 字段)
                .peek(field -> field.setAccessible(true))
                .toList();
    }
}

字典翻译工具,关键是在于会把原对象转换成 JSON 格式,给JSON 对象 put 一个新的翻译后的字典字段,比如用户状态字段“status”,添加一个新的“statusDesc”,原对象字段和新的翻译字段会一起返回不会破坏本来的接口数据结构。

但是有一个需要注意的点,在 translateSingle 方法 中,返回的是一个 JSON 类型的对象。如图:

SpringBoot 字典翻译:Redis 缓存 + 注解驱动

SpringBoot 字典翻译:Redis 缓存 + 注解驱动

查询的是一个集合,集合里面的对象是 SysOrderDTO 对象,但是字典返回对象的是 JSON 格式,这样写的原因就不需要在 DTO 上写额外的翻译字段自动添加。

如果还想返回原对象类型,需要在 DTO 上先写好翻译字段 比如“statusDesc” ,把 translateSingle 方法返回代码:

return (T) dataJson;

改成

return (T) JSON.parseObject(dataJson.toJSONString(), clazz);

DTO 需要把翻译字段先加上。

看一下效果这是返回的参数:

{
    "code": 1000,
    "message": "操作成功!",
    "result": [
        {
            "consignee": "张三",
            "deliveryStatus": "1",
            "id": "1985344329423447730",
            "orderAddress": "南京市",
            "orderAmount": 5999.00,
            "orderName": "iPhone17",
            "orderStatus": "1",
            "paymentStatus": "1",
            "paymentStatusDesc": "已交款",
            "deliveryStatusDesc": "已收货",
            "orderStatusDesc": "已完成"
        },
        {
            "consignee": "李四",
            "deliveryStatus": "2",
            "id": "1986177542243341505",
            "orderAddress": "江宁区",
            "orderAmount": 4999.00,
            "orderName": "小米 17", 
            "orderStatus": "0",
            "paymentStatus": "0",
            "paymentStatusDesc": "未交款",
            "deliveryStatusDesc": "退货",
            "orderStatusDesc": "未完成 "
        }
    ]
}

DTO 对象

@Datapublic class SysOrderDTO {
    @ApiModelProperty("订单 ID")
    private String id;
    @ApiModelProperty("订单名称")
    private String orderName;
    @ApiModelProperty("订单金额")
    private BigDecimal orderAmount;
    @ApiModelProperty("订单地址")
    private String orderAddress;
    @ApiModelProperty("收货人")
    private String consignee;
    @ApiModelProperty("交款状态  1.已交款  0.未交款")
    @Dict(code = "payment_status")
    private String paymentStatus;
    @ApiModelProperty("收货状态  0.未收货 1.已收货 2.退货")
    @Dict(code = "delivery_status")
    private String deliveryStatus;
    @ApiModelProperty("订单状态  0.未完成  1.已完成")
    @Dict(code = "order_status")
    private String orderStatus;
}

三个状态都是通过字典翻译注解来修饰的,返回的时候自动加上字段。

以上关于SpringBoot 字典翻译:Redis 缓存 + 注解驱动的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。

「点点赞赏,手留余香」

0

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » SpringBoot 字典翻译:Redis 缓存 + 注解驱动

发表回复