前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Spring Boot 3 整合 Mybatis-Plus 实现数据权限控制

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

原创
作者头像
Harry技术
发布2025-01-13 09:33:27
发布2025-01-13 09:33:27
1340
举报

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

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

DataScopeAspect 类

代码语言:java
复制
@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 注解类

代码语言:java
复制
@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语句,虽说也能实现数据权限的控制,但同时代码可读性很差.

image-20241227093236025
image-20241227093236025
image-20241227093726000
image-20241227093726000

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

MyBatis-Plus 数据权限插件

地址: https://baomidou.com/plugins/data-permission/

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

插件原理

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

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

实现DataPermissionHandler

代码语言:java
复制
/**
 * 数据权限控制器
 *
 * @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上使用自定义注解

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

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

定义权限枚举

代码语言:java
复制
/**
 * 数据权限枚举
 *
 * @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: https://gitee.com/harry-tech/harry.git

gitcode: https://gitcode.com/harry-tech/harry.git

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

gitee: https://gitee.com/harry-tech/harry-vue.git

https://gitcode.com/harry-tech/harry-vue.git

效果展示

Watermark 水印

Watermark_水印_20241231_120615_compressed
Watermark_水印_20241231_120615_compressed

暗黑模式

暗黑_20241231_121444_compressed
暗黑_20241231_121444_compressed

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

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring Boot 3 整合 Mybatis-Plus 实现数据权限控制
    • MyBatis-Plus 数据权限插件
      • 插件原理
    • Spring Boot 3 项目整合DataPermissionInterceptor 实现数据权限
      • 实现DataPermissionHandler
    • 项目示例
      • 基于SpringBoot3+Vue3前后端分离的Java快速开发框架
      • 平台简介
      • 内置功能
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档