fix(persistence): migrate all pkg/persistence imports to plugins/infra/database and plugins/infra/cache

- Replace db.DB(ctx) with database.DB(ctx) from plugins/infra/database
- Replace db.Redis/db.PrefixedKey/db.GetJSON/db.SetJSON with cachepkg.* from plugins/infra/cache
- Replace pkg/persistence/idgen with pkg/idgen (already exists)
- Replace pkg/persistence/batchwriter with pkg/batchwriter (already exists)
- Replace pkg/persistence/migrator with pkg/migrator (already exists)
- Replace pkg/persistence/logstore with plugins/domain/risk_control/logstore
- Delete defunct pkg/{persistence,cap,message_gateway,push,shared,task}
- Fix vet issues: db alias in domain_test.go, driver_asynq_worker.TaskHandler reference
- Update Makefile architecture guard
- Update docs and skill references
- Update go.mod: gorilla/sessions promotion to direct dependency
This commit is contained in:
ryan
2026-08-28 10:59:24 +08:00
parent fb6a3edb89
commit 416603b616
223 changed files with 1304 additions and 10057 deletions
@@ -0,0 +1,9 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package driver_asynq_cron provides Asynq cron schedule driver plugin for Cordis.
package driver_asynq_cron
const (
errLoadLocationFailed = "failed to load location: %w"
)
+2 -1
View File
@@ -14,6 +14,7 @@ import (
"github.com/hibiken/asynq"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/plugins/drivers/driver_asynq_worker"
)
// Option configures the Asynq cron scheduler driver plugin.
@@ -61,7 +62,7 @@ type Plugin struct {
// New creates a new Asynq Cron Scheduler driver plugin.
func New(opts ...Option) *Plugin {
p := &Plugin{
redisOpt: asynq.RedisClientOpt{Addr: "127.0.0.1:6379"},
redisOpt: driver_asynq_worker.RedisOpt,
location: time.Local,
}
@@ -0,0 +1,37 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package driver_asynq_cron
import (
"context"
"time"
db "github.com/Rain-kl/Wavelet/plugins/infra/database"
)
// Schedule 定时任务配置表
type Schedule struct {
ID uint64 `json:"id,string" gorm:"primaryKey"`
Name string `json:"name" gorm:"size:128;not null"`
TaskType string `json:"task_type" gorm:"size:64;not null"`
Cron string `json:"cron" gorm:"size:64;not null"`
Payload string `json:"payload" gorm:"type:text"`
IsActive bool `json:"is_active" gorm:"not null;default:true"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;index"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// TableName 表名
func (Schedule) TableName() string {
return "w_schedules"
}
// ListActiveSchedules 查询所有已启用的定时任务配置
func ListActiveSchedules(ctx context.Context) ([]Schedule, error) {
var schedules []Schedule
if err := db.DB(ctx).Where("is_active = ?", true).Find(&schedules).Error; err != nil {
return nil, err
}
return schedules, nil
}
@@ -0,0 +1,140 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package driver_asynq_cron
import (
"context"
"fmt"
"os/signal"
"sync"
"syscall"
"time"
"github.com/hibiken/asynq"
"github.com/Rain-kl/Wavelet/pkg/logger"
"github.com/Rain-kl/Wavelet/plugins/drivers/driver_asynq_worker"
)
var (
activeScheduler *asynq.Scheduler
schedulerMutex sync.Mutex
quitChan chan struct{}
schedulerOnce sync.Once
)
// GetAsynqClient 获取全局 AsynqClient
func GetAsynqClient() *asynq.Client {
return driver_asynq_worker.AsynqClient
}
// StartScheduler 启动调度器 (该函数阻塞,直到调度器退出)
func StartScheduler() error {
var err error
schedulerOnce.Do(func() {
quitChan = make(chan struct{})
done := quitChan
// 初始化并运行首次调度
if err = ReloadScheduler(); err != nil {
err = fmt.Errorf("initial reload failed: %w", err)
return
}
signalCtx, stopSignals := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stopSignals()
if waitForStop(done, signalCtx.Done()) {
StopScheduler()
}
})
return err
}
// StopScheduler 停止调度服务并解除 StartScheduler 阻塞
func StopScheduler() {
schedulerMutex.Lock()
defer schedulerMutex.Unlock()
if activeScheduler != nil {
activeScheduler.Shutdown()
activeScheduler = nil
}
if quitChan != nil {
close(quitChan)
quitChan = nil
}
}
// ReloadScheduler 重载调度器配置 (线程安全)
func ReloadScheduler() error {
schedulerMutex.Lock()
defer schedulerMutex.Unlock()
// 1. 如果有运行中的调度器,先关闭它
if activeScheduler != nil {
activeScheduler.Shutdown()
activeScheduler = nil
}
// 2. 从数据库载入启用的定时任务配置
schedules, err := ListActiveSchedules(context.Background())
if err != nil {
return fmt.Errorf("load schedules from db failed: %w", err)
}
location, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
return fmt.Errorf(errLoadLocationFailed, err)
}
// 3. 实例化新的调度器
newScheduler := asynq.NewScheduler(
driver_asynq_worker.RedisOpt,
&asynq.SchedulerOpts{
Location: location,
},
)
// 4. 遍历并注册任务
for _, s := range schedules {
meta := driver_asynq_worker.GetTaskMeta(s.TaskType)
if meta == nil {
continue // 忽略排程配置中无效的任务类型
}
// 构造 Asynq 载荷。定时任务使用对应 Meta 中的 Asynq 标识,同时将数据库中保存的 json 作为参数
t := asynq.NewTask(meta.AsynqTask, []byte(s.Payload))
if _, err := newScheduler.Register(
s.Cron,
t,
asynq.MaxRetry(meta.MaxRetry),
asynq.Queue(meta.Queue),
); err != nil {
// 定时任务配置可能有误(如 Cron 格式不被 Asynq 识别),记录日志并跳过
logger.ErrorF(context.Background(), "[Scheduler] 注册定时任务失败 id=%d name=%s: %v", s.ID, s.Name, err)
continue
}
}
// 5. 启动并替换全局调度器。进程信号由 StartScheduler 统一处理。
if err := newScheduler.Start(); err != nil {
return fmt.Errorf("start scheduler failed: %w", err)
}
activeScheduler = newScheduler
logger.InfoF(context.Background(), "[Scheduler] 成功重新加载定时任务,共注册 %d 个活动任务", len(schedules))
return nil
}
func waitForStop(done <-chan struct{}, signals <-chan struct{}) bool {
select {
case <-done:
return false
case <-signals:
return true
}
}
@@ -0,0 +1,42 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package driver_asynq_cron
import "testing"
func TestWaitForStop(t *testing.T) {
tests := []struct {
name string
closeDone bool
closeSignal bool
wantSignal bool
}{
{
name: "explicit stop",
closeDone: true,
},
{
name: "process signal",
closeSignal: true,
wantSignal: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
done := make(chan struct{})
signals := make(chan struct{})
if tt.closeDone {
close(done)
}
if tt.closeSignal {
close(signals)
}
if got := waitForStop(done, signals); got != tt.wantSignal {
t.Errorf("waitForStop() = %t, want %t", got, tt.wantSignal)
}
})
}
}