package service import ( "context" "fmt" "strconv" "time" "github.com/redis/go-redis/v9" "hyapp/pkg/appcode" "hyapp/pkg/idgen" "hyapp/pkg/xerr" ) const releaseLuckyGiftSendLockScript = ` if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) end return 0 ` type LuckyGiftSendLocker interface { AcquireLuckyGiftSendLock(ctx context.Context, appCode string, userID int64, ttl time.Duration) (func(context.Context), error) } type RedisLuckyGiftSendLocker struct { client *redis.Client } func NewRedisLuckyGiftSendLocker(client *redis.Client) *RedisLuckyGiftSendLocker { return &RedisLuckyGiftSendLocker{client: client} } func (l *RedisLuckyGiftSendLocker) AcquireLuckyGiftSendLock(ctx context.Context, appCode string, userID int64, ttl time.Duration) (func(context.Context), error) { if l == nil || l.client == nil { return nil, nil } if userID <= 0 { return nil, xerr.New(xerr.InvalidArgument, "lucky gift sender is invalid") } if ttl <= 0 { ttl = 5 * time.Second } key := luckyGiftSendLockKey(appCode, userID) token := idgen.New("lucky_gift_send_lock") ok, err := l.client.SetNX(ctx, key, token, ttl).Result() if err != nil { return nil, err } if !ok { return nil, xerr.New(xerr.Conflict, "lucky gift send is already processing") } return func(releaseCtx context.Context) { _ = l.client.Eval(releaseCtx, releaseLuckyGiftSendLockScript, []string{key}, token).Err() }, nil } func luckyGiftSendLockKey(rawAppCode string, userID int64) string { return fmt.Sprintf("lucky_gift_send:%s:%s", appcode.Normalize(rawAppCode), strconv.FormatInt(userID, 10)) }