如何在项目中自定义注解实现权限数据管理案例

news2024/11/23 13:39:44

如何在项目中自定义注解实现权限数据管理案例

  • 一、准备工程基本功能
    • 1. 创建工程并添加依赖
    • 2. 配置数据库信息
    • 3. Mybatis-Plus 代码生成器生成基本项目结构
    • 4. 因为项目中引入了spring-security,所有接口被保护了,所以用户实体和service分别实现UserDetails,UserDetailsService接口
    • 5. 测试
  • 二、自定义@DataScope注解,实现只返回自己能看到的信息
    • 1. 思路
    • 2. 定义注解
    • 3. 编写切面类
    • 4. 完善测试接口
    • 5. 测试

一、准备工程基本功能

1. 创建工程并添加依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2. 配置数据库信息

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql:///ry?serverTimezone=Asia/Shanghai&useSSL=false

3. Mybatis-Plus 代码生成器生成基本项目结构

@SpringBootTest
class DataScopeApplicationTests {
    @Test
    void contextLoads() {
        FastAutoGenerator.create("jdbc:mysql:///ry?serverTimezone=Asia/Shanghai&useSSL=false", "root", "123456")
                .globalConfig(builder -> {
                    builder.author("fk") // 设置作者
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("E:\\spring-boot-integration\\data-scope\\src\\main\\java"); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent("com.kun.ds") // 设置父包名
                            .pathInfo(Collections.singletonMap(OutputFile.xml, "E:\\spring-boot-integration\\data-scope\\src\\main\\resources\\mapper\\")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("sys_dept","sys_role","sys_user") // 设置需要生成的表名
                            .addTablePrefix("sys_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }
}

4. 因为项目中引入了spring-security,所有接口被保护了,所以用户实体和service分别实现UserDetails,UserDetailsService接口

@TableName("sys_user")
public class User implements Serializable, UserDetails {
	
	....前边省略实体类原有部分.....
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return userName;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService, UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(User::getUsername,username);
        User user = getOne(wrapper);
        if (user == null){
            throw new UsernameNotFoundException("用户不存在!");
        }
        return user;
    }
}

5. 测试

@RestController
@RequestMapping("/dept")
public class DeptController {

    @Autowired
    IDeptService deptService;

    @GetMapping("/")
    public List<Dept> getAllDepts(){
        return deptService.list();
    }
}

在这里插入图片描述
现在查出的是所有的部门信息。

二、自定义@DataScope注解,实现只返回自己能看到的信息

1. 思路

  • 这里的思路很简单,就是根据权限,动态的给你的SQL后边追加过滤条件。
  • 创建一个实体类BaseEntity,里边是一个map叫做params,key就是data_scope,value就是要追加的SQL。
  • 然后让每个实体类都继承这个BaseEntity,接口参数传入实体类。
  • 利用AOP前置通知给map中放一个SQL语句,mapper中用==${params.data_scope}==追加实现功能。
public class BaseEntity {
    @TableField(exist = false)
    private Map<String,String> params = new HashMap<>();

    public Map<String, String> getParams() {
        return params;
    }

    public void setParams(Map<String, String> params) {
        this.params = params;
    }
}

让所以的实体类继承上边的BaseEntity

2. 定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataScope {
	//别名
    String deptAlias() default "";

    String userAlias() default "";
}

3. 编写切面类

因为要根据用户角色生成SQL,所以先给USER实体类添加roles属性,在service中设置角色,以便切面使用

	//实体类添加修改部分
	@TableField(exist = false)
    @JsonIgnore
    private List<Role> roles;
    
    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

	@Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles.stream().map(r -> new SimpleGrantedAuthority(r.getRoleKey())).collect(Collectors.toList());
    }


	//service修改
	@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService, UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<User> qw = new QueryWrapper<>();
        qw.lambda().eq(User::getUserName, username);
        User user = getOne(qw);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        user.setRoles(userMapper.getRolesByUid(user.getUserId()));
        return user;
    }
}
<select id="getRolesByUid" resultType="com.kun.ds.entity.Role">
    select r.* from sys_role r,sys_user_role ur where r.role_id=ur.role_id and ur.user_id=#{userId}
</select>

下边是切面类代码,详见注释:

@Aspect
@Component
public class DataScopeAspect {
    //权限的分类
    public static final String DATA_SCOPE_ALL = "1";//查所有
    public static final String DATA_SCOPE_CUSTOM = "2";//表中自定义
    public static final String DATA_SCOPE_DEPT = "3";//本部门
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";//本部门及子部门
    public static final String DATA_SCOPE_SELF = "5";//只能看自己
    public static final String DATA_SCOPE = "data_scope";//param的key

