SpringBoot自定义注解+AOP处理数据字典自动翻译

最近项目中数据字典的配置使用越来越多,导致代码中有大量的数据字典翻译工作,使得代码的整洁性和维护性变差。排除掉对数据结果进行“清洗”的方案后,还是决定通过注解的方式来解决这个问题。参考了一些文章后完成了这个功能(具体实在记不清了,雷同处,海涵!)。最近有些时间顺便整理一下思路。

目标功能是这样,在返回数据的实体上通过注解标记当前字段(dict_field)对应的code值,然后通过反射读取这个字段的code和字段对应的值,然后从字典表中基于code和当前的值(key)获取对应的value,然后将value赋值在另一个字段(dict_value_field,当然也可以根据需要自行处理),返回处理后的内容。

 

1 字典表字段

这里主要是code,dict_key,dict_value这三个字段

实体对象

/*
 
 */
package org.springblade.modules.system.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;

/**
 * 实体类
 *
 * @author Chill
 */
@Data
@TableName("blade_dict")
@ApiModel(value = "Dict对象", description = "Dict对象")
public class Dict implements Serializable {
    public final  static Long TOPDICT_PARENTID=0L;//父类的parentid
    public final  static String KUAIDI_COM="kuaidi_com";//快递公司
    public final  static String CANCEL_REASON="cancelreason";//取消订单的原因
    public final  static Integer IS_SEALED_YES = 1;//封存
    public final  static Integer IS_SEALED_NO = 0;//没有封存
	private static final long serialVersionUID = 1L;

	/**
	 * 主键
	 */
	@JsonSerialize(using = ToStringSerializer.class)
	@ApiModelProperty(value = "主键")
	@TableId(value = "id", type = IdType.ID_WORKER)
	private Long id;

	/**
	 * 父主键
	 */
	@JsonSerialize(using = ToStringSerializer.class)
	@ApiModelProperty(value = "父主键")
	private Long parentId;

	/**
	 * 字典码
	 */
	@ApiModelProperty(value = "字典码")
	private String code;

	/**
	 * 字典值
	 */
	@ApiModelProperty(value = "字典值")
	private String dictKey;

	/**
	 * 字典名称
	 */
	@ApiModelProperty(value = "字典名称")
	private String dictValue;

	/**
	 * 排序
	 */
	@ApiModelProperty(value = "排序")
	private Integer sort;

	/**
	 * 字典备注
	 */
	@ApiModelProperty(value = "字典备注")
	private String remark;

	/**
	 * 是否已封存
	 */
	@ApiModelProperty(value = "是否已封存")
	private Integer isSealed;

	/**
	 * 是否已删除
	 */
	@TableLogic
	@ApiModelProperty(value = "是否已删除")
	private Integer isDeleted;


}

2 基本思路,步骤

2.1 新建注解DataDict,主要是用户标记实体字段对应的code。

package org.springblade.modules.focus.aspect;

import io.swagger.annotations.ApiModelProperty;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface DataDict {
    @ApiModelProperty(value = "可以用于设置默认的key值")
    String dictKey() default "";
    @ApiModelProperty(value = "需要翻译的code")
    String code() default "";

}

2.2 新建注解DataDictClass,这个注解主要给AOP使用,放在需要翻译的方法上,然后AOP基于这个注解做一个切面,进行数据处理。

package org.springblade.modules.focus.aspect;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DataDictClass {

}

2.3 切面逻辑

2.3.1 切入点-基于注解dataDictClass

 @Pointcut("@annotation(dataDictClass)")
    public void doDataDictClass(DataDictClass dataDictClass) {
    }

2.3.2 数据处理,先说一下当前项目的返回数据。结果全部封装在R对象中。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springblade.core.tool.api;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.Optional;
import org.springblade.core.tool.utils.ObjectUtil;
import org.springframework.lang.Nullable;

