📄 系统数据权限使用
内部资料,请刷新扫码登录
pigcloud
# 前言
在任何系统的设计中,权限管理都是必不可少的部分。权限管理可以细分为 功能权限 和 数据权限:
- 功能权限:定义用户能够执行的操作。例如,增加产品等操作。
- 数据权限:规定用户能够访问的数据范围。例如,只能查看本部门的数据等。
其中,功能权限通常基于 RBAC(基于角色的访问控制)方案,而数据权限则需根据不同业务场景定制,在项目早期阶段进行设计和实施。
# 数据权限粒度
在数据权限管理中,可以将数据的访问权限划分为多个粒度:
- 查询全部数据:可以访问系统中所有数据。
- 查询本部门数据:只能访问当前用户所属部门的数据。
- 查询本部门及其子部门数据:不仅可以访问本部门数据,还能访问下属子部门的数据。
- 查询自定义部门范围数据:根据需要定义可访问的特定部门数据。
- 查询本人数据:仅能访问当前用户自身的数据。
# 使用
# 1. 数据权限要求:
为了实现上述数据权限,数据表必须具备两个关键字段:
- 部门列(dept_id):每条数据应关联到某个部门。
- 创建用户列(create_by):每条数据应关联到创建该数据的用户。
# 2. 自定义 SQL 方法(基于 XML)
在数据权限的实现中,通过 Mapper 自定义 XML 可以实现灵活的数据查询。如下所述,传递一个 空的 DataScope
对象
作为查询参数,即可根据当前用户的角色自动应用数据权限过滤规则。
注意:mapper.xml SQL 正常写,不要考虑数据权限
<select id="getUserVosPage" resultMap="baseResultMap">
SELECT
d.name dept_name
FROM
sys_user u
LEFT JOIN sys_dept d ON d.dept_id = u.dept_id
</select>
该方法根据用户角色自动匹配对应的权限范围,开发者不需要手动处理复杂的权限逻辑。
# 3. 使用 MyBatis-Plus 的扩展方法
如果使用 MyBatis-Plus,业务中的 Mapper 需要继承 PigxBaseMapper
,从而可以直接使用以下扩展方法实现数据权限控制:
SelectListByScope(DataScope.of())
:根据数据权限范围,查询符合条件的数据列表。SelectPageByScope(DataScope.of())
:分页查询符合数据权限条件的数据。SelectCountByScope(DataScope.of().func(DataScopeFuncEnum.COUNT))
:使用聚合函数count
,返回符合数据权限条件的数据条目数。
# 代码示例:
// 查询符合数据权限的数据列表
List<YourEntity> list = yourMapper.SelectListByScope(DataScope.of());
// 分页查询数据
Page<YourEntity> page = yourMapper.SelectPageByScope(DataScope.of());
// 使用聚合函数 count 查询
int count = yourMapper.SelectCountByScope(DataScope.of().func(DataScopeFuncEnum.COUNT));
# 4. MyBatis-Plus 的连表查询
需要注意的是,MyBatis-Plus-Join 不支持连表查询的数据权限控制。对于需要进行连表查询的复杂 SQL,建议通过 XML 自定义 SQL 实现。对于较为复杂的查询场景,推荐直接在 XML 中编写 SQL,以便灵活控制数据权限逻辑。
# 【原理】数据权限实现
- mybatis 拦截器,拦截处理参数列表带有 datascope 参数的 mapper 方法。
- 查询当前用户所属角色的数据权限配置
- 拼接一条新的 SQL, 就是对所有数据加一个部门过滤,参考下边源码处理
select * from (" + originalSql + ") temp_data_scope where temp_data_scope." + scopeName + " in (" + join + ")";
public class DataScopeInnerInterceptor implements DataScopeInterceptor {
@Setter
private DataScopeHandle dataScopeHandle;
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
String originalSql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
// 查找参数中包含DataScope类型的参数
DataScope dataScope = findDataScopeObject(parameterObject);
if (dataScope == null) {
return;
}
// 返回true 不拦截直接返回原始 SQL
if (dataScopeHandle.calcScope(dataScope)) {
return;
}
List<Long> deptIds = dataScope.getDeptList();
// 1.无数据权限限制,则直接返回 0 条数据
if (CollUtil.isEmpty(deptIds) && StrUtil.isBlank(dataScope.getUsername())) {
originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE 1 = 2",
dataScope.getFunc().getType(), originalSql);
}
// 2.如果为本人权限则走下面
else if (StrUtil.isNotBlank(dataScope.getUsername())) {
originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s = '%s'",
dataScope.getFunc().getType(), originalSql, dataScope.getScopeUserName(), dataScope.getUsername());
}
// 3.都没有,则是其他权限,走下面
else {
String join = CollectionUtil.join(deptIds, ",");
originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s IN (%s)",
dataScope.getFunc().getType(), originalSql, dataScope.getScopeDeptName(), join);
}
mpBs.sql(originalSql);
}
/**
* 查找参数是否包括DataScope对象
* @param parameterObj 参数列表
* @return DataScope
*/
private DataScope findDataScopeObject(Object parameterObj) {
if (parameterObj instanceof DataScope) {
return (DataScope) parameterObj;
} else if (parameterObj instanceof Map) {
for (Object val : ((Map<?, ?>) parameterObj).values()) {
if (val instanceof DataScope) {
return (DataScope) val;
}
}
}
return null;
}
}