Explorar el Código

feat: support channel request proxy

1808837298@qq.com hace 1 año
padre
commit
cf63ab59cf

+ 2 - 1
constant/channel_setting.go

@@ -1,5 +1,6 @@
 package constant
 
 var (
-	ForceFormat = "force_format" // ForceFormat 强制格式化为OpenAI格式
+	ForceFormat        = "force_format" // ForceFormat 强制格式化为OpenAI格式
+	ChanelSettingProxy = "proxy"        // Proxy 代理
 )

+ 28 - 0
docs/channel/orher_setting.md

@@ -0,0 +1,28 @@
+# 渠道而外设置说明
+
+该配置用于设置一些额外的渠道参数,可以通过 JSON 对象进行配置。主要包含以下两个设置项:
+
+1. force_format
+    - 用于标识是否对数据进行强制格式化为 OpenAI 格式
+    - 类型为布尔值,设置为 true 时启用强制格式化
+
+2. proxy
+    - 用于配置网络代理
+    - 类型为字符串,填写代理地址(例如 socks5 协议的代理地址)
+
+--------------------------------------------------------------
+
+## JSON 格式示例
+
+以下是一个示例配置,启用强制格式化并设置了代理地址:
+
+```json
+{
+    "force_format": true,
+    "proxy": "socks5://xxxxxxx"
+}
+```
+
+--------------------------------------------------------------
+
+通过调整上述 JSON 配置中的值,可以灵活控制渠道的额外行为,比如是否进行格式化以及使用特定的网络代理。

+ 15 - 5
relay/channel/api_request.go

@@ -39,7 +39,7 @@ func DoApiRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody
 	if err != nil {
 		return nil, fmt.Errorf("setup request header failed: %w", err)
 	}
-	resp, err := doRequest(c, req)
+	resp, err := doRequest(c, req, info)
 	if err != nil {
 		return nil, fmt.Errorf("do request failed: %w", err)
 	}
@@ -62,7 +62,7 @@ func DoFormRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBod
 	if err != nil {
 		return nil, fmt.Errorf("setup request header failed: %w", err)
 	}
-	resp, err := doRequest(c, req)
+	resp, err := doRequest(c, req, info)
 	if err != nil {
 		return nil, fmt.Errorf("do request failed: %w", err)
 	}
@@ -90,8 +90,18 @@ func DoWssRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody
 	return targetConn, nil
 }
 
-func doRequest(c *gin.Context, req *http.Request) (*http.Response, error) {
-	resp, err := service.GetHttpClient().Do(req)
+func doRequest(c *gin.Context, req *http.Request, info *common.RelayInfo) (*http.Response, error) {
+	var client *http.Client
+	var err error
+	if proxyURL, ok := info.ChannelSetting["proxy"]; ok {
+		client, err = service.NewProxyHttpClient(proxyURL.(string))
+		if err != nil {
+			return nil, fmt.Errorf("new proxy http client failed: %w", err)
+		}
+	} else {
+		client = service.GetHttpClient()
+	}
+	resp, err := client.Do(req)
 	if err != nil {
 		return nil, err
 	}
@@ -120,7 +130,7 @@ func DoTaskApiRequest(a TaskAdaptor, c *gin.Context, info *common.TaskRelayInfo,
 	if err != nil {
 		return nil, fmt.Errorf("setup request header failed: %w", err)
 	}
-	resp, err := doRequest(c, req)
+	resp, err := doRequest(c, req, info.ToRelayInfo())
 	if err != nil {
 		return nil, fmt.Errorf("do request failed: %w", err)
 	}

+ 45 - 0
service/http_client.go

@@ -1,7 +1,12 @@
 package service
 
 import (
+	"context"
+	"fmt"
+	"golang.org/x/net/proxy"
+	"net"
 	"net/http"
+	"net/url"
 	"one-api/common"
 	"time"
 )
@@ -30,3 +35,43 @@ func GetHttpClient() *http.Client {
 func GetImpatientHttpClient() *http.Client {
 	return impatientHTTPClient
 }
+
+// NewProxyHttpClient 创建支持代理的 HTTP 客户端
+func NewProxyHttpClient(proxyURL string) (*http.Client, error) {
+	if proxyURL == "" {
+		return http.DefaultClient, nil
+	}
+
+	// 解析代理URL
+	parsedURL, err := url.Parse(proxyURL)
+	if err != nil {
+		return nil, err
+	}
+
+	switch parsedURL.Scheme {
+	case "http", "https":
+		return &http.Client{
+			Transport: &http.Transport{
+				Proxy: http.ProxyURL(parsedURL),
+			},
+		}, nil
+
+	case "socks5":
+		// 创建 SOCKS5 代理拨号器
+		dialer, err := proxy.SOCKS5("tcp", parsedURL.Host, nil, proxy.Direct)
+		if err != nil {
+			return nil, err
+		}
+
+		return &http.Client{
+			Transport: &http.Transport{
+				DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
+					return dialer.Dial(network, addr)
+				},
+			},
+		}, nil
+
+	default:
+		return nil, fmt.Errorf("unsupported proxy scheme: %s", parsedURL.Scheme)
+	}
+}

+ 2 - 4
web/src/pages/Channel/EditChannel.js

@@ -966,7 +966,6 @@ const EditChannel = (props) => {
             value={inputs.weight}
             autoComplete="new-password"
           />
-          {inputs.type === 8 && (
           <>
             <div style={{ marginTop: 10 }}>
               <Typography.Text strong>
@@ -999,9 +998,8 @@ const EditChannel = (props) => {
               }}
             >
               {t('填入模板')}
-              </Typography.Text>
-            </>
-          )}
+            </Typography.Text>
+          </>
         </Spin>
       </SideSheet>
     </>