fix:自动补货更新
This commit is contained in:
@@ -20,6 +20,23 @@ const (
|
||||
rockPurchaseNoteManualPrefix = "手动采购"
|
||||
)
|
||||
|
||||
// ErrAutoPurchaseCooldown 表示自动补货仍在冷却窗口内,本次不应产生任何采购流水。
|
||||
var ErrAutoPurchaseCooldown = errors.New("自动采购处于冷却中")
|
||||
|
||||
// rockAutoPurchaseCooldownRemaining 返回自动补货还需等待的时长,0 表示可以执行。
|
||||
// 冷却只认最近一次“自动”尝试:手动采购不影响自动补货节奏。
|
||||
func rockAutoPurchaseCooldownRemaining(cfg *model.RockConfig, now time.Time) time.Duration {
|
||||
if cfg == nil || cfg.PurchaseCooldownMinutes <= 0 || cfg.LastAutoAttemptAt == nil {
|
||||
return 0
|
||||
}
|
||||
cooldown := time.Duration(cfg.PurchaseCooldownMinutes) * time.Minute
|
||||
elapsed := now.Sub(*cfg.LastAutoAttemptAt)
|
||||
if elapsed >= cooldown {
|
||||
return 0
|
||||
}
|
||||
return cooldown - elapsed
|
||||
}
|
||||
|
||||
// PurchaseTokens 执行采购并入库,同时记录完整采购流水。
|
||||
// 流程:积分/库存预检 → 降级计算 → claim 批量提取 → 创建上游打包记录 → 拉取包详情 → 幂等导入本地。
|
||||
// 后续任一步失败都会用 claim 数据兜底入库,保证已扣积分的 Token 不会丢失。
|
||||
@@ -28,6 +45,16 @@ func (s *RockService) PurchaseTokens(ctx context.Context, count int, isAuto bool
|
||||
defer s.mu.Unlock()
|
||||
|
||||
startedAt := time.Now()
|
||||
// 冷却判断必须在创建流水之前:被拦下的触发不留下任何“未采购”记录。
|
||||
if isAuto {
|
||||
cfg, err := s.GetConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if remain := rockAutoPurchaseCooldownRemaining(cfg, startedAt); remain > 0 {
|
||||
return nil, fmt.Errorf("%w(冷却时间 %d 分钟)", ErrAutoPurchaseCooldown, cfg.PurchaseCooldownMinutes)
|
||||
}
|
||||
}
|
||||
trigger := "manual"
|
||||
if isAuto {
|
||||
trigger = "auto"
|
||||
@@ -53,20 +80,8 @@ func (s *RockService) purchaseTokensWithRecorder(ctx context.Context, count int,
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if isAuto {
|
||||
// 冷却以最近一次“尝试”为准:失败也生效,避免上游故障时每个触发点反复请求。
|
||||
last := cfg.LastAttemptAt
|
||||
if last == nil {
|
||||
last = cfg.LastPurchasedAt
|
||||
}
|
||||
if last != nil && cfg.PurchaseCooldownMinutes > 0 {
|
||||
cooldown := time.Duration(cfg.PurchaseCooldownMinutes) * time.Minute
|
||||
if now.Sub(*last) < cooldown {
|
||||
return nil, fmt.Errorf("自动采购处于冷却中(冷却时间 %d 分钟)", cfg.PurchaseCooldownMinutes)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.updatePurchaseAttempt(ctx, cfg.ID, now)
|
||||
// 记录本次尝试;自动补货同时推进自动冷却,手动采购只更新总尝试时间。
|
||||
s.markPurchaseAttempt(ctx, cfg.ID, now, isAuto)
|
||||
|
||||
// 把采购追踪器挂到 context,客户端会把每个上游调用的原始报文交给 rec。
|
||||
ctx = WithRockTrace(ctx, &RockTrace{OnStage: rec.addStage})
|
||||
@@ -369,10 +384,14 @@ func purchaseNote(isAuto bool, at time.Time) string {
|
||||
return prefix + "-" + at.Format("20060102-150405")
|
||||
}
|
||||
|
||||
func (s *RockService) updatePurchaseAttempt(ctx context.Context, cfgID uint, at time.Time) {
|
||||
func (s *RockService) markPurchaseAttempt(ctx context.Context, cfgID uint, at time.Time, isAuto bool) {
|
||||
updates := map[string]interface{}{"last_attempt_at": at}
|
||||
if isAuto {
|
||||
updates["last_auto_attempt_at"] = at
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Model(&model.RockConfig{}).
|
||||
Where("id = ?", cfgID).
|
||||
Update("last_attempt_at", at).Error; err != nil {
|
||||
Updates(updates).Error; err != nil {
|
||||
log.Printf("[RockService] 记录采购尝试时间失败: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -468,6 +487,10 @@ func (s *RockService) CheckStockAndAutoPurchase(ctx context.Context) {
|
||||
if !cfg.AutoPurchaseEnabled {
|
||||
return
|
||||
}
|
||||
// 冷却期内静默跳过:不调用 PurchaseTokens,也不会产生“未采购”流水。
|
||||
if remain := rockAutoPurchaseCooldownRemaining(cfg, time.Now()); remain > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
buyCount := cfg.AutoPurchaseCount
|
||||
if buyCount <= 0 {
|
||||
@@ -476,6 +499,10 @@ func (s *RockService) CheckStockAndAutoPurchase(ctx context.Context) {
|
||||
log.Printf("[RockService] 触发自动补货,采购数量: %d", buyCount)
|
||||
tokens, err := s.PurchaseTokens(ctx, buyCount, true)
|
||||
if err != nil {
|
||||
// 并发触发时后到的请求会被 PurchaseTokens 拦下,这里不再重复记错误日志。
|
||||
if errors.Is(err, ErrAutoPurchaseCooldown) {
|
||||
return
|
||||
}
|
||||
log.Printf("[RockService] 自动补货失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user