83 lines
2.5 KiB
Go
83 lines
2.5 KiB
Go
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()),
|
|
)
|
|
}
|
|
}
|