一个完整的权限管理通常包括功能权限和数据权限两大部分,具体如下图所示
这篇文章主要分析右边数据权限部分的实现,主要技术点如下:
- 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来做的