Skip to content

钩子

钩子(Hooks)允许你在 API 执行的不同阶段插入自定义逻辑。

钩子类型

VEF Framework 提供以下钩子类型:

钩子类型触发时机适用 API
BeforeCreate创建记录前CreateApi, CreateManyApi
AfterCreate创建记录后CreateApi, CreateManyApi
BeforeUpdate更新记录前UpdateApi, UpdateManyApi
AfterUpdate更新记录后UpdateApi, UpdateManyApi
BeforeDelete删除记录前DeleteApi, DeleteManyApi
AfterDelete删除记录后DeleteApi, DeleteManyApi
BeforeQuery查询执行前FindOneApi, FindAllApi, FindPageApi
AfterQuery查询执行后FindOneApi, FindAllApi, FindPageApi

基本用法

添加单个钩子

go
apis.NewCreateApi[models.User, payloads.UserParams]().
    WithHook(api.BeforeCreate, func(ctx fiber.Ctx, params *payloads.UserParams) error {
        // Validate business rules
        if params.Username == "admin" {
            return errors.New("cannot create user with username 'admin'")
        }
        return nil
    })

添加多个钩子

go
apis.NewCreateApi[models.User, payloads.UserParams]().
    WithHook(api.BeforeCreate, validateUserParams).
    WithHook(api.BeforeCreate, hashPassword).
    WithHook(api.AfterCreate, sendWelcomeEmail).
    WithHook(api.AfterCreate, createDefaultSettings)

钩子函数签名

创建钩子

go
// BeforeCreate hook
func beforeCreateHook(ctx fiber.Ctx, params *payloads.UserParams) error {
    // params is the input parameters
    // Return error to abort the operation
    return nil
}

// AfterCreate hook
func afterCreateHook(ctx fiber.Ctx, model *models.User) error {
    // model is the created record
    return nil
}

更新钩子

go
// BeforeUpdate hook
func beforeUpdateHook(ctx fiber.Ctx, params *payloads.UserParams, existing *models.User) error {
    // params is the input parameters
    // existing is the current record in database
    return nil
}

// AfterUpdate hook
func afterUpdateHook(ctx fiber.Ctx, model *models.User) error {
    // model is the updated record
    return nil
}

删除钩子

go
// BeforeDelete hook
func beforeDeleteHook(ctx fiber.Ctx, model *models.User) error {
    // model is the record to be deleted
    // Return error to prevent deletion
    return nil
}

// AfterDelete hook
func afterDeleteHook(ctx fiber.Ctx, model *models.User) error {
    // model is the deleted record
    return nil
}

查询钩子

go
// BeforeQuery hook
func beforeQueryHook(ctx fiber.Ctx, search *payloads.UserSearch) error {
    // search is the search parameters
    return nil
}

// AfterQuery hook (for FindPageApi)
func afterQueryHook(ctx fiber.Ctx, items []models.User, total int64) error {
    // items is the query result
    // total is the total count
    return nil
}

常见使用场景

数据验证

go
func validateUserParams(ctx fiber.Ctx, params *payloads.UserParams) error {
    // Check if username is unique
    var count int64
    orm.DB().Model(&models.User{}).
        Where("username = ?", params.Username).
        Count(&count)
    
    if count > 0 {
        return errors.New("username already exists")
    }
    
    return nil
}

密码加密

go
func hashPassword(ctx fiber.Ctx, params *payloads.UserParams) error {
    if params.Password != "" {
        hashedPassword, err := bcrypt.GenerateFromPassword(
            []byte(params.Password),
            bcrypt.DefaultCost,
        )
        if err != nil {
            return err
        }
        params.Password = string(hashedPassword)
    }
    return nil
}

发送通知

go
func sendWelcomeEmail(ctx fiber.Ctx, user *models.User) error {
    // Send welcome email asynchronously
    go func() {
        emailService.SendWelcomeEmail(user.Email, user.Username)
    }()
    return nil
}

审计日志

go
func logUserCreation(ctx fiber.Ctx, user *models.User) error {
    currentUser := contextx.GetCurrentUser(ctx)
    
    auditLog := &models.AuditLog{
        Action:     "create_user",
        TargetId:   user.Id,
        TargetType: "user",
        OperatorId: currentUser.Id,
        Details:    fmt.Sprintf("Created user: %s", user.Username),
        CreatedAt:  time.Now(),
    }
    
    return orm.DB().Create(auditLog).Error
}