@ApiModel(
    description = "返回信息"
)
public class R<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(
        value = "状态码",
        required = true
    )
    private int code;
    @ApiModelProperty(
        value = "是否成功",
        required = true
    )
    private boolean success;
    @ApiModelProperty("承载数据")
    private T data;
    @ApiModelProperty(
        value = "返回消息",
        required = true
    )
    private String msg;

    private R(IResultCode resultCode) {
        this(resultCode, (Object)null, resultCode.getMessage());
    }

    private R(IResultCode resultCode, String msg) {
        this(resultCode, (Object)null, msg);
    }

    private R(IResultCode resultCode, T data) {
        this(resultCode, data, resultCode.getMessage());
    }

    private R(IResultCode resultCode, T data, String msg) {
        this(resultCode.getCode(), data, msg);
    }

    private R(int code, T data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
        this.success = ResultCode.SUCCESS.code == code;
    }

    public R() {
    }
}

这里需要处理的数据类型

2.3.2.1: 分页数据R<IPage<T>>

2.3.2.2: 普通列表R<List<T>>

2.3.2.3 :普通数据R<T>

所以这里主要针对这几种数据来处理

    @Around("@annotation(dataDictClass)")
    public Object translation(final ProceedingJoinPoint pjp, DataDictClass dataDictClass) throws Throwable {
        // 拿到要返回的数据--这里可以打印下看看内容
        Object resultR = pjp.proceed();
        if (ObjectUtil.isNull(resultR)) {
            return resultR;
        }
        Object obj;
        Object resultPage = ((R) resultR).getData();

        if (resultPage instanceof IPage) {
            // 分页的情况
            return translationPage(pjp);
        }
        Object result = ((R) resultR).getData();
        result = translate(result);
        ((R) resultR).setData(result);
        // 返回处理后的数据
        return resultR;
    }

完整代码

package org.springblade.modules.focus.aspect;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springblade.core.tool.api.R;
import org.springblade.modules.focus.dto.DataDictDTO;
import org.springblade.modules.system.entity.Dict;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 字典注解内容翻译
 */
@Aspect
@Component
@SuppressWarnings({"unused"})
public class DatDictAspect {

//    @Autowired
//    private DictUtils dictUtils;
    private Map<String, String> dictInfoMap = new ConcurrentHashMap<>();

    @Pointcut("@annotation(dataDictClass)")
    public void doDataDictClass(DataDictClass dataDictClass) {
    }

    @Around("@annotation(dataDictClass)")
    public Object translation(final ProceedingJoinPoint pjp, DataDictClass dataDictClass) throws Throwable {
        Object resultR = pjp.proceed();
        if (ObjectUtil.isNull(resultR)) {
            return resultR;
        }
        Object obj;
        Object resultPage = ((R) resultR).getData();

        if (resultPage instanceof IPage) {
            // 分页的情况
            return translationPage(pjp);
        }
        Object result = ((R) resultR).getData();
        result = translate(result);
        ((R) resultR).setData(result);
        return resultR;
    }

    public Object translationPage(final ProceedingJoinPoint pjp) throws Throwable {
        Object resultR = pjp.proceed();
        if (resultR == null) {
            return resultR;
        }
        Object resultPage = ((R) resultR).getData();
        IPage page = (IPage) resultPage;
        Object result = ((IPage) resultPage).getRecords();
        result = translate(result);
        page.setRecords((List) result);
        ((R) resultR).setData(page);
        return resultR;
    }

    private Object translate(Object result) {
        Object obj;
        if (result instanceof List || result instanceof ArrayList) {
            List olist = ((List) result);
            if (olist.size() == 0) {
                return result;
            }
            obj = olist.get(0);
        } else {
            obj = result;
        }
        // 获取所有配置注解的code 和实体字段
        List<DataDictDTO> dictParams = getDictMapping(obj.getClass());
        if (dictParams.size() == 0) {
            return result;
        }

        //根据code 获取对应的字典内容
//        List<Dict> dictInfos = dictUtils.getDicts(getCodes(dictParams));

        List<Dict> dictInfos = getDictTest();


        if (dictInfos == null && dictInfos.size() == 0) {
            return result;
        }
        //先把字典值转成map
        for (Dict dict : dictInfos) {
            dictInfoMap.put(dict.getCode() + "_" + dict.getDictKey(), dict.getDictValue());
        }
        if (result instanceof List || result instanceof ArrayList) {
            for (Object entity : (List) result) {
                assign(entity, dictParams, dictInfoMap);
            }
        } else {
            assign(result, dictParams, dictInfoMap);
        }
        return result;
    }

