41 lines
1012 B
Go
41 lines
1012 B
Go
package stocklock
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
var (
|
|
ErrSoldOut = errors.New("库存不足")
|
|
ErrDuplicate = errors.New("每人限购一件,已经抢过了")
|
|
)
|
|
|
|
// 原子地:检查是否已购买 → 检查库存 → 扣库存 → 记录用户
|
|
var reserveScript = redis.NewScript(`
|
|
if redis.call("SISMEMBER", KEYS[2], ARGV[1]) == 1 then
|
|
return -2
|
|
end
|
|
local left = tonumber(redis.call("GET", KEYS[1]) or "0")
|
|
if left <= 0 then
|
|
return -1
|
|
end
|
|
redis.call("DECR", KEYS[1])
|
|
redis.call("SADD", KEYS[2], ARGV[1])
|
|
return left - 1`)
|
|
|
|
// Reserve 预扣库存。成功后应把订单消息发到 MQ,由消费者异步落库。
|
|
func Reserve(ctx context.Context, rdb *redis.Client, sku, userID string) (left int, err error) {
|
|
n, err := reserveScript.Run(ctx, rdb, []string{"stock:" + sku, "stock:" + sku + ":buyers"}, userID).Int()
|
|
switch {
|
|
case err != nil:
|
|
return 0, err
|
|
case n == -1:
|
|
return 0, ErrSoldOut
|
|
case n == -2:
|
|
return 0, ErrDuplicate
|
|
}
|
|
return n, nil
|
|
}
|