级联删除

go
func deleteUserRelatedData(ctx fiber.Ctx, user *models.User) error {
    // Delete user's settings
    if err := orm.DB().Where("user_id = ?", user.Id).Delete(&models.UserSettings{}).Error; err != nil {
        return err
    }
    
    // Delete user's tokens
    if err := orm.DB().Where("user_id = ?", user.Id).Delete(&models.UserToken{}).Error; err != nil {
        return err
    }
    
    return nil
}

防止删除

go
func checkUserDeletable(ctx fiber.Ctx, user *models.User) error {
    // Prevent deleting admin user
    if user.Username == "admin" {
        return errors.New("cannot delete admin user")
    }
    
    // Check if user has active orders
    var orderCount int64
    orm.DB().Model(&models.Order{}).
        Where("user_id = ? AND status = ?", user.Id, "active").
        Count(&orderCount)
    
    if orderCount > 0 {
        return errors.New("cannot delete user with active orders")
    }
    
    return nil
}

钩子执行顺序

钩子按照添加顺序执行:

go
apis.NewCreateApi[models.User, payloads.UserParams]().
    WithHook(api.BeforeCreate, hook1). // Executed first
    WithHook(api.BeforeCreate, hook2). // Executed second
    WithHook(api.BeforeCreate, hook3)  // Executed third

如果任何钩子返回错误,后续钩子将不会执行,操作将被中止。

事务中的钩子

钩子在同一个数据库事务中执行:

go
// All hooks run in the same transaction
apis.NewCreateApi[models.User, payloads.UserParams]().
    WithHook(api.BeforeCreate, validateUser).    // Transaction started
    WithHook(api.AfterCreate, createUserProfile) // Same transaction
    // Transaction committed after all hooks succeed

如果任何钩子返回错误,整个事务将回滚。

访问上下文数据

在钩子中访问请求上下文:

go
func myHook(ctx fiber.Ctx, params *payloads.UserParams) error {
    // Get current user
    currentUser := contextx.GetCurrentUser(ctx)
    
    // Get request ID
    requestId := contextx.GetRequestId(ctx)
    
    // Get custom context value
    tenantId := ctx.Locals("tenantId").(string)
    
    return nil
}

完整示例

go
package resources

import (
    "my-app/internal/modules/user/models"
    "my-app/internal/modules/user/payloads"
    "my-app/internal/services"
    
    "github.com/ilxqx/vef-framework-go/api"
    "github.com/ilxqx/vef-framework-go/apis"
    "github.com/ilxqx/vef-framework-go/contextx"
    "github.com/gofiber/fiber/v2"
)

type UserResource struct {
    api.Resource
    apis.CreateApi[models.User, payloads.UserParams]
    apis.UpdateApi[models.User, payloads.UserParams]
    apis.DeleteApi[models.User]
}

func NewUserResource(emailService *services.EmailService) api.Resource {
    return &UserResource{
        Resource: api.NewResource("smp/sys/user"),
        
        CreateApi: apis.NewCreateApi[models.User, payloads.UserParams]().
            WithHook(api.BeforeCreate, validateUniqueUsername).
            WithHook(api.BeforeCreate, hashUserPassword).
            WithHook(api.AfterCreate, func(ctx fiber.Ctx, user *models.User) error {
                // Send welcome email
                go emailService.SendWelcomeEmail(user.Email, user.Username)
                return nil
            }).
            WithHook(api.AfterCreate, logUserCreation),
        
        UpdateApi: apis.NewUpdateApi[models.User, payloads.UserParams]().
            WithHook(api.BeforeUpdate, func(ctx fiber.Ctx, params *payloads.UserParams, existing *models.User) error {
                // Prevent changing admin username
                if existing.Username == "admin" && params.Username != "admin" {
                    return errors.New("cannot change admin username")
                }
                return nil
            }).
            WithHook(api.AfterUpdate, logUserUpdate),
        
        DeleteApi: apis.NewDeleteApi[models.User]().
            WithHook(api.BeforeDelete, checkUserDeletable).
            WithHook(api.BeforeDelete, deleteUserRelatedData).
            WithHook(api.AfterDelete, logUserDeletion),
    }
}

下一步

基于 Apache License 2.0 许可发布