数据权限控制
作者:唐亚峰 | battcn
字数统计:1.4k 字
学习目标
掌握 Wemirr Platform 数据权限的设计原理和使用方法
什么是数据权限?
数据权限是在功能权限(能否访问某个菜单/按钮)基础上,进一步控制用户能看到哪些数据。
权限层级
┌─────────────────────────────────────────────────────────┐
│ 权限体系 │
├─────────────────────────────────────────────────────────┤
│ 租户权限 │ 最顶层隔离,不同租户数据完全隔离 │
├─────────────────────────────────────────────────────────┤
│ 功能权限 │ 控制用户能访问哪些菜单和按钮 │
├─────────────────────────────────────────────────────────┤
│ 数据权限 │ 控制用户能看到哪些数据范围 │
└─────────────────────────────────────────────────────────┘数据权限类型(真实代码)
java
// 来自 DataScopeType.java
@Getter
@AllArgsConstructor
public enum DataScopeType implements IEnum<Integer> {
ALL(50, "全部"), // 查看所有数据
THIS_LEVEL_CHILDREN(40, "本级以及子级"), // 本部门及下级
THIS_LEVEL(30, "本级"), // 仅本部门
CUSTOMIZE(20, "自定义"), // 自定义范围
SELF(10, "个人"), // 仅本人
IGNORE(0, "跟随系统上下文"); // 跟随系统
private final Integer type;
private final String desc;
}值越大,权限越大:ALL(50) > THIS_LEVEL_CHILDREN(40) > THIS_LEVEL(30) > CUSTOMIZE(20) > SELF(10)
核心 API(真实代码)
@DataScope 注解
java
// 来自 DataScope.java - 标记在 Mapper 类或方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScope {
/**
* 是否忽略数据权限
*/
boolean ignore() default false;
/**
* 数据权限字段配置
*/
DataColumn[] columns() default {};
}@DataColumn 注解
java
// 来自 DataColumn.java - 定义数据权限字段
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataColumn {
/**
* 表别名(用于多表关联查询)
*/
String alias() default "";
/**
* 字段名称(默认 created_by)
*/
String name() default Entity.CREATE_USER_COLUMN;
/**
* Java 类型
*/
Class<?> javaClass() default Long.class;
/**
* 权限资源类型(默认 USER)
*/
DataResourceType resource() default DataResourceType.USER;
/**
* 权限范围(默认跟随系统)
*/
DataScopeType scopeType() default DataScopeType.IGNORE;
}注解方式使用
在 Mapper 上使用
java
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
// 使用默认字段 created_by 进行数据权限过滤
@DataScope(columns = @DataColumn)
IPage<Order> selectPageList(IPage<Order> page, @Param("req") OrderPageReq req);
// 指定表别名和字段
@DataScope(columns = @DataColumn(alias = "o", name = "created_by"))
List<OrderVO> selectListWithDetail(@Param("req") OrderPageReq req);
// 多字段数据权限
@DataScope(columns = {
@DataColumn(alias = "o", name = "created_by", resource = DataResourceType.USER),
@DataColumn(alias = "o", name = "org_id", resource = DataResourceType.ORG)
})
IPage<OrderVO> selectComplexPage(IPage<OrderVO> page);
// 忽略数据权限
@DataScope(ignore = true)
Order selectByIdIgnorePermission(Long id);
}编程方式使用(真实代码)
DataPermissionRuleHolder API
java
// 来自 DataPermissionRuleHolder.java - 编程式数据权限控制
public final class DataPermissionRuleHolder {
/**
* 获取当前的 DataPermissionRule
*/
public static DataPermissionRule peek();
/**
* 入栈一个 DataPermissionRule
*/
public static DataPermissionRule push(DataPermissionRule rule);
/**
* 弹出最顶部 DataPermissionRule
*/
public static void poll();
/**
* 清除 ThreadLocal
*/
public static void clear();
}DataPermissionUtils 工具类
java
// 来自 DataPermissionUtils.java
// 使用默认规则执行(created_by 字段)
List<Order> orders = DataPermissionUtils.executeDefaultDataPermissionRule(() -> {
return orderMapper.selectList(null);
});
// 使用指定规则执行
DataPermissionRule rule = DataPermissionRule.builder()
.columns(List.of(
DataPermissionRule.Column.builder()
.alias("o")
.name("created_by")
.resource(DataResourceType.USER)
.build()
))
.build();
IPage<Order> page = DataPermissionUtils.executeWithRule(rule, () -> {
return orderMapper.selectPage(req.buildPage(), wrapper);
});
// 忽略数据权限
DataPermissionRule ignoreRule = DataPermissionRule.builder()
.ignore(true)
.build();
Order order = DataPermissionUtils.executeWithRule(ignoreRule, () -> {
return orderMapper.selectById(id);
});DataPermissionRule 规则类
java
// 来自 DataPermissionRule.java
@Builder
public class DataPermissionRule {
/**
* 是否忽略数据权限
*/
private boolean ignore;
/**
* 规则字段列表
*/
private List<Column> columns;
@Data
@Builder
public static class Column {
private String alias; // 表别名
@Builder.Default
private String name = Entity.CREATE_USER_COLUMN; // 字段名(默认 created_by)
@Builder.Default
private Class<?> javaClass = Long.class; // Java 类型
@Builder.Default
private DataResourceType resource = DataResourceType.USER; // 资源类型
@Builder.Default
private DataScopeType scopeType = DataScopeType.IGNORE; // 权限范围
}
}数据权限实现原理
DataScopePermissionHandler
java
// 来自 DataScopePermissionHandler.java
@Slf4j
@RequiredArgsConstructor
public class DataScopePermissionHandler implements MultiDataPermissionHandler {
private final AuthenticationContext context;
@Override
public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
// 1. 匿名用户不处理
if (context.anonymous()) {
return null;
}
// 2. 优先从 DataPermissionRuleHolder 获取规则(编程式)
DataPermissionRule rule = DataPermissionRuleHolder.peek();
if (rule == null) {
// 3. 其次从注解获取规则
rule = DataPermissionUtils.getDataPermissionRuleByMappedStatementId(mappedStatementId);
}
// 4. 构建 SQL 条件
return buildAnnotationExpression(table, rule);
}
private Expression buildAnnotationExpression(Table table, DataPermissionRule rule) {
if (rule == null || rule.isIgnore()) {
return null;
}
final DataPermission permission = context.dataPermission();
// 全部权限不过滤
if (permission.getScopeType() == DataScopeType.ALL) {
return null;
}
// 构建条件
List<Expression> conditions = DataPermissionUtils.buildConditions(
context, table, rule.getColumns()
);
return conditions.stream().reduce(AndExpression::new).orElse(null);
}
}数据权限服务实现
java
// 来自 DataScopeServiceImpl.java
@Slf4j
@Service
@RequiredArgsConstructor
public class DataScopeServiceImpl implements DataScopeService {
private final RoleMapper roleMapper;
private final DataPermissionRefMapper dataPermissionRefMapper;
private final UserMapper userMapper;
private final OrgService orgService;
@Override
public DataPermission getDataScopeById(Long userId, Long orgId) {
List<Role> list = roleMapper.findRoleByUserId(userId);
if (CollectionUtils.isEmpty(list)) {
return DataPermission.builder().build();
}
// 找到权限最大的角色(值越大权限越大)
Role role = list.stream()
.max(Comparator.comparingInt(item -> item.getScopeType().getType()))
.get();
DataPermission permission = DataPermission.builder()
.scopeType(role.getScopeType())
.build();
List<Long> userIdList = null;
if (role.getScopeType() == CUSTOMIZE) {
// 自定义:查询角色关联的组织下的用户
List<Long> orgIdList = dataPermissionRefMapper.selectList(...)
.stream().map(DataPermissionRef::getDataId).toList();
userIdList = userMapper.selectList(Wraps.<User>lbQ()
.in(User::getOrgId, orgIdList))
.stream().map(Entity::getId).toList();
} else if (role.getScopeType() == THIS_LEVEL) {
// 本级:同组织用户
userIdList = userMapper.selectList(Wraps.<User>lbQ()
.eq(User::getOrgId, orgId))
.stream().map(Entity::getId).toList();
} else if (role.getScopeType() == THIS_LEVEL_CHILDREN) {
// 本级及子级
List<Long> orgIdList = orgService.getFullTreeIdPath(orgId);
userIdList = userMapper.selectList(Wraps.<User>lbQ()
.in(User::getOrgId, orgIdList))
.stream().map(Entity::getId).toList();
}
if (userIdList != null) {
permission.getDataPermissionMap().put(DataResourceType.USER, userIdList);
}
return permission;
}
}实战示例
Service 层使用
java
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderMapper orderMapper;
// 方式一:依赖 Mapper 上的 @DataScope 注解
public IPage<Order> pageList(OrderPageReq req) {
return orderMapper.selectPageList(req.buildPage(), req);
}
// 方式二:编程式控制
public IPage<Order> pageListWithCustomRule(OrderPageReq req) {
DataPermissionRule rule = DataPermissionRule.builder()
.columns(List.of(
DataPermissionRule.Column.builder()
.name("created_by")
.resource(DataResourceType.USER)
.build()
))
.build();
return DataPermissionUtils.executeWithRule(rule, () -> {
return orderMapper.selectPage(req.buildPage(),
Wraps.<Order>lbQ().orderByDesc(Order::getCreatedTime));
});
}
// 方式三:临时忽略数据权限(管理员操作)
public Order getByIdForAdmin(Long id) {
return DataPermissionUtils.executeWithRule(
DataPermissionRule.builder().ignore(true).build(),
() -> orderMapper.selectById(id)
);
}
}多表关联查询
java
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
// XML 中的 SQL:SELECT o.*, u.nick_name FROM t_order o LEFT JOIN t_user u ON o.created_by = u.id
@DataScope(columns = @DataColumn(alias = "o", name = "created_by"))
IPage<OrderVO> selectPageWithUser(IPage<OrderVO> page, @Param("req") OrderPageReq req);
}配置说明
yaml
# application.yml
extend:
mybatis-plus:
data-permission:
enabled: true # 启用数据权限
remote: true # 是否远程获取权限(对时效性要求高时开启)最佳实践
1. 优先级
DataPermissionRuleHolder(编程式) > @DataScope 注解编程式控制优先级最高,可在运行时动态覆盖注解配置。
2. 字段规范
sql
CREATE TABLE t_order (
id bigint PRIMARY KEY,
-- 业务字段...
-- 数据权限字段(必须)
created_by bigint COMMENT '创建人ID',
org_id bigint COMMENT '所属组织ID',
-- 审计字段
created_time datetime,
last_modify_time datetime
);
-- 添加索引
CREATE INDEX idx_created_by ON t_order(created_by);3. 常见问题
Q: 多角色如何处理? A: 取权限最大的角色(scopeType 值最大)。
Q: 如何临时忽略数据权限? A: 使用 @DataScope(ignore = true) 或 DataPermissionUtils.executeWithRule(ignoreRule, ...)。
