From fba8cc82aef9f739deee1c8b2781f6de4dbd99ae Mon Sep 17 00:00:00 2001 From: win Date: Sun, 22 Mar 2026 03:38:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Sora=20=E8=AF=B7=E6=B1=82=E4=BC=98?= =?UTF-8?q?=E5=85=88=E8=B5=B0=20curl=5Fcffi=20sidecar=EF=BC=88Chrome=20?= =?UTF-8?q?=E6=8C=87=E7=BA=B9=E7=BB=95=E8=BF=87=20Cloudflare=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/service/sora_sdk_client.go | 71 ++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/backend/internal/service/sora_sdk_client.go b/backend/internal/service/sora_sdk_client.go index f9221c5b..a8812ff5 100644 --- a/backend/internal/service/sora_sdk_client.go +++ b/backend/internal/service/sora_sdk_client.go @@ -478,7 +478,7 @@ func (c *SoraSDKClient) GetWatermarkFreeURLCustom(ctx context.Context, account * } var resp *http.Response if c.httpUpstream != nil { - resp, err = c.httpUpstream.Do(req, proxyURL, accountID, accountConcurrency) + resp, err = c.doSoraHTTP(req, proxyURL, accountID, accountConcurrency) } else { resp, err = http.DefaultClient.Do(req) } @@ -901,7 +901,7 @@ func (c *SoraSDKClient) exchangeSessionToken(ctx context.Context, account *Accou var resp *http.Response if c.httpUpstream != nil { - resp, err = c.httpUpstream.Do(req, proxyURL, accountID, accountConcurrency) + resp, err = c.doSoraHTTP(req, proxyURL, accountID, accountConcurrency) } else { resp, err = http.DefaultClient.Do(req) } @@ -1024,3 +1024,70 @@ func (c *SoraSDKClient) debugLogf(format string, args ...any) { log.Printf("[SoraSDK] "+format, args...) } } + +// doSoraHTTP 执行 Sora HTTP 请求,优先走 curl_cffi sidecar(Chrome TLS 指纹绕过 Cloudflare) +// 如果 sidecar 未启用或不可用,回退到 httpUpstream.Do() / http.DefaultClient +func (c *SoraSDKClient) doSoraHTTP(req *http.Request, proxyURL string, accountID int64, accountConcurrency int) (*http.Response, error) { + // 检查 sidecar 是否启用 + if c.cfg != nil && c.cfg.Sora.Client.CurlCFFISidecar.Enabled && c.cfg.Sora.Client.CurlCFFISidecar.BaseURL != "" { + resp, err := c.doViaSidecar(req) + if err == nil { + return resp, nil + } + // sidecar 失败,回退到直连 + logger.LegacyPrintf("service.sora", "Warning: sidecar failed, falling back to direct: %v", err) + } + + // 回退路径 + if c.httpUpstream != nil { + return c.httpUpstream.Do(req, proxyURL, accountID, accountConcurrency) + } + return http.DefaultClient.Do(req) +} + +// doViaSidecar 通过 curl_cffi sidecar 发送请求(Chrome TLS 指纹) +func (c *SoraSDKClient) doViaSidecar(originalReq *http.Request) (*http.Response, error) { + sidecarURL := strings.TrimRight(c.cfg.Sora.Client.CurlCFFISidecar.BaseURL, "/") + "/proxy" + + // 读取原始请求体 + var bodyStr 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) + // 恢复 body 以备回退 + originalReq.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + } + + // 构建 sidecar 请求 + headers := make(map[string]string) + for k, vs := range originalReq.Header { + if len(vs) > 0 { + headers[k] = vs[0] + } + } + + payload := map[string]any{ + "url": originalReq.URL.String(), + "method": originalReq.Method, + "headers": headers, + "body": bodyStr, + "session_key": fmt.Sprintf("account_%d", 0), // 简单的 session key + } + + payloadBytes, err := json.Marshal(payload) + if err != nil { + return nil, fmt.Errorf("marshal sidecar payload: %w", err) + } + + ctx := originalReq.Context() + sidecarReq, err := http.NewRequestWithContext(ctx, http.MethodPost, sidecarURL, bytes.NewReader(payloadBytes)) + if err != nil { + return nil, fmt.Errorf("create sidecar request: %w", err) + } + sidecarReq.Header.Set("Content-Type", "application/json") + + return http.DefaultClient.Do(sidecarReq) +}