timeout_counter_cache.go 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. package repository
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "github.com/Wei-Shaw/sub2api/internal/service"
  7. "github.com/redis/go-redis/v9"
  8. )
  9. const timeoutCounterPrefix = "timeout_count:account:"
  10. // timeoutCounterIncrScript 使用 Lua 脚本原子性地增加计数并返回当前值
  11. // 如果 key 不存在,则创建并设置过期时间
  12. var timeoutCounterIncrScript = redis.NewScript(`
  13. local key = KEYS[1]
  14. local ttl = tonumber(ARGV[1])
  15. local count = redis.call('INCR', key)
  16. if count == 1 then
  17. redis.call('EXPIRE', key, ttl)
  18. end
  19. return count
  20. `)
  21. type timeoutCounterCache struct {
  22. rdb *redis.Client
  23. }
  24. // NewTimeoutCounterCache 创建超时计数器缓存实例
  25. func NewTimeoutCounterCache(rdb *redis.Client) service.TimeoutCounterCache {
  26. return &timeoutCounterCache{rdb: rdb}
  27. }
  28. // IncrementTimeoutCount 增加账户的超时计数,返回当前计数值
  29. // windowMinutes 是计数窗口时间(分钟),超过此时间计数器会自动重置
  30. func (c *timeoutCounterCache) IncrementTimeoutCount(ctx context.Context, accountID int64, windowMinutes int) (int64, error) {
  31. key := fmt.Sprintf("%s%d", timeoutCounterPrefix, accountID)
  32. ttlSeconds := windowMinutes * 60
  33. if ttlSeconds < 60 {
  34. ttlSeconds = 60 // 最小1分钟
  35. }
  36. result, err := timeoutCounterIncrScript.Run(ctx, c.rdb, []string{key}, ttlSeconds).Int64()
  37. if err != nil {
  38. return 0, fmt.Errorf("increment timeout count: %w", err)
  39. }
  40. return result, nil
  41. }
  42. // GetTimeoutCount 获取账户当前的超时计数
  43. func (c *timeoutCounterCache) GetTimeoutCount(ctx context.Context, accountID int64) (int64, error) {
  44. key := fmt.Sprintf("%s%d", timeoutCounterPrefix, accountID)
  45. val, err := c.rdb.Get(ctx, key).Int64()
  46. if err == redis.Nil {
  47. return 0, nil
  48. }
  49. if err != nil {
  50. return 0, fmt.Errorf("get timeout count: %w", err)
  51. }
  52. return val, nil
  53. }
  54. // ResetTimeoutCount 重置账户的超时计数
  55. func (c *timeoutCounterCache) ResetTimeoutCount(ctx context.Context, accountID int64) error {
  56. key := fmt.Sprintf("%s%d", timeoutCounterPrefix, accountID)
  57. return c.rdb.Del(ctx, key).Err()
  58. }
  59. // GetTimeoutCountTTL 获取计数器剩余过期时间
  60. func (c *timeoutCounterCache) GetTimeoutCountTTL(ctx context.Context, accountID int64) (time.Duration, error) {
  61. key := fmt.Sprintf("%s%d", timeoutCounterPrefix, accountID)
  62. return c.rdb.TTL(ctx, key).Result()
  63. }