package service import ( "context" "errors" "fmt" "strconv" "sub2api/internal/config" "sub2api/internal/model" "sub2api/internal/repository" "gorm.io/gorm" ) var ( ErrRegistrationDisabled = errors.New("registration is currently disabled") ) // SettingService 系统设置服务 type SettingService struct { settingRepo *repository.SettingRepository cfg *config.Config } // NewSettingService 创建系统设置服务实例 func NewSettingService(settingRepo *repository.SettingRepository, cfg *config.Config) *SettingService { return &SettingService{ settingRepo: settingRepo, cfg: cfg, } } // GetAllSettings 获取所有系统设置 func (s *SettingService) GetAllSettings(ctx context.Context) (*model.SystemSettings, error) { settings, err := s.settingRepo.GetAll(ctx) if err != nil { return nil, fmt.Errorf("get all settings: %w", err) } return s.parseSettings(settings), nil } // GetPublicSettings 获取公开设置(无需登录) func (s *SettingService) GetPublicSettings(ctx context.Context) (*model.PublicSettings, error) { keys := []string{ model.SettingKeyRegistrationEnabled, model.SettingKeyEmailVerifyEnabled, model.SettingKeyTurnstileEnabled, model.SettingKeyTurnstileSiteKey, model.SettingKeySiteName, model.SettingKeySiteLogo, model.SettingKeySiteSubtitle, model.SettingKeyApiBaseUrl, model.SettingKeyContactInfo, } settings, err := s.settingRepo.GetMultiple(ctx, keys) if err != nil { return nil, fmt.Errorf("get public settings: %w", err) } return &model.PublicSettings{ RegistrationEnabled: settings[model.SettingKeyRegistrationEnabled] == "true", EmailVerifyEnabled: settings[model.SettingKeyEmailVerifyEnabled] == "true", TurnstileEnabled: settings[model.SettingKeyTurnstileEnabled] == "true", TurnstileSiteKey: settings[model.SettingKeyTurnstileSiteKey], SiteName: s.getStringOrDefault(settings, model.SettingKeySiteName, "Sub2API"), SiteLogo: settings[model.SettingKeySiteLogo], SiteSubtitle: s.getStringOrDefault(settings, model.SettingKeySiteSubtitle, "Subscription to API Conversion Platform"), ApiBaseUrl: settings[model.SettingKeyApiBaseUrl], ContactInfo: settings[model.SettingKeyContactInfo], }, nil } // UpdateSettings 更新系统设置 func (s *SettingService) UpdateSettings(ctx context.Context, settings *model.SystemSettings) error { updates := make(map[string]string) // 注册设置 updates[model.SettingKeyRegistrationEnabled] = strconv.FormatBool(settings.RegistrationEnabled) updates[model.SettingKeyEmailVerifyEnabled] = strconv.FormatBool(settings.EmailVerifyEnabled) // 邮件服务设置(只有非空才更新密码) updates[model.SettingKeySmtpHost] = settings.SmtpHost updates[model.SettingKeySmtpPort] = strconv.Itoa(settings.SmtpPort) updates[model.SettingKeySmtpUsername] = settings.SmtpUsername if settings.SmtpPassword != "" { updates[model.SettingKeySmtpPassword] = settings.SmtpPassword } updates[model.SettingKeySmtpFrom] = settings.SmtpFrom updates[model.SettingKeySmtpFromName] = settings.SmtpFromName updates[model.SettingKeySmtpUseTLS] = strconv.FormatBool(settings.SmtpUseTLS) // Cloudflare Turnstile 设置(只有非空才更新密钥) updates[model.SettingKeyTurnstileEnabled] = strconv.FormatBool(settings.TurnstileEnabled) updates[model.SettingKeyTurnstileSiteKey] = settings.TurnstileSiteKey if settings.TurnstileSecretKey != "" { updates[model.SettingKeyTurnstileSecretKey] = settings.TurnstileSecretKey } // OEM设置 updates[model.SettingKeySiteName] = settings.SiteName updates[model.SettingKeySiteLogo] = settings.SiteLogo updates[model.SettingKeySiteSubtitle] = settings.SiteSubtitle updates[model.SettingKeyApiBaseUrl] = settings.ApiBaseUrl updates[model.SettingKeyContactInfo] = settings.ContactInfo // 默认配置 updates[model.SettingKeyDefaultConcurrency] = strconv.Itoa(settings.DefaultConcurrency) updates[model.SettingKeyDefaultBalance] = strconv.FormatFloat(settings.DefaultBalance, 'f', 8, 64) return s.settingRepo.SetMultiple(ctx, updates) } // IsRegistrationEnabled 检查是否开放注册 func (s *SettingService) IsRegistrationEnabled(ctx context.Context) bool { value, err := s.settingRepo.GetValue(ctx, model.SettingKeyRegistrationEnabled) if err != nil { // 默认开放注册 return true } return value == "true" } // IsEmailVerifyEnabled 检查是否开启邮件验证 func (s *SettingService) IsEmailVerifyEnabled(ctx context.Context) bool { value, err := s.settingRepo.GetValue(ctx, model.SettingKeyEmailVerifyEnabled) if err != nil { return false } return value == "true" } // GetSiteName 获取网站名称 func (s *SettingService) GetSiteName(ctx context.Context) string { value, err := s.settingRepo.GetValue(ctx, model.SettingKeySiteName) if err != nil || value == "" { return "Sub2API" } return value } // GetDefaultConcurrency 获取默认并发量 func (s *SettingService) GetDefaultConcurrency(ctx context.Context) int { value, err := s.settingRepo.GetValue(ctx, model.SettingKeyDefaultConcurrency) if err != nil { return s.cfg.Default.UserConcurrency } if v, err := strconv.Atoi(value); err == nil && v > 0 { return v } return s.cfg.Default.UserConcurrency } // GetDefaultBalance 获取默认余额 func (s *SettingService) GetDefaultBalance(ctx context.Context) float64 { value, err := s.settingRepo.GetValue(ctx, model.SettingKeyDefaultBalance) if err != nil { return s.cfg.Default.UserBalance } if v, err := strconv.ParseFloat(value, 64); err == nil && v >= 0 { return v } return s.cfg.Default.UserBalance } // InitializeDefaultSettings 初始化默认设置 func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error { // 检查是否已有设置 _, err := s.settingRepo.GetValue(ctx, model.SettingKeyRegistrationEnabled) if err == nil { // 已有设置,不需要初始化 return nil } if !errors.Is(err, gorm.ErrRecordNotFound) { return fmt.Errorf("check existing settings: %w", err) } // 初始化默认设置 defaults := map[string]string{ model.SettingKeyRegistrationEnabled: "true", model.SettingKeyEmailVerifyEnabled: "false", model.SettingKeySiteName: "Sub2API", model.SettingKeySiteLogo: "", model.SettingKeyDefaultConcurrency: strconv.Itoa(s.cfg.Default.UserConcurrency), model.SettingKeyDefaultBalance: strconv.FormatFloat(s.cfg.Default.UserBalance, 'f', 8, 64), model.SettingKeySmtpPort: "587", model.SettingKeySmtpUseTLS: "false", } return s.settingRepo.SetMultiple(ctx, defaults) } // parseSettings 解析设置到结构体 func (s *SettingService) parseSettings(settings map[string]string) *model.SystemSettings { result := &model.SystemSettings{ RegistrationEnabled: settings[model.SettingKeyRegistrationEnabled] == "true", EmailVerifyEnabled: settings[model.SettingKeyEmailVerifyEnabled] == "true", SmtpHost: settings[model.SettingKeySmtpHost], SmtpUsername: settings[model.SettingKeySmtpUsername], SmtpFrom: settings[model.SettingKeySmtpFrom], SmtpFromName: settings[model.SettingKeySmtpFromName], SmtpUseTLS: settings[model.SettingKeySmtpUseTLS] == "true", TurnstileEnabled: settings[model.SettingKeyTurnstileEnabled] == "true", TurnstileSiteKey: settings[model.SettingKeyTurnstileSiteKey], SiteName: s.getStringOrDefault(settings, model.SettingKeySiteName, "Sub2API"), SiteLogo: settings[model.SettingKeySiteLogo], SiteSubtitle: s.getStringOrDefault(settings, model.SettingKeySiteSubtitle, "Subscription to API Conversion Platform"), ApiBaseUrl: settings[model.SettingKeyApiBaseUrl], ContactInfo: settings[model.SettingKeyContactInfo], } // 解析整数类型 if port, err := strconv.Atoi(settings[model.SettingKeySmtpPort]); err == nil { result.SmtpPort = port } else { result.SmtpPort = 587 } if concurrency, err := strconv.Atoi(settings[model.SettingKeyDefaultConcurrency]); err == nil { result.DefaultConcurrency = concurrency } else { result.DefaultConcurrency = s.cfg.Default.UserConcurrency } // 解析浮点数类型 if balance, err := strconv.ParseFloat(settings[model.SettingKeyDefaultBalance], 64); err == nil { result.DefaultBalance = balance } else { result.DefaultBalance = s.cfg.Default.UserBalance } // 敏感信息直接返回,方便测试连接时使用 result.SmtpPassword = settings[model.SettingKeySmtpPassword] result.TurnstileSecretKey = settings[model.SettingKeyTurnstileSecretKey] return result } // getStringOrDefault 获取字符串值或默认值 func (s *SettingService) getStringOrDefault(settings map[string]string, key, defaultValue string) string { if value, ok := settings[key]; ok && value != "" { return value } return defaultValue } // IsTurnstileEnabled 检查是否启用 Turnstile 验证 func (s *SettingService) IsTurnstileEnabled(ctx context.Context) bool { value, err := s.settingRepo.GetValue(ctx, model.SettingKeyTurnstileEnabled) if err != nil { return false } return value == "true" } // GetTurnstileSecretKey 获取 Turnstile Secret Key func (s *SettingService) GetTurnstileSecretKey(ctx context.Context) string { value, err := s.settingRepo.GetValue(ctx, model.SettingKeyTurnstileSecretKey) if err != nil { return "" } return value }