diff --git a/backend/internal/handler/admin/setting_handler.go b/backend/internal/handler/admin/setting_handler.go index 3d587a21..49e7aeed 100644 --- a/backend/internal/handler/admin/setting_handler.go +++ b/backend/internal/handler/admin/setting_handler.go @@ -177,7 +177,6 @@ func (h *SettingHandler) GetSettings(c *gin.Context) { EnableCCHSigning: settings.EnableCCHSigning, WebSearchEmulationEnabled: settings.WebSearchEmulationEnabled, BalanceLowNotifyEnabled: settings.BalanceLowNotifyEnabled, - BalanceLowNotifyThresholdType: settings.BalanceLowNotifyThresholdType, BalanceLowNotifyThreshold: settings.BalanceLowNotifyThreshold, AccountQuotaNotifyEmails: settings.AccountQuotaNotifyEmails, PaymentEnabled: paymentCfg.Enabled, @@ -310,10 +309,9 @@ type UpdateSettingsRequest struct { EnableCCHSigning *bool `json:"enable_cch_signing"` // Balance low notification - BalanceLowNotifyEnabled *bool `json:"balance_low_notify_enabled"` - BalanceLowNotifyThresholdType *string `json:"balance_low_notify_threshold_type"` - BalanceLowNotifyThreshold *float64 `json:"balance_low_notify_threshold"` - AccountQuotaNotifyEmails *[]string `json:"account_quota_notify_emails"` + BalanceLowNotifyEnabled *bool `json:"balance_low_notify_enabled"` + BalanceLowNotifyThreshold *float64 `json:"balance_low_notify_threshold"` + AccountQuotaNotifyEmails *[]string `json:"account_quota_notify_emails"` // Payment configuration (integrated into settings, full replace) PaymentEnabled *bool `json:"payment_enabled"` @@ -898,12 +896,6 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { } return previousSettings.BalanceLowNotifyEnabled }(), - BalanceLowNotifyThresholdType: func() string { - if req.BalanceLowNotifyThresholdType != nil { - return *req.BalanceLowNotifyThresholdType - } - return previousSettings.BalanceLowNotifyThresholdType - }(), BalanceLowNotifyThreshold: func() float64 { if req.BalanceLowNotifyThreshold != nil { return *req.BalanceLowNotifyThreshold @@ -1063,7 +1055,6 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { EnableMetadataPassthrough: updatedSettings.EnableMetadataPassthrough, EnableCCHSigning: updatedSettings.EnableCCHSigning, BalanceLowNotifyEnabled: updatedSettings.BalanceLowNotifyEnabled, - BalanceLowNotifyThresholdType: updatedSettings.BalanceLowNotifyThresholdType, BalanceLowNotifyThreshold: updatedSettings.BalanceLowNotifyThreshold, AccountQuotaNotifyEmails: updatedSettings.AccountQuotaNotifyEmails, PaymentEnabled: updatedPaymentCfg.Enabled, diff --git a/backend/internal/handler/dto/settings.go b/backend/internal/handler/dto/settings.go index 8da7c6f2..e29f72da 100644 --- a/backend/internal/handler/dto/settings.go +++ b/backend/internal/handler/dto/settings.go @@ -150,10 +150,9 @@ type SystemSettings struct { PaymentCancelRateLimitMode string `json:"payment_cancel_rate_limit_window_mode"` // Balance low notification - BalanceLowNotifyEnabled bool `json:"balance_low_notify_enabled"` - BalanceLowNotifyThresholdType string `json:"balance_low_notify_threshold_type"` - BalanceLowNotifyThreshold float64 `json:"balance_low_notify_threshold"` - AccountQuotaNotifyEmails []string `json:"account_quota_notify_emails"` + BalanceLowNotifyEnabled bool `json:"balance_low_notify_enabled"` + BalanceLowNotifyThreshold float64 `json:"balance_low_notify_threshold"` + AccountQuotaNotifyEmails []string `json:"account_quota_notify_emails"` } type DefaultSubscriptionSetting struct { diff --git a/backend/internal/handler/user_handler.go b/backend/internal/handler/user_handler.go index 48528d55..4fb72ce7 100644 --- a/backend/internal/handler/user_handler.go +++ b/backend/internal/handler/user_handler.go @@ -33,10 +33,9 @@ type ChangePasswordRequest struct { // UpdateProfileRequest represents the update profile request payload type UpdateProfileRequest struct { - Username *string `json:"username"` - BalanceNotifyEnabled *bool `json:"balance_notify_enabled"` - BalanceNotifyThresholdType *string `json:"balance_notify_threshold_type"` - BalanceNotifyThreshold *float64 `json:"balance_notify_threshold"` + Username *string `json:"username"` + BalanceNotifyEnabled *bool `json:"balance_notify_enabled"` + BalanceNotifyThreshold *float64 `json:"balance_notify_threshold"` } // GetProfile handles getting user profile @@ -101,10 +100,9 @@ func (h *UserHandler) UpdateProfile(c *gin.Context) { } svcReq := service.UpdateProfileRequest{ - Username: req.Username, - BalanceNotifyEnabled: req.BalanceNotifyEnabled, - BalanceNotifyThresholdType: req.BalanceNotifyThresholdType, - BalanceNotifyThreshold: req.BalanceNotifyThreshold, + Username: req.Username, + BalanceNotifyEnabled: req.BalanceNotifyEnabled, + BalanceNotifyThreshold: req.BalanceNotifyThreshold, } updatedUser, err := h.userService.UpdateProfile(c.Request.Context(), subject.UserID, svcReq) if err != nil { diff --git a/backend/internal/service/balance_notify_service.go b/backend/internal/service/balance_notify_service.go index e1f6bd8b..0d7e4c09 100644 --- a/backend/internal/service/balance_notify_service.go +++ b/backend/internal/service/balance_notify_service.go @@ -51,12 +51,16 @@ func (s *BalanceNotifyService) CheckBalanceAfterDeduction(ctx context.Context, u return } - globalEnabled, globalThresholdType, globalThresholdValue := s.getBalanceNotifyConfig(ctx) + globalEnabled, globalThreshold := s.getBalanceNotifyConfig(ctx) if !globalEnabled { return } - threshold := s.resolveEffectiveThreshold(user, globalThresholdType, globalThresholdValue) + // User custom threshold overrides system default + threshold := globalThreshold + if user.BalanceNotifyThreshold != nil { + threshold = *user.BalanceNotifyThreshold + } if threshold <= 0 { return } @@ -76,30 +80,6 @@ func (s *BalanceNotifyService) CheckBalanceAfterDeduction(ctx context.Context, u } } -// resolveEffectiveThreshold computes the actual USD threshold based on type and user settings. -// When user sets a custom threshold, their type is used independently (defaults to "fixed" if unset). -func (s *BalanceNotifyService) resolveEffectiveThreshold(user *User, globalType string, globalValue float64) float64 { - if user.BalanceNotifyThreshold != nil { - thresholdType := user.BalanceNotifyThresholdType - if thresholdType == "" { - thresholdType = ThresholdTypeFixed // user custom value defaults to fixed, not inherited - } - return computeThreshold(thresholdType, *user.BalanceNotifyThreshold, user.TotalRecharged) - } - return computeThreshold(globalType, globalValue, user.TotalRecharged) -} - -// computeThreshold converts a threshold value to USD based on type. -func computeThreshold(thresholdType string, value, totalRecharged float64) float64 { - if thresholdType == ThresholdTypePercentage { - if totalRecharged <= 0 { - return 0 // no recharge history → skip percentage check - } - return totalRecharged * value / 100 - } - return value // fixed USD amount -} - // quotaDim describes one quota dimension for notification checking. type quotaDim struct { name string @@ -154,21 +134,13 @@ func (s *BalanceNotifyService) asyncSendQuotaAlert(adminEmails []string, account } // getBalanceNotifyConfig reads global balance notification settings. -func (s *BalanceNotifyService) getBalanceNotifyConfig(ctx context.Context) (enabled bool, thresholdType string, threshold float64) { - keys := []string{ - SettingKeyBalanceLowNotifyEnabled, - SettingKeyBalanceLowNotifyThresholdType, - SettingKeyBalanceLowNotifyThreshold, - } +func (s *BalanceNotifyService) getBalanceNotifyConfig(ctx context.Context) (enabled bool, threshold float64) { + keys := []string{SettingKeyBalanceLowNotifyEnabled, SettingKeyBalanceLowNotifyThreshold} settings, err := s.settingRepo.GetMultiple(ctx, keys) if err != nil { - return false, ThresholdTypeFixed, 0 + return false, 0 } enabled = settings[SettingKeyBalanceLowNotifyEnabled] == "true" - thresholdType = settings[SettingKeyBalanceLowNotifyThresholdType] - if thresholdType == "" { - thresholdType = ThresholdTypeFixed - } if v := settings[SettingKeyBalanceLowNotifyThreshold]; v != "" { if f, err := strconv.ParseFloat(v, 64); err == nil { threshold = f diff --git a/backend/internal/service/domain_constants.go b/backend/internal/service/domain_constants.go index 3de0e343..2704e0d0 100644 --- a/backend/internal/service/domain_constants.go +++ b/backend/internal/service/domain_constants.go @@ -251,13 +251,8 @@ const ( SettingKeyEnableCCHSigning = "enable_cch_signing" // Balance Low Notification - SettingKeyBalanceLowNotifyEnabled = "balance_low_notify_enabled" // 全局开关 - SettingKeyBalanceLowNotifyThresholdType = "balance_low_notify_threshold_type" // "fixed" | "percentage" - SettingKeyBalanceLowNotifyThreshold = "balance_low_notify_threshold" // 默认阈值(USD 或百分比) - - // Threshold type constants - ThresholdTypeFixed = "fixed" - ThresholdTypePercentage = "percentage" + SettingKeyBalanceLowNotifyEnabled = "balance_low_notify_enabled" // 全局开关 + SettingKeyBalanceLowNotifyThreshold = "balance_low_notify_threshold" // 默认阈值(USD) // Account Quota Notification SettingKeyAccountQuotaNotifyEmails = "account_quota_notify_emails" // 管理员通知邮箱列表(JSON 数组) diff --git a/backend/internal/service/setting_service.go b/backend/internal/service/setting_service.go index 9b307426..f0cf750a 100644 --- a/backend/internal/service/setting_service.go +++ b/backend/internal/service/setting_service.go @@ -608,11 +608,6 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet // Balance low notification updates[SettingKeyBalanceLowNotifyEnabled] = strconv.FormatBool(settings.BalanceLowNotifyEnabled) - thresholdType := settings.BalanceLowNotifyThresholdType - if thresholdType != ThresholdTypeFixed && thresholdType != ThresholdTypePercentage { - thresholdType = ThresholdTypeFixed - } - updates[SettingKeyBalanceLowNotifyThresholdType] = thresholdType updates[SettingKeyBalanceLowNotifyThreshold] = strconv.FormatFloat(settings.BalanceLowNotifyThreshold, 'f', 8, 64) accountQuotaNotifyEmailsJSON, err := json.Marshal(settings.AccountQuotaNotifyEmails) if err != nil { @@ -1252,10 +1247,6 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin // Balance low notification result.BalanceLowNotifyEnabled = settings[SettingKeyBalanceLowNotifyEnabled] == "true" - result.BalanceLowNotifyThresholdType = settings[SettingKeyBalanceLowNotifyThresholdType] - if result.BalanceLowNotifyThresholdType == "" { - result.BalanceLowNotifyThresholdType = ThresholdTypeFixed - } if v, err := strconv.ParseFloat(settings[SettingKeyBalanceLowNotifyThreshold], 64); err == nil && v >= 0 { result.BalanceLowNotifyThreshold = v } diff --git a/backend/internal/service/settings_view.go b/backend/internal/service/settings_view.go index b28d2247..debc2b19 100644 --- a/backend/internal/service/settings_view.go +++ b/backend/internal/service/settings_view.go @@ -108,9 +108,8 @@ type SystemSettings struct { EnableCCHSigning bool // 是否对 billing header cch 进行签名(默认 false) // Balance low notification - BalanceLowNotifyEnabled bool - BalanceLowNotifyThresholdType string // "fixed" (default) | "percentage" - BalanceLowNotifyThreshold float64 + BalanceLowNotifyEnabled bool + BalanceLowNotifyThreshold float64 // Account quota notification AccountQuotaNotifyEmails []string diff --git a/backend/internal/service/user_service.go b/backend/internal/service/user_service.go index 26021a9b..e6b9a210 100644 --- a/backend/internal/service/user_service.go +++ b/backend/internal/service/user_service.go @@ -62,12 +62,11 @@ type UserRepository interface { // UpdateProfileRequest 更新用户资料请求 type UpdateProfileRequest struct { - Email *string `json:"email"` - Username *string `json:"username"` - Concurrency *int `json:"concurrency"` - BalanceNotifyEnabled *bool `json:"balance_notify_enabled"` - BalanceNotifyThresholdType *string `json:"balance_notify_threshold_type"` - BalanceNotifyThreshold *float64 `json:"balance_notify_threshold"` + Email *string `json:"email"` + Username *string `json:"username"` + Concurrency *int `json:"concurrency"` + BalanceNotifyEnabled *bool `json:"balance_notify_enabled"` + BalanceNotifyThreshold *float64 `json:"balance_notify_threshold"` } // ChangePasswordRequest 修改密码请求 @@ -144,11 +143,6 @@ func (s *UserService) UpdateProfile(ctx context.Context, userID int64, req Updat if req.BalanceNotifyEnabled != nil { user.BalanceNotifyEnabled = *req.BalanceNotifyEnabled } - if req.BalanceNotifyThresholdType != nil { - if *req.BalanceNotifyThresholdType == ThresholdTypeFixed || *req.BalanceNotifyThresholdType == ThresholdTypePercentage { - user.BalanceNotifyThresholdType = *req.BalanceNotifyThresholdType - } - } if req.BalanceNotifyThreshold != nil { if *req.BalanceNotifyThreshold <= 0 { user.BalanceNotifyThreshold = nil // clear to system default diff --git a/frontend/src/api/admin/settings.ts b/frontend/src/api/admin/settings.ts index ec290be5..31284289 100644 --- a/frontend/src/api/admin/settings.ts +++ b/frontend/src/api/admin/settings.ts @@ -137,7 +137,6 @@ export interface SystemSettings { // Balance & quota notification balance_low_notify_enabled: boolean - balance_low_notify_threshold_type: 'fixed' | 'percentage' balance_low_notify_threshold: number account_quota_notify_emails: string[] } @@ -241,7 +240,6 @@ export interface UpdateSettingsRequest { payment_cancel_rate_limit_window_mode?: string // Balance & quota notification balance_low_notify_enabled?: boolean - balance_low_notify_threshold_type?: 'fixed' | 'percentage' balance_low_notify_threshold?: number account_quota_notify_emails?: string[] } diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 880a81ee..8e10bf2a 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -4633,12 +4633,8 @@ export default { title: 'Balance Low Notification', description: 'Send email notification when user balance falls below threshold', enabled: 'Enable Balance Low Notification', - thresholdType: 'Threshold Type', - typeFixed: 'Fixed Amount', - typePercentage: 'Percentage of Recharged', threshold: 'Default Threshold', thresholdHint: 'Used when user has not set a custom value', - percentageHint: 'Notify when balance falls below this percentage of total recharged amount', thresholdPlaceholder: 'Enter amount', }, quotaNotify: { diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 41d94e06..1b82f419 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -4797,12 +4797,8 @@ export default { title: '余额不足提醒', description: '当用户余额低于阈值时发送邮件提醒', enabled: '启用余额不足提醒', - thresholdType: '阈值类型', - typeFixed: '固定金额', - typePercentage: '充值百分比', - threshold: '提醒阈值', + threshold: '默认提醒阈值', thresholdHint: '用户未自定义时使用此值', - percentageHint: '当余额低于累计充值额的此百分比时提醒', thresholdPlaceholder: '输入金额', }, quotaNotify: { diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue index 50b532fb..faab49fe 100644 --- a/frontend/src/views/admin/SettingsView.vue +++ b/frontend/src/views/admin/SettingsView.vue @@ -2675,43 +2675,13 @@ -
- -
- -
- - -
-
- -
- -
- - {{ form.balance_low_notify_threshold_type === 'percentage' ? '%' : '$' }} - - -
-

- {{ form.balance_low_notify_threshold_type === 'percentage' - ? t('admin.settings.balanceNotify.percentageHint') - : t('admin.settings.balanceNotify.thresholdHint') }} -

+
+ +
+ $ +
+

{{ t('admin.settings.balanceNotify.thresholdHint') }}

@@ -3026,7 +2996,6 @@ const form = reactive({ enable_cch_signing: false, // Balance & quota notification balance_low_notify_enabled: false, - balance_low_notify_threshold_type: 'fixed' as 'fixed' | 'percentage', balance_low_notify_threshold: 0, account_quota_notify_emails: [] as string[] }) @@ -3591,7 +3560,6 @@ async function saveSettings() { payment_cancel_rate_limit_window_mode: form.payment_cancel_rate_limit_window_mode, // Balance & quota notification balance_low_notify_enabled: form.balance_low_notify_enabled, - balance_low_notify_threshold_type: form.balance_low_notify_threshold_type, balance_low_notify_threshold: Number(form.balance_low_notify_threshold) || 0, account_quota_notify_emails: (form.account_quota_notify_emails || []).filter((e: string) => e.trim() !== ''), }