bugfix
This commit is contained in:
@@ -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()),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user