修复AI时间节点缓存过期与刷新策略
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -95,19 +96,41 @@ func (s *SmokeAINextSmokeService) GetOrGenerate(ctx context.Context, user *userm
|
||||
if err != nil {
|
||||
return AINextSmokeSuggestion{}, err
|
||||
}
|
||||
var cachedSuggestion *AINextSmokeSuggestion
|
||||
if cachedAdvice != nil {
|
||||
return s.buildFromCache(ctx, cachedAdvice)
|
||||
v, buildErr := s.buildFromCache(ctx, cachedAdvice)
|
||||
if buildErr != nil {
|
||||
log.Printf("[smoke_ai_next] cache_build_failed uid=%d plan_date=%s advice_id=%d err=%v", user.ID, planDate.Format("2006-01-02"), cachedAdvice.ID, buildErr)
|
||||
} else if !s.shouldRefreshCache(asOf, planDate, v) {
|
||||
log.Printf("[smoke_ai_next] cache_hit uid=%d plan_date=%s advice_id=%d", user.ID, planDate.Format("2006-01-02"), cachedAdvice.ID)
|
||||
return v, nil
|
||||
} else {
|
||||
log.Printf("[smoke_ai_next] cache_stale uid=%d plan_date=%s advice_id=%d suggested_at=%s nodes=%d", user.ID, planDate.Format("2006-01-02"), cachedAdvice.ID, v.SuggestedAt, len(v.TimeNodes))
|
||||
cachedSuggestion = &v
|
||||
}
|
||||
}
|
||||
|
||||
if s.cfg.APIKey == "" || s.cfg.Model == "" || s.cfg.BaseURL == "" {
|
||||
if cachedSuggestion != nil {
|
||||
log.Printf("[smoke_ai_next] ai_disabled_reuse_cache uid=%d plan_date=%s", user.ID, planDate.Format("2006-01-02"))
|
||||
return *cachedSuggestion, nil
|
||||
}
|
||||
return AINextSmokeSuggestion{}, ErrAINextServiceDisabled
|
||||
}
|
||||
|
||||
allowed, err := s.isAllowed(ctx, user, planDate)
|
||||
if err != nil {
|
||||
if cachedSuggestion != nil {
|
||||
log.Printf("[smoke_ai_next] allow_check_failed_reuse_cache uid=%d plan_date=%s err=%v", user.ID, planDate.Format("2006-01-02"), err)
|
||||
return *cachedSuggestion, nil
|
||||
}
|
||||
return AINextSmokeSuggestion{}, err
|
||||
}
|
||||
if !allowed {
|
||||
if cachedSuggestion != nil {
|
||||
log.Printf("[smoke_ai_next] no_refresh_permission_reuse_cache uid=%d plan_date=%s", user.ID, planDate.Format("2006-01-02"))
|
||||
return *cachedSuggestion, nil
|
||||
}
|
||||
return AINextSmokeSuggestion{}, ErrAINextLocked
|
||||
}
|
||||
|
||||
@@ -182,12 +205,43 @@ func (s *SmokeAINextSmokeService) GetOrGenerate(ctx context.Context, user *userm
|
||||
Advice: strings.TrimSpace(output.Advice),
|
||||
TokensIn: tokensIn,
|
||||
TokensOut: tokensOut,
|
||||
CreateTime: &createTime,
|
||||
UpdateTime: &updateTime,
|
||||
}
|
||||
|
||||
if err := s.db.WithContext(ctx).Create(&adviceRecord).Error; err != nil {
|
||||
return AINextSmokeSuggestion{}, fmt.Errorf("save ai next smoke advice: %w", err)
|
||||
if cachedAdvice == nil {
|
||||
adviceRecord.CreateTime = &createTime
|
||||
if err := s.db.WithContext(ctx).Create(&adviceRecord).Error; err != nil {
|
||||
return AINextSmokeSuggestion{}, fmt.Errorf("save ai next smoke advice: %w", err)
|
||||
}
|
||||
log.Printf("[smoke_ai_next] cache_write_create uid=%d plan_date=%s advice_id=%d", user.ID, planDate.Format("2006-01-02"), adviceRecord.ID)
|
||||
} else {
|
||||
adviceRecord.ID = cachedAdvice.ID
|
||||
adviceRecord.CreateTime = cachedAdvice.CreateTime
|
||||
|
||||
var tokensInValue interface{}
|
||||
if tokensIn != nil {
|
||||
tokensInValue = *tokensIn
|
||||
}
|
||||
var tokensOutValue interface{}
|
||||
if tokensOut != nil {
|
||||
tokensOutValue = *tokensOut
|
||||
}
|
||||
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&smokemodel.SmokeAIAdvice{}).
|
||||
Where("id = ?", cachedAdvice.ID).
|
||||
Updates(map[string]interface{}{
|
||||
"provider": adviceRecord.Provider,
|
||||
"model": adviceRecord.Model,
|
||||
"input_snapshot": adviceRecord.InputSnapshot,
|
||||
"advice": adviceRecord.Advice,
|
||||
"tokens_in": tokensInValue,
|
||||
"tokens_out": tokensOutValue,
|
||||
"updatetime": updateTime,
|
||||
}).Error; err != nil {
|
||||
return AINextSmokeSuggestion{}, fmt.Errorf("refresh ai next smoke advice: %w", err)
|
||||
}
|
||||
log.Printf("[smoke_ai_next] cache_write_refresh uid=%d plan_date=%s advice_id=%d", user.ID, planDate.Format("2006-01-02"), adviceRecord.ID)
|
||||
}
|
||||
|
||||
// 2) 写入时间节点(每个时间点一条)
|
||||
@@ -196,6 +250,14 @@ func (s *SmokeAINextSmokeService) GetOrGenerate(ctx context.Context, user *userm
|
||||
return AINextSmokeSuggestion{}, err
|
||||
}
|
||||
|
||||
if cachedAdvice != nil {
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("ai_advice_id = ?", adviceRecord.ID).
|
||||
Delete(&smokemodel.SmokeAINextSmoke{}).Error; err != nil {
|
||||
return AINextSmokeSuggestion{}, fmt.Errorf("clear old ai next smoke nodes: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.saveNodes(ctx, int(user.ID), planDate, adviceRecord.ID, notBeforeAt, suggestedAt, nodes); err != nil {
|
||||
return AINextSmokeSuggestion{}, err
|
||||
}
|
||||
@@ -231,6 +293,11 @@ func (s *SmokeAINextSmokeService) GetCached(ctx context.Context, user *usermodel
|
||||
if err != nil {
|
||||
return AINextSmokeSuggestion{}, false, err
|
||||
}
|
||||
if s.shouldRefreshCache(time.Now().In(time.Local), planDate, suggestion) {
|
||||
log.Printf("[smoke_ai_next] cache_auto_expired uid=%d plan_date=%s advice_id=%d", user.ID, planDate.Format("2006-01-02"), cachedAdvice.ID)
|
||||
return AINextSmokeSuggestion{}, false, nil
|
||||
}
|
||||
log.Printf("[smoke_ai_next] cache_auto_hit uid=%d plan_date=%s advice_id=%d", user.ID, planDate.Format("2006-01-02"), cachedAdvice.ID)
|
||||
return suggestion, true, nil
|
||||
}
|
||||
|
||||
@@ -239,6 +306,7 @@ func (s *SmokeAINextSmokeService) getCachedAdvice(ctx context.Context, uid int,
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("uid = ? AND type = ? AND advice_date = ? AND prompt_version = ? AND (deletetime IS NULL OR deletetime = 0)",
|
||||
uid, SmokeAIAdviceTypeNextSmoke, dateOnly(planDate).Format("2006-01-02"), promptVersion).
|
||||
Order("id DESC").
|
||||
First(&record).Error
|
||||
if err == nil {
|
||||
return &record, nil
|
||||
@@ -342,6 +410,30 @@ func (s *SmokeAINextSmokeService) normalizeNodes(raw []string, asOf time.Time, p
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *SmokeAINextSmokeService) shouldRefreshCache(asOf time.Time, planDate time.Time, suggestion AINextSmokeSuggestion) bool {
|
||||
if len(suggestion.TimeNodes) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
suggestedAt, err := parseFlexibleTime(suggestion.SuggestedAt, planDate)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
asOf = asOf.In(time.Local)
|
||||
planDate = dateOnly(planDate)
|
||||
if dateOnly(suggestedAt) != planDate {
|
||||
return true
|
||||
}
|
||||
|
||||
// 当天建议如果已经到点(或过期),视为缓存失效,触发刷新。
|
||||
if planDate.Equal(dateOnly(asOf)) && !suggestedAt.After(asOf.Add(2*time.Minute)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *SmokeAINextSmokeService) computeMinNotBefore(asOf time.Time, planDate time.Time, defaultSuggestion NextSmokeSuggestion, profile *adviceUserProfile) time.Time {
|
||||
asOf = asOf.In(time.Local)
|
||||
planDate = dateOnly(planDate)
|
||||
|
||||
Reference in New Issue
Block a user