error_passthrough_cache.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. package repository
  2. import (
  3. "context"
  4. "encoding/json"
  5. "log"
  6. "sync"
  7. "time"
  8. "github.com/Wei-Shaw/sub2api/internal/model"
  9. "github.com/Wei-Shaw/sub2api/internal/service"
  10. "github.com/redis/go-redis/v9"
  11. )
  12. const (
  13. errorPassthroughCacheKey = "error_passthrough_rules"
  14. errorPassthroughPubSubKey = "error_passthrough_rules_updated"
  15. errorPassthroughCacheTTL = 24 * time.Hour
  16. )
  17. type errorPassthroughCache struct {
  18. rdb *redis.Client
  19. localCache []*model.ErrorPassthroughRule
  20. localMu sync.RWMutex
  21. }
  22. // NewErrorPassthroughCache 创建错误透传规则缓存
  23. func NewErrorPassthroughCache(rdb *redis.Client) service.ErrorPassthroughCache {
  24. return &errorPassthroughCache{
  25. rdb: rdb,
  26. }
  27. }
  28. // Get 从缓存获取规则列表
  29. func (c *errorPassthroughCache) Get(ctx context.Context) ([]*model.ErrorPassthroughRule, bool) {
  30. // 先检查本地缓存
  31. c.localMu.RLock()
  32. if c.localCache != nil {
  33. rules := c.localCache
  34. c.localMu.RUnlock()
  35. return rules, true
  36. }
  37. c.localMu.RUnlock()
  38. // 从 Redis 获取
  39. data, err := c.rdb.Get(ctx, errorPassthroughCacheKey).Bytes()
  40. if err != nil {
  41. if err != redis.Nil {
  42. log.Printf("[ErrorPassthroughCache] Failed to get from Redis: %v", err)
  43. }
  44. return nil, false
  45. }
  46. var rules []*model.ErrorPassthroughRule
  47. if err := json.Unmarshal(data, &rules); err != nil {
  48. log.Printf("[ErrorPassthroughCache] Failed to unmarshal rules: %v", err)
  49. return nil, false
  50. }
  51. // 更新本地缓存
  52. c.localMu.Lock()
  53. c.localCache = rules
  54. c.localMu.Unlock()
  55. return rules, true
  56. }
  57. // Set 设置缓存
  58. func (c *errorPassthroughCache) Set(ctx context.Context, rules []*model.ErrorPassthroughRule) error {
  59. data, err := json.Marshal(rules)
  60. if err != nil {
  61. return err
  62. }
  63. if err := c.rdb.Set(ctx, errorPassthroughCacheKey, data, errorPassthroughCacheTTL).Err(); err != nil {
  64. return err
  65. }
  66. // 更新本地缓存
  67. c.localMu.Lock()
  68. c.localCache = rules
  69. c.localMu.Unlock()
  70. return nil
  71. }
  72. // Invalidate 使缓存失效
  73. func (c *errorPassthroughCache) Invalidate(ctx context.Context) error {
  74. // 清除本地缓存
  75. c.localMu.Lock()
  76. c.localCache = nil
  77. c.localMu.Unlock()
  78. // 清除 Redis 缓存
  79. return c.rdb.Del(ctx, errorPassthroughCacheKey).Err()
  80. }
  81. // NotifyUpdate 通知其他实例刷新缓存
  82. func (c *errorPassthroughCache) NotifyUpdate(ctx context.Context) error {
  83. return c.rdb.Publish(ctx, errorPassthroughPubSubKey, "refresh").Err()
  84. }
  85. // SubscribeUpdates 订阅缓存更新通知
  86. func (c *errorPassthroughCache) SubscribeUpdates(ctx context.Context, handler func()) {
  87. go func() {
  88. sub := c.rdb.Subscribe(ctx, errorPassthroughPubSubKey)
  89. defer func() { _ = sub.Close() }()
  90. ch := sub.Channel()
  91. for {
  92. select {
  93. case <-ctx.Done():
  94. return
  95. case msg := <-ch:
  96. if msg == nil {
  97. return
  98. }
  99. // 清除本地缓存,下次访问时会从 Redis 或数据库重新加载
  100. c.localMu.Lock()
  101. c.localCache = nil
  102. c.localMu.Unlock()
  103. // 调用处理函数
  104. handler()
  105. }
  106. }
  107. }()
  108. }