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()), ) } else { slog.DebugContext(ctx, "ai outfit cleanup: nothing to clean") } }