补齐相关日志日志

This commit is contained in:
2026-09-16 00:23:49 +08:00
parent 8c2ea84939
commit b2e5eef3c8
39 changed files with 1576 additions and 176 deletions
+44 -8
View File
@@ -67,13 +67,17 @@ func (s *AIRecommendService) Submit(ctx context.Context, userID int64, request *
// 1. 抢占活跃位:抢不到说明该用户已有任务在跑,幂等返回它。
acquired, runningTaskID, err := redisdao.AIRecommendTaskAcquire(ctx, userID, taskID, aiRecommendSlotTTL)
if err != nil {
slog.ErrorContext(ctx, "ai recommend submit: acquire slot failed", "user_id", userID, "error", err)
return nil, errcode.CacheError
}
if !acquired {
if runningTaskID == "" {
// 活跃位恰好在两次调用之间过期,让前端稍后重试即可。
slog.InfoContext(ctx, "ai recommend submit: slot expired between calls, ask retry", "user_id", userID)
return nil, errcode.TooManyRequests
}
slog.InfoContext(ctx, "ai recommend submit: task already running, return existing",
"user_id", userID, "task_id", runningTaskID)
return &dto.AIRecommendSubmitResponse{
TaskID: runningTaskID,
Status: AIRecommendTaskPending,
@@ -84,8 +88,10 @@ func (s *AIRecommendService) Submit(ctx context.Context, userID int64, request *
if qerr := consumeDailyQuota(ctx, userID, QuotaTypeAI, 1, "AI 推荐消耗"); qerr != nil {
s.releaseSlot(context.WithoutCancel(ctx), userID, taskID)
if qerr == errcode.QuotaInsufficient {
slog.InfoContext(ctx, "ai recommend submit: quota insufficient", "user_id", userID)
return nil, errcode.AIRecommendQuotaLimit
}
slog.ErrorContext(ctx, "ai recommend submit: consume quota failed", "user_id", userID, "error", qerr)
return nil, qerr
}
@@ -96,6 +102,8 @@ func (s *AIRecommendService) Submit(ctx context.Context, userID int64, request *
Status: AIRecommendTaskPending,
CreateTime: nowUnix(),
}); serr != nil {
slog.ErrorContext(ctx, "ai recommend submit: save task snapshot failed",
"user_id", userID, "task_id", taskID, "error", serr)
cleanupCtx := context.WithoutCancel(ctx)
refundDailyQuota(cleanupCtx, userID, QuotaTypeAI, 1)
s.releaseSlot(cleanupCtx, userID, taskID)
@@ -106,6 +114,8 @@ func (s *AIRecommendService) Submit(ctx context.Context, userID int64, request *
reqCopy := *request
go s.runTask(taskID, userID, &reqCopy)
slog.InfoContext(ctx, "ai recommend submitted", "user_id", userID, "task_id", taskID)
return &dto.AIRecommendSubmitResponse{
TaskID: taskID,
Status: AIRecommendTaskPending,
@@ -153,26 +163,39 @@ func (s *AIRecommendService) Save(ctx context.Context, userID int64, request *dt
outfit, err := s.outfitDao.OutfitGetByUUID(ctx, request.OutfitUUID)
if err != nil {
if isNotFound(err) {
slog.WarnContext(ctx, "ai recommend save: outfit not found",
"user_id", userID, "outfit_uuid", request.OutfitUUID)
return errcode.OutfitNotFound
}
slog.ErrorContext(ctx, "ai recommend save: query outfit failed",
"user_id", userID, "outfit_uuid", request.OutfitUUID, "error", err)
return errcode.DatabaseError
}
if outfit.UserID != userID {
slog.InfoContext(ctx, "ai recommend save: permission denied",
"user_id", userID, "outfit_uuid", request.OutfitUUID)
return errcode.OutfitNoPermission
}
// 仅待保存状态可保存;已正常则幂等返回。
if outfit.Status == OutfitStatusNormal {
slog.DebugContext(ctx, "ai recommend save: outfit already saved, skip",
"user_id", userID, "outfit_uuid", request.OutfitUUID)
return nil
}
if outfit.Status != OutfitStatusUnsaved {
slog.WarnContext(ctx, "ai recommend save: outfit status not saveable",
"user_id", userID, "outfit_uuid", request.OutfitUUID, "status", outfit.Status)
return errcode.OutfitUpdateFailed
}
if uerr := s.outfitDao.OutfitUpdateByUUID(ctx, request.OutfitUUID, map[string]any{
"status": OutfitStatusNormal,
}); uerr != nil {
slog.ErrorContext(ctx, "ai recommend save: update outfit failed",
"user_id", userID, "outfit_uuid", request.OutfitUUID, "error", uerr)
return errcode.OutfitUpdateFailed
}
slog.InfoContext(ctx, "ai recommend outfit saved", "user_id", userID, "outfit_uuid", request.OutfitUUID)
return nil
}
@@ -196,7 +219,7 @@ func (s *AIRecommendService) runTask(taskID string, userID int64, request *dto.A
s.releaseSlot(context.WithoutCancel(ctx), userID, taskID)
}()
out, bizErr := s.generate(ctx, userID, request)
out, bizErr := s.generate(ctx, taskID, userID, request)
if bizErr != nil {
s.finishFailed(ctx, taskID, userID, bizErr.Code(), formatBizErr(bizErr))
return
@@ -206,11 +229,11 @@ func (s *AIRecommendService) runTask(taskID string, userID int64, request *dto.A
TaskID: taskID,
UserID: userID,
Status: AIRecommendTaskSuccess,
Result: mustMarshal(out),
Result: mustMarshal(ctx, out),
CreateTime: nowUnix(),
}); serr != nil {
// 结果已落库为主,快照写失败只影响前端轮询展示,记录即可。
slog.ErrorContext(ctx, "save ai recommend task result failed",
// 结果已落库为主,快照写失败只影响前端轮询展示,降为 Warn。
slog.WarnContext(ctx, "save ai recommend task result failed",
"task_id", taskID, "user_id", userID, "error", serr)
}
}
@@ -249,13 +272,16 @@ func (s *AIRecommendService) releaseSlot(ctx context.Context, userID int64, task
}
// generate 执行一次完整的推荐生成:查衣橱 → 取运势 → 调大模型 → 校验 → 落库。
func (s *AIRecommendService) generate(ctx context.Context, userID int64, request *dto.AIRecommendCreateRequest) (*dto.AIRecommendCreateResponse, *errcode.Error) {
func (s *AIRecommendService) generate(ctx context.Context, taskID string, userID int64, request *dto.AIRecommendCreateRequest) (*dto.AIRecommendCreateResponse, *errcode.Error) {
// 1. 加载用户可用衣物(已成功处理)。
clothes, cerr := s.processedClothes(ctx, userID)
if cerr != nil {
slog.ErrorContext(ctx, "ai recommend generate: load clothes failed",
"user_id", userID, "task_id", taskID, "error", cerr)
return nil, errcode.DatabaseError
}
if len(clothes) == 0 {
slog.InfoContext(ctx, "ai recommend generate: no usable clothes", "user_id", userID, "task_id", taskID)
return nil, errcode.AIRecommendFailed.WithDetails("衣橱暂无可用衣物")
}
@@ -267,11 +293,13 @@ func (s *AIRecommendService) generate(ctx context.Context, userID int64, request
client := tokenhub.NewClient()
result, gerr := client.GenerateOutfitRecommendation(ctx, zodiac, fortune, clothes, request.Scene)
if gerr != nil {
slog.ErrorContext(ctx, "generate outfit recommendation failed", "user_id", userID, "error", gerr)
slog.ErrorContext(ctx, "generate outfit recommendation failed", "user_id", userID, "task_id", taskID, "error", gerr)
return nil, errcode.AIRecommendFailed.WithDetails("大模型调用失败")
}
result.ValidateClothesIDs(clothes)
if len(result.Items) == 0 {
slog.WarnContext(ctx, "ai recommend generate: no valid items after validation",
"user_id", userID, "task_id", taskID)
return nil, errcode.AIRecommendFailed.WithDetails("未生成有效搭配")
}
@@ -302,6 +330,8 @@ func (s *AIRecommendService) generate(ctx context.Context, userID int64, request
}
return tx.Create(&items).Error
}); err != nil {
slog.ErrorContext(ctx, "ai recommend generate: save outfit failed",
"user_id", userID, "task_id", taskID, "error", err)
return nil, errcode.OutfitCreateFailed
}
@@ -365,6 +395,9 @@ func (s *AIRecommendService) userFortune(ctx context.Context, userID int64) (str
zodiac := ""
if user, err := s.userDao.UserGetByID(ctx, userID); err == nil && user.BirthDay != 0 {
zodiac = zodiacByUnix(user.BirthDay)
} else if err != nil {
slog.WarnContext(ctx, "ai recommend generate: get user for fortune failed, degrade",
"user_id", userID, "error", err)
}
fortune := &tokenhub.FortuneItem{Zodiac: zodiac}
@@ -380,6 +413,9 @@ func (s *AIRecommendService) userFortune(ctx context.Context, userID int64) (str
LuckyNumber: h.LuckyNumber,
Description: h.DailyText,
}
} else if err != nil {
slog.WarnContext(ctx, "ai recommend generate: get horoscope failed, degrade",
"user_id", userID, "zodiac", zodiac, "error", err)
}
}
return zodiac, fortune
@@ -411,10 +447,10 @@ func formatBizErr(e *errcode.Error) string {
}
// mustMarshal 序列化任务结果;失败时返回 nil(任务仍标记成功,前端可去「我的搭配」查看)。
func mustMarshal(v any) json.RawMessage {
func mustMarshal(ctx context.Context, v any) json.RawMessage {
data, err := json.Marshal(v)
if err != nil {
slog.Error("marshal ai recommend task result failed", "error", err)
slog.ErrorContext(ctx, "marshal ai recommend task result failed", "error", err)
return nil
}
return data