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

一、准备工程基本功能

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部门
    在这里插入图片描述
    在这里插入图片描述
    到这里自定义注解实现权限数据管理就完成了,点击跳转源码仓库地址
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
自定义注解可以用于实现接口管理。在开发步骤,我们可以使用自定义的注解类来标记接口,然后通过相应的处理器来处理这些标记的接口。 首先,我们需要定义一个自定义注解类,比如`@CustomEndpoint`。这个注解可以使用`@Retention`注解来指定其保留策略为`RUNTIME`,表示在运行时仍然可用。同时,可以使用`@Target`注解来指定注解的作用目标为类。这样,我们就可以在接口上使用`@CustomEndpoint`注解来标记该接口需要进行管理。 接着,在接口管理的处理器,我们可以使用反射来扫描并处理所有标记了`@CustomEndpoint`注解的接口。我们可以通过获取所有带有该注解的类,然后对这些类进行相应的操作,比如注册、调用等。 在这个处理器,我们可以使用类似于`@Component`注解的机制来扫描和管理这些接口。我们可以定义一个`HandlerMapping`类,该类负责维护一个接口与处理器的映射关系。当扫描到一个标记了`@CustomEndpoint`注解的接口时,我们可以将其实例化,并将其加入到`HandlerMapping`进行管理。 通过这种方式,我们就可以实现自定义注解来进行接口管理。每当有新的接口需要加入管理时,我们只需要在接口上添加`@CustomEndpoint`注解即可。而处理器会自动将其加入到管理,方便后续的操作和调用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [SpringMVC自定义注解实现接口调用](https://blog.csdn.net/asoklove/article/details/114986898)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* [基于SpringBoot 的图书管理系统](https://download.csdn.net/download/weixin_52395743/85850783)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anton丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值