mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 08:36: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
155 lines
3.2 KiB
Markdown
155 lines
3.2 KiB
Markdown
# Wavelet 异步任务代码示例 (Cordis 插件化架构)
|
|
|
|
这些示例用于在 Cordis 插件中开发或修改 Asynq 任务时快速套用。
|
|
|
|
---
|
|
|
|
## 1. 任务定义与 Handler 编写
|
|
|
|
在对应的业务插件中(如 `backend/plugins/domain/user/tasks.go`):
|
|
|
|
```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)` 中:
|
|
|
|
```go
|
|
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. 业务中投递异步任务
|
|
|
|
```go
|
|
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. 任务处理函数单元测试
|
|
|
|
```go
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
```
|