| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- package service
- import (
- "context"
- "encoding/json"
- "fmt"
- "strconv"
- "time"
- "github.com/redis/go-redis/v9"
- "gorm.io/gorm"
- "spider/internal/model"
- )
- const settingsCacheKey = "spider:cache:settings"
- const settingsCacheTTL = 5 * time.Minute
- // SettingsService provides hot-reloadable access to managed_settings.
- type SettingsService struct {
- db *gorm.DB
- redis *redis.Client
- }
- // NewSettingsService creates a new SettingsService.
- func NewSettingsService(db *gorm.DB, rdb *redis.Client) *SettingsService {
- return &SettingsService{db: db, redis: rdb}
- }
- // Load 从数据库加载所有设置到 Redis 缓存
- func (s *SettingsService) Load(ctx context.Context) error {
- var settings []model.ManagedSetting
- if err := s.db.WithContext(ctx).Find(&settings).Error; err != nil {
- return fmt.Errorf("load settings from db: %w", err)
- }
- if len(settings) == 0 {
- // Nothing to cache; ensure any stale cache is cleared.
- return s.redis.Del(ctx, settingsCacheKey).Err()
- }
- fields := make([]interface{}, 0, len(settings)*2)
- for _, setting := range settings {
- fields = append(fields, setting.KeyName, setting.Value)
- }
- pipe := s.redis.Pipeline()
- pipe.HSet(ctx, settingsCacheKey, fields...)
- pipe.Expire(ctx, settingsCacheKey, settingsCacheTTL)
- _, err := pipe.Exec(ctx)
- if err != nil {
- return fmt.Errorf("cache settings to redis: %w", err)
- }
- return nil
- }
- // Get 获取设置值(先读 Redis 缓存,缓存不存在则读 DB 并回填)
- func (s *SettingsService) Get(ctx context.Context, key string) (string, error) {
- // Try cache first.
- val, err := s.redis.HGet(ctx, settingsCacheKey, key).Result()
- if err == nil {
- return val, nil
- }
- // Cache miss or Redis error — fall back to DB.
- var setting model.ManagedSetting
- if err := s.db.WithContext(ctx).Where("key_name = ?", key).First(&setting).Error; err != nil {
- return "", fmt.Errorf("setting %q not found: %w", key, err)
- }
- // Back-fill the cache entry.
- pipe := s.redis.Pipeline()
- pipe.HSet(ctx, settingsCacheKey, key, setting.Value)
- pipe.Expire(ctx, settingsCacheKey, settingsCacheTTL)
- pipe.Exec(ctx) //nolint:errcheck — best-effort
- return setting.Value, nil
- }
- // GetInt 获取整数类型设置
- func (s *SettingsService) GetInt(ctx context.Context, key string, defaultVal int) int {
- raw, err := s.Get(ctx, key)
- if err != nil {
- return defaultVal
- }
- v, err := strconv.Atoi(raw)
- if err != nil {
- return defaultVal
- }
- return v
- }
- // GetFloat 获取浮点类型设置
- func (s *SettingsService) GetFloat(ctx context.Context, key string, defaultVal float64) float64 {
- raw, err := s.Get(ctx, key)
- if err != nil {
- return defaultVal
- }
- v, err := strconv.ParseFloat(raw, 64)
- if err != nil {
- return defaultVal
- }
- return v
- }
- // GetBool 获取布尔类型设置
- func (s *SettingsService) GetBool(ctx context.Context, key string, defaultVal bool) bool {
- raw, err := s.Get(ctx, key)
- if err != nil {
- return defaultVal
- }
- v, err := strconv.ParseBool(raw)
- if err != nil {
- return defaultVal
- }
- return v
- }
- // GetJSON 获取 JSON 类型设置,解析到 target
- func (s *SettingsService) GetJSON(ctx context.Context, key string, target interface{}) error {
- raw, err := s.Get(ctx, key)
- if err != nil {
- return err
- }
- return json.Unmarshal([]byte(raw), target)
- }
- // Set 更新设置(更新 DB + 清除缓存)
- func (s *SettingsService) Set(ctx context.Context, key, value string) error {
- result := s.db.WithContext(ctx).Model(&model.ManagedSetting{}).
- Where("key_name = ?", key).
- Update("value", value)
- if result.Error != nil {
- return fmt.Errorf("update setting %q in db: %w", key, result.Error)
- }
- if result.RowsAffected == 0 {
- return fmt.Errorf("setting %q not found", key)
- }
- // Invalidate cache so next read reloads from DB.
- return s.Invalidate(ctx)
- }
- // Invalidate 清除缓存,下次读取时从 DB 加载
- func (s *SettingsService) Invalidate(ctx context.Context) error {
- return s.redis.Del(ctx, settingsCacheKey).Err()
- }
|