SpringAOP切面实例实现对数据过滤返回,SpringAOP切面实现对用户权限控制,通过@Around注解过滤修改方法返回值

文章介绍了如何在已有的系统中使用SpringAOP、自定义注解和反射实现权限控制。首先,引入AOP相关依赖,然后定义了两个注解`@FilterByUser`和`@Filter`。接着,通过实体类和数据库表实现权限数据的存储和查询。在切面`PowerAspect`中,根据注解过滤返回的结果,实现了全等和模糊匹配的过滤逻辑。最后,展示了过滤前后的输出效果。
摘要由CSDN通过智能技术生成

需求内容:

在系统已经完成的情况下,添加以下权限:

·城市为“上海”和“深圳”的“部门一”用户,只能看到用户表数据中城市为“上海或深圳”且部门为“部门一的子部门”。

所用技术包含,自定义注解,SpringAOP切面,反射以及其他SpringBoot项目常用
-----分割线-----
2023年2月27日 17:44:05新增需求:按照字段模糊匹配,比如权限配置“河南” 可以显示“河南洛阳”、“河南郑州”
-----分割线-----

实现:

步骤一:导入SpringAOP相关依赖pom.xml
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.6.6</version>
        </dependency>
步骤二:自定义两个注解
package cn.fy.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Fy
 * 自定义注解,在实体类中有该注解的字段即可以被过滤
 * @Date 2022年12月14日 11:12:59
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Filter {

    //{"bumen":["部门11","部门12","部门13","部门14"]} 则keyName为 bumen
    //此处用“bumen”只是为了证明可以和实体类的dept不同
    String value() default "";


}

package cn.fy.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterByUser {

}

步骤三:需要用到的实体类

1.要查询要过滤的数据实体类,需继承2实体类,或(包含2实体类中需要查询的字段,并修改对应切面中获取前端传递用户账号的方法)

package cn.fy.dto;

import cn.fy.anno.Filter;
import lombok.Data;
import java.io.Serializable;
@Data
public class User extends QueryDto implements Serializable {
    private String userName;
    @Filter("bumen")
    //写成拼音只是为了证明可以与实体类字段名不一致
    private String dept;
    @Filter("chengshi")
    private String city;
}

2.接收前端传递参数的查询实体类

package cn.fy.dto;
import lombok.Data;
@Data
public class QueryDto {
    int pageIndex;
    int pageSize;
    String role;
    //登录的用户账号
    String userName;
}

3.权限数据库表对应的实体类和对应Mapper

package cn.fy.dto;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
/**
 * @author Fy
 * @since 2023-02-10
 */
@Data
@TableName("user_power")
public class UserPower implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId("id")
    private Integer id;
    @TableField("user_name")
    private String userName;
    @TableField("create_time")
    private String createTime;
    @TableField("json")
    private String json;
}

package cn.fy.sql;

import cn.fy.dto.UserPower;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

/**
 * @author Fy
 * @since 2023-02-10
 */
@Mapper
public interface UserPowerMapper extends BaseMapper<UserPower> {

}
步骤四:切面具体实现
package cn.fy.aspect;

import cn.fy.anno.Filter;
import cn.fy.dto.UserPower;
import cn.fy.dto.QueryDto;
import cn.fy.sql.UserPowerMapper;
import cn.fy.sql.UserPowerMapper;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

@Component
@Aspect
@Slf4j
public class PowerAspect {

    @Resource
    private UserPowerMapper userPowerMapper;

    //需要限定包的话则自行添加@exectution
    @Pointcut("@annotation(cn.fy.anno.FilterByUser)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        Object reDto = null;
        Object[] args = joinPoint.getArgs();
        try {
            //执行方法并获得返回值
            reDto = joinPoint.proceed(args);
        } catch (Throwable throwable) {
            log.error("", throwable);
            throw new RuntimeException(throwable);
        }

        QueryDto queryDto = null;
        for (Object arg : args) {
            if (arg instanceof QueryDto) {
                queryDto = (QueryDto) arg;
                break;
            }
        }
        if (queryDto == null) {
            return reDto;
        }

        //此部分为MybatisPlus查询数据库方法,可自行替换
        LambdaQueryWrapper<UserPower> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserPower::getUserName, queryDto.getUserName());
        UserPower powerDto = userPowerMapper.selectOne(queryWrapper);


