数据权限
VEF Framework 支持行级数据访问控制,允许根据用户角色限制数据访问范围。
数据范围概念
数据范围(DataScope)定义了用户可以访问的数据边界:
| 数据范围 | 说明 |
|---|---|
AllDataScope | 访问所有数据 |
SelfDataScope | 只能访问自己创建的数据 |
DepartmentDataScope | 访问本部门数据 |
DepartmentAndChildDataScope | 访问本部门及下级部门数据 |
CustomDataScope | 自定义数据范围 |
内置数据范围
AllDataScope
go
import "github.com/ilxqx/vef-framework-go/datascope"
// Allow access to all data
apis.NewFindPageApi[models.User, payloads.UserSearch]().
WithDataScope(datascope.NewAllDataScope())SelfDataScope
go
// Only access data created by current user
apis.NewFindPageApi[models.Order, payloads.OrderSearch]().
WithDataScope(datascope.NewSelfDataScope())
// SQL: WHERE created_by = {current_user_id}DepartmentDataScope
go
// Access data in current user's department
apis.NewFindPageApi[models.User, payloads.UserSearch]().
WithDataScope(datascope.NewDepartmentDataScope())
// SQL: WHERE department_id = {current_user_department_id}自定义数据范围
实现 DataScope 接口
go
package datascope
import (
"github.com/ilxqx/vef-framework-go/datascope"
"github.com/ilxqx/vef-framework-go/contextx"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
)
// TenantDataScope limits data to current tenant
type TenantDataScope struct{}
func NewTenantDataScope() datascope.DataScope {
return &TenantDataScope{}
}
func (s *TenantDataScope) Apply(ctx fiber.Ctx, db *gorm.DB) *gorm.DB {
currentUser := contextx.GetCurrentUser(ctx)
return db.Where("tenant_id = ?", currentUser.TenantId)
}使用自定义数据范围
go
apis.NewFindPageApi[models.Order, payloads.OrderSearch]().
WithDataScope(datascope.NewTenantDataScope())部门数据范围
DepartmentAndChildDataScope
go
// Access data in current department and child departments
type DepartmentAndChildDataScope struct{}
func NewDepartmentAndChildDataScope() datascope.DataScope {
return &DepartmentAndChildDataScope{}
}
func (s *DepartmentAndChildDataScope) Apply(ctx fiber.Ctx, db *gorm.DB) *gorm.DB {
currentUser := contextx.GetCurrentUser(ctx)
// Get all child department IDs
var deptIds []string
orm.DB().Raw(`
WITH RECURSIVE dept_tree AS (
SELECT id FROM departments WHERE id = ?
UNION ALL
SELECT d.id FROM departments d
INNER JOIN dept_tree dt ON d.parent_id = dt.id
)
SELECT id FROM dept_tree
`, currentUser.DepartmentId).Pluck("id", &deptIds)
return db.Where("department_id IN ?", deptIds)
}动态数据范围
基于角色的数据范围
go
type RoleBasedDataScope struct{}
func NewRoleBasedDataScope() datascope.DataScope {
return &RoleBasedDataScope{}
}
func (s *RoleBasedDataScope) Apply(ctx fiber.Ctx, db *gorm.DB) *gorm.DB {
currentUser := contextx.GetCurrentUser(ctx)
// Super admin sees all data
if currentUser.IsSuperAdmin() {
return db
}
// Get user's data scope from role
var dataScope string
orm.DB().
Table("roles").
Select("data_scope").
Where("id IN ?", currentUser.RoleIds).
Order("FIELD(data_scope, 'all', 'department_and_child', 'department', 'self')").
Limit(1).
Pluck("data_scope", &dataScope)
switch dataScope {
case "all":
return db
case "department_and_child":
return NewDepartmentAndChildDataScope().Apply(ctx, db)
case "department":
return db.Where("department_id = ?", currentUser.DepartmentId)
case "self":
return db.Where("created_by = ?", currentUser.Id)
default:
return db.Where("created_by = ?", currentUser.Id)
}
}组合数据范围
多条件数据范围
go
type CompositeDataScope struct {
scopes []datascope.DataScope
}
func NewCompositeDataScope(scopes ...datascope.DataScope) datascope.DataScope {
return &CompositeDataScope{scopes: scopes}
}
func (s *CompositeDataScope) Apply(ctx fiber.Ctx, db *gorm.DB) *gorm.DB {
for _, scope := range s.scopes {
db = scope.Apply(ctx, db)
}
return db
}
// Usage
apis.NewFindPageApi[models.Order, payloads.OrderSearch]().
WithDataScope(NewCompositeDataScope(
datascope.NewTenantDataScope(),
datascope.NewDepartmentDataScope(),
))数据范围与写入操作
自动设置数据范围字段
go
apis.NewCreateApi[models.Order, payloads.OrderParams]().
WithHook(api.BeforeCreate, func(ctx fiber.Ctx, params *payloads.OrderParams) error {
currentUser := contextx.GetCurrentUser(ctx)
// Auto-set tenant and department
params.TenantId = currentUser.TenantId
params.DepartmentId = currentUser.DepartmentId
return nil
})验证更新权限
go
apis.NewUpdateApi[models.Order, payloads.OrderParams]().
WithHook(api.BeforeUpdate, func(ctx fiber.Ctx, params *payloads.OrderParams, existing *models.Order) error {
currentUser := contextx.GetCurrentUser(ctx)
// Check if user can update this record
if !currentUser.IsSuperAdmin() {
if existing.TenantId != currentUser.TenantId {
return errors.New("cannot update record from different tenant")
}
}
return nil
})完整示例
go
package resources
import (
"my-app/internal/modules/order/models"
"my-app/internal/modules/order/payloads"
"github.com/ilxqx/vef-framework-go/api"
"github.com/ilxqx/vef-framework-go/apis"
"github.com/ilxqx/vef-framework-go/datascope"
)
type OrderResource struct {
api.Resource
apis.FindPageApi[models.Order, payloads.OrderSearch]
apis.CreateApi[models.Order, payloads.OrderParams]
apis.UpdateApi[models.Order, payloads.OrderParams]
apis.DeleteApi[models.Order]
}
func NewOrderResource() api.Resource {
return &OrderResource{
Resource: api.NewResource("smp/order/order"),
// Apply role-based data scope to queries
FindPageApi: apis.NewFindPageApi[models.Order, payloads.OrderSearch]().
WithPermission("order:read").
WithDataScope(datascope.NewRoleBasedDataScope()),
// Auto-set tenant and department on create
CreateApi: apis.NewCreateApi[models.Order, payloads.OrderParams]().
WithPermission("order:create").
WithHook(api.BeforeCreate, autoSetDataScopeFields),
// Validate data scope on update
UpdateApi: apis.NewUpdateApi[models.Order, payloads.OrderParams]().
WithPermission("order:update").
WithDataScope(datascope.NewRoleBasedDataScope()).
WithHook(api.BeforeUpdate, validateDataScopeAccess),
// Apply data scope to delete
DeleteApi: apis.NewDeleteApi[models.Order]().
WithPermission("order:delete").
WithDataScope(datascope.NewRoleBasedDataScope()),
}
}