    /**
     * 测试用---实际中最好把所有的数据封装在redis中
     * @return
     */
    private   List<Dict> getDictTest(){
        List<Dict> dicts = new ArrayList<>();
        Dict dict1 = new Dict();
        dict1.setCode("channelCode");
        dict1.setDictKey("20");
        dict1.setDictValue("版本20");
        dicts.add(dict1);
        return dicts;
    }



    /**
     * 单个设置值
     * 这里的逻辑是获取配置注解的字段名称---比如 dict
     * 然后将从字典拿到的值赋值给另一个字段---dictValue
     * 当然可以根据具体情况去处理
     *
     * @param entity
     * @param dictParams
     * @param dictInfoMap
     */
    public void assign(Object entity, List<DataDictDTO> dictParams, Map<String, String> dictInfoMap) {
        for (DataDictDTO dictParam : dictParams) {
            String code = dictParam.getCode();
            String dictName = dictParam.getFieldName();
            try {
                Class c = entity.getClass();
                if (c != null) {
                    Field f = c.getDeclaredField(dictName);
                    f.setAccessible(true);
                    Object preValue = f.get(entity);
                    if (ObjectUtil.isNotNull(preValue)) {
                        // 需要赋值的字段
                        Field fvalue = c.getDeclaredField(dictName + "Value");
                        fvalue.setAccessible(true);
                        fvalue.set(entity, dictInfoMap.getOrDefault(code + "_" + preValue.toString(), preValue.toString()));
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 获取实体中所有需要翻译的code
     * @param dataDictDTOS
     * @return
     */
    private List<String> getCodes(List<DataDictDTO> dataDictDTOS) {
        List<String> codes = new ArrayList<>();
        if (CollectionUtil.isEmpty(dataDictDTOS)) {
            return codes;
        }
        for (DataDictDTO dataDictDTO : dataDictDTOS) {
            codes.add(dataDictDTO.getCode());
        }
        return codes;
    }


    /**
     * 获取实体中配置的code 和对应的字段内容
     * @param cla
     * @return
     */
    private List<DataDictDTO> getDictMapping(Class cla) {
        Field[] fields = cla.getDeclaredFields();
        List<DataDictDTO> list = new ArrayList<>();
        DataDictDTO dataDictDTO;
        DataDict dataDict;
        for (Field field : fields) {
            if (field.isAnnotationPresent(DataDict.class)) {
                dataDict = field.getAnnotation(DataDict.class);
                dataDictDTO = new DataDictDTO(dataDict.code(), field.getName());
                list.add(dataDictDTO);
            }
        }
        return list;
    }

}


3 测试

3.1 实体添加注解

package org.springblade.modules.thirdparty.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springblade.modules.focus.aspect.DataDict;

import javax.validation.constraints.NotNull;

@Data
@ApiModel("测试对象")
public class SharpernerVO {
    @NotNull
    @ApiModelProperty("id")
    private String serId;
    @NotNull
    @ApiModelProperty("需要翻译的字段")
    @DataDict(code = "channelCode")
    private String dictKey;
    @NotNull
    @ApiModelProperty("翻译后的值放在这个字段")
    private String dictKeyValue;

    public SharpernerVO() {
    }

    public SharpernerVO(@NotNull String dictKey, @NotNull String dictKeyValue) {
        this.dictKey = "20";
        this.dictKeyValue = dictKeyValue;
    }
}

3.2 方法添加注解

   @GetMapping(value = "/dict")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "话费提交接口", notes = "话费提交接口")
    @DataDictClass
    public R dict() {
        return R.data(sharpenerdaservice.getDicts());
    }
3.3 测试结果
{
  "code": 200,
  "success": true,
  "data": [
    {
      "serId": "",
      "dictKey": "20",
      "dictKeyValue": "版本20"
    }
  ],
  "msg": "操作成功"
}

已经将字典配置的value返回过来。

 

 

 

  • 4
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值