        log.info(JSON.toJSONString(powerDto));
        log.info("---查询时间戳---" + System.currentTimeMillis());
        JSONObject jsonObject = null;
        if (powerDto == null) {
            return reDto;
        }
        try {
            jsonObject = JSON.parseObject(powerDto.getJson());
        } catch (Exception e) {
            log.error("", e);
        }
        if (jsonObject == null) {
            return reDto;
        }
        try {
            if (reDto instanceof List) {
                //是集合
                List list = (List) reDto;
                List successList = new ArrayList();
                //循环整个集合
                for (Object o : list) {
                    Field[] fields = o.getClass().getDeclaredFields();
                    List<Boolean> booleanList = new ArrayList<>();
                    for (Field field : fields) {
                        field.setAccessible(true);
                        Filter annotation = field.getAnnotation(Filter.class);
                        if (annotation != null) {
                            String keyName = annotation.value();
                            Object o1 = jsonObject.get(keyName);
                            //如果获取到了key的话
                            if (!ObjectUtils.isEmpty(o1)) {
                                List list1 = (List) o1;
                                //如果当前记录在记录中的话
                                booleanList.add(list1.contains(field.get(o)));
                                if (list1.contains(field.get(o))) {
                                    log.info("key为" + keyName + ":的值【" + field.get(o) + "】在配置的权限中");
                                }
                            }
                        }
                    }
                    if (!booleanList.contains(false)) {
                        //证明这个数据是对的
                        successList.add(o);
                    }
                }
                return successList;
            } else {
                return reDto;
            }
        } catch (Exception e) {
            log.error("", e);
        }
        return reDto;
    }


}

2023年2月27日 17:46:25新增优化可以模糊匹配

步骤一:Filter注解新增like()属性默认false全等匹配,如果为true则为模糊匹配


