diff --git a/backend/internal/handler/admin/setting_handler.go b/backend/internal/handler/admin/setting_handler.go index 5a543d6c..0e3e0a2f 100644 --- a/backend/internal/handler/admin/setting_handler.go +++ b/backend/internal/handler/admin/setting_handler.go @@ -47,6 +47,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) { response.Success(c, dto.SystemSettings{ RegistrationEnabled: settings.RegistrationEnabled, EmailVerifyEnabled: settings.EmailVerifyEnabled, + PromoCodeEnabled: settings.PromoCodeEnabled, SMTPHost: settings.SMTPHost, SMTPPort: settings.SMTPPort, SMTPUsername: settings.SMTPUsername, @@ -90,6 +91,7 @@ type UpdateSettingsRequest struct { // 注册设置 RegistrationEnabled bool `json:"registration_enabled"` EmailVerifyEnabled bool `json:"email_verify_enabled"` + PromoCodeEnabled bool `json:"promo_code_enabled"` // 邮件服务设置 SMTPHost string `json:"smtp_host"` @@ -240,6 +242,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { settings := &service.SystemSettings{ RegistrationEnabled: req.RegistrationEnabled, EmailVerifyEnabled: req.EmailVerifyEnabled, + PromoCodeEnabled: req.PromoCodeEnabled, SMTPHost: req.SMTPHost, SMTPPort: req.SMTPPort, SMTPUsername: req.SMTPUsername, @@ -314,6 +317,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { response.Success(c, dto.SystemSettings{ RegistrationEnabled: updatedSettings.RegistrationEnabled, EmailVerifyEnabled: updatedSettings.EmailVerifyEnabled, + PromoCodeEnabled: updatedSettings.PromoCodeEnabled, SMTPHost: updatedSettings.SMTPHost, SMTPPort: updatedSettings.SMTPPort, SMTPUsername: updatedSettings.SMTPUsername, diff --git a/backend/internal/handler/auth_handler.go b/backend/internal/handler/auth_handler.go index 882e4cf2..89f34aae 100644 --- a/backend/internal/handler/auth_handler.go +++ b/backend/internal/handler/auth_handler.go @@ -195,6 +195,15 @@ type ValidatePromoCodeResponse struct { // ValidatePromoCode 验证优惠码(公开接口,注册前调用) // POST /api/v1/auth/validate-promo-code func (h *AuthHandler) ValidatePromoCode(c *gin.Context) { + // 检查优惠码功能是否启用 + if h.settingSvc != nil && !h.settingSvc.IsPromoCodeEnabled(c.Request.Context()) { + response.Success(c, ValidatePromoCodeResponse{ + Valid: false, + ErrorCode: "PROMO_CODE_DISABLED", + }) + return + } + var req ValidatePromoCodeRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "Invalid request: "+err.Error()) diff --git a/backend/internal/handler/dto/settings.go b/backend/internal/handler/dto/settings.go index 19356e46..01f39478 100644 --- a/backend/internal/handler/dto/settings.go +++ b/backend/internal/handler/dto/settings.go @@ -4,6 +4,7 @@ package dto type SystemSettings struct { RegistrationEnabled bool `json:"registration_enabled"` EmailVerifyEnabled bool `json:"email_verify_enabled"` + PromoCodeEnabled bool `json:"promo_code_enabled"` SMTPHost string `json:"smtp_host"` SMTPPort int `json:"smtp_port"` @@ -55,6 +56,7 @@ type SystemSettings struct { type PublicSettings struct { RegistrationEnabled bool `json:"registration_enabled"` EmailVerifyEnabled bool `json:"email_verify_enabled"` + PromoCodeEnabled bool `json:"promo_code_enabled"` TurnstileEnabled bool `json:"turnstile_enabled"` TurnstileSiteKey string `json:"turnstile_site_key"` SiteName string `json:"site_name"` diff --git a/backend/internal/service/auth_service.go b/backend/internal/service/auth_service.go index 386b43fc..854e7732 100644 --- a/backend/internal/service/auth_service.go +++ b/backend/internal/service/auth_service.go @@ -153,8 +153,8 @@ func (s *AuthService) RegisterWithVerification(ctx context.Context, email, passw return "", nil, ErrServiceUnavailable } - // 应用优惠码(如果提供) - if promoCode != "" && s.promoService != nil { + // 应用优惠码(如果提供且功能已启用) + if promoCode != "" && s.promoService != nil && s.settingService != nil && s.settingService.IsPromoCodeEnabled(ctx) { if err := s.promoService.ApplyPromoCode(ctx, user.ID, promoCode); err != nil { // 优惠码应用失败不影响注册,只记录日志 log.Printf("[Auth] Failed to apply promo code for user %d: %v", user.ID, err) diff --git a/backend/internal/service/domain_constants.go b/backend/internal/service/domain_constants.go index da1b9377..3bb63ffa 100644 --- a/backend/internal/service/domain_constants.go +++ b/backend/internal/service/domain_constants.go @@ -71,6 +71,7 @@ const ( // 注册设置 SettingKeyRegistrationEnabled = "registration_enabled" // 是否开放注册 SettingKeyEmailVerifyEnabled = "email_verify_enabled" // 是否开启邮件验证 + SettingKeyPromoCodeEnabled = "promo_code_enabled" // 是否启用优惠码功能 // 邮件服务设置 SettingKeySMTPHost = "smtp_host" // SMTP服务器地址 diff --git a/backend/internal/service/setting_service.go b/backend/internal/service/setting_service.go index 5ab73588..d77dd30d 100644 --- a/backend/internal/service/setting_service.go +++ b/backend/internal/service/setting_service.go @@ -60,6 +60,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings keys := []string{ SettingKeyRegistrationEnabled, SettingKeyEmailVerifyEnabled, + SettingKeyPromoCodeEnabled, SettingKeyTurnstileEnabled, SettingKeyTurnstileSiteKey, SettingKeySiteName, @@ -88,6 +89,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings return &PublicSettings{ RegistrationEnabled: settings[SettingKeyRegistrationEnabled] == "true", EmailVerifyEnabled: settings[SettingKeyEmailVerifyEnabled] == "true", + PromoCodeEnabled: settings[SettingKeyPromoCodeEnabled] != "false", // 默认启用 TurnstileEnabled: settings[SettingKeyTurnstileEnabled] == "true", TurnstileSiteKey: settings[SettingKeyTurnstileSiteKey], SiteName: s.getStringOrDefault(settings, SettingKeySiteName, "Sub2API"), @@ -125,6 +127,7 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any return &struct { RegistrationEnabled bool `json:"registration_enabled"` EmailVerifyEnabled bool `json:"email_verify_enabled"` + PromoCodeEnabled bool `json:"promo_code_enabled"` TurnstileEnabled bool `json:"turnstile_enabled"` TurnstileSiteKey string `json:"turnstile_site_key,omitempty"` SiteName string `json:"site_name"` @@ -140,6 +143,7 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any }{ RegistrationEnabled: settings.RegistrationEnabled, EmailVerifyEnabled: settings.EmailVerifyEnabled, + PromoCodeEnabled: settings.PromoCodeEnabled, TurnstileEnabled: settings.TurnstileEnabled, TurnstileSiteKey: settings.TurnstileSiteKey, SiteName: settings.SiteName, @@ -162,6 +166,7 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet // 注册设置 updates[SettingKeyRegistrationEnabled] = strconv.FormatBool(settings.RegistrationEnabled) updates[SettingKeyEmailVerifyEnabled] = strconv.FormatBool(settings.EmailVerifyEnabled) + updates[SettingKeyPromoCodeEnabled] = strconv.FormatBool(settings.PromoCodeEnabled) // 邮件服务设置(只有非空才更新密码) updates[SettingKeySMTPHost] = settings.SMTPHost @@ -248,6 +253,15 @@ func (s *SettingService) IsEmailVerifyEnabled(ctx context.Context) bool { return value == "true" } +// IsPromoCodeEnabled 检查是否启用优惠码功能 +func (s *SettingService) IsPromoCodeEnabled(ctx context.Context) bool { + value, err := s.settingRepo.GetValue(ctx, SettingKeyPromoCodeEnabled) + if err != nil { + return true // 默认启用 + } + return value != "false" +} + // GetSiteName 获取网站名称 func (s *SettingService) GetSiteName(ctx context.Context) string { value, err := s.settingRepo.GetValue(ctx, SettingKeySiteName) @@ -297,6 +311,7 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error { defaults := map[string]string{ SettingKeyRegistrationEnabled: "true", SettingKeyEmailVerifyEnabled: "false", + SettingKeyPromoCodeEnabled: "true", // 默认启用优惠码功能 SettingKeySiteName: "Sub2API", SettingKeySiteLogo: "", SettingKeyDefaultConcurrency: strconv.Itoa(s.cfg.Default.UserConcurrency), @@ -328,6 +343,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin result := &SystemSettings{ RegistrationEnabled: settings[SettingKeyRegistrationEnabled] == "true", EmailVerifyEnabled: settings[SettingKeyEmailVerifyEnabled] == "true", + PromoCodeEnabled: settings[SettingKeyPromoCodeEnabled] != "false", // 默认启用 SMTPHost: settings[SettingKeySMTPHost], SMTPUsername: settings[SettingKeySMTPUsername], SMTPFrom: settings[SettingKeySMTPFrom], diff --git a/backend/internal/service/settings_view.go b/backend/internal/service/settings_view.go index 05494272..919344e5 100644 --- a/backend/internal/service/settings_view.go +++ b/backend/internal/service/settings_view.go @@ -3,6 +3,7 @@ package service type SystemSettings struct { RegistrationEnabled bool EmailVerifyEnabled bool + PromoCodeEnabled bool SMTPHost string SMTPPort int @@ -58,6 +59,7 @@ type SystemSettings struct { type PublicSettings struct { RegistrationEnabled bool EmailVerifyEnabled bool + PromoCodeEnabled bool TurnstileEnabled bool TurnstileSiteKey string SiteName string diff --git a/frontend/src/api/admin/settings.ts b/frontend/src/api/admin/settings.ts index c9a09e7d..6e2ade00 100644 --- a/frontend/src/api/admin/settings.ts +++ b/frontend/src/api/admin/settings.ts @@ -12,6 +12,7 @@ export interface SystemSettings { // Registration settings registration_enabled: boolean email_verify_enabled: boolean + promo_code_enabled: boolean // Default settings default_balance: number default_concurrency: number @@ -64,6 +65,7 @@ export interface SystemSettings { export interface UpdateSettingsRequest { registration_enabled?: boolean email_verify_enabled?: boolean + promo_code_enabled?: boolean default_balance?: number default_concurrency?: number site_name?: string diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index d1eca6a1..120dac27 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -2726,7 +2726,9 @@ export default { enableRegistration: 'Enable Registration', enableRegistrationHint: 'Allow new users to register', emailVerification: 'Email Verification', - emailVerificationHint: 'Require email verification for new registrations' + emailVerificationHint: 'Require email verification for new registrations', + promoCode: 'Promo Code', + promoCodeHint: 'Allow users to use promo codes during registration' }, turnstile: { title: 'Cloudflare Turnstile', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 86ac7ae5..4f7dcf64 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -2879,7 +2879,9 @@ export default { enableRegistration: '开放注册', enableRegistrationHint: '允许新用户注册', emailVerification: '邮箱验证', - emailVerificationHint: '新用户注册时需要验证邮箱' + emailVerificationHint: '新用户注册时需要验证邮箱', + promoCode: '优惠码', + promoCodeHint: '允许用户在注册时使用优惠码' }, turnstile: { title: 'Cloudflare Turnstile', diff --git a/frontend/src/stores/app.ts b/frontend/src/stores/app.ts index 7e3c71a0..9c4db599 100644 --- a/frontend/src/stores/app.ts +++ b/frontend/src/stores/app.ts @@ -312,6 +312,7 @@ export const useAppStore = defineStore('app', () => { return { registration_enabled: false, email_verify_enabled: false, + promo_code_enabled: true, turnstile_enabled: false, turnstile_site_key: '', site_name: siteName.value, diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 1b7ae15d..37c9f030 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -70,6 +70,7 @@ export interface SendVerifyCodeResponse { export interface PublicSettings { registration_enabled: boolean email_verify_enabled: boolean + promo_code_enabled: boolean turnstile_enabled: boolean turnstile_site_key: string site_name: string diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue index 41edb707..7ebca114 100644 --- a/frontend/src/views/admin/SettingsView.vue +++ b/frontend/src/views/admin/SettingsView.vue @@ -323,6 +323,21 @@ + + +
+
+ +

+ {{ t('admin.settings.registration.promoCodeHint') }} +

+
+ +
@@ -1013,6 +1028,7 @@ type SettingsForm = SystemSettings & { const form = reactive({ registration_enabled: true, email_verify_enabled: false, + promo_code_enabled: true, default_balance: 0, default_concurrency: 1, site_name: 'Sub2API', @@ -1135,6 +1151,7 @@ async function saveSettings() { const payload: UpdateSettingsRequest = { registration_enabled: form.registration_enabled, email_verify_enabled: form.email_verify_enabled, + promo_code_enabled: form.promo_code_enabled, default_balance: form.default_balance, default_concurrency: form.default_concurrency, site_name: form.site_name, diff --git a/frontend/src/views/auth/RegisterView.vue b/frontend/src/views/auth/RegisterView.vue index bfdc08e8..e1355bbd 100644 --- a/frontend/src/views/auth/RegisterView.vue +++ b/frontend/src/views/auth/RegisterView.vue @@ -96,7 +96,7 @@ -
+