settings_service.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. package service
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "strconv"
  7. "time"
  8. "github.com/redis/go-redis/v9"
  9. "gorm.io/gorm"
  10. "spider/internal/model"
  11. )
  12. const settingsCacheKey = "spider:cache:settings"
  13. const settingsCacheTTL = 5 * time.Minute
  14. // SettingsService provides hot-reloadable access to managed_settings.
  15. type SettingsService struct {
  16. db *gorm.DB
  17. redis *redis.Client
  18. }
  19. // NewSettingsService creates a new SettingsService.
  20. func NewSettingsService(db *gorm.DB, rdb *redis.Client) *SettingsService {
  21. return &SettingsService{db: db, redis: rdb}
  22. }
  23. // Load 从数据库加载所有设置到 Redis 缓存
  24. func (s *SettingsService) Load(ctx context.Context) error {
  25. var settings []model.ManagedSetting
  26. if err := s.db.WithContext(ctx).Find(&settings).Error; err != nil {
  27. return fmt.Errorf("load settings from db: %w", err)
  28. }
  29. if len(settings) == 0 {
  30. // Nothing to cache; ensure any stale cache is cleared.
  31. return s.redis.Del(ctx, settingsCacheKey).Err()
  32. }
  33. fields := make([]interface{}, 0, len(settings)*2)
  34. for _, setting := range settings {
  35. fields = append(fields, setting.KeyName, setting.Value)
  36. }
  37. pipe := s.redis.Pipeline()
  38. pipe.HSet(ctx, settingsCacheKey, fields...)
  39. pipe.Expire(ctx, settingsCacheKey, settingsCacheTTL)
  40. _, err := pipe.Exec(ctx)
  41. if err != nil {
  42. return fmt.Errorf("cache settings to redis: %w", err)
  43. }
  44. return nil
  45. }
  46. // Get 获取设置值(先读 Redis 缓存,缓存不存在则读 DB 并回填)
  47. func (s *SettingsService) Get(ctx context.Context, key string) (string, error) {
  48. // Try cache first.
  49. val, err := s.redis.HGet(ctx, settingsCacheKey, key).Result()
  50. if err == nil {
  51. return val, nil
  52. }
  53. // Cache miss or Redis error — fall back to DB.
  54. var setting model.ManagedSetting
  55. if err := s.db.WithContext(ctx).Where("key_name = ?", key).First(&setting).Error; err != nil {
  56. return "", fmt.Errorf("setting %q not found: %w", key, err)
  57. }
  58. // Back-fill the cache entry.
  59. pipe := s.redis.Pipeline()
  60. pipe.HSet(ctx, settingsCacheKey, key, setting.Value)
  61. pipe.Expire(ctx, settingsCacheKey, settingsCacheTTL)
  62. pipe.Exec(ctx) //nolint:errcheck — best-effort
  63. return setting.Value, nil
  64. }
  65. // GetInt 获取整数类型设置
  66. func (s *SettingsService) GetInt(ctx context.Context, key string, defaultVal int) int {
  67. raw, err := s.Get(ctx, key)
  68. if err != nil {
  69. return defaultVal
  70. }
  71. v, err := strconv.Atoi(raw)
  72. if err != nil {
  73. return defaultVal
  74. }
  75. return v
  76. }
  77. // GetFloat 获取浮点类型设置
  78. func (s *SettingsService) GetFloat(ctx context.Context, key string, defaultVal float64) float64 {
  79. raw, err := s.Get(ctx, key)
  80. if err != nil {
  81. return defaultVal
  82. }
  83. v, err := strconv.ParseFloat(raw, 64)
  84. if err != nil {
  85. return defaultVal
  86. }
  87. return v
  88. }
  89. // GetBool 获取布尔类型设置
  90. func (s *SettingsService) GetBool(ctx context.Context, key string, defaultVal bool) bool {
  91. raw, err := s.Get(ctx, key)
  92. if err != nil {
  93. return defaultVal
  94. }
  95. v, err := strconv.ParseBool(raw)
  96. if err != nil {
  97. return defaultVal
  98. }
  99. return v
  100. }
  101. // GetJSON 获取 JSON 类型设置,解析到 target
  102. func (s *SettingsService) GetJSON(ctx context.Context, key string, target interface{}) error {
  103. raw, err := s.Get(ctx, key)
  104. if err != nil {
  105. return err
  106. }
  107. return json.Unmarshal([]byte(raw), target)
  108. }
  109. // Set 更新设置(更新 DB + 清除缓存)
  110. func (s *SettingsService) Set(ctx context.Context, key, value string) error {
  111. result := s.db.WithContext(ctx).Model(&model.ManagedSetting{}).
  112. Where("key_name = ?", key).
  113. Update("value", value)
  114. if result.Error != nil {
  115. return fmt.Errorf("update setting %q in db: %w", key, result.Error)
  116. }
  117. if result.RowsAffected == 0 {
  118. return fmt.Errorf("setting %q not found", key)
  119. }
  120. // Invalidate cache so next read reloads from DB.
  121. return s.Invalidate(ctx)
  122. }
  123. // Invalidate 清除缓存,下次读取时从 DB 加载
  124. func (s *SettingsService) Invalidate(ctx context.Context) error {
  125. return s.redis.Del(ctx, settingsCacheKey).Err()
  126. }