Spring Boot 3 整合 Mybatis-Plus 实现数据权限控制

Spring Boot 3 整合 Mybatis-Plus 实现数据权限控制

早期使用spring boot的时候写数据权限是通过使用自定义一个切面@Aspect配合自定义DataScope注解来实现。

DataScopeAspect 类

scss 复制代码
@Aspect
@Component
public class DataScopeAspect {
​
    @Resource
    private SysDeptService sysDeptService;
​
    @Resource
    private SysUserRoleService sysUserRoleService;
​
    @Resource
    private SysRoleDeptService sysRoleDeptService;
​
    /**
     * 配置织入点
     */
    @Pointcut("@annotation(cn.harry.common.annotation.DataScope)")
    public void dataScopePointCut() {
    }
​
    @Before("dataScopePointCut()")
    public void doBefore(JoinPoint point) {
        handleDataScope(point);
    }
​
    protected void handleDataScope(final JoinPoint joinPoint) {
        Object params = joinPoint.getArgs()[0];
​
        if (params instanceof Map) {
            SysUser user = SecurityUtils.getSysUser();
            // 如果不是超级管理员,则进行数据过滤
            if (!SecurityUtils.isAdmin(user.getId())) {
                // 根据用户ID 获取数据权限标识 如果数据权限包含全部数据 直接返回
                List<Integer> dataScopes = sysUserRoleService
                        .listDataScopesByUserId(user.getId());
                if (!CollectionUtils.isEmpty(dataScopes)) {
                    if (dataScopes.contains(DataScopeEnums.ALL.getKey())) {
                        return;
                    }
                }
                Map map = (Map) params;
                map.put(CommonConstant.SQL_FILTER, this.getSQLFilter(user, joinPoint));
            }
            return;
        }
        throw new ApiException("数据权限接口,只能是Map类型参数,且不能为NULL");
    }
​
    /**
     * 获取数据过滤的SQL
     * @param user
     * @param point
     * @return
     */
    private String getSQLFilter(SysUser user, JoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        DataScope dataFilter = signature.getMethod().getAnnotation(DataScope.class);
        // 获取表的别名
        String tableAlias = dataFilter.tableAlias();
        if (StringUtils.isNotBlank(tableAlias)) {
            tableAlias += ".";
        }
        // 部门ID列表
        Set<Long> deptIdList = new HashSet<>();
        StringBuilder sqlFilter = new StringBuilder();
​
        // 1. 根据用户ID 获取角色列表
        List<Long> roleIdList = sysUserRoleService.listRoleIdByUserId(user.getId());
        if (!roleIdList.isEmpty()) {
            // 2.用户角色对应的部门ID列表
            List<Long> idList = sysRoleDeptService.queryDeptIdList(roleIdList);
            deptIdList.addAll(idList);
        }
​
        // 用户子部门ID列表
        if (dataFilter.subDept()) {
            List<Long> subDeptIdList = sysDeptService.getSubDeptIdList(user.getDeptId());
            deptIdList.addAll(subDeptIdList);
        }
        sqlFilter.append(" (");
​
        if (!deptIdList.isEmpty()) {
            sqlFilter.append(tableAlias).append(dataFilter.deptId()).append(" in(")
                    .append(StringUtils.join(deptIdList, ",")).append(")");
        }
​
        // 没有设置本部门数据权限,也能查询本部门数据 user.getDeptId()
        if (dataFilter.user()) {
            if (!deptIdList.isEmpty()) {
                sqlFilter.append(" or ");
            }
            sqlFilter.append(tableAlias).append(dataFilter.deptId()).append("=")
                    .append(user.getDeptId());
        }
​
        sqlFilter.append(")");
​
        if ("()".equals(sqlFilter.toString().trim())) {
            return null;
        }
​
        return sqlFilter.toString();
    }
​
}

DataScope 注解类

less 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
​
    /** 表的别名 */
    String tableAlias() default "";