    @Before("@annotation(dataScope)")
    public void doBefore(JoinPoint jp, DataScope dataScope){
        //防止SQL注入
        clearDataScope(jp);
        //获取当前用户信息
        User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        //超级管理员,不需要权限过滤
        if (user.getUserId() == 1L){
            return;
        }

        //其他权限生成过滤sql语句
        StringBuilder sql = new StringBuilder();
        List<Role> roles = user.getRoles();

        //select * from sys_dept d where d.del_flag='0' and (xxx OR xxx OR xxx)
        //d.dept_id in(select rd.dept_id from sys_user_role ur,sys_role_dept rd where ur.user_id=2 and ur.role_id=rd.role_id) 代表一个 xxx
        for (Role role:roles){
            String ds = role.getDataScope();
            //循环遍历角色根据权限生成sql
            if (DATA_SCOPE_ALL.equals(ds)) {
                //如果用户能够查看所有数据,这里什么都用不做
                return;
            } else if (DATA_SCOPE_CUSTOM.equals(ds)) {
                //自定义的数据权限,那么就根据 用户角色去查找到部门 id
                sql.append(String.format(" OR %s.dept_id in(select rd.dept_id from sys_role_dept rd where rd.role_id=%d)", dataScope.deptAlias(), role.getRoleId()));
            } else if (DATA_SCOPE_DEPT.equals(ds)) {
                //只看自己的部门
                sql.append(String.format(" OR %s.dept_id=%d", dataScope.deptAlias(), user.getDeptId()));
            } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(ds)) {
                //自己部门和子部门
                sql.append(String.format(" OR %s.dept_id in(select dept_id from sys_dept where dept_id=%d or find_in_set(%d,`ancestors`))", dataScope.deptAlias(), user.getDeptId(), user.getDeptId()));
            } else if (DATA_SCOPE_SELF.equals(ds)) {
                //只能看自己
                String s = dataScope.userAlias();
                if ("".equals(s)) {
                    //数据权限仅限于本人
                    sql.append(" OR 1=0");
                } else {
                    sql.append(String.format(" OR %s.user_id=%d", dataScope.userAlias(), user.getUserId()));
                }
            }
        }

        //and(xxx or xxx)
        Object arg = jp.getArgs()[0];
        if (arg != null && arg instanceof BaseEntity){
            BaseEntity baseEntity = (BaseEntity) arg;
            baseEntity.getParams().put(DATA_SCOPE," AND ("+sql.substring(4)+")");
        }
    }

    /**
     * 如果params中已经有参数,则删掉
     * @param jp
     */
    private void clearDataScope(JoinPoint jp) {
        Object arg = jp.getArgs()[0];
        if (arg != null && arg instanceof BaseEntity){
            BaseEntity baseEntity = (BaseEntity) arg;
            baseEntity.getParams().put(DATA_SCOPE,"");
        }
    }
    
}

4. 完善测试接口

修改刚才的测试接口,传入参数

@RestController
@RequestMapping("/dept")
public class DeptController {

    @Autowired
    IDeptService deptService;

    @GetMapping("/")
    public List<Dept> getAllDepts(Dept dept){
        return deptService.getAllDepts(dept);
    }
}