/**
 * @author Fy
 * 自定义注解,在实体类中有该注解的字段
 * 即为区局角色可编辑的字段
 * @Date 2022年12月14日 11:12:59
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Filter {

    //{"jsdw":["莘闵","南区","奉贤","金山"]} 则keyName为 jsdw
    String value() default "";

    //默认false匹配规则为全等匹配,为true则为模糊匹配
    boolean like() default false;


}

步骤二:增加模糊的部分在100行到111行

package cn.fy.aspect;

import cn.fy.anno.Filter;
import cn.fy.dto.UserPower;
import cn.fy.dto.QueryDto;
import cn.fy.sql.UserPowerMapper;
import cn.fy.sql.UserPowerMapper;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

@Component
@Aspect
@Slf4j
public class PowerAspect {

    @Resource
    private UserPowerMapper userPowerMapper;

    //需要限定包的话则自行添加@exectution
    @Pointcut("@annotation(cn.fy.anno.FilterByUser)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        Object reDto = null;
        Object[] args = joinPoint.getArgs();
        try {
            //执行方法并获得返回值
            reDto = joinPoint.proceed(args);
        } catch (Throwable throwable) {
            log.error("", throwable);
            throw new RuntimeException(throwable);
        }

        QueryDto queryDto = null;
        for (Object arg : args) {
            if (arg instanceof QueryDto) {
                queryDto = (QueryDto) arg;
                break;
            }
        }
        if (queryDto == null) {
            return reDto;
        }

        //此部分为MybatisPlus查询数据库方法,可自行替换
        LambdaQueryWrapper<UserPower> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserPower::getUserName, queryDto.getUserName());
        UserPower powerDto = userPowerMapper.selectOne(queryWrapper);


        log.info(JSON.toJSONString(powerDto));
        log.info("---查询时间戳---" + System.currentTimeMillis());
        JSONObject jsonObject = null;
        if (powerDto == null) {
            return reDto;
        }
        try {
            jsonObject = JSON.parseObject(powerDto.getJson());
        } catch (Exception e) {
            log.error("", e);
        }
        if (jsonObject == null) {
            return reDto;
        }
        try {
            if (reDto instanceof List) {
                //是集合
                List list = (List) reDto;
                List successList = new ArrayList();
                //循环整个集合
                for (Object o : list) {
                    Field[] fields = o.getClass().getDeclaredFields();
                    List<Boolean> booleanList = new ArrayList<>();
                    for (Field field : fields) {
                        field.setAccessible(true);
                        if (field.get(o) == null) {
                            continue;
                        }
                        Filter annotation = field.getAnnotation(Filter.class);
                        if (annotation != null) {
                            String keyName = annotation.value();
                            Object o1 = jsonObject.get(keyName);
                            boolean isLike = annotation.like();
                            //like模糊匹配
                            if (!isLike) {
                                //全等匹配
                                //如果获取到了key的话
                                if (!ObjectUtils.isEmpty(o1)) {
                                    List list1 = (List) o1;
                                    //如果当前记录在记录中的话
                                    boolean contains = list1.contains(field.get(o));
                                    booleanList.add(contains);
                                    if (list1.contains(field.get(o))) {
                                        log.info("key为" + keyName + ":的值【" + field.get(o) + "】在配置的权限中");
                                    }
                                }
                            } else {
                                //模糊匹配
                                if (!ObjectUtils.isEmpty(o1)) {
                                    List list1 = (List) o1;
                                    //如果当前记录在记录中的话
                                    List<Boolean> list2 = new ArrayList<>();
                                    for (Object o2 : list1) {
                                        boolean contains = field.get(o).toString().contains(o2.toString());
                                        list2.add(contains);
                                        if (contains) {
                                            break;
                                        }
                                    }
                                    if (list2.contains(true)) {
                                        booleanList.add(true);
                                    } else {
                                        booleanList.add(false);
                                    }
                                    if (list1.contains(field.get(o))) {
                                        log.info("key为" + keyName + ":的值【" + field.get(o) + "】在配置的权限中");
                                    }
                                }
                            }
                        }
                    }
                    if (!booleanList.contains(false)) {
                        //证明这个数据是对的
                        successList.add(o);
                    }
                }
                return successList;
            } else {
                return reDto;
            }
        } catch (Exception e) {
            log.error("", e);
        }
        return reDto;
    }


}

用法

1.需要过滤返回值的方法添加注解@FilterByUser

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2o1ptGjs-1676532488414)(C:\Users\song.cai\AppData\Roaming\Typora\typora-user-images\1676531140607.png)]

2.数据Dto在需要过滤的字段添加@Filter注解,值为数据库中json字段的key

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EKD75W9x-1676532488415)(C:\Users\song.cai\AppData\Roaming\Typora\typora-user-images\1676531216000.png)]

3.数据库中添加一条记录

在这里插入图片描述

4.完成配置的效果

原输出结果

[User(userName=user163122156, dept=部门11, city=北京), 
User(userName=user163122156, dept=部门11, city=上海), 
User(userName=user163122156, dept=部门2, city=上海), 
User(userName=user163122156, dept=部门11, city=深圳)]

加过滤之后输出结果

[User(userName=user163122156, dept=部门11, city=上海)]

实现原理描述

利用@Aspect注解来对切面进行编写,通过注解形式的切入点表达式,对加了@FilterByUser注解的方法进行过滤。利用@Around注解过滤修改原方法的返回值,在切面中通过反射获取原方法返回实体类中加了@Filter注解的字段,通过去查询数据库对该实体类中该字段的值进行比较过滤,多个@Filter需要全部校验通过才放行该对象,否则直接过滤掉不展示。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值