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