account_traffic_policy.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. package service
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/sha256"
  6. "encoding/json"
  7. "fmt"
  8. "net/http"
  9. "time"
  10. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  11. )
  12. const AccountTrafficPolicyKey = "account_traffic_control"
  13. type AccountTrafficPolicy struct {
  14. StrictRPMEnabled bool `json:"strict_rpm_enabled"`
  15. RPM int `json:"rpm"`
  16. Burst int `json:"burst"`
  17. AdaptiveEnabled bool `json:"adaptive_enabled"`
  18. AdaptiveMode string `json:"adaptive_mode"`
  19. MinConcurrency int `json:"min_concurrency"`
  20. FailureThreshold int `json:"failure_threshold"`
  21. FailureWindowSeconds int `json:"failure_window_seconds"`
  22. RecoverySeconds int `json:"recovery_seconds"`
  23. }
  24. func DefaultAccountTrafficPolicy() AccountTrafficPolicy {
  25. return AccountTrafficPolicy{RPM: 60, Burst: 5, AdaptiveMode: "observe", MinConcurrency: 1, FailureThreshold: 3, FailureWindowSeconds: 60, RecoverySeconds: 60}
  26. }
  27. func ParseAccountTrafficPolicy(extra map[string]any) (AccountTrafficPolicy, error) {
  28. p := DefaultAccountTrafficPolicy()
  29. value, exists := extra[AccountTrafficPolicyKey]
  30. if !exists || value == nil {
  31. return p, nil
  32. }
  33. encoded, err := json.Marshal(value)
  34. if err != nil {
  35. return p, err
  36. }
  37. d := json.NewDecoder(bytes.NewReader(encoded))
  38. d.DisallowUnknownFields()
  39. if err = d.Decode(&p); err != nil {
  40. return p, infraerrors.BadRequest("ACCOUNT_TRAFFIC_INVALID", "流量控制配置格式无效")
  41. }
  42. if p.RPM < 1 || p.RPM > 60000 || p.Burst < 1 || p.Burst > p.RPM || p.MinConcurrency < 1 || p.MinConcurrency > 10000 || p.FailureThreshold < 1 || p.FailureThreshold > 100 || p.FailureWindowSeconds < 10 || p.FailureWindowSeconds > 3600 || p.RecoverySeconds < 10 || p.RecoverySeconds > 3600 || (p.AdaptiveMode != "observe" && p.AdaptiveMode != "automatic") {
  43. return p, infraerrors.BadRequest("ACCOUNT_TRAFFIC_INVALID", "请检查 RPM、突发额度、并发下限、失败阈值与恢复周期;突发额度不能大于 RPM")
  44. }
  45. return p, nil
  46. }
  47. func (p AccountTrafficPolicy) Enabled() bool { return p.StrictRPMEnabled || p.AdaptiveEnabled }
  48. func (p AccountTrafficPolicy) Enforces() bool {
  49. return p.StrictRPMEnabled || (p.AdaptiveEnabled && p.AdaptiveMode == "automatic")
  50. }
  51. type AccountTrafficPlan struct {
  52. AccountID int64
  53. Revision int64
  54. Signature string
  55. HardLimit int
  56. Policy AccountTrafficPolicy
  57. }
  58. func AccountTrafficPlanFor(a *Account) (AccountTrafficPlan, error) {
  59. if a == nil {
  60. return AccountTrafficPlan{}, nil
  61. }
  62. p, err := ParseAccountTrafficPolicy(a.Extra)
  63. if err != nil {
  64. return AccountTrafficPlan{}, err
  65. }
  66. limit := a.Mode1EffectiveConcurrency()
  67. if p.AdaptiveEnabled && limit <= 0 {
  68. return AccountTrafficPlan{}, infraerrors.BadRequest("ACCOUNT_TRAFFIC_INVALID", "启用自适应并发前,请先设置正数的账号并发上限")
  69. }
  70. encoded, _ := json.Marshal(struct {
  71. Policy AccountTrafficPolicy
  72. Limit int
  73. }{p, limit})
  74. revision := int64(0)
  75. if !a.UpdatedAt.IsZero() {
  76. revision = a.UpdatedAt.UnixMicro()
  77. }
  78. return AccountTrafficPlan{AccountID: a.ID, Revision: revision, Signature: fmt.Sprintf("%x", sha256.Sum256(encoded)), HardLimit: limit, Policy: p}, nil
  79. }
  80. type accountTrafficPlanKey struct{}
  81. type accountTrafficCoveredKey struct{}
  82. type accountTrafficConfigErrorKey struct{}
  83. // Attach only safe policy metadata, never account credentials. GET discovery
  84. // and status polling are outside the model-request budget.
  85. func WithAccountTrafficRequest(req *http.Request, a *Account) *http.Request {
  86. if req == nil || req.Method == http.MethodGet || req.Method == http.MethodHead {
  87. return req
  88. }
  89. if a == nil || a.Extra[AccountTrafficPolicyKey] == nil {
  90. return req
  91. }
  92. plan, err := AccountTrafficPlanFor(a)
  93. if err != nil {
  94. return req.WithContext(context.WithValue(req.Context(), accountTrafficConfigErrorKey{}, err))
  95. }
  96. if !plan.Policy.Enabled() {
  97. return req
  98. }
  99. return req.WithContext(context.WithValue(req.Context(), accountTrafficPlanKey{}, plan))
  100. }
  101. type AccountTrafficState struct {
  102. EffectiveConcurrency int `json:"effective_concurrency"`
  103. RecommendedConcurrency int `json:"recommended_concurrency"`
  104. InFlight int `json:"in_flight"`
  105. RequestsLastMinute int `json:"requests_last_minute"`
  106. Accepted int64 `json:"accepted"`
  107. RejectedRPM int64 `json:"rejected_rpm"`
  108. RejectedConcurrency int64 `json:"rejected_concurrency"`
  109. Upstream429 int64 `json:"upstream_429"`
  110. Upstream5xx int64 `json:"upstream_5xx"`
  111. Completed int64 `json:"completed"`
  112. AverageDurationMS float64 `json:"average_duration_ms"`
  113. LastAdjustmentAt *time.Time `json:"last_adjustment_at,omitempty"`
  114. }
  115. type AccountTrafficAdmission struct {
  116. Allowed bool
  117. SkipObservation bool
  118. Reason string
  119. RetryAfter time.Duration
  120. State AccountTrafficState
  121. }
  122. type AccountTrafficCache interface {
  123. Acquire(context.Context, AccountTrafficPlan, string) (AccountTrafficAdmission, error)
  124. Refresh(context.Context, int64, string) (bool, error)
  125. Finish(context.Context, AccountTrafficPlan, string, int, int64) error
  126. Snapshot(context.Context, AccountTrafficPlan) (AccountTrafficState, error)
  127. Sync(context.Context, AccountTrafficPlan) error
  128. }
  129. type AccountTrafficProvider interface{ AccountTrafficController() *AccountTrafficService }
  130. type AccountTrafficLimitError struct {
  131. Reason string
  132. RetryAfter time.Duration
  133. Status int
  134. }
  135. func (e *AccountTrafficLimitError) Error() string { return "ACCOUNT_TRAFFIC_LIMIT: " + e.Reason }
  136. func (e *AccountTrafficLimitError) FailoverError() *UpstreamFailoverError {
  137. status := e.Status
  138. if status == 0 {
  139. status = 429
  140. }
  141. seconds := int64((e.RetryAfter + time.Second - 1) / time.Second)
  142. if seconds < 1 {
  143. seconds = 1
  144. }
  145. errorType := AccountTrafficErrorType(status)
  146. body, _ := json.Marshal(map[string]any{"error": map[string]string{"type": errorType, "code": "account_traffic_limit", "message": e.Reason}})
  147. return &UpstreamFailoverError{StatusCode: status, ResponseBody: body, ResponseHeaders: http.Header{"Retry-After": []string{fmt.Sprint(seconds)}}, RequestScopedTransient: true, Scope: GatewayFailureScopeRequest, Reason: "account_traffic_limit", NextAccountAction: NextAccountStop, ClientStatusCode: status, ClientMessage: e.Reason}
  148. }
  149. func (e *AccountTrafficLimitError) As(target any) bool {
  150. if p, ok := target.(**UpstreamFailoverError); ok {
  151. *p = e.FailoverError()
  152. return true
  153. }
  154. return false
  155. }
  156. func AccountTrafficErrorType(status int) string {
  157. if status == http.StatusBadRequest {
  158. return "invalid_request_error"
  159. }
  160. if status >= 500 {
  161. return "api_error"
  162. }
  163. return "rate_limit_error"
  164. }