From 1110dbad837a86dd594399959e625e20a0a9b885 Mon Sep 17 00:00:00 2001 From: sleet0922 Date: Sat, 11 Apr 2026 22:30:32 +0800 Subject: [PATCH] fix #66 --- proxy/handler.go | 24 +++++++++++++++++++ proxy/handler_scope_test.go | 46 +++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 proxy/handler_scope_test.go diff --git a/proxy/handler.go b/proxy/handler.go index ca4d6eb..1e82031 100644 --- a/proxy/handler.go +++ b/proxy/handler.go @@ -1425,6 +1425,24 @@ func parseRetryAfter(body []byte) time.Duration { return 2 * time.Minute } +func isMissingScopeUnauthorized(body []byte) bool { + if len(body) == 0 { + return false + } + + code := strings.ToLower(strings.TrimSpace(gjson.GetBytes(body, "error.code").String())) + if code != "missing_scope" { + return false + } + + msg := strings.ToLower(gjson.GetBytes(body, "error.message").String()) + if strings.Contains(msg, "api.responses.write") { + return true + } + + return strings.Contains(msg, "scope") +} + // applyCooldown 根据上游状态码设置智能冷却 func (h *Handler) applyCooldown(account *auth.Account, statusCode int, body []byte, resp *http.Response) { switch statusCode { @@ -1436,6 +1454,12 @@ func (h *Handler) applyCooldown(account *auth.Account, statusCode int, body []by // 原子标志瞬间置位,阻止其他并发请求再选到该账号 atomic.StoreInt32(&account.Disabled, 1) + if isMissingScopeUnauthorized(body) { + log.Printf("账号 %d 收到 missing_scope 401,保留在号池", account.ID()) + atomic.StoreInt32(&account.Disabled, 0) + return + } + if h.store.GetAutoCleanUnauthorized() { // 开启自动清理时,401 立即从号池删除 log.Printf("账号 %d 收到 401,立即清理", account.ID()) diff --git a/proxy/handler_scope_test.go b/proxy/handler_scope_test.go new file mode 100644 index 0000000..dfc3db6 --- /dev/null +++ b/proxy/handler_scope_test.go @@ -0,0 +1,46 @@ +package proxy + +import "testing" + +func TestIsMissingScopeUnauthorized(t *testing.T) { + tests := []struct { + name string + body string + want bool + }{ + { + name: "missing scope for responses write", + body: `{"error":{"message":"Missing required scope: api.responses.write","type":"invalid_request_error","code":"missing_scope"}}`, + want: true, + }, + { + name: "missing scope generic message", + body: `{"error":{"message":"missing scope for this operation","type":"invalid_request_error","code":"missing_scope"}}`, + want: true, + }, + { + name: "unauthorized invalid api key", + body: `{"error":{"message":"Invalid API key","type":"invalid_request_error","code":"invalid_api_key"}}`, + want: false, + }, + { + name: "empty body", + body: ``, + want: false, + }, + { + name: "invalid json", + body: `not-json`, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isMissingScopeUnauthorized([]byte(tt.body)) + if got != tt.want { + t.Fatalf("isMissingScopeUnauthorized() = %v, want %v", got, tt.want) + } + }) + } +}