data-permission

SKILL.md

行级数据权限开发指南

通用模板。如果项目有专属技能(如 leniu-data-permission),优先使用。

设计原则

  1. 对业务透明:数据权限通过拦截器自动注入 SQL 条件,业务代码无需感知。
  2. 声明式配置:通过注解声明字段映射关系,框架自动拼接过滤条件。
  3. 可扩展:权限类型(部门、本人、自定义等)可通过枚举或策略模式扩展。
  4. 安全兜底:未配置权限范围时默认为"仅本人",避免数据泄露。

权限类型设计

类型 标识 SQL 效果 适用场景
全部数据 1 不拼接条件 超管、全局数据查看
自定义权限 2 dept_id IN (角色关联的部门ID) 跨部门协作
本部门 3 dept_id = ? 部门经理
本部门及以下 4 dept_id IN (当前部门及子部门) 上级部门
仅本人 5 created_by = ? 普通员工
部门及以下或本人 6 dept_id IN (...) OR created_by = ? 混合场景

实现模式

架构概览

Controller -> Service (加注解) -> Mapper -> MyBatis 拦截器
                                              |
                                    自动注入 WHERE 条件
                                              |
                                   [你的权限处理器] (查询当前用户权限范围)

步骤 1:定义注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {
    DataColumn[] value();
    String joinStr() default "AND";  // 多角色权限连接方式
}

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataColumn {
    String key() default "deptName";   // 占位符关键字
    String value() default "dept_id";  // 对应的表字段名
    String permission() default "";     // 拥有此权限则不过滤
}

步骤 2:实现 MyBatis 拦截器

@Intercepts({@Signature(type = Executor.class, method = "query", args = {...})})
public class DataPermissionInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. 从线程上下文获取 @DataPermission 注解
        // 2. 获取当前用户的角色及数据权限范围
        // 3. 根据权限类型拼接 WHERE 条件
        // 4. 修改原始 SQL,追加过滤条件
        return invocation.proceed();
    }
}

步骤 3:在 Service / Mapper 上使用

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;

    // 按部门 + 创建人过滤
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "created_by")
    })
    @Override
    public List<OrderVo> listWithPermission(OrderQuery query) {
        return orderMapper.selectList(buildWrapper(query));
    }
}

步骤 4:确保数据库表有权限字段

CREATE TABLE biz_order (
    id           BIGINT       NOT NULL COMMENT '主键',
    -- 业务字段 ...
    dept_id      BIGINT       DEFAULT NULL COMMENT '所属部门',   -- 必须
    created_by   BIGINT       DEFAULT NULL COMMENT '创建人',     -- 必须
    created_time DATETIME     DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
);

多表关联(使用表别名)

// SQL: SELECT o.*, u.user_name FROM biz_order o LEFT JOIN sys_user u ON ...
@DataPermission({
    @DataColumn(key = "deptName", value = "o.dept_id"),
    @DataColumn(key = "userName", value = "o.created_by")
})
List<OrderVo> selectWithJoin(@Param("query") OrderQuery query);

临时忽略数据权限

// 使用工具类忽略权限过滤,查全量数据
Long total = [你的权限工具类].ignore(() -> orderService.count());

// 无返回值
[你的权限工具类].ignore(() -> {
    configService.refreshAll();
    return null;
});

指定权限标识跳过过滤

// 拥有 order:all 权限的角色不过滤
@DataPermission({
    @DataColumn(key = "deptName", value = "dept_id", permission = "order:all")
})

选型建议

方案 优点 缺点 适用场景
MyBatis 拦截器 对业务透明、自动注入 依赖 MyBatis 绝大多数 Java Web 项目
AOP + SQL 改写 框架无关 需自行解析 SQL 非 MyBatis 项目
数据库视图 完全透明 难以动态切换 权限固定的场景
应用层过滤 实现简单 性能差(全量查出再过滤) 数据量小

多角色权限计算

  • SELECT 查询:多角色权限用 OR 连接(并集,看到更多数据)
  • UPDATE / DELETE:多角色权限用 AND 连接(交集,更安全)

常见错误

// 1. 注解放在 Controller 层(无效,拦截器在 Mapper 执行前生效)
@Controller
public class OrderController {
    @DataPermission({...})  // 无效!应在 Service 或 Mapper 上
    public Result<?> list() { }
}

// 2. 表别名不匹配
@DataColumn(key = "deptName", value = "user.dept_id")  // SQL 中别名是 u
// 应为 value = "u.dept_id"

// 3. 在权限服务内部调用带权限的方法(死循环)
public String getDeptAndChild(Long deptId) {
    deptService.list(wrapper);  // 如果 list 也带 @DataPermission -> 死循环
    // 应直接用 Mapper 或 ignore() 包装
}

// 4. 忘记在表中添加部门/创建人字段
// 没有 dept_id / created_by 字段,权限 SQL 会报错

// 5. 超级管理员测试数据权限
// 超管通常跳过权限过滤,应使用普通用户账号测试

// 6. @DataPermission 注解为空
@DataPermission  // 空注解,无 @DataColumn 映射,不会生效

问题排查

检查项 可能原因 解决方案
超级管理员? 超管自动跳过权限 用普通用户测试
角色数据范围? 范围为"全部数据" 修改角色数据权限配置
注解位置? 不在 Service / Mapper 层 移动注解到正确位置
表别名? value 别名与 SQL 不一致 检查并修正别名
Unknown column? 表中没有该字段 检查数据库表结构

调试:开启 SQL 日志查看拼接结果

# MyBatis SQL 日志
logging:
  level:
    [你的Mapper包路径]: debug
Weekly Installs
3
GitHub Stars
8
First Seen
7 days ago
Installed on
gemini-cli3
github-copilot3
codex3
kimi-cli3
cursor3
amp3