fix(billing): 重排任务复用钱包预留

平台额度跨窗口重排时保留任务已有的钱包预留,后续 Worker 领取复用同一幂等键,避免重复 reserve/release 事务。\n\n补齐排队任务取消后的原子结算释放,并允许任务 attempt 持久化软负载、满载原因和选择原因等有界路由快照。\n\n验证:go test ./...;go vet ./...;钱包预留复用与排队取消 PostgreSQL 集成测试。
This commit is contained in:
2026-08-03 18:09:47 +08:00
parent 13faf1d072
commit c8d04ca731
4 changed files with 147 additions and 1 deletions
+21 -1
View File
@@ -901,6 +901,12 @@ SET status = 'cancelled',
error = NULL,
error_code = 'task_cancelled',
error_message = NULLIF($2::text, ''),
billing_status = CASE
WHEN run_mode NOT IN ('production', 'acceptance', 'acceptance_canary') OR gateway_user_id IS NULL THEN 'not_required'
WHEN reservation_amount > 0 THEN 'pending'
ELSE 'released'
END,
billing_updated_at = now(),
remote_task_payload = '{}'::jsonb,
locked_by = NULL,
locked_at = NULL,
@@ -930,7 +936,20 @@ WHERE task_id = $1::uuid
if _, err := tx.Exec(ctx, `DELETE FROM gateway_task_admissions WHERE task_id = $1::uuid`, taskID); err != nil {
return err
}
return nil
payloadJSON, _ := json.Marshal(map[string]any{"taskId": taskID, "reason": "queued_cancelled"})
_, err = tx.Exec(ctx, `
INSERT INTO settlement_outbox (
task_id, event_type, action, amount, currency, pricing_snapshot, payload, status, next_attempt_at
)
SELECT id, 'task.billing.release', 'release', reservation_amount, billing_currency,
pricing_snapshot, $2::jsonb, 'pending', now()
FROM gateway_tasks
WHERE id = $1::uuid
AND run_mode IN ('production', 'acceptance', 'acceptance_canary')
AND gateway_user_id IS NOT NULL
AND reservation_amount > 0
ON CONFLICT (task_id, event_type) DO NOTHING`, taskID, string(payloadJSON))
return err
})
if err != nil {
return GatewayTask{}, false, err
@@ -1568,6 +1587,7 @@ func minimalTaskAttemptUsage(usage map[string]any) map[string]any {
func minimalTaskAttemptMetrics(metrics map[string]any) map[string]any {
return whitelistedTaskAttemptMap(metrics, []string{
"platformPriority", "currentPriority", "loadRatio", "loadAvoided",
"loadMetrics", "fullReasons", "selectionReason",
"cacheAffinityKey", "cacheAdjustedPriority", "cacheAffinitySamples",
"cacheAffinityScore", "cacheAffinityConfidence", "cacheAffinityHitRatio",
"cacheAffinityEMAHitRatio", "cacheAffinityLastHitRatio",