From a16db8e367eb55c2c4b6ca44d885932337ce6be7 Mon Sep 17 00:00:00 2001 From: win Date: Sun, 22 Mar 2026 12:04:31 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=8F=8C=E6=A8=A1=E5=9E=8B=E5=AE=A1?= =?UTF-8?q?=E6=9F=A5=20Critical=20=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Sora session_key 按 accountID 隔离(消除跨账号指纹关联) 2. 有 per-account 代理的 Sora 账号跳过 sidecar(保持代理 IP) 3. 请求体用 base64 编码传输(防止二进制数据损坏) 4. Node.js 代理 Body 用 GetBody 安全复制(修复重试时 Body 枯竭) --- backend/internal/repository/http_upstream.go | 7 +++++- backend/internal/service/sora_sdk_client.go | 26 ++++++++++++++------ tools/sora-curl-cffi-sidecar/app.py | 16 ++++++++++-- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/backend/internal/repository/http_upstream.go b/backend/internal/repository/http_upstream.go index 8d73dc9c..6e97e192 100644 --- a/backend/internal/repository/http_upstream.go +++ b/backend/internal/repository/http_upstream.go @@ -286,7 +286,12 @@ func (s *httpUpstreamService) doViaNodeTLSProxy(req *http.Request, proxyURL stri // 克隆请求,避免修改原始 req(重试时需要原始 URL) proxyReq := req.Clone(req.Context()) - proxyReq.Body = req.Body // Clone 不复制 Body + // 安全复制 Body:优先用 GetBody 工厂方法 + if req.GetBody != nil { + proxyReq.Body, _ = req.GetBody() + } else { + proxyReq.Body = req.Body + } // 保存原始目标主机,通过自定义头传给 Node.js 代理 originalHost := req.URL.Host diff --git a/backend/internal/service/sora_sdk_client.go b/backend/internal/service/sora_sdk_client.go index f59c257d..0e5a2a1c 100644 --- a/backend/internal/service/sora_sdk_client.go +++ b/backend/internal/service/sora_sdk_client.go @@ -3,6 +3,7 @@ package service import ( "bytes" "context" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -1026,11 +1027,19 @@ func (c *SoraSDKClient) debugLogf(format string, args ...any) { } // doSoraHTTP 执行 Sora HTTP 请求,优先走 curl_cffi sidecar(Chrome TLS 指纹绕过 Cloudflare) -// 如果 sidecar 未启用或不可用,回退到 httpUpstream.Do() / http.DefaultClient +// 如果 sidecar 未启用、账号有独立代理、或 sidecar 不可用,回退到 httpUpstream.Do() func (c *SoraSDKClient) doSoraHTTP(req *http.Request, proxyURL string, accountID int64, accountConcurrency int) (*http.Response, error) { + // 如果账号配置了独立代理,跳过 sidecar(sidecar 不支持 per-account 代理) + if proxyURL != "" { + if c.httpUpstream != nil { + return c.httpUpstream.Do(req, proxyURL, accountID, accountConcurrency) + } + return http.DefaultClient.Do(req) + } + // 检查 sidecar 是否启用 if c.cfg != nil && c.cfg.Sora.Client.CurlCFFISidecar.Enabled && c.cfg.Sora.Client.CurlCFFISidecar.BaseURL != "" { - resp, err := c.doViaSidecar(req) + resp, err := c.doViaSidecar(req, accountID) if err == nil { return resp, nil } @@ -1046,17 +1055,17 @@ func (c *SoraSDKClient) doSoraHTTP(req *http.Request, proxyURL string, accountID } // doViaSidecar 通过 curl_cffi sidecar 发送请求(Chrome TLS 指纹) -func (c *SoraSDKClient) doViaSidecar(originalReq *http.Request) (*http.Response, error) { +func (c *SoraSDKClient) doViaSidecar(originalReq *http.Request, accountID int64) (*http.Response, error) { sidecarURL := strings.TrimRight(c.cfg.Sora.Client.CurlCFFISidecar.BaseURL, "/") + "/proxy" - // 读取原始请求体 - var bodyStr string + // 读取原始请求体,用 base64 编码(安全传输二进制数据) + var bodyB64 string if originalReq.Body != nil { bodyBytes, err := io.ReadAll(originalReq.Body) if err != nil { return nil, fmt.Errorf("read request body: %w", err) } - bodyStr = string(bodyBytes) + bodyB64 = base64.StdEncoding.EncodeToString(bodyBytes) // 恢复 body 以备回退 originalReq.Body = io.NopCloser(bytes.NewReader(bodyBytes)) } @@ -1073,8 +1082,9 @@ func (c *SoraSDKClient) doViaSidecar(originalReq *http.Request) (*http.Response, "url": originalReq.URL.String(), "method": originalReq.Method, "headers": headers, - "body": bodyStr, - "session_key": fmt.Sprintf("account_%d", 0), // 简单的 session key + "body": bodyB64, + "is_base64": true, + "session_key": fmt.Sprintf("account_%d", accountID), // 每账号独立会话 } payloadBytes, err := json.Marshal(payload) diff --git a/tools/sora-curl-cffi-sidecar/app.py b/tools/sora-curl-cffi-sidecar/app.py index c79f780a..2f7bc17f 100644 --- a/tools/sora-curl-cffi-sidecar/app.py +++ b/tools/sora-curl-cffi-sidecar/app.py @@ -4,7 +4,7 @@ sub2api 通过 HTTP 调用此服务转发 Sora 请求 """ from flask import Flask, request, Response from curl_cffi import requests as cffi_requests -import json, os, time, threading +import json, os, time, threading, base64 app = Flask(__name__) @@ -54,18 +54,30 @@ def proxy(): method = data.get("method", "GET").upper() headers = data.get("headers", {}) body = data.get("body") + is_base64 = data.get("is_base64", False) session_key = data.get("session_key", "default") if not url: return json.dumps({"error": "url required"}), 400 + # 解码 base64 编码的请求体(安全传输二进制数据) + req_data = None + if body: + if is_base64: + try: + req_data = base64.b64decode(body) + except Exception: + req_data = body.encode("utf-8") if isinstance(body, str) else body + else: + req_data = body.encode("utf-8") if isinstance(body, str) else body + try: sess = get_session(session_key) resp = sess.request( method=method, url=url, headers=headers, - data=body.encode("utf-8") if isinstance(body, str) else body, + data=req_data, timeout=TIMEOUT, allow_redirects=True, )