mirror of
https://gh-proxy.org/https://github.com/klinsmaya/new-api.git
synced 2026-11-09 09:57:06 +08:00
45 lines
1.7 KiB
Go
45 lines
1.7 KiB
Go
package model
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"github.com/QuantumNous/new-api/common"
|
|
)
|
|
|
|
// SyncDirectPayCredit is replayable: the cumulative credit watermark belongs to
|
|
// the same hash generation as Quota. Hydration sets both from one DB snapshot;
|
|
// existing hashes preserve both. No live quota is replaced with a DB balance.
|
|
// A delayed/unknown-result call is safe to retry, even after cache expiration.
|
|
func SyncDirectPayCredit(userID int) error {
|
|
if !common.RedisEnabled {
|
|
return nil
|
|
}
|
|
var user User
|
|
if err := DB.Select("id", "direct_pay_credit_total").First(&user, userID).Error; err != nil {
|
|
return err
|
|
}
|
|
const script = `
|
|
if tonumber(redis.call('HGET', KEYS[1], 'Id') or '0') ~= tonumber(ARGV[1])
|
|
or redis.call('HEXISTS', KEYS[1], 'Quota') == 0 then return 0 end
|
|
-- An old hydrator may already include the credit in Quota without its watermark.
|
|
-- Never infer zero: replaying here would mint a second spendable credit.
|
|
local previous = tonumber(redis.call('HGET', KEYS[1], 'DirectPayCreditTotal'))
|
|
if previous == nil then return -2 end
|
|
local incoming = tonumber(ARGV[2])
|
|
if previous >= incoming then return 1 end
|
|
local delta = incoming - previous
|
|
local quota = tonumber(redis.call('HGET', KEYS[1], 'Quota'))
|
|
if quota == nil or quota + delta > tonumber(ARGV[3]) then return -1 end
|
|
redis.call('HINCRBY', KEYS[1], 'Quota', string.format('%.0f', delta))
|
|
redis.call('HSET', KEYS[1], 'DirectPayCreditTotal', ARGV[2])
|
|
return 1`
|
|
result, err := common.RDB.Eval(context.Background(), script, []string{getUserCacheKey(userID)}, userID, user.DirectPayCreditTotal, common.MaxWalletQuota).Int()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if result < 0 {
|
|
return errors.New("direct payment cache quota requires review")
|
|
}
|
|
return nil
|
|
}
|