Skip to content

数据权限控制

作者:唐亚峰 | 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, ...)


下一步