简单的controller响应体码值转换

简单的controller响应体码值转换

1. 需求

服务整体架构为微服务集群架构,模块为oracle数据库不同用户,码值信息缓存于公共用户中,其他服务想要从数据库中获取码值比较费劲,所以就建立了一个专门的缓存码值管理模块,cache模块,将码值按照固定格式存储于redis中,方便所有微服务模块获取。有很多页面需要进行码值转换,列表信息数据库中存储的往往是码值的值,但是显示的时候想要显示其描述信息,所以我写了一个切面专门用于page列表的码值转换,让转换更加方便。

2.具体实现过程

这个功能实现是有具体要求的,我们系统是前后端分离,所以我们响应信息为固定对象Result格式,具体包含data,code,message三个属性,具体实现步骤如下:

  • 1.定义好切面注解TableCode,其中有keyNameMapping字段,列表信息column值和codeTypeCd对应关系
  • 2.定义好切面,拦截需要转义的方法,拦截响应信息,获取Result中对象的pageBean对象,从缓存组件中获取缓存信息,进行码值转义

3. 编码部分如下

3.1 切面注解格式如下

切面注解主要定义好返回pageBean对象中,哪些列需要进行转化,对应要转化为码值的哪些值

package cn.git.common.annotation;

import java.lang.annotation.*;


/**
 * 列表页面返回信息处理
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2021-06-30
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TableCode {
    /**
     * 列表信息column值和codeTypeCd对应关系
     * 如下: pageBean对象第一列为客户类型码值,第二列为国家码值
     * eg: typeCd=customerTypeCd,nation=NationCd
     * @return 对应关系串
     */
    String keyNameMapping() default "";

    /**
     * 非必填项
     * 注解是否风险使用,默认否
     * 如果风险使用 则码表获取从风险码表中获取
     */
    boolean ifRisk() default false;
}

3.2 列表转换切面实现类

此类中主要通过缓存平台获取码值信息,再获取响应信息中Result的page类型对象,进行转义,我们系统是双系统共用通用组件,有三个系统,其中风险单独,押品以及信贷共用一套码值,所以系统转义需要区分是否为风险,如果是风险需要获取风险码值信息。

package cn.git.common.aspect;

import cn.git.common.annotation.TableCode;
import cn.git.common.constant.CommonConstant;
import cn.git.common.exception.ServiceException;
import cn.git.common.page.PageBean;
import cn.git.redis.RedisUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 列表码值转换切面类
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2021-06-30
 */
