fix(notify): remove percentage threshold from balance notification

Balance low notification only supports fixed USD amount threshold.
Percentage threshold is a quota concept, not applicable to balance.
Reverted threshold_type from admin settings, user profile, and all
backend/frontend layers. DB fields (balance_notify_threshold_type,
total_recharged) retained for potential future quota use.
This commit is contained in:
erio
2026-04-12 15:01:10 +08:00
parent 9e33d0c4c0
commit cef22c70ab
12 changed files with 37 additions and 140 deletions

View File

@@ -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

View File

@@ -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 数组)

View File

@@ -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
}

View File

@@ -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

View File

@@ -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