​
    /** true:没有本部门数据权限,也能查询本人数据 */
    boolean user() default true;
​
    /** true:拥有子部门数据权限 */
    boolean subDept() default false;
​
    /** 部门ID */
    String deptId() default "dept_id";
​
    /** 用户ID */
    String userId() default "user_id";
​
}

早期Spring Boot 项目中实现项目权限的写法,入参使用map,service层实现时使用wrapper.apply拼接了sql语句,虽说也能实现数据权限的控制,但同时代码可读性很差.

在整合Spring Boot 3框架的时候,发现MyBatis-Plus 提供了一个数据权限插件,这样我就可以不用破坏它原本的结构,实现数据权限的管理。我们看一下MyBatis-Plus 数据权限插件。

MyBatis-Plus 数据权限插件

地址: baomidou.com/plugins/dat...

DataPermissionInterceptor 是 MyBatis-Plus 提供的一个插件,用于实现数据权限控制。它通过拦截执行的 SQL 语句,并动态拼接权限相关的 SQL 片段,来实现对用户数据访问的控制。

插件原理

DataPermissionInterceptor 的工作原理与租户插件类似,它会在 SQL 执行前拦截 SQL 语句,并根据用户权限动态添加权限相关的 SQL 片段。这样,只有用户有权限访问的数据才会被查询出来。

Spring Boot 3 项目整合DataPermissionInterceptor 实现数据权限

实现DataPermissionHandler

ini 复制代码
​
/**
 * 数据权限控制器
 *
 * @author harry
 * @公众号 Harry技术
 */
@Slf4j
public class MybatisPlusDataPermissionHandler implements DataPermissionHandler {
​
    @Override
    @SneakyThrows
    public Expression getSqlSegment(Expression where, String mappedStatementId) {
​
        log.info(" 数据权限控制器 :{}", mappedStatementId);
​
        Class<?> clazz = Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(StringPool.DOT)));
        String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(StringPool.DOT) + 1);
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                DataScope annotation = method.getAnnotation(DataScope.class);
                // 如果没有注解或者是超级管理员,直接返回
                if (annotation == null || SecurityUtils.isRoot()) {
                    return where;
                }
                return dataScopeFilter(annotation, where);
            }
        }
        return where;
    }
​
    @SneakyThrows
    private Expression dataScopeFilter(DataScope annotation, Expression where) {
        String deptAlias = annotation.deptAlias();
        String userAlias = annotation.userAlias();
        String deptIdColumnName = annotation.deptIdColumnName();
        String userIdColumnName = annotation.userIdColumnName();
​
​
        String deptColumnName = StrUtil.isNotBlank(deptAlias) ? (deptAlias + StringPool.DOT + deptIdColumnName) : deptIdColumnName;
        String userColumnName = StrUtil.isNotBlank(userAlias) ? (userAlias + StringPool.DOT + userIdColumnName) : userIdColumnName;
​
        // 获取当前用户的数据权限
        Integer dataScope = SecurityUtils.getDataScope();
​
        DataScopeEnums dataScopeEnum = IBaseEnum.getEnumByValue(dataScope, DataScopeEnums.class);
​
        Long deptId, userId;
        String appendSqlStr;
        switch (dataScopeEnum) {
            case ALL:
                return where;
            case DEPT:
                deptId = SecurityUtils.getDeptId();
                appendSqlStr = deptColumnName + StringPool.EQUALS + deptId;
                break;
            case SELF:
                userId = SecurityUtils.getUserId();
                appendSqlStr = userColumnName + StringPool.EQUALS + userId;
                break;
            // 默认部门及子部门数据权限
            default:
                deptId = SecurityUtils.getDeptId();
                appendSqlStr = deptColumnName + " IN ( SELECT id FROM sys_dept WHERE id = " + deptId + " OR FIND_IN_SET( " + deptId + " , ancestors ) )";
                break;
        }
​
        if (StrUtil.isBlank(appendSqlStr)) {
            return where;
        }
​
        Expression appendExpression = CCJSqlParserUtil.parseCondExpression(appendSqlStr);
​
        if (where == null) {
            return appendExpression;
        }
​
        return new AndExpression(where, appendExpression);
    }
}
​