@Aspect
@Slf4j
@Component
public class TableCodeTypeAspect {

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 处理返回结果,根据注解中的配置转换数据中的codeTypeCd。
     * @param joinPoint 切面的连接点,用于获取方法等信息。
     * @param result 方法返回的结果对象。
     * @param tableCode 注解类,用于获取注解上的配置信息。
     */
    @AfterReturning(pointcut = "@annotation(tableCode)", returning = "result")
    public void pointCut(JoinPoint joinPoint, Object result, TableCode tableCode) {
        try {
            // 判断是否处理风险数据
            boolean ifRisk = tableCode.ifRisk();

            // 处理列与codeTypeCd的映射关系
            if (StrUtil.isNotBlank(tableCode.keyNameMapping())
                    && tableCode.keyNameMapping().split(CommonConstant.EQUAL_SPLIT_FLAG).length
                    >= CommonConstant.MIN_SIZE) {
                // 将映射关系字符串转换为List<Map<String, String>>
                List<Map<String, String>> mappingList = Arrays.stream(tableCode.keyNameMapping()
                        .split(CommonConstant.COMMA_FLAG))
                        .map(mappingStr -> {
                            String[] mappingArray = mappingStr.split(CommonConstant.EQUAL_SPLIT_FLAG);
                            Map<String, String> mappingMap = new HashMap<>(CommonConstant.DEFAULT_MAP_SIZE);
                            mappingMap.put(mappingArray[0], mappingArray[1]);
                            return mappingMap;
                        }).collect(Collectors.toList());

                // 根据映射关系和是否处理风险数据,获取code名称的缓存信息
                Map<String, String> cacheMap = getCodeName(mappingList, ifRisk);

                // 处理返回结果对象,转换其中的codeTypeCd
                Field[] fields = result.getClass().getDeclaredFields();
                // 修改访问权限以访问私有字段
                boolean accessFlag = fields[2].isAccessible();
                fields[2].setAccessible(true);
                Object outVO = fields[2].get(result);
                if (ObjectUtil.isNotEmpty(outVO)) {
                    // 在outVO中找到PageBean对象,并处理其中的result数据
                    Field[] outVOFields = outVO.getClass().getDeclaredFields();
                    Field pageBeanField = Arrays.stream(outVOFields).filter(localField -> {
                        boolean checkResult = false;
                        try {
                            boolean voAccessFlag = localField.isAccessible();
                            localField.setAccessible(true);
                            Object outVOFieldValue = localField.get(outVO);
                            checkResult = outVOFieldValue instanceof PageBean;
                            localField.setAccessible(voAccessFlag);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                        return checkResult;
                    }).findAny().orElse(null);
                    if (ObjectUtil.isNotNull(pageBeanField)) {
                        // 获取PageBean对象并处理其中的result数据
                        boolean pageBeanAccessible = pageBeanField.isAccessible();
                        pageBeanField.setAccessible(true);
                        Object pageBean = pageBeanField.get(outVO);
                        // 在PageBean中找到result字段并处理
                        Field[] pageBeanFields = pageBean.getClass().getDeclaredFields();
                        Field resultField = Arrays.stream(pageBeanFields).filter(localField -> {
                            String fieldName = localField.getName();
                            if (CommonConstant.RESULT_FLAG.equals(fieldName)) {
                                return true;
                            }
                            return false;
                        }).findAny().orElse(null);
                        boolean resultFieldAccessible = resultField.isAccessible();
                        resultField.setAccessible(true);
                        Object pageBeanResult = resultField.get(pageBean);
                        // 如果result不为空,则进行转换处理
                        if (ObjectUtil.isNotEmpty(pageBeanResult)) {
                            pageBeanResult = convertPageBeanCodeTypeCd(pageBeanResult, mappingList, cacheMap);
                        }
                        // 恢复访问权限
                        resultField.setAccessible(resultFieldAccessible);
                        pageBeanField.setAccessible(pageBeanAccessible);
                        fields[2].setAccessible(accessFlag);
                    }
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理pageBean中result对应的codeTypeCd转换成codeName
     * @param pageBeanResult 结果集,预期为一个对象列表,这些对象需要进行码值转换
     * @param mappingList 关联对应关系列表,用于指示需要进行转换的字段及其对应的码表类型代码
     * @param cacheMap 缓存的码表名称到码值的映射,用于快速查找码值对应的名称
     * @return 转换完成的pageBeanResult对象,其中的码值已替换为码名称
     */
    public Object convertPageBeanCodeTypeCd(Object pageBeanResult,
                                            List<Map<String, String>> mappingList,
                                            Map<String, String> cacheMap) {
        List<Object> resultList = (List<Object>) pageBeanResult;
        for (Map<String, String> mappingMap : mappingList) {
            List<String> entityColumnList = new ArrayList<>(mappingMap.keySet());
            // 根据映射关系获取实体列名称和对应的系统码类型代码
            String entityColumn = entityColumnList.get(0);
            String sysCodeTypeCd = mappingMap.get(entityColumn);

            // 遍历结果集,将码值转换为码名称
            resultList = resultList.stream().map(result -> {
                try {
                    // 获取并设置字段可访问,进行码值转换
                    Field field = result.getClass().getDeclaredField(entityColumn);
                    boolean accessible = field.isAccessible();
                    field.setAccessible(true);
                    String codeCd = StrUtil.toString(field.get(result));
                    field.set(result, cacheMap.get(StrUtil.format(CommonConstant.TWO_STR_APPEND_TEMPLATE, sysCodeTypeCd,
                            codeCd)));
                    // 恢复字段访问性设置
                    field.setAccessible(accessible);
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    e.printStackTrace();
                    throw new ServiceException("PageBean对象转换缓存信息转换异常!");
                }
                return result;
            }).collect(Collectors.toList());
        }
        return resultList;
    }

    /**
     * 通过codeTypeCd查询缓存列表信息,转换为Map格式
     * key: codeTypeCd:codeCd value: codeName
     * @param mappingList 映射列表,用于指示需要查询的码表类型代码
     * @param ifRisk 是否为风险相关码表
     * @return 码表名称到码值的映射,用于码值转换
     */
    public Map<String, String> getCodeName(List<Map<String, String>> mappingList, boolean ifRisk) {
        Map<String, String> finalMap = new HashMap<>(CommonConstant.DEFAULT_MAP_SIZE);
        for (Map<String, String> stringStringMap : mappingList) {
            List<String> entityColumnList = new ArrayList<>(stringStringMap.keySet());
            // 获取列名称和系统码类型代码
            String entityColumn = entityColumnList.get(0);
            String sysCodeTypeCd = stringStringMap.get(entityColumn);

            // 根据是否风险相关查询不同的码表缓存
            List<JSONObject> cacheJsonObject;
            if (ifRisk) {
                // 风险相关码表查询
                if (ObjectUtil.isEmpty(redisUtil.hget(CommonConstant.TABLE_RISK_TC_SYS_CODES, sysCodeTypeCd))) {
                    throw new ServiceException("分页列表码值查询为空,请正确传入@TableCode中参数!");
                }
                cacheJsonObject = ((List<Object>)
                        redisUtil.hget(CommonConstant.TABLE_RISK_TC_SYS_CODES, sysCodeTypeCd)).stream().map(cache->
                        (JSONObject)JSON.toJSON(cache)
                ).collect(Collectors.toList());
            } else {
                // 非风险相关码表查询
                if (ObjectUtil.isEmpty(redisUtil.hget(CommonConstant.TABLE_TC_SYS_CODES, sysCodeTypeCd))) {
                    throw new ServiceException("分页列表码值查询为空,请正确传入@TableCode中参数!");
                }
                cacheJsonObject = ((List<Object>)
                        redisUtil.hget(CommonConstant.TABLE_TC_SYS_CODES, sysCodeTypeCd)).stream().map(cache->
                        (JSONObject)JSON.toJSON(cache)
                ).collect(Collectors.toList());
            }

            // 处理查询结果,转换为名称到码值的映射
            for (JSONObject cacheTcSysCodes : cacheJsonObject) {
                Map<String, String> resultMap = new HashMap<>(CommonConstant.DEFAULT_MAP_SIZE);
                resultMap.put(StrUtil.format(CommonConstant.TWO_STR_APPEND_TEMPLATE, sysCodeTypeCd,
                        cacheTcSysCodes.getString(CommonConstant.CODE_CD_FLAG)),
                        cacheTcSysCodes.getString(CommonConstant.CODE_NAME_FLAG));
                finalMap.putAll(resultMap);
            }
        }
        return finalMap;
    }
}

3.3 码值格式

码值从数据库读取,然后存储到redis中

3.3.1 代码部分格式

我的码值存储于redis中,并且以hash格式存储,总体格式:hash tableName codeTypeCd list,其中CacheTcSysCodes为数据库对应对象类型,具体内容如下:

package cn.git.cache.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 字典表带码值数据
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2021-04-12
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CacheTcSysCodes {
    /**
     * 码值主键
     */
    private String codeId;

    /**
     * 父键值
     */
    private String parentCodeId;

    /**
     * 码值级别
     */
    private String codeLevelCd;

    /**
     * 码值value
     */
    private String codeCd;

    /**
     * 码值key
     */
    private String codeKey;

    /**
     * 码值名称
     */
    private String codeName;

    /**
     * 码值类型cd
     */
    private String codeTypeCd;

    /**
     * 码值排序列
     */
    private String orderNo;

    /**
     * 默认Ind
     */
    private String defaultInd;

    /**
     * 对应codeId子集
     */
    private List<CacheTcSysCodes> tcSysCodesList;
}
3.3.2 redis缓存存储格式

最终存储到redis中格式如下:
在这里插入图片描述

3.4 代码controller调用部分格式

在这里插入图片描述

4. 测试部分

此处返回对的逾期,对应codeTypeCd类型为ContractStatusCd,具体内容可以参照上面redis中存储的格式,响应pageBean对象中,查询数据库的contractStatusCd值为5,对应缓存中的codeCd值应该是5,通过转义后codeName展示信息为逾期,其余码值转换类似。
在这里插入图片描述

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值