如何自定义注解编写切面实现对数据的过滤(或代码增强)

Java资深小白,不足之处,或者有任何错误欢迎指出。	--蓝紫

问题

系统导航栏上新加年份筛选,现要求整个系统的展示数据皆响应当前用户选择的年份。以项目全生命周期系统为例,问题拆解如下:

  1. 全局可选择年份,项目表中存在年份字段,其余相关业务表仅存项目主键。
  2. 要求从项目登记、项目前期、准备、实施、竣工、项目档案、统计分析等等全部去过滤年份。
  3. 因为是后加需求,系统所有的功能查询已实现,要求尽量做到较小改动代码实现该功能。
  4. 涉及数据库表数量众多,大约40+个表左右。
  5. 相关业务表仅存项目主键,且主键的命名字段分为asset_id和project_id两种,复杂SQL中可能存在表取了别名。

思路与实现

思路一:不改动SQL对结果集进行过滤筛选

  1. 查出当前年份存在的所有项目编号。
  2. 对结果集进行过滤,排除掉不在当年年份中存在的项目编号数据。
    在这里插入图片描述
    结论:虽能实现预期效果,但是操作繁琐,需要对涉及相关的所有接口进行判断过滤。

思路二:在思路一的解决方式上,提取工具类

频繁调用list集合,并对同一数据过滤,很适合抽取出通用的工具类,减少开发代码量。
在这里插入图片描述
结论:不能达到预期效果。项目编号在表中的命名不一致,分别为project_id、asset_id两种,抽象类无法反射出两个不同的字段。

思路三:定义切面,根据关键词参数插入限制的SQL

  1. 自定义一个注解,接收项目编号的别名,用于区分字段:asset_id(缩写a)、project_id(缩写p)。
import java.lang.annotation.*;

/**
 * 项目年份过滤注解
 *
 * @author linshuixin
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AssetYearScope
{
    /**
     * 表的项目编号别名
     * asset_id:a
     * project_id:p
     */
    public String assetAlias() default "";
}
  1. 定义切面,切入点中加入所需要的代码逻辑。
import com.rmfm.common.core.utils.StringUtils;
import com.rmfm.common.core.web.domain.BaseEntity;
import com.rmfm.ecp.annotation.AssetYearScope;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 项目年份过滤处理
 *
 * @author linshuixin
 */
@Aspect
@Component
public class AssetYearScopeAspect {

    /**
     * TODO 获取缓存中的全局年份 目前先写死
     */
    public static final String YEAR = "2024";


    /**
     * 数据权限过滤关键字
     */
    public static final String YEAR_SCOPE = "yearScope";

    @Before("@annotation(yearScope)")
    public void doBefore(JoinPoint point, AssetYearScope yearScope) throws Throwable {
        clearAssetYearScope(point);
        yearScopeFilter(point,yearScope.assetAlias());
    }

    /**
     * 项目年份过滤
     *
     * @param joinPoint   切点
     */
    public static void yearScopeFilter(JoinPoint joinPoint,  String dataSopeType) {
        StringBuilder sqlString = new StringBuilder();
        //当传入a时 使用asset_id字段拼接
        if("a".equals(dataSopeType)){
            sqlString.append(StringUtils.format(
                    " AND asset_id IN (SELECT DISTINCT asset_id FROM ecp_register WHERE  is_deleted = 2 and year = '{}') ",YEAR));
        }
        //当传入p时 使用project_id字段拼接
        if("p".equals(dataSopeType)){
            sqlString.append(StringUtils.format(
                    " AND project_id IN (SELECT DISTINCT asset_id FROM ecp_register WHERE  is_deleted = 2 and year = '{}') ",YEAR));
        }
		//对目标切入面代码进行sql拼接
        if (StringUtils.isNotBlank(sqlString.toString()))
        {
            Object params = joinPoint.getArgs()[0];
            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
            {
                BaseEntity baseEntity = (BaseEntity) params;
                baseEntity.getParams().put(YEAR_SCOPE, " AND (" + sqlString.substring(4) + ")");
            }
        }
    }

    /**
     * 拼接权限sql前先清空params.yearScope参数防止注入
     * @param joinPoint
     */
    private void clearAssetYearScope(final JoinPoint joinPoint) {
        Object params = joinPoint.getArgs()[0];
        if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
            BaseEntity baseEntity = (BaseEntity) params;
            baseEntity.getParams().put(YEAR_SCOPE, "");
        }
    }
}

  1. 在具体的实现方法上加入注解定位切入点,在目标SQL中加入定义的关键词参数进行拼接。
    在这里插入图片描述
    在这里插入图片描述
  2. 效果
    在这里插入图片描述

总结

  • 切面特性:在不改变原码的基础上,对代码进行动态增强。
  • 使用切面满足了需求的同时,代码量少且改动小,也便于维护。
  • 业务场景延伸:除了常规的日志、事务、消息通知外,我们可以实际编码中用该方式对数据灵活处理,比如用户权限数据限制,过滤,拦截等。
  • 少而精,非必要不使用:既然是动态代理肯定是有资源消耗的,且此类代码逻辑一般偏复杂对新手不友好。
  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值