主要参考了若依的数据权限处理,但是考虑到使用了MyBatis-plus,就试着结合了一下子。下面是具体代码。
1.注解
这里和若依的注解一致,增加了一些自己业务的东西。考虑到可能自定义权限有多个,所以增加了customType用来指定自定义权限方法。将注解放在dao层方法上就可以了。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
/**
* 机构表别名
* */
String orgAlias() default "";
/**
* 部门表别名
* */
String deptAlias() default "";
/**
* 用户表别名
* */
String userAlias() default "";
/**
* 权限字符(用于多个角色匹配符合要求的权限)多个权限用逗号分隔开来
*/
String permission() default "";
/**
* 自定义权限方法
*/
String customType() default "";
}
2.处理器
这里dataScopeFilter方法就是去处理数据权限,根据业务就好了。需要注意的是在存储到线程的时候,需要把你最后生成的sql的首位 and 或者 or删除去除掉,这是因为再后面mybatis-plus再拼接sql时,他会有自己and。如果不删除,可能会出现net.sf.jsqlparser.parser.ParseException: Encountered unexpected token: ,这个问题也会因为mybatis版本冲突产生。
@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 final static ThreadLocal<String> threadLocal = new ThreadLocal<>();
@Before(value = "@annotation(dataScope)")
public void doBefore(JoinPoint joinPoint, DataScope dataScope){
// 非系统管理员
if(!SaUserUtil.isAdmin()){
dataScopeFilter(dataScope.orgAlias(), dataScope.deptAlias(), dataScope.userAlias(), dataScope.customType());
}
}
/**
* 执行完方法后,将线程删除
* */
@After("@annotation(dataScope)")
public void doAfter(DataScope dataScope){
threadLocal.remove();
}
/**
* 出现异常时,线程删除
* */
@AfterThrowing("@annotation(dataScope)")
public void doAfterThrowing(DataScope dataScope){
threadLocal.remove();
}
public static void dataScopeFilter(String orgAlias,String deptAlias,String userAlias,String customType){
// 获取当前用户信息
SysUser sysUser = SaUserUtil.getSysUser();
// 获取当前登录用户角色信息
List<SysRole> sysRoles = SaUserUtil.getSysRoles();
// 所有权限标识集合
List<String> conditions = new ArrayList<>();
// sql数据
StringBuilder sqlString = new StringBuilder();
for (SysRole role : sysRoles) {
String roleScope = role.getDataScope();
if (!DATA_SCOPE_CUSTOM.equals(roleScope) && conditions.contains(roleScope))
{
continue;
}
if (DATA_SCOPE_ALL.equals(roleScope))
{
sqlString = new StringBuilder();
conditions.add(roleScope);
break;
}
else if (DATA_SCOPE_CUSTOM.equals(roleScope)){
}
else if (DATA_SCOPE_DEPT.equals(roleScope)){
}
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(roleScope)){
}
else if (DATA_SCOPE_SELF.equals(roleScope))
{
if (StringUtil.isNotBlank(userAlias))
{
sqlString.append(StringUtil.format(" or {}.user_id = {} ", userAlias, sysUser.getUserId()));
}
else
{
// 数据权限为仅本人且没有userAlias别名
sqlString.append(StringUtil.format(" or user_id= {} ", sysUser.getUserId()));
}
}
conditions.add(roleScope);
}
// sql存储
if (StringUtil.isNotBlank(sqlString.toString()))
{
// 去除sql 首位的 and 或 or,根据业务去除。
threadLocal.set(sqlString.substring(4));
}
// 多角色情况下,所有角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据
if (StringUtil.isEmpty(conditions))
{
threadLocal.set(sqlString.append(" limit 0 ").toString());
}
}
}
3.拦截器
实现mybatis-plus的数据权限接口,将处理器中生成的sql拼接到执行sql中。
AndExpression()方法会在拼接sql时提供and。
@Slf4j
public class CustomDataPermissionHandler implements DataPermissionHandler {
@Override
public Expression getSqlSegment(Expression where, String mappedStatementId) {
try {
String sql = DataScopeAspect.threadLocal.get();
// 加入sql
if(StringUtil.isNotEmpty(sql)){
Expression sqlSegmentExpression = CCJSqlParserUtil.parseCondExpression(sql);
where = new AndExpression(where, sqlSegmentExpression);
}
return where;
} catch (JSQLParserException e) {
log.error("数据权限失败!,{}",e.getMessage());
throw new ServiceException(ResultEnum.SELECT_FAIL);
}
}
}
4.注册拦截器
将拦截器注册到mybatis-plus中。需要注意如果用到分页插件,需在分页插件前进行注册。
@Component
public class MyBatisPlusConfig implements MetaObjectHandler {
/**
* 添加插件(官网最新)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 权限
interceptor.addInnerInterceptor(new DataPermissionInterceptor(new CustomDataPermissionHandler()));
// 分页
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Override
public void insertFill(MetaObject metaObject) {
// 起始版本 3.3.0(推荐使用)
this.strictInsertFill(metaObject,"status",String.class,"1");
this.strictInsertFill(metaObject,"delFlag",String.class,"1");
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
}
@Override
public void updateFill(MetaObject metaObject) {
// 起始版本 3.3.0(推荐使用)
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}