fix(v2): 退款自愈扫描加回溯窗(防 LIMIT 尾部饥饿)+ 清死行

RefundApplyTask 的候选查询按 out_trade_no ASC/id ASC 取前 200,succeeded 退款历史
只增不减、partially_refunded 长期驻留,超过 limit 后最新的崩溃窗口永远排在候选外
扫不到。两路查询都加 since 回溯窗:RefundStore.ListDistinctOutTradeNosByStatusSince
(completed_at>=since)、OrderStore.ListOrdersByStatusSince(updated_at>=since),旧的
无窗方法原样保留。RefundApplyTask 新增 lookback/now 参数,config.ReconcileConfig 新增
RefundApplyLookbackMin(默认 48h)。顺带按 T6 复审补 OrderTTLMin 字段注释(须 ≫ crypto
orderTTL 15min),清 sync_test.go 死行 `_ = time.Second`。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
This commit is contained in:
wangjia
2026-07-10 17:59:57 +08:00
parent 727ed74899
commit 9117de7dcf
9 changed files with 212 additions and 14 deletions
+21
View File
@@ -144,6 +144,27 @@ func (s *RefundStore) ListDistinctOutTradeNosByStatus(status model.RefundStatus,
return out, nil
}
// ListDistinctOutTradeNosByStatusSince 同 ListDistinctOutTradeNosByStatus,但加
// completed_at>=since 回溯窗——防「LIMIT 尾部饥饿」:succeeded 退款历史只增不减,
// 若不设窗口,按 out_trade_no ASC 取前 limit 条,超过 limit 后最新出现的"退款已成但
// 订单卡 paid"崩溃窗口(本扫描要自愈的目标)会被排在候选之外、永远扫不到。completed_at
// 是 MarkRefundStatus 翻成 succeeded/failed 时打的时间戳(即"进入该终态"的时刻),succeeded
// 是终态、之后不会再被改动,用它做窗口锚点比 updated_at 更精确。调用方(RefundApplyTask)
// 用 now-lookback 算 since,lookback 只覆盖近期崩溃窗口——历史一致性由已收敛状态保证,
// 不需要扫全表。
func (s *RefundStore) ListDistinctOutTradeNosByStatusSince(status model.RefundStatus, since time.Time, limit int) ([]string, error) {
if limit <= 0 || limit > 500 {
limit = 200
}
var out []string
if err := s.db.Model(&model.Refund{}).Where("status = ? AND completed_at >= ?", status, since).
Group("out_trade_no").Order("out_trade_no ASC").Limit(limit).
Pluck("out_trade_no", &out).Error; err != nil {
return nil, fmt.Errorf("store.ListDistinctOutTradeNosByStatusSince: %w", err)
}
return out, nil
}
// ListStuckRefunds 列 status 落在给定集合、且 updated_at 早于 before 的退款行,供
// 「卡滞 processing/manual_pending 退款告警」只读观测扫描用(不改状态)。updated_at
// 用作「进入当前状态」的近似时刻——本表除 MarkRefundStatus/CreateRefundGuarded 外