Skip to content

数据权限

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()),
    }
}

下一步