Skip to content

数据权限

youlai-gin 支持数据权限控制,实现方案与 youlai-boot 完全对齐。

基于 GORM Scope + 中间件 的组合方案:

  • ApplyDataScope:对 GORM 查询应用数据权限过滤
  • DataScopeFilter:GORM Scope 函数,链式调用更优雅
  • HasPermissionToUser:判断是否有权限操作目标用户

与 youlai-boot 对齐

特性youlai-bootyoulai-gin
多角色策略并集(OR)策略并集(OR)策略
自定义字段名deptIdColumnName, userIdColumnNameDeptIDColumn, UserIDColumn
超级管理员豁免ROOT 角色跳过ROOT 角色跳过
注解机制@DataPermission无(需手动调用)

整体架构图

数据范围枚举 DataScopeEnum

位置:pkg/middleware/data_scope.go

go
const (
    DataScopeAll             = 1 // 全部数据 - 最高权限
    DataScopeDeptAndChildren = 2 // 部门及子部门
    DataScopeDept            = 3 // 本部门
    DataScopeSelf            = 4 // 仅本人
    DataScopeCustom          = 5 // 自定义部门
)

注意:枚举值越小,权限范围越大。

核心组件

1. DataPermissionConfig 配置

go
type DataPermissionConfig struct {
    DeptAlias    string // 部门表别名
    DeptIDColumn string // 部门ID字段名,默认 "dept_id"
    UserAlias    string // 用户表别名
    UserIDColumn string // 用户ID字段名,默认 "create_by"
}

2. RoleDataScope 模型

支持多角色数据权限,每个角色独立维护数据权限信息:

go
type RoleDataScope struct {
    RoleCode      string  // 角色编码
    DataScope     int     // 数据权限范围值 (1-5)
    CustomDeptIDs []int64 // 自定义部门ID列表(CUSTOM 时使用)
}

3. 多角色并集策略

核心逻辑位于 buildUnionCondition

go
// 构建多角色并集查询条件
func buildUnionCondition(db *gorm.DB, dataScopes []auth.RoleDataScope, 
    deptColumn, userColumn string, userID int64) *gorm.DB {
    
    // 收集各角色的条件
    var orConditions []string
    var args []interface{}
    
    for _, ds := range dataScopes {
        cond, condArgs := buildRoleCondition(ds, deptColumn, userColumn, userID)
        if cond != "" {
            orConditions = append(orConditions, cond)
            args = append(args, condArgs...)
        }
    }
    
    // 使用 OR 连接所有条件
    // (条件1) OR (条件2) OR (条件3)
    ...
}

各权限类型生成的 WHERE 条件

权限类型生成的 WHERE 条件
ALL无过滤
DEPT_AND_CHILDRENdept_id IN (部门及子部门ID列表)
DEPTdept_id = 当前部门ID
SELFcreate_by = 当前用户ID
CUSTOMdept_id IN (自定义部门ID列表)

使用示例

基本使用

go
import (
    "youlai-gin/pkg/auth"
    "youlai-gin/pkg/middleware"
)

// 方式一:ApplyDataScope
func ListUsers(c *gin.Context) {
    user := auth.MustGetUser(c)
    db := database.DB.Model(&User{})
    
    // 应用数据权限过滤
    db = middleware.ApplyDataScope(db, user, middleware.DataPermissionConfig{
        DeptIDColumn: "dept_id",
        UserIDColumn: "create_by",
    })
    
    var users []User
    db.Find(&users)
    c.JSON(200, users)
}

// 方式二:DataScopeFilter (GORM Scope)
func ListUsers(c *gin.Context) {
    user := auth.MustGetUser(c)
    
    var users []User
    database.DB.Scopes(middleware.DataScopeFilter(user, middleware.DataPermissionConfig{
        DeptIDColumn: "dept_id",
        UserIDColumn: "create_by",
    })).Find(&users)
    
    c.JSON(200, users)
}

自定义字段名

go
// 任务表使用不同的字段名
func ListTasks(c *gin.Context) {
    user := auth.MustGetUser(c)
    
    var tasks []Task
    database.DB.Scopes(middleware.DataScopeFilter(user, middleware.DataPermissionConfig{
        DeptIDColumn: "org_id",
        UserIDColumn: "creator_id",
    })).Find(&tasks)
    
    c.JSON(200, tasks)
}