再Mapper上使用自定义注解

less 复制代码
@Override
@DataScope(userIdColumnName = "id")
List<SysUser> selectList(IPage<SysUser> page, @Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);

userIdColumnName = "id"表示我们将用户ID传递为id

定义权限枚举

less 复制代码
​
/**
 * 数据权限枚举
 *
 * @author harry
 * @公众号 Harry技术
 */
@Getter
@AllArgsConstructor
public enum DataScopeEnums implements IBaseEnum<Integer> {
    /**
     * value 越小,数据权限范围越大
     */
    ALL(0, "所有数据权限"),
    DEPT_AND_SUB(1, "本部门及子部门数据"),
    DEPT(2, "本部门数据权限"),
    SELF(3, "本人数据权限"),
    ;
    private final Integer value;
​
    private final String label;
​
}
​

定义四类枚举值:根据不同的枚举类型,拼接sql语句

关于上面的数据的实现已经整合到Harry技术项目中,你可以下载源码学习使用

项目示例

基于SpringBoot3+Vue3前后端分离的Java快速开发框架

平台简介

基于 JDK 17、Spring Boot 3、Spring Security 6、JWT、Redis、Mybatis-Plus、Knife4j等构建后端,基于Vue 3、Element-Plus 、TypeScript等构建前端的分离单体权限管理系统。

  • 🚀 开发框架: 使用 Spring Boot 3 和 Vue 3,以及 Element-Plus 等主流技术栈,实时更新。
  • 🔐 安全认证: 结合 Spring Security 和 JWT 提供安全、无状态、分布式友好的身份验证和授权机制。
  • 🔑 权限管理: 基于 RBAC 模型,实现细粒度的权限控制,涵盖接口方法和按钮级别。
  • 🛠️ 功能模块: 包括用户管理、角色管理、菜单管理、部门管理、字典管理等多个功能。
  • 📘 接口文档: 自动生成接口文档,支持在线调试,提高开发效率。

内置功能

  • 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
  • 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
  • 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
  • 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
  • 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
  • 参数管理:对系统动态配置常用参数。
  • 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
  • 登录日志:系统登录日志记录查询包含登录异常。
  • 系统接口:根据业务代码自动生成相关的api接口文档,引入swagger接口文档服务的工具(Knife4j)。
后端开发

Gitee仓库地址: gitee.com/harry-tech/...

前端开发
  • 本项目是前后端分离的,还需要部署前端,才能运行起来

Gitee仓库地址: gitee.com/harry-tech/...

效果展示

Watermark 水印

暗黑模式

觉着有帮助,给个Star再走呗 ~~~~

公众号搜"Harry技术",关注我,带你看不一样的人间烟火!

相关推荐
Linux520小飞鱼37 分钟前
T-SQL语言的学习路线
开发语言·后端·golang
风月歌2 小时前
基于Spring Boot的海滨体育馆管理系统的设计与实现
java·spring boot·后端
GitNohup2 小时前
Spring boot接入xxl-job
spring boot·xxl-job
檀越剑指大厂7 小时前
【Java基础】使用Apache POI和Spring Boot实现Excel文件上传和解析功能
java·spring boot·apache
苹果酱05677 小时前
Golang的网络流量分配策略
java·spring boot·毕业设计·layui·课程设计
小青柑-7 小时前
Go语言中的接收器(Receiver)详解
开发语言·后端·golang
fangxiang20089 小时前
spring boot 集成 knife4j
java·spring boot
C++小厨神9 小时前
Bash语言的计算机基础
开发语言·后端·golang
BinaryBardC9 小时前
Bash语言的软件工程
开发语言·后端·golang
凡人的AI工具箱9 小时前
每天40分玩转Django:Django DevOps实践指南
运维·后端·python·django·devops