mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-13 15:54:34 +00:00
fix(topup): settle recharge orders atomically
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
)
|
||||
|
||||
type cacheQuotaResult int
|
||||
|
||||
const (
|
||||
cacheQuotaInsufficient cacheQuotaResult = iota
|
||||
cacheQuotaOK
|
||||
cacheQuotaMiss
|
||||
)
|
||||
|
||||
// 下列脚本都是守卫式的:只在完整哈希(Id 匹配且配额字段存在)上操作,
|
||||
// 哈希缺失时返回 miss 而不是创建残缺哈希。脚本不修改 TTL(HINCRBY 天然保留
|
||||
// 水合时设置的 TTL),因此即使某个写库路径绕过了缓存,偏差也会在一个 TTL
|
||||
// 窗口内随缓存过期而自愈。
|
||||
|
||||
const userQuotaReserveScript = `
|
||||
if tonumber(redis.call('HGET', KEYS[1], 'Id') or '0') ~= tonumber(ARGV[2])
|
||||
or tonumber(redis.call('HGET', KEYS[1], 'CacheSchema') or '0') ~= tonumber(ARGV[3])
|
||||
or redis.call('HEXISTS', KEYS[1], 'Quota') == 0 then
|
||||
return -1
|
||||
end
|
||||
local quota = tonumber(redis.call('HGET', KEYS[1], 'Quota'))
|
||||
if quota == nil or quota < tonumber(ARGV[1]) then
|
||||
return 0
|
||||
end
|
||||
redis.call('HINCRBY', KEYS[1], 'Quota', -tonumber(ARGV[1]))
|
||||
return 1`
|
||||
|
||||
const userQuotaDeltaScript = `
|
||||
if tonumber(redis.call('HGET', KEYS[1], 'Id') or '0') ~= tonumber(ARGV[2])
|
||||
or tonumber(redis.call('HGET', KEYS[1], 'CacheSchema') or '0') ~= tonumber(ARGV[3])
|
||||
or redis.call('HEXISTS', KEYS[1], 'Quota') == 0 then
|
||||
return -1
|
||||
end
|
||||
redis.call('HINCRBY', KEYS[1], 'Quota', tonumber(ARGV[1]))
|
||||
return 1`
|
||||
|
||||
func quotaResultFromLua(result int, err error) (cacheQuotaResult, error) {
|
||||
if err != nil {
|
||||
return cacheQuotaMiss, err
|
||||
}
|
||||
switch result {
|
||||
case 1:
|
||||
return cacheQuotaOK, nil
|
||||
case 0:
|
||||
return cacheQuotaInsufficient, nil
|
||||
default:
|
||||
return cacheQuotaMiss, nil
|
||||
}
|
||||
}
|
||||
|
||||
func cacheTryReserveUserQuota(userID int, amount int64) (cacheQuotaResult, error) {
|
||||
result, err := common.RDB.Eval(context.Background(), userQuotaReserveScript,
|
||||
[]string{getUserCacheKey(userID)}, amount, userID, userCacheSchemaVersion).Int()
|
||||
return quotaResultFromLua(result, err)
|
||||
}
|
||||
|
||||
func cacheApplyUserQuotaDelta(userID int, delta int64) (cacheQuotaResult, error) {
|
||||
result, err := common.RDB.Eval(context.Background(), userQuotaDeltaScript,
|
||||
[]string{getUserCacheKey(userID)}, delta, userID, userCacheSchemaVersion).Int()
|
||||
return quotaResultFromLua(result, err)
|
||||
}
|
||||
Reference in New Issue
Block a user