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 }