表别名支持

go
// JOIN 查询时使用表别名
func ListUsersWithDept(c *gin.Context) {
    user := auth.MustGetUser(c)
    
    var users []User
    database.DB.Table("sys_user u").
        Select("u.*, d.name as dept_name").
        Joins("LEFT JOIN sys_dept d ON u.dept_id = d.id").
        Scopes(middleware.DataScopeFilter(user, middleware.DataPermissionConfig{
            DeptAlias:    "u",
            DeptIDColumn: "dept_id",
            UserAlias:    "u",
            UserIDColumn: "create_by",
        })).
        Find(&users)
    
    c.JSON(200, users)
}

// 生成的 WHERE 条件:
// WHERE (u.dept_id IN (1, 2, 3)) OR (u.create_by = 100)

多角色数据权限

用户拥有多个角色时,采用并集策略(OR 连接)

go
// 用户拥有两个角色:
// - 部门管理员: dataScope = 3 (本部门)
// - 普通用户: dataScope = 4 (本人)

// 生成的 WHERE 条件:
// WHERE (dept_id = 10) OR (create_by = 100)

// 用户可以看到本部门的数据 OR 自己创建的数据

获取可查看用户范围

go
import "youlai-gin/pkg/middleware"

func ListUsers(c *gin.Context) {
    user := auth.MustGetUser(c)
    
    // 返回 "all" 或部门ID列表
    scopeType, deptIDs := middleware.GetViewableUserIDs(user)
    
    var users []User
    db := database.DB.Model(&User{})
    
    if scopeType == "all" {
        // 全部数据权限,不过滤
    } else if len(deptIDs) > 0 {
        // 按部门ID过滤
        db = db.Where("dept_id IN ?", deptIDs)
    } else {
        // 仅本人
        db = db.Where("id = ?", user.UserID)
    }
    
    db.Find(&users)
    c.JSON(200, users)
}

判断是否有权限操作目标用户

go
import "youlai-gin/pkg/middleware"

func UpdateUser(c *gin.Context) {
    currentUser := auth.MustGetUser(c)
    targetUserID := c.Param("id")
    
    // 检查是否有权限操作目标用户
    if !middleware.HasPermissionToUser(currentUser, targetUserID, database.DB) {
        c.JSON(403, gin.H{"msg": "无权限操作此用户"})
        return
    }
    
    // 执行更新操作
    ...
}

数据库设计

sys_role 表

sql
CREATE TABLE `sys_role` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `code` varchar(32) NOT NULL COMMENT '角色编码',
  `data_scope` tinyint COMMENT '数据权限(1-全部 2-部门及子部门 3-本部门 4-本人 5-自定义)',
  PRIMARY KEY (`id`)
);

sys_role_dept 表(自定义权限)

sql
CREATE TABLE `sys_role_dept` (
  `role_id` bigint NOT NULL COMMENT '角色ID',
  `dept_id` bigint NOT NULL COMMENT '部门ID',
  UNIQUE INDEX `uk_roleid_deptid`(`role_id`, `dept_id`)
);

权限模块结构

pkg/
├── auth/
│   └── model.go              # RoleDataScope 模型定义
├── middleware/
│   └── data_scope.go         # 数据权限中间件
│       ├── DataScopeEnum            # 数据权限枚举
│       ├── DataPermissionConfig     # 配置结构
│       ├── ApplyDataScope           # 应用数据权限函数
│       ├── DataScopeFilter          # GORM Scope 函数
│       ├── GetViewableUserIDs       # 获取可查看用户ID
│       └── HasPermissionToUser      # 判断是否有权限操作用户

internal/system/permission/
├── model/
│   └── user_permissions_vo.go # UserPermissionsVO
└── service/
    └── permission_service.go  # GetUserDataScopes, HasAllDataScope

注意事项

  1. ROOT 角色自动跳过:拥有 ROOT 角色的用户不受数据权限限制
  2. 必须手动调用 ApplyDataScope:在查询前显式调用过滤函数
  3. 并集策略:多角色时权限取并集,用户能看到所有角色权限范围内的数据
  4. 枚举值对齐:与 youlai-boot 保持一致,确保前后端数据权限值匹配
  5. 表别名:JOIN 查询时需指定正确的表别名,避免字段歧义

基于 MIT 许可发布