This commit is contained in:
2026-09-15 23:04:13 +08:00
parent ade2453083
commit 8c2ea84939
17 changed files with 749 additions and 161 deletions
+82
View File
@@ -0,0 +1,82 @@
package service
import (
"context"
"log/slog"
"time"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqldao"
)
// AI 推荐每次生成都会落一条 status=3(待用户保存)的穿搭:
// 用户不调用 /ai-recommend/save 就会长期堆积在 outfits / outfit_items 中,
// 并且会出现在 GET /outfit 列表里。这里按保留期兜底清理,连同明细一起软删除。
const (
// aiUnsavedOutfitRetention 未保存 AI 穿搭的保留时长。
aiUnsavedOutfitRetention = 7 * 24 * time.Hour
// aiUnsavedOutfitCleanupInterval 清理任务执行间隔。
aiUnsavedOutfitCleanupInterval = time.Hour
// aiUnsavedOutfitCleanupBatch 单批清理条数,避免长事务持锁。
aiUnsavedOutfitCleanupBatch = 500
// aiUnsavedOutfitCleanupMaxRounds 单次执行的最大批次轮数,防止一次跑太久。
aiUnsavedOutfitCleanupMaxRounds = 20
)
// StartAIOutfitCleanupScheduler 启动「未保存 AI 穿搭」清理定时任务:
// 启动后立即执行一次,之后每小时执行一次。
func StartAIOutfitCleanupScheduler() {
go func() {
slog.Info("ai unsaved outfit cleanup scheduler started",
"interval", aiUnsavedOutfitCleanupInterval.String(),
"retention", aiUnsavedOutfitRetention.String(),
)
for {
func() {
defer func() {
if r := recover(); r != nil {
slog.Error("ai unsaved outfit cleanup panic", "recover", r)
}
}()
runAIOutfitCleanup()
}()
time.Sleep(aiUnsavedOutfitCleanupInterval)
}
}()
}
// runAIOutfitCleanup 清理超过保留期仍未保存的 AI 穿搭。
// 条件更新天然幂等,多实例并发执行无害,无需分布式锁。
func runAIOutfitCleanup() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
ctx, span := global.Tracer.Start(ctx, "scheduler.AIOutfitCleanup")
defer span.End()
deadline := time.Now().Add(-aiUnsavedOutfitRetention).Unix()
dao := mysqldao.NewOutfitDao()
var total int64
for round := 0; round < aiUnsavedOutfitCleanupMaxRounds; round++ {
affected, err := dao.OutfitCleanupExpiredUnsavedAI(ctx, deadline, aiUnsavedOutfitCleanupBatch)
if err != nil {
slog.ErrorContext(ctx, "cleanup unsaved ai outfits failed", "error", err, "cleaned", total)
return
}
total += affected
if affected < aiUnsavedOutfitCleanupBatch {
break
}
}
if total > 0 {
slog.InfoContext(ctx, "cleaned unsaved ai outfits",
"count", total,
"retention_hours", int(aiUnsavedOutfitRetention.Hours()),
)
}
}