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
+6 -6
View File
@@ -7,16 +7,16 @@ import (
"net/http"
"strconv"
persistence "github.com/Rain-kl/Wavelet/pkg/persistence"
"github.com/Rain-kl/Wavelet/pkg/response"
"github.com/Rain-kl/Wavelet/plugins/domain/auth"
"github.com/Rain-kl/Wavelet/plugins/infra/database"
"github.com/gin-gonic/gin"
)
// ListAuthSources lists all configured authentication sources.
func ListAuthSources(c *gin.Context) {
var sources []auth.AuthSource
gormDB := persistence.DB(c.Request.Context())
gormDB := database.DB(c.Request.Context())
if err := gormDB.Order("id ASC").Find(&sources).Error; err != nil {
response.AbortInternal(c, "获取认证源列表失败")
return
@@ -51,7 +51,7 @@ func CreateAuthSource(c *gin.Context) {
return
}
gormDB := persistence.DB(c.Request.Context())
gormDB := database.DB(c.Request.Context())
if err := gormDB.Create(&source).Error; err != nil {
response.AbortBadRequest(c, "创建认证源失败: "+err.Error())
return
@@ -70,7 +70,7 @@ func UpdateAuthSource(c *gin.Context) {
return
}
gormDB := persistence.DB(c.Request.Context())
gormDB := database.DB(c.Request.Context())
var existing auth.AuthSource
if err := gormDB.First(&existing, id).Error; err != nil {
response.AbortNotFound(c, "认证源不存在")
@@ -115,7 +115,7 @@ func ToggleAuthSource(c *gin.Context) {
return
}
gormDB := persistence.DB(c.Request.Context())
gormDB := database.DB(c.Request.Context())
var existing auth.AuthSource
if err := gormDB.First(&existing, id).Error; err != nil {
response.AbortNotFound(c, "认证源不存在")
@@ -147,7 +147,7 @@ func DeleteAuthSource(c *gin.Context) {
return
}
gormDB := persistence.DB(c.Request.Context())
gormDB := database.DB(c.Request.Context())
if err := gormDB.Delete(&auth.AuthSource{}, id).Error; err != nil {
response.AbortInternal(c, "删除认证源失败")
return
+6 -5
View File
@@ -15,9 +15,10 @@ import (
"github.com/Rain-kl/Wavelet/pkg/logger"
mail "github.com/Rain-kl/Wavelet/pkg/mail"
db "github.com/Rain-kl/Wavelet/pkg/persistence"
"github.com/Rain-kl/Wavelet/pkg/response"
"github.com/Rain-kl/Wavelet/plugins/domain/cap"
cachepkg "github.com/Rain-kl/Wavelet/plugins/infra/cache"
db "github.com/Rain-kl/Wavelet/plugins/infra/database"
"github.com/Rain-kl/Wavelet/plugins/infra/storage/objectstore"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
@@ -350,15 +351,15 @@ func invalidateCachesAfterConfigUpdate(ctx context.Context, key string) {
invalidateSystemConfigCaches(ctx, key)
if key == ConfigKeyStorageConfig {
if db.Redis != nil {
_ = db.Redis.Publish(ctx, "upload:access_cache:invalidate", "reset").Err()
if cachepkg.Redis != nil {
_ = cachepkg.Redis.Publish(ctx, "upload:access_cache:invalidate", "reset").Err()
}
objectstore.ResetCache()
objectstore.PublishCacheInvalidation(ctx)
}
if key == ConfigKeyFileAccessWhitelist {
if db.Redis != nil {
_ = db.Redis.Publish(ctx, "upload:access_cache:invalidate", "reset").Err()
if cachepkg.Redis != nil {
_ = cachepkg.Redis.Publish(ctx, "upload:access_cache:invalidate", "reset").Err()
}
}
+1 -1
View File
@@ -20,8 +20,8 @@ import (
"gorm.io/gorm"
"github.com/Rain-kl/Wavelet/pkg/config"
db "github.com/Rain-kl/Wavelet/pkg/persistence"
"github.com/Rain-kl/Wavelet/pkg/response"
db "github.com/Rain-kl/Wavelet/plugins/infra/database"
)
const (
+13 -13
View File
@@ -17,12 +17,12 @@ import (
"github.com/Rain-kl/Wavelet/pkg/config"
"github.com/Rain-kl/Wavelet/pkg/logger"
db "github.com/Rain-kl/Wavelet/pkg/persistence"
"github.com/Rain-kl/Wavelet/pkg/persistence/logstore"
"github.com/Rain-kl/Wavelet/pkg/response"
"github.com/Rain-kl/Wavelet/pkg/task"
"github.com/Rain-kl/Wavelet/pkg/util"
"github.com/Rain-kl/Wavelet/plugins/domain/risk_control"
"github.com/Rain-kl/Wavelet/plugins/domain/risk_control/logstore"
db "github.com/Rain-kl/Wavelet/plugins/infra/database"
"github.com/Rain-kl/Wavelet/plugins/drivers/driver_asynq_worker"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
@@ -497,16 +497,16 @@ const (
)
// LogDBSwitchMeta 描述切换日志数据库任务。
var LogDBSwitchMeta = task.TaskMeta{
var LogDBSwitchMeta = driver_asynq_worker.TaskMeta{
Type: TaskTypeLogDBSwitch,
AsynqTask: LogDBSwitchTask,
Name: "切换日志数据库",
Description: "复制迁移用户访问日志并在成功后切换日志主库(期间禁止日志写入)",
SupportsTime: false,
MaxRetry: task.DefaultMaxRetry,
Queue: task.QueueDefault,
MaxRetry: driver_asynq_worker.DefaultMaxRetry,
Queue: driver_asynq_worker.QueueDefault,
Retryable: true,
Params: []task.TaskParam{
Params: []driver_asynq_worker.TaskParam{
{Name: "target", Label: "目标日志库", Type: "string", Required: true,
Placeholder: "postgres|sqlite|clickhouse", Description: "迁移目标:postgres(主库为 PG 时)、sqlite(主库为 SQLite 时)或 clickhouse"},
},
@@ -553,7 +553,7 @@ func validTarget(v string) bool {
}
// Execute 执行迁移。
func (h *LogDBSwitchHandler) Execute(ctx context.Context, payload []byte) (*task.TaskResult, error) {
func (h *LogDBSwitchHandler) Execute(ctx context.Context, payload []byte) (*driver_asynq_worker.TaskResult, error) {
var p logDBSwitchPayload
if err := json.Unmarshal(payload, &p); err != nil {
return nil, fmt.Errorf("参数解析失败: %w", err)
@@ -565,10 +565,10 @@ func (h *LogDBSwitchHandler) Execute(ctx context.Context, payload []byte) (*task
source, err := currentLogDatabase(ctx)
if err != nil {
task.AppendLog(ctx, "读取日志主库失败: %v", err)
driver_asynq_worker.AppendLog(ctx, "读取日志主库失败: %v", err)
return nil, err
}
task.AppendLog(ctx, "开始切换日志数据库:%s -> %s", source, p.Target)
driver_asynq_worker.AppendLog(ctx, "开始切换日志数据库:%s -> %s", source, p.Target)
if err := setMigrationFlag(ctx, "migrating"); err != nil {
return nil, err
@@ -612,8 +612,8 @@ func (h *LogDBSwitchHandler) Execute(ctx context.Context, payload []byte) (*task
return nil, err
}
logstore.InvalidateCache()
task.AppendLog(ctx, "日志数据库已切换为 %s,写入恢复", p.Target)
return &task.TaskResult{Message: fmt.Sprintf("日志数据库已从 %s 切换为 %s", source, p.Target)}, nil
driver_asynq_worker.AppendLog(ctx, "日志数据库已切换为 %s,写入恢复", p.Target)
return &driver_asynq_worker.TaskResult{Message: fmt.Sprintf("日志数据库已从 %s 切换为 %s", source, p.Target)}, nil
}
func validateSwitch(ctx context.Context, target string) error {
@@ -676,7 +676,7 @@ func copyUserAccessLogs(ctx context.Context, src, dst *logstore.Store) error {
}
afterID = rows[len(rows)-1].ID
copied += len(rows)
task.AppendLog(ctx, "已复制用户访问日志 %d 条", copied)
driver_asynq_worker.AppendLog(ctx, "已复制用户访问日志 %d 条", copied)
if len(rows) < copyBatchSize {
break
}
+1 -1
View File
@@ -18,8 +18,8 @@ import (
"github.com/Rain-kl/Wavelet/pkg/config"
"github.com/Rain-kl/Wavelet/pkg/logger"
"github.com/Rain-kl/Wavelet/pkg/persistence/logstore"
"github.com/Rain-kl/Wavelet/pkg/response"
"github.com/Rain-kl/Wavelet/plugins/domain/risk_control/logstore"
)
var startTime = time.Now()
+16 -16
View File
@@ -16,8 +16,8 @@ import (
"github.com/Rain-kl/Wavelet/pkg/logger"
"github.com/Rain-kl/Wavelet/pkg/response"
"github.com/Rain-kl/Wavelet/pkg/task"
"github.com/Rain-kl/Wavelet/pkg/task/scheduler"
"github.com/Rain-kl/Wavelet/plugins/drivers/driver_asynq_cron"
"github.com/Rain-kl/Wavelet/plugins/drivers/driver_asynq_worker"
)
// ListTaskTypes 获取支持的任务类型列表
@@ -26,12 +26,12 @@ import (
// @Tags admin
// @Produce json
// @Security SessionCookie
// @Success 200 {object} response.Any{data=[]task.TaskMeta} "任务类型列表"
// @Success 200 {object} response.Any{data=[]driver_asynq_worker.TaskMeta} "任务类型列表"
// @Failure 401 {object} response.Any "未登录"
// @Failure 403 {object} response.Any "无管理员权限"
// @Router /api/v1/admin/tasks/types [get]
func ListTaskTypes(c *gin.Context) {
c.JSON(http.StatusOK, response.OK(task.GetDispatchableTasks()))
c.JSON(http.StatusOK, response.OK(driver_asynq_worker.GetDispatchableTasks()))
}
// DispatchTaskRequest 下发任务请求
@@ -64,7 +64,7 @@ func DispatchTask(c *gin.Context) {
return
}
meta := task.GetTaskMeta(req.TaskType)
meta := driver_asynq_worker.GetTaskMeta(req.TaskType)
if meta == nil {
response.AbortBadRequest(c, InvalidTaskType)
return
@@ -75,13 +75,13 @@ func DispatchTask(c *gin.Context) {
payloadBytes = []byte(req.Payload)
}
validated, err := task.ValidateAndNormalizePayload(meta.AsynqTask, payloadBytes)
validated, err := driver_asynq_worker.ValidateAndNormalizePayload(meta.AsynqTask, payloadBytes)
if err != nil {
response.AbortBadRequest(c, err.Error())
return
}
taskID, err := task.DispatchTask(c.Request.Context(), req.TaskType, validated, "manual")
taskID, err := driver_asynq_worker.DispatchTask(c.Request.Context(), req.TaskType, validated, "manual")
if err != nil {
response.AbortInternal(c, fmt.Sprintf("%s: %v", TaskDispatchFailed, err))
return
@@ -112,7 +112,7 @@ func ListTaskExecutions(c *gin.Context) {
}
if req.TaskType != "" {
if meta := task.GetTaskMeta(req.TaskType); meta != nil {
if meta := driver_asynq_worker.GetTaskMeta(req.TaskType); meta != nil {
req.TaskType = meta.AsynqTask
}
}
@@ -181,7 +181,7 @@ func RetryTask(c *gin.Context) {
return
}
newTaskID, err := task.RetryTask(c.Request.Context(), id)
newTaskID, err := driver_asynq_worker.RetryTask(c.Request.Context(), id)
if err != nil {
errMsg := err.Error()
switch {
@@ -254,7 +254,7 @@ func CreateSchedule(c *gin.Context) {
}
// 校验关联的异步任务类型
meta := task.GetTaskMeta(req.TaskType)
meta := driver_asynq_worker.GetTaskMeta(req.TaskType)
if meta == nil {
response.AbortBadRequest(c, InvalidTaskType)
return
@@ -265,7 +265,7 @@ func CreateSchedule(c *gin.Context) {
if strings.TrimSpace(req.Payload) != "" {
payloadBytes = []byte(req.Payload)
}
validated, err := task.ValidateAndNormalizePayload(meta.AsynqTask, payloadBytes)
validated, err := driver_asynq_worker.ValidateAndNormalizePayload(meta.AsynqTask, payloadBytes)
if err != nil {
response.AbortBadRequest(c, err.Error())
return
@@ -285,7 +285,7 @@ func CreateSchedule(c *gin.Context) {
}
// 触发调度服务重载
if err := scheduler.ReloadScheduler(); err != nil {
if err := driver_asynq_cron.ReloadScheduler(); err != nil {
logger.ErrorF(c.Request.Context(), "[TaskAdmin] 重载调度器失败: %v", err)
}
@@ -343,7 +343,7 @@ func UpdateSchedule(c *gin.Context) {
}
// 校验关联的异步任务类型
meta := task.GetTaskMeta(req.TaskType)
meta := driver_asynq_worker.GetTaskMeta(req.TaskType)
if meta == nil {
response.AbortBadRequest(c, InvalidTaskType)
return
@@ -354,7 +354,7 @@ func UpdateSchedule(c *gin.Context) {
if strings.TrimSpace(req.Payload) != "" {
payloadBytes = []byte(req.Payload)
}
validated, err := task.ValidateAndNormalizePayload(meta.AsynqTask, payloadBytes)
validated, err := driver_asynq_worker.ValidateAndNormalizePayload(meta.AsynqTask, payloadBytes)
if err != nil {
response.AbortBadRequest(c, err.Error())
return
@@ -372,7 +372,7 @@ func UpdateSchedule(c *gin.Context) {
}
// 触发调度服务重载
if err := scheduler.ReloadScheduler(); err != nil {
if err := driver_asynq_cron.ReloadScheduler(); err != nil {
logger.ErrorF(c.Request.Context(), "[TaskAdmin] 重载调度器失败: %v", err)
}
@@ -405,7 +405,7 @@ func DeleteSchedule(c *gin.Context) {
}
// 触发调度服务重载
if err := scheduler.ReloadScheduler(); err != nil {
if err := driver_asynq_cron.ReloadScheduler(); err != nil {
logger.ErrorF(c.Request.Context(), "[TaskAdmin] 重载调度器失败: %v", err)
}
+2 -2
View File
@@ -16,12 +16,12 @@ import (
"gorm.io/gorm"
"github.com/Rain-kl/Wavelet/core/contracts"
"github.com/Rain-kl/Wavelet/pkg/idgen"
"github.com/Rain-kl/Wavelet/pkg/logger"
db "github.com/Rain-kl/Wavelet/pkg/persistence"
"github.com/Rain-kl/Wavelet/pkg/persistence/idgen"
"github.com/Rain-kl/Wavelet/pkg/response"
"github.com/Rain-kl/Wavelet/pkg/util"
"github.com/Rain-kl/Wavelet/plugins/domain/auth"
db "github.com/Rain-kl/Wavelet/plugins/infra/database"
)
const minPasswordLength = 8
+13 -12
View File
@@ -17,9 +17,10 @@ import (
"gorm.io/gorm"
"github.com/Rain-kl/Wavelet/pkg/cache/ram"
db "github.com/Rain-kl/Wavelet/pkg/persistence"
"github.com/Rain-kl/Wavelet/pkg/persistence/idgen"
"github.com/Rain-kl/Wavelet/pkg/idgen"
"github.com/Rain-kl/Wavelet/pkg/util"
cachepkg "github.com/Rain-kl/Wavelet/plugins/infra/cache"
db "github.com/Rain-kl/Wavelet/plugins/infra/database"
)
const (
@@ -482,7 +483,7 @@ func GetLatestTaskExecutionByTaskType(ctx context.Context, taskType string) (*Ta
// AppendTaskExecutionLog 将日志追加到 Redis 缓冲,任务完成后再持久化到数据库。
func AppendTaskExecutionLog(ctx context.Context, taskID string, logLine string) error {
if db.Redis == nil {
if cachepkg.Redis == nil {
return errors.New("redis client is not initialized")
}
@@ -490,7 +491,7 @@ func AppendTaskExecutionLog(ctx context.Context, taskID string, logLine string)
line := fmt.Sprintf("[%s] %s\n", now, logLine)
key := taskExecutionLogRedisKey(taskID)
_, err := db.Redis.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
_, err := cachepkg.Redis.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
pipe.RPush(ctx, key, line)
pipe.LTrim(ctx, key, -taskExecutionLogMaxLines, -1)
pipe.Expire(ctx, key, taskExecutionLogExpiration)
@@ -504,12 +505,12 @@ func AppendTaskExecutionLog(ctx context.Context, taskID string, logLine string)
// FlushTaskExecutionLog 将 Redis 中的完整任务日志写入数据库,并在成功后清理缓存。
func FlushTaskExecutionLog(ctx context.Context, taskID string) error {
if db.Redis == nil {
if cachepkg.Redis == nil {
return errors.New("redis client is not initialized")
}
key := taskExecutionLogRedisKey(taskID)
logLines, err := db.Redis.LRange(ctx, key, 0, -1).Result()
logLines, err := cachepkg.Redis.LRange(ctx, key, 0, -1).Result()
if err != nil {
return fmt.Errorf("get task execution log from redis: %w", err)
}
@@ -528,7 +529,7 @@ func FlushTaskExecutionLog(ctx context.Context, taskID string) error {
return fmt.Errorf("persist task execution log: task %q not found", taskID)
}
if err := db.Redis.Del(ctx, key).Err(); err != nil {
if err := cachepkg.Redis.Del(ctx, key).Err(); err != nil {
return fmt.Errorf("delete persisted task execution log from redis: %w", err)
}
return nil
@@ -658,15 +659,15 @@ func CleanupTaskExecutionLogs(ctx context.Context, now time.Time) (TaskExecution
}
func taskExecutionLogRedisKey(taskID string) string {
return db.PrefixedKey(taskExecutionLogRedisKeyPrefix + taskID)
return cachepkg.PrefixedKey(taskExecutionLogRedisKeyPrefix + taskID)
}
func loadTaskExecutionLog(ctx context.Context, execution *TaskExecution) error {
if db.Redis == nil {
if cachepkg.Redis == nil {
return nil
}
logLines, err := db.Redis.LRange(ctx, taskExecutionLogRedisKey(execution.TaskID), 0, -1).Result()
logLines, err := cachepkg.Redis.LRange(ctx, taskExecutionLogRedisKey(execution.TaskID), 0, -1).Result()
if err != nil {
return fmt.Errorf("get task execution log from redis: %w", err)
}
@@ -679,12 +680,12 @@ func loadTaskExecutionLog(ctx context.Context, execution *TaskExecution) error {
}
func loadTaskExecutionLogs(ctx context.Context, executions []TaskExecution) error {
if db.Redis == nil || len(executions) == 0 {
if cachepkg.Redis == nil || len(executions) == 0 {
return nil
}
commands := make([]*redis.StringSliceCmd, len(executions))
_, err := db.Redis.Pipelined(ctx, func(pipe redis.Pipeliner) error {
_, err := cachepkg.Redis.Pipelined(ctx, func(pipe redis.Pipeliner) error {
for i := range executions {
commands[i] = pipe.LRange(ctx, taskExecutionLogRedisKey(executions[i].TaskID), 0, -1)
}
+9 -9
View File
@@ -13,8 +13,8 @@ import (
"gorm.io/gorm"
"github.com/Rain-kl/Wavelet/pkg/cache/ram"
db "github.com/Rain-kl/Wavelet/pkg/persistence"
"github.com/Rain-kl/Wavelet/pkg/util"
cachepkg "github.com/Rain-kl/Wavelet/plugins/infra/cache"
)
const (
@@ -104,14 +104,14 @@ func ensureSystemConfigCacheListener() {
}
func startSystemConfigCacheInvalidationListener() {
if db.Redis == nil {
if cachepkg.Redis == nil {
return
}
systemConfigListenerCtx, systemConfigListenerCancel = context.WithCancel(context.Background())
systemConfigListenerDone = make(chan struct{})
redisClient := db.Redis // 捕获当前客户端:goroutine 不读可变全局,避免与测试置空 db.Redis 竞争
redisClient := cachepkg.Redis // 捕获当前客户端:goroutine 不读可变全局,避免与测试置空 cachepkg.Redis 竞争
util.Go(func() {
listenerCtx := systemConfigListenerCtx
defer close(systemConfigListenerDone)
@@ -169,8 +169,8 @@ func InvalidateSystemConfigCache(ctx context.Context, key string) error {
ram.Delete(ConfigCacheType, key)
// Broadcast to other nodes and clean legacy Redis cache key
if db.Redis != nil {
_ = db.HDel(ctx, SystemConfigRedisHashKey, key)
if cachepkg.Redis != nil {
_ = cachepkg.HDel(ctx, SystemConfigRedisHashKey, key)
publishSystemConfigBroadcast(ctx, ConfigCacheType, key)
}
return nil
@@ -184,22 +184,22 @@ func InvalidateAllSystemConfigCaches(ctx context.Context) error {
ram.UpdateTypeItems(ConfigCacheType, nil)
// Broadcast to other nodes and clean legacy Redis cache keys
if db.Redis != nil {
_ = db.Redis.Del(ctx, db.PrefixedKey(SystemConfigRedisHashKey), db.PrefixedKey(SystemConfigVisibleListRedisKey)).Err()
if cachepkg.Redis != nil {
_ = cachepkg.Redis.Del(ctx, cachepkg.PrefixedKey(SystemConfigRedisHashKey), cachepkg.PrefixedKey(SystemConfigVisibleListRedisKey)).Err()
publishSystemConfigBroadcast(ctx, ConfigCacheType, "*")
}
return nil
}
func publishSystemConfigBroadcast(ctx context.Context, configType string, key string) {
if db.Redis == nil {
if cachepkg.Redis == nil {
return
}
payload, err := json.Marshal(systemConfigBroadcastMessage{Type: configType, Key: key})
if err != nil {
return
}
_ = db.Redis.Publish(ctx, SystemConfigBroadcastChannel, payload).Err()
_ = cachepkg.Redis.Publish(ctx, SystemConfigBroadcastChannel, payload).Err()
}
// ResetSystemConfigRAMCacheForTest clears only the process-local RAM cache.
+7 -6
View File
@@ -14,7 +14,8 @@ import (
"github.com/redis/go-redis/v9/maintnotifications"
"gorm.io/gorm"
db "github.com/Rain-kl/Wavelet/pkg/persistence"
"github.com/Rain-kl/Wavelet/plugins/infra/cache"
"github.com/Rain-kl/Wavelet/plugins/infra/database"
)
func setupSystemConfigTest(t *testing.T) (*gorm.DB, func()) {
@@ -51,15 +52,15 @@ func setupSystemConfigTest(t *testing.T) (*gorm.DB, func()) {
},
})
previousRedis := db.Redis
db.SetDB(sqliteDB)
db.Redis = redisClient
previousRedis := cache.Redis
database.SetDB(sqliteDB)
cache.Redis = redisClient
cleanup := func() {
StopSystemConfigCacheListener()
ResetSystemConfigRAMCacheForTest()
db.SetDB(nil)
db.Redis = previousRedis
database.SetDB(nil)
cache.Redis = previousRedis
_ = redisClient.Close()
mr.Close()
}