//service
@Service
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements IDeptService {

    @Autowired
    DeptMapper deptMapper;

    @Override
    @DataScope(deptAlias = "d")
    public List<Dept> getAllDepts(Dept dept) {
        return deptMapper.getAllDepts(dept);
    }
}
<select id="getAllDepts" resultType="com.kun.ds.entity.Dept">
     select * from sys_dept d where d.del_flag='0'
     ${params.data_scope}
</select>

5. 测试

  • 普通角色–data_scope=2,自定义权限
    在这里插入图片描述
  • data_scope=3,只看自己部门–105部门
    在这里插入图片描述
    在这里插入图片描述
    到这里自定义注解实现权限数据管理就完成了,点击跳转源码仓库地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/527050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一封普通的SOA检讨书

近来许多文章关于SOA是否应当被看作是一个失败。Gartner分析师们也参与了这场争论&#xff0c;写了一封虚拟的信&#xff0c;以项目经理、企业架构师或首席开发工程师的名义&#xff0c;致“CIO、CEO、CFO、CTO和所有股东”&#xff0c;表明为什么作者承认SOA完全是场失败&…

自闭症儿童为何越来越多?可能与这3大原因有关

自闭症儿童&#xff0c;常被亲切的称为“来自星星的孩子”&#xff0c;这个名字虽好听, 但对孩子来说&#xff0c;却是恶梦般的存在。患有自闭症的宝贝不仅和外界隔绝&#xff0c;就连自己的父母仿佛也“形同陌路”般。 而近年来&#xff0c;自闭症的患病率有不断走高的趋势&a…

知识库AI部署搭建-唯一客服系统文档中心

唯一客服系统知识库服务&#xff0c;支持向量形式个性化训练ChatGPT&#xff0c;该服务是独立搭建的&#xff0c;下面是一些介绍 安装docker 现在基于GPT相应实现自建本地知识库&#xff0c;必不可少的就是向量数据库&#xff0c;现在介绍下qdrant向量数据库的安装。 因为qdran…

加密解密软件VMProtect教程(四):准备项目之使用MAP文件

VMProtect是新一代软件保护实用程序。VMProtect支持德尔菲、Borland C Builder、Visual C/C、Visual Basic&#xff08;本机&#xff09;、Virtual Pascal和XCode编译器。 同时&#xff0c;VMProtect有一个内置的反汇编程序&#xff0c;可以与Windows和Mac OS X可执行文件一起…

JSON, AJAX

文章目录 JSON和AJAX文档介绍1. JSON介绍1.1 JSON快速入门1.2 JSON和字符串转换1.2.1 JSON转字符串1.2.2 字符串转JSON1.2.3 JSON和字符串转换细节 1.3 JSON在java中使用1.3.1 Java对象和JSON字符串转换1.3.2 List对象和JSON字符串转换1.3.3 Map对象和JSON字符串转换 2. Ajax介…

Python实现SMS-Activate接口调用,获取手机号和验证码

前言 本文是该专栏的第27篇,后面会持续分享python的各种干货知识,值得关注。 对于SMS-Activate平台及其注册操作方法,这里就不过多详述了。尤其随着chatgpt的火爆,让sms-activate的热度也随之上涨。可能多数同学,是通过网页操作来获取手机号。而本文主要来介绍使用python…

Lua脚本语言快速上手(针对redis)

目录 基本介绍 设计目的 Lua 特性 lua基本语法 变量 流程控制 redis执行lua脚本 - EVAL指令 案例1&#xff1a;基本案例 案例2&#xff1a;动态传参 案例3&#xff1a;执行redis类库方法 案例4&#xff1a;给redis类库方法动态传参 案例5&#xff1a;pcall函数的使…

linux学习[10]磁盘与文件系统(1):查看磁盘容量指令df 评估文件系统的磁盘使用量指令 du

文章目录 前言1. df指令2. du指令 前言 TF卡制作的过程中涉及到了磁盘分区格式化等问题&#xff0c;当时对具体的指令理解不是特别深刻&#xff1b;由此引申到我对linux中的整个磁盘与文件系统没有一个全面的认识&#xff0c;这个磁盘与文件系统的系列博客章节就对这些进行记录…

