数据权限
youlai-gin 支持数据权限控制,实现方案与 youlai-boot 完全对齐。
基于 GORM Scope + 中间件 的组合方案:
- ApplyDataScope:对 GORM 查询应用数据权限过滤
- DataScopeFilter:GORM Scope 函数,链式调用更优雅
- HasPermissionToUser:判断是否有权限操作目标用户
与 youlai-boot 对齐
| 特性 | youlai-boot | youlai-gin |
|---|---|---|
| 多角色策略 | 并集(OR)策略 | 并集(OR)策略 |
| 自定义字段名 | deptIdColumnName, userIdColumnName | DeptIDColumn, 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_CHILDREN | dept_id IN (部门及子部门ID列表) |
| DEPT | dept_id = 当前部门ID |
| SELF | create_by = 当前用户ID |
| CUSTOM | dept_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注意事项
- ROOT 角色自动跳过:拥有 ROOT 角色的用户不受数据权限限制
- 必须手动调用 ApplyDataScope:在查询前显式调用过滤函数
- 并集策略:多角色时权限取并集,用户能看到所有角色权限范围内的数据
- 枚举值对齐:与 youlai-boot 保持一致,确保前后端数据权限值匹配
- 表别名:JOIN 查询时需指定正确的表别名,避免字段歧义
