setting_panel_rate_limit.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. package service
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "log/slog"
  8. "strings"
  9. "time"
  10. )
  11. // PanelRateLimitSettings 面板 API 限流配置。
  12. // 认证后的面板接口按「用户 ID」维度限流(与客户端 IP 无关,反向代理/共享出口
  13. // 不会被误伤);无需认证的公开接口按安全客户端 IP 维度限流(内网/回环地址跳过)。
  14. type PanelRateLimitSettings struct {
  15. // Enabled 总开关
  16. Enabled bool `json:"enabled"`
  17. // UserRPM 每用户每分钟请求数上限(认证面板接口全量计数;0 = 不限制)
  18. UserRPM int `json:"user_rpm"`
  19. // HeavyRPM 每用户每分钟重查询上限(usage/dashboard 等聚合统计接口;0 = 不限制)
  20. HeavyRPM int `json:"heavy_rpm"`
  21. // ExemptAdmin 管理员账号是否豁免按用户限流
  22. ExemptAdmin bool `json:"exempt_admin"`
  23. // PublicIPRPM 无需认证的公开接口每 IP 每分钟上限(0 = 不限制)
  24. PublicIPRPM int `json:"public_ip_rpm"`
  25. }
  26. // 面板限流 RPM 的取值上限,防止配置异常大的值失去意义。
  27. const panelRateLimitRPMMax = 100000
  28. const (
  29. panelRateLimitCacheTTL = 60 * time.Second
  30. panelRateLimitErrorTTL = 5 * time.Second
  31. panelRateLimitDBTimeout = 5 * time.Second
  32. )
  33. // cachedPanelRateLimitSettings 进程内缓存条目(60s TTL)。
  34. type cachedPanelRateLimitSettings struct {
  35. settings PanelRateLimitSettings
  36. expiresAt int64 // unix nano
  37. }
  38. // DefaultPanelRateLimitSettings 返回默认面板限流配置。
  39. // 默认启用但阈值宽松:正常前端交互远达不到,仅拦截脚本高频刷接口打爆数据库的行为。
  40. func DefaultPanelRateLimitSettings() *PanelRateLimitSettings {
  41. return &PanelRateLimitSettings{
  42. Enabled: true,
  43. UserRPM: 240,
  44. HeavyRPM: 60,
  45. ExemptAdmin: true,
  46. PublicIPRPM: 300,
  47. }
  48. }
  49. // normalizePanelRateLimitSettings 修正非法取值(负数归零、超上限截断)。
  50. func normalizePanelRateLimitSettings(s *PanelRateLimitSettings) {
  51. if s == nil {
  52. return
  53. }
  54. if s.UserRPM < 0 {
  55. s.UserRPM = 0
  56. }
  57. if s.HeavyRPM < 0 {
  58. s.HeavyRPM = 0
  59. }
  60. if s.PublicIPRPM < 0 {
  61. s.PublicIPRPM = 0
  62. }
  63. if s.UserRPM > panelRateLimitRPMMax {
  64. s.UserRPM = panelRateLimitRPMMax
  65. }
  66. if s.HeavyRPM > panelRateLimitRPMMax {
  67. s.HeavyRPM = panelRateLimitRPMMax
  68. }
  69. if s.PublicIPRPM > panelRateLimitRPMMax {
  70. s.PublicIPRPM = panelRateLimitRPMMax
  71. }
  72. }
  73. // GetPanelRateLimitSettings 获取面板 API 限流配置(直读 DB,供管理端读写路径使用)。
  74. // 缺失/空/解析失败 → 返回默认配置。
  75. func (s *SettingService) GetPanelRateLimitSettings(ctx context.Context) (*PanelRateLimitSettings, error) {
  76. value, err := s.settingRepo.GetValue(ctx, SettingKeyPanelRateLimitSettings)
  77. if err != nil {
  78. if errors.Is(err, ErrSettingNotFound) {
  79. return DefaultPanelRateLimitSettings(), nil
  80. }
  81. return nil, fmt.Errorf("get panel rate limit settings: %w", err)
  82. }
  83. if strings.TrimSpace(value) == "" {
  84. return DefaultPanelRateLimitSettings(), nil
  85. }
  86. settings := &PanelRateLimitSettings{}
  87. if err := json.Unmarshal([]byte(value), settings); err != nil {
  88. slog.Warn("failed to unmarshal panel rate limit settings, falling back to defaults",
  89. "error", err, "key", SettingKeyPanelRateLimitSettings)
  90. return DefaultPanelRateLimitSettings(), nil
  91. }
  92. normalizePanelRateLimitSettings(settings)
  93. return settings, nil
  94. }
  95. // SetPanelRateLimitSettings 保存面板 API 限流配置,并立即刷新进程内缓存,
  96. // 使当前节点的下一个请求即生效(多节点部署最迟 60s 内生效)。
  97. func (s *SettingService) SetPanelRateLimitSettings(ctx context.Context, settings *PanelRateLimitSettings) error {
  98. if settings == nil {
  99. return fmt.Errorf("settings cannot be nil")
  100. }
  101. if settings.UserRPM < 0 || settings.HeavyRPM < 0 || settings.PublicIPRPM < 0 {
  102. return fmt.Errorf("rate limit values cannot be negative")
  103. }
  104. if settings.UserRPM > panelRateLimitRPMMax || settings.HeavyRPM > panelRateLimitRPMMax || settings.PublicIPRPM > panelRateLimitRPMMax {
  105. return fmt.Errorf("rate limit values must be at most %d", panelRateLimitRPMMax)
  106. }
  107. data, err := json.Marshal(settings)
  108. if err != nil {
  109. return fmt.Errorf("marshal panel rate limit settings: %w", err)
  110. }
  111. if err := s.settingRepo.Set(ctx, SettingKeyPanelRateLimitSettings, string(data)); err != nil {
  112. return err
  113. }
  114. s.storePanelRateLimitCache(*settings, panelRateLimitCacheTTL)
  115. return nil
  116. }
  117. // GetPanelRateLimitSettingsCached 返回面板限流配置(进程内缓存,60s TTL)。
  118. // 面板每个认证请求的热路径都会调用,绝不能每次访问 DB;
  119. // DB 错误时返回最近一次已知值(无缓存则返回默认值),并以短 TTL 快速重试。
  120. func (s *SettingService) GetPanelRateLimitSettingsCached(ctx context.Context) PanelRateLimitSettings {
  121. if s == nil || s.settingRepo == nil {
  122. return *DefaultPanelRateLimitSettings()
  123. }
  124. if cached, ok := s.panelRateLimitCache.Load().(*cachedPanelRateLimitSettings); ok && cached != nil {
  125. if time.Now().UnixNano() < cached.expiresAt {
  126. return cached.settings
  127. }
  128. }
  129. result, _, _ := s.panelRateLimitSF.Do("panel_rate_limit_settings", func() (any, error) {
  130. // 二次检查,避免排队的 goroutine 重复查询
  131. if cached, ok := s.panelRateLimitCache.Load().(*cachedPanelRateLimitSettings); ok && cached != nil {
  132. if time.Now().UnixNano() < cached.expiresAt {
  133. return cached.settings, nil
  134. }
  135. }
  136. if ctx == nil {
  137. ctx = context.Background()
  138. }
  139. // 独立 context:断开请求取消链,避免客户端断连污染缓存
  140. dbCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), panelRateLimitDBTimeout)
  141. defer cancel()
  142. settings, err := s.GetPanelRateLimitSettings(dbCtx)
  143. if err != nil {
  144. slog.Warn("failed to get panel rate limit settings", "error", err)
  145. // 保留最近一次已知值,短 TTL 快速重试
  146. fallback := *DefaultPanelRateLimitSettings()
  147. if prior, ok := s.panelRateLimitCache.Load().(*cachedPanelRateLimitSettings); ok && prior != nil {
  148. fallback = prior.settings
  149. }
  150. s.storePanelRateLimitCache(fallback, panelRateLimitErrorTTL)
  151. return fallback, nil
  152. }
  153. s.storePanelRateLimitCache(*settings, panelRateLimitCacheTTL)
  154. return *settings, nil
  155. })
  156. if settings, ok := result.(PanelRateLimitSettings); ok {
  157. return settings
  158. }
  159. return *DefaultPanelRateLimitSettings()
  160. }
  161. func (s *SettingService) storePanelRateLimitCache(settings PanelRateLimitSettings, ttl time.Duration) {
  162. s.panelRateLimitCache.Store(&cachedPanelRateLimitSettings{
  163. settings: settings,
  164. expiresAt: time.Now().Add(ttl).UnixNano(),
  165. })
  166. }