电脑技巧:分享六个有趣好玩的网站,值得收藏

目录 1、Weavesilk 2、一键抠图 3、狗屁不通文章生成器 4、小霸王在线小游戏 5、世界名画在线拼图 6、寻找不动的emoji 今天小编给大家分享六个有趣好玩的网站&#xff0c;值得收藏&#xff01; 1、Weavesilk Weavesilk是一个光线绘画网站&#xff0c;它不需要有任何绘画…

基于python计算生态的第三方库总结与介绍

摘要&#xff1a;Python语言有超过12万个第三方库&#xff0c;覆盖信息技术几乎所有领域。即使在每个方向&#xff0c;也会有大量的专业人员开发多个第三方库来给出具体设计。正是因为python有了这么多“隐形的翅膀”&#xff0c;所以python的功能才足够庞大。本文主要针对pyth…

浏览器指纹

目录 下载安装与运行 软件目前可以随机的指纹 指纹随机化的好处 什么时候不需要指纹随机化 如何在软件上设置指纹 进入指纹设置的两个入口 指纹设置的两个步骤 如何获取随机指纹 设置过程&#xff08;动画演示&#xff09; 常见问题 浏览器指纹的有效期 同一个电脑…

【网站搭建】想搭建属于自己的网站吗,教你用ECS免费搭建网站

文章目录 前言ECS尾声 前言 Hello小伙伴们好久不见啦&#xff0c;博主一直忙得不可开交&#xff01; 久别重逢&#xff0c;博主最近发现了一个搭建网站的好方法&#xff0c;想要搭建网站的小伙伴们快来试试看吧&#xff01; ECS 用ECS搭建网站&#xff0c;可以说真的太爽啦…

CMS搭建篇:内容模型配置-题库管理模型

微信小程序云开发实战-答题积分赛小程序 CMS搭建篇:内容模型配置-题库管理模型 内容模型 内容模型是对数据库中存储的数据结构的描述,包含了内容的属性定义。通过内容模型,内容管理可以自动生成内容管理界面。 这里,我们需要建立一个内容模型,描述题库所具有的属性,如:题…

用友BIP成功入围工信部《2022年信息技术应用创新解决方案》

近日&#xff0c;由工业和信息化部网络安全产业发展中心&#xff08;工业和信息化部信息中心&#xff09;发布了2022年&#xff08;第四届&#xff09;信息技术应用创新解决方案征集工作成果&#xff0c;用友网络科技有限公司&#xff08;以下简称“用友”&#xff09;申报的“…

html实现一个一闪一闪的按钮,CSS实现一个一闪一闪的按钮,Css闪烁点标

效果 实现 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title><style>#app {margin: auto 38%;margin-top: 10%;}/** 关键*/.lay-btn-box {position: relative;}.lay-btn {background: #59b0fb;border-r…

【C++项目设计】tcmalloc高并发内存池

&#x1f9f8;&#x1f9f8;&#x1f9f8;各位大佬大家好&#xff0c;我是猪皮兄弟&#x1f9f8;&#x1f9f8;&#x1f9f8; 文章目录 一、项目介绍二、池化技术与内存池池化技术内存池内存池需要解决的问题 三、malloc四、定长内存池&#xff08;了解内存池&&后面的…

51单片机(十一)蜂鸣器

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

使用ntpd同步服务端时间

安装ntpd 服务服务&#xff1a;yum install ntp -y ps&#xff1a;离线安装 1. 下载rpm离线包&#xff1a;autogen-libopts-5.18-5.el7.x86_64.rpm、ntp-4.2.6p5-29.el7.centos.2.x86_64.rpm、ntpdate-4.2.6p5-29.el7.centos.2.x86_64.rpm 2. 安装&#xff1a;rpm -Uvh --force…

logback按天归档日志

效果图 logback.xml文件配置 <configuration debug"false"><!--日志输出到文件--><appender name"BaseLogFile" class"ch.qos.logback.core.rolling.RollingFileAppender"><file>logs/logback.log</file><…

计算机网络 | 五种I/O模型

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…