mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 15:46:37 +08:00
f2ab94501c
- Relocated go.mod and go.sum into backend/ root directory - Stripped redundant backend/ segments from all Go imports (github.com/Rain-kl/Wavelet/...) - Unified Makefile, swagger, and build-test to execute in backend/ module context - Ensured 100% build-test, code-check, format, and swagger pass
3.2 KiB
3.2 KiB
Wavelet 异步任务代码示例 (Cordis 插件化架构)
这些示例用于在 Cordis 插件中开发或修改 Asynq 任务时快速套用。
1. 任务定义与 Handler 编写
在对应的业务插件中(如 backend/plugins/domain/user/tasks.go):
package user
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/hibiken/asynq"
)
// 异步任务类型标识。格式推荐为 "{plugin}:{action}"
const TaskTypeSendEmail = "user:send_email"
type SendEmailPayload struct {
To string `json:"to"`
Subject string `json:"subject"`
Body string `json:"body"`
}
// Handler 处理函数
func (p *Plugin) handleSendEmail(ctx context.Context, t *asynq.Task) error {
var req SendEmailPayload
if err := json.Unmarshal(t.Payload(), &req); err != nil {
return fmt.Errorf("解析任务参数: %w", err)
}
req.To = strings.TrimSpace(req.To)
req.Subject = strings.TrimSpace(req.Subject)
req.Body = strings.TrimSpace(req.Body)
if req.To == "" || req.Subject == "" || req.Body == "" {
return errors.New("to、subject、body 不能为空")
}
// 执行实际邮件发送业务逻辑
return p.emailSvc.Send(ctx, req.To, req.Subject, req.Body)
}
2. 插件内自包含注册 (Apply)
在插件的 Apply(ctx *core.Context) 中:
package user
import (
"time"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/core/extpoints"
)
func (p *Plugin) Apply(ctx *core.Context) error {
// 1. 注册 Asynq 异步任务消费处理器
ctx.Task().Register(
TaskTypeSendEmail,
p.handleSendEmail,
extpoints.WithTaskRetry(3),
extpoints.WithTaskTimeout(2*time.Minute),
)
// 2. 注册 Cron 调度任务(例如每天凌晨 3 点清理过期 Token)
ctx.Schedule().RegisterCron(
"0 3 * * *",
"user:cleanup_expired_tokens",
map[string]any{"scope": "expired"},
)
return nil
}
3. 业务中投递异步任务
func (s *UserService) TriggerWelcomeEmail(ctx context.Context, toEmail, username string) error {
payload, _ := json.Marshal(SendEmailPayload{
To: toEmail,
Subject: "欢迎加入",
Body: fmt.Sprintf("你好 %s,欢迎使用我们的平台!", username),
})
task := asynq.NewTask(
TaskTypeSendEmail,
payload,
asynq.MaxRetry(3),
)
_, err := s.taskClient.EnqueueContext(ctx, task)
return err
}
4. 任务处理函数单元测试
func TestSendEmailPayloadValidation(t *testing.T) {
tests := []struct {
name string
payload []byte
wantErr bool
}{
{
name: "valid payload",
payload: []byte(`{"to":"user@example.com","subject":"hi","body":"welcome"}`),
wantErr: false,
},
{
name: "empty payload",
payload: nil,
wantErr: true,
},
{
name: "missing required fields",
payload: []byte(`{"to":"user@example.com","subject":""}`),
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var req SendEmailPayload
err := json.Unmarshal(tt.payload, &req)
if err == nil {
if strings.TrimSpace(req.To) == "" || strings.TrimSpace(req.Subject) == "" || strings.TrimSpace(req.Body) == "" {
err = errors.New("missing fields")
}
}
if (err != nil) != tt.wantErr {
t.Fatalf("validation error = %v, wantErr = %v", err, tt.wantErr)
}
})
}
}