基于MybatisPlus的数据权限管理

1,286 阅读2分钟

一个完整的权限管理通常包括功能权限数据权限两大部分,具体如下图所示

4378AFAD-CEE8-4842-8994-2D221EB47CD4.png

这篇文章主要分析右边数据权限部分的实现,主要技术点如下:

  • MyBatisPlus数据权限插件
  • 自定义注解

下面是详细的步骤

1. 创建自定义注解

创建一个自定义注解 @DataPermission,用于标识需要自动添加数据权限过滤的方法

/**
 * 数据权限注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {

}

2. 实现注解拦截器

/**
 * 数据权限注解切面
 */
@Aspect
@Component
public class DataPermissionAspect {

    /**
     * 环绕通知,拦截带有 @DataPermission 注解的方法
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(DataPermission)()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 获取方法上的 @DataPermission 注解
        DataPermission dataPermission = method.getAnnotation(DataPermission.class);

        if (dataPermission != null) {
            // 将注解信息存储在上下文中,供 MyBatis 拦截器使用
            DataPermissionContext.set(dataPermission);
        }

        try {
            // 执行目标方法
            return joinPoint.proceed();
        } finally {
            // 方法执行完毕,清除数据权限上下文,避免内存泄露
            DataPermissionContext.clear();
        }
    }
}

3. 实现MyBatis-Plus 拦截器

在自定义拦截器中加入对应的sql拼接代码

/**
 * Mybatis数据权限拦截器
 */
public class MybatisDataPermissionHandler implements MultiDataPermissionHandler {

    @Override
    public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
        try {
            // 获取当前线程中的数据权限信息
            DataPermission dataPermission = DataPermissionContext.get();
            if (dataPermission == null) {
                return null;
            }

            // 数据权限相关的 SQL 片段
            User user = SecurityUtils.getCurrentUser();
            String dataScope = user.getPost() == null ? null : user.getPost().getDataScope();
            long deptId = user.getDept() == null ? -1 : user.getDept().getId();
            if (!StringUtils.hasText(dataScope)) {
                return null;
            }
            String sqlSegment;
            if (DataScopeEnum.ALL.getValue().equals(dataScope)) {
                return null;
            } else if (DataScopeEnum.DEPT.getValue().equals(dataScope)) {
                sqlSegment = "dept_id = " + deptId;
            } else {
                sqlSegment = "create_by_id = " + user.getId();
            }
            return CCJSqlParserUtil.parseCondExpression(sqlSegment);
        } catch (JSQLParserException e) {
            e.printStackTrace();
            return null;
        }
    }
}

4. 接着,注册到MyBatis-Plus的拦截器配置中

/**
 * Mybatis配置文件
 */
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor paginationInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new DataPermissionInterceptor(new MybatisDataPermissionHandler()));
        // 如果配置多个插件, 切记分页最后添加
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

5. 最后,在具体的查询方法上使用注解

可以用在controller、service、dao或mapper的任意方法上,下面是一个controller方法的示例

@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/api/test")
public class TestController {

    private UserService userService;

    @DataPermission
    @GetMapping(value = "/list")
    public RestfulResult list() {
        return RestfulResult.success(userService.list());
    }
}

总结

上面主要是基于MyBatis-Plus的数据权限插件来实现的,MyBatis-Plus版本为3.5.5,如果不是MyBatis-Plus的项目,完全可以自己实现相应的数据权限拦截,通过集成 JSQLParser 实现动态 SQL 解析与修改就能实现类似的功能,其实MyBatisPlus底层也是基于JSQLParser来做的