数据权限-ruoyi-plus
|Word count:1.8k|Reading time:8min|Post View:
在MyBatis plus的配置类中,第一个拦截器是 PlusDataPermissionInterceptor
1 2 3 4 5 6 7 8 9 10 11 12
| public class MybatisPlusConfig {
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PlusDataPermissionInterceptor()); ... return interceptor; } }
|
这是项目定义的数据权限拦截器,用于拦截所有的sql,然后在其中添加数据权限过滤条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| public class PlusDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler();
@Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { return; } if (dataPermissionHandler.isInvalid(ms.getId())) { return; } PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); mpBs.sql(parserSingle(mpBs.sql(), ms.getId())); } @Override public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh); MappedStatement ms = mpSh.mappedStatement(); SqlCommandType sct = ms.getSqlCommandType(); if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) { if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { return; } PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); mpBs.sql(parserMulti(mpBs.sql(), ms.getId())); } } @Override protected void processSelect(Select select, int index, String sql, Object obj) { SelectBody selectBody = select.getSelectBody(); if (selectBody instanceof PlainSelect) { this.setWhere((PlainSelect) selectBody, (String) obj); } else if (selectBody instanceof SetOperationList) { SetOperationList setOperationList = (SetOperationList) selectBody; List<SelectBody> selectBodyList = setOperationList.getSelects(); selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj)); } }
@Override protected void processUpdate(Update update, int index, String sql, Object obj) { Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false); if (null != sqlSegment) { update.setWhere(sqlSegment); } }
@Override protected void processDelete(Delete delete, int index, String sql, Object obj) { Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false); if (null != sqlSegment) { delete.setWhere(sqlSegment); } }
protected void setWhere(PlainSelect plainSelect, String mappedStatementId) { Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true); if (null != sqlSegment) { plainSelect.setWhere(sqlSegment); } }
}
|
以一个数据权限为“4”(部门及部门以下)的用户在查询部门数据为例,最终程序会调用SysDeptMaper.java文件中的 selectDeptList
方法。
1 2 3 4
| @DataPermission({ @DataColumn(key = "deptName", value = "dept_id") }) List<SysDept> selectDeptList(@Param(Constants.WRAPPER) Wrapper<SysDept> queryWrapper);
|
在获取Mapper实现文件中的sql语句之后,从数据库查询数据之前,会调用 beforeQuery
方法,分析添加在 selectDeptList
方法上的注解,然后分析sql语句。因为是查询,会调用拦截器中的 processSelect
方法,该方法中又调用了 setWhere
,在已有的sql语句上根据用户的数据权限追加where条件语句。
setWhere
方法中调用了 PlusDataPermissionHandler
的 getSqlSegment
。PlusDataPermissionHandler
是项目定义的数据权限处理器,目的是用于在拦截的 sql中添加数据权限过滤条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) { DataColumn[] dataColumns = findAnnotation(mappedStatementId); if (ArrayUtil.isEmpty(dataColumns)) { invalidCacheSet.add(mappedStatementId); return where; } LoginUser currentUser = DataPermissionHelper.getVariable("user"); if (ObjectUtil.isNull(currentUser)) { currentUser = LoginHelper.getLoginUser(); DataPermissionHelper.setVariable("user", currentUser); } if (LoginHelper.isAdmin()) { return where; } String dataFilterSql = buildDataFilter(dataColumns, isSelect); if (StringUtils.isBlank(dataFilterSql)) { return where; } try { Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql); Parenthesis parenthesis = new Parenthesis(expression); if (ObjectUtil.isNotNull(where)) { return new AndExpression(where, parenthesis); } else { return parenthesis; } } catch (JSQLParserException e) { throw new ServiceException("数据权限解析异常 => " + e.getMessage()); } }
private String buildDataFilter(DataColumn[] dataColumns, boolean isSelect) { String joinStr = isSelect ? " OR " : " AND "; LoginUser user = DataPermissionHelper.getVariable("user"); StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(beanResolver); DataPermissionHelper.getContext().forEach(context::setVariable); Set<String> conditions = new HashSet<>(); for (RoleDTO role : user.getRoles()) { user.setRoleId(role.getRoleId()); DataScopeType type = DataScopeType.findCode(role.getDataScope()); if (ObjectUtil.isNull(type)) { throw new ServiceException("角色数据范围异常 => " + role.getDataScope()); } if (type == DataScopeType.ALL) { return ""; } boolean isSuccess = false; for (DataColumn dataColumn : dataColumns) { if (dataColumn.key().length != dataColumn.value().length) { throw new ServiceException("角色数据范围异常 => key与value长度不匹配"); } if (!StringUtils.containsAny(type.getSqlTemplate(), Arrays.stream(dataColumn.key()).map(key -> "#" + key).toArray(String[]::new) )) { continue; } for (int i = 0; i < dataColumn.key().length; i++) { context.setVariable(dataColumn.key()[i], dataColumn.value()[i]); }
String sql = parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class); conditions.add(joinStr + sql); isSuccess = true; } if (!isSuccess && StringUtils.isNotBlank(type.getElseSql())) { conditions.add(joinStr + type.getElseSql()); } }
if (CollUtil.isNotEmpty(conditions)) { String sql = StreamUtils.join(conditions, Function.identity(), ""); return sql.substring(joinStr.length()); } return ""; }
|
其中的 String sql = parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class);
语句将会根据用户的数据权限生成过滤语句。type
的类型为 DataScopeType
,该类定义了用户可以有哪些数据权限。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public enum DataScopeType {
ALL("1", "", ""),
CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", ""),
DEPT("3", " #{#deptName} = #{#user.deptId} ", ""),
DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", ""),
SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "); ... }
|
比如用户的数据权限为4,则对应为 " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )"
,其中的 #deptName
和 #user.deptId
会被 context
中的值替换。
@sdss.getDeptAndChild()
对应的是 SysDataScopeServiceImpl
类中的 getDeptAndChild
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| @RequiredArgsConstructor @Service("sdss") public class SysDataScopeServiceImpl implements ISysDataScopeService {
private final SysRoleDeptMapper roleDeptMapper; private final SysDeptMapper deptMapper;
@Override public String getRoleCustom(Long roleId) { List<SysRoleDept> list = roleDeptMapper.selectList( new LambdaQueryWrapper<SysRoleDept>() .select(SysRoleDept::getDeptId) .eq(SysRoleDept::getRoleId, roleId)); if (CollUtil.isNotEmpty(list)) { return StreamUtils.join(list, rd -> Convert.toStr(rd.getDeptId())); } return null; }
@Override public String getDeptAndChild(Long deptId) { List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>() .select(SysDept::getDeptId) .apply(DataBaseHelper.findInSet(deptId, "ancestors"))); List<Long> ids = StreamUtils.toList(deptList, SysDept::getDeptId); ids.add(deptId); List<SysDept> list = deptMapper.selectList(new LambdaQueryWrapper<SysDept>() .select(SysDept::getDeptId) .in(SysDept::getDeptId, ids)); if (CollUtil.isNotEmpty(list)) { return StreamUtils.join(list, d -> Convert.toStr(d.getDeptId())); } return null; } }
|
项目中的数据权限实现涉及的类如下:
类 |
说明 |
功能 |
PlusDataPermissionInterceptor |
数据权限 sql 拦截器 |
用于拦截所有 sql。 检查是否标注了 DataPermission 注解 |
PlusDataPermissionHandler |
数据权限处理器 |
在拦截的 sql中添加数据权限过滤条件 |
SysDataScopeService |
自定义 Bean 处理数据权限 |
用于自定义扩展 |
DataPermission |
组注解 |
用于标注开启数据权限 (默认过滤部门权限) |
DataColumn |
具体的数据权限字段标注 |
用于替换数据权限模板内的 key 变量 |
DataScopeType |
模板定义 |
用于定义数据权限模板 |
SysDataScopeService |
数据权限实现 |
用于自定义扩展 |
参考:
【RuoYi-Vue-Plus】学习笔记 09 - 数据权限调用流程分析(参照 Mybatis Plus 数据权限插件)