This commit is contained in:
2026-09-15 16:02:45 +08:00
parent 112c5f8e46
commit ade2453083
70 changed files with 1441 additions and 707 deletions
+23 -11
View File
@@ -300,10 +300,16 @@ func consumeCosQuota(ctx context.Context, userID int64, n int64, relatedID int64
if delta > remain {
delta = remain
}
if err := tx.Model(&mysqlmodel.QuotaGrant{}).
res := tx.Model(&mysqlmodel.QuotaGrant{}).
Where("id = ? AND used + ? <= amount", grants[i].ID, delta).
UpdateColumn("used", gorm.Expr("used + ?", delta)).Error; err != nil {
return err
UpdateColumn("used", gorm.Expr("used + ?", delta))
if res.Error != nil {
return res.Error
}
if res.RowsAffected == 0 {
// 凭证在行锁下被并发改写(理论上不应发生)。此时若继续累加会少扣额度,
// 直接回滚整个事务并报额度不足,宁可让用户重试也不超额放行。
return errcode.QuotaInsufficient
}
remain -= delta
}
@@ -415,25 +421,31 @@ func refundCosQuotaTx(tx *gorm.DB, userID int64, vipExpire int64, n int64, relat
}
remain -= delta
}
if remain > 0 {
// 无足够已用额度可回退(数据异常),跳过写流水。
return nil
}
}
// 3. 写回退流水。
// 3. 写回退流水:变更量取「实际回退量」。
// 此前 `remain > 0` 时直接 return,导致额度已被部分改回却没有流水(账实不一致);
// 同时 ChangeNum 写的是请求量 n,退不满时数字虚高。现统一按实际回退量记账,
// 退不满/退不动时在原因里标注并告警(已用额度不足通常意味着重复回退或数据异常,必须留痕)。
refunded := n - remain
flowReason := reason
if refunded < n {
flowReason = fmt.Sprintf("%s(实际回退 %d/%d,可用已退额度不足)", reason, refunded, n)
slog.WarnContext(context.Background(), "refund cos quota partially applied",
"user_id", userID, "want", n, "refunded", refunded, "related_id", relatedID)
}
after := cosRemainTx(tx, userID, vipExpire, now)
flow := &mysqlmodel.QuotaFlowLog{
QuotaFlowUUID: genUUID(),
UserID: userID,
ChangeTarget: QuotaChangeTargetUse,
ChangeType: QuotaChangeTypeRefund,
ChangeNum: n,
OldValue: after - n,
ChangeNum: refunded,
OldValue: after - refunded,
NewValue: after,
QuotaType: QuotaTypeCos,
RelatedID: relatedID,
Reason: reason,
Reason: flowReason,
}
// 回退类流水无业务订单号,用流水 UUID 填充 order_no 避免唯一索引空串冲突。
flow.OrderNo = flow.QuotaFlowUUID