Просмотр исходного кода

Merge branch 'main-upstream' into fix/volcengine_default_baseurl

# Conflicts:
#	web/src/components/settings/personal/cards/AccountManagement.jsx
Seefs 5 месяцев назад
Родитель
Сommit
bf9a5f5b52

+ 3 - 0
controller/channel.go

@@ -8,6 +8,7 @@ import (
 	"one-api/constant"
 	"one-api/dto"
 	"one-api/model"
+	"one-api/service"
 	"strconv"
 	"strings"
 
@@ -633,6 +634,7 @@ func AddChannel(c *gin.Context) {
 		common.ApiError(c, err)
 		return
 	}
+	service.ResetProxyClientCache()
 	c.JSON(http.StatusOK, gin.H{
 		"success": true,
 		"message": "",
@@ -894,6 +896,7 @@ func UpdateChannel(c *gin.Context) {
 		return
 	}
 	model.InitChannelCache()
+	service.ResetProxyClientCache()
 	channel.Key = ""
 	clearChannelInfo(&channel.Channel)
 	c.JSON(http.StatusOK, gin.H{

+ 39 - 5
service/http_client.go

@@ -7,12 +7,17 @@ import (
 	"net/http"
 	"net/url"
 	"one-api/common"
+	"sync"
 	"time"
 
 	"golang.org/x/net/proxy"
 )
 
-var httpClient *http.Client
+var (
+	httpClient      *http.Client
+	proxyClientLock sync.Mutex
+	proxyClients    = make(map[string]*http.Client)
+)
 
 func InitHttpClient() {
 	if common.RelayTimeout == 0 {
@@ -28,12 +33,31 @@ func GetHttpClient() *http.Client {
 	return httpClient
 }
 
+// ResetProxyClientCache 清空代理客户端缓存,确保下次使用时重新初始化
+func ResetProxyClientCache() {
+	proxyClientLock.Lock()
+	defer proxyClientLock.Unlock()
+	for _, client := range proxyClients {
+		if transport, ok := client.Transport.(*http.Transport); ok && transport != nil {
+			transport.CloseIdleConnections()
+		}
+	}
+	proxyClients = make(map[string]*http.Client)
+}
+
 // NewProxyHttpClient 创建支持代理的 HTTP 客户端
 func NewProxyHttpClient(proxyURL string) (*http.Client, error) {
 	if proxyURL == "" {
 		return http.DefaultClient, nil
 	}
 
+	proxyClientLock.Lock()
+	if client, ok := proxyClients[proxyURL]; ok {
+		proxyClientLock.Unlock()
+		return client, nil
+	}
+	proxyClientLock.Unlock()
+
 	parsedURL, err := url.Parse(proxyURL)
 	if err != nil {
 		return nil, err
@@ -41,11 +65,16 @@ func NewProxyHttpClient(proxyURL string) (*http.Client, error) {
 
 	switch parsedURL.Scheme {
 	case "http", "https":
-		return &http.Client{
+		client := &http.Client{
 			Transport: &http.Transport{
 				Proxy: http.ProxyURL(parsedURL),
 			},
-		}, nil
+		}
+		client.Timeout = time.Duration(common.RelayTimeout) * time.Second
+		proxyClientLock.Lock()
+		proxyClients[proxyURL] = client
+		proxyClientLock.Unlock()
+		return client, nil
 
 	case "socks5", "socks5h":
 		// 获取认证信息
@@ -67,13 +96,18 @@ func NewProxyHttpClient(proxyURL string) (*http.Client, error) {
 			return nil, err
 		}
 
-		return &http.Client{
+		client := &http.Client{
 			Transport: &http.Transport{
 				DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
 					return dialer.Dial(network, addr)
 				},
 			},
-		}, nil
+		}
+		client.Timeout = time.Duration(common.RelayTimeout) * time.Second
+		proxyClientLock.Lock()
+		proxyClients[proxyURL] = client
+		proxyClientLock.Unlock()
+		return client, nil
 
 	default:
 		return nil, fmt.Errorf("unsupported proxy scheme: %s", parsedURL.Scheme)

+ 39 - 10
web/src/components/settings/PersonalSetting.jsx

@@ -19,7 +19,14 @@ For commercial licensing, please contact support@quantumnous.com
 
 import React, { useContext, useEffect, useState } from 'react';
 import { useNavigate } from 'react-router-dom';
-import { API, copy, showError, showInfo, showSuccess } from '../../helpers';
+import {
+  API,
+  copy,
+  showError,
+  showInfo,
+  showSuccess,
+  setStatusData,
+} from '../../helpers';
 import { UserContext } from '../../context/User';
 import { Modal } from '@douyinfe/semi-ui';
 import { useTranslation } from 'react-i18next';
@@ -71,18 +78,40 @@ const PersonalSetting = () => {
   });
 
   useEffect(() => {
-    let status = localStorage.getItem('status');
-    if (status) {
-      status = JSON.parse(status);
-      setStatus(status);
-      if (status.turnstile_check) {
+    let saved = localStorage.getItem('status');
+    if (saved) {
+      const parsed = JSON.parse(saved);
+      setStatus(parsed);
+      if (parsed.turnstile_check) {
         setTurnstileEnabled(true);
-        setTurnstileSiteKey(status.turnstile_site_key);
+        setTurnstileSiteKey(parsed.turnstile_site_key);
+      } else {
+        setTurnstileEnabled(false);
+        setTurnstileSiteKey('');
       }
     }
-    getUserData().then((res) => {
-      console.log(userState);
-    });
+    // Always refresh status from server to avoid stale flags (e.g., admin just enabled OAuth)
+    (async () => {
+      try {
+        const res = await API.get('/api/status');
+        const { success, data } = res.data;
+        if (success && data) {
+          setStatus(data);
+          setStatusData(data);
+          if (data.turnstile_check) {
+            setTurnstileEnabled(true);
+            setTurnstileSiteKey(data.turnstile_site_key);
+          } else {
+            setTurnstileEnabled(false);
+            setTurnstileSiteKey('');
+          }
+        }
+      } catch (e) {
+        // ignore and keep local status
+      }
+    })();
+
+    getUserData();
   }, []);
 
   useEffect(() => {

+ 53 - 21
web/src/components/settings/personal/cards/AccountManagement.jsx

@@ -28,6 +28,7 @@ import {
   Tabs,
   TabPane,
   Popover,
+  Modal,
 } from '@douyinfe/semi-ui';
 import {
   IconMail,
@@ -83,6 +84,9 @@ const AccountManagement = ({
       </Popover>
     );
   };
+  const isBound = (accountId) => Boolean(accountId);
+  const [showTelegramBindModal, setShowTelegramBindModal] = React.useState(false);
+
   return (
     <Card className='!rounded-2xl'>
       {/* 卡片头部 */}
@@ -142,7 +146,7 @@ const AccountManagement = ({
                       size='small'
                       onClick={() => setShowEmailBindModal(true)}
                     >
-                      {userState.user && userState.user.email !== ''
+                      {isBound(userState.user?.email)
                         ? t('修改绑定')
                         : t('绑定')}
                     </Button>
@@ -165,10 +169,11 @@ const AccountManagement = ({
                         {t('微信')}
                       </div>
                       <div className='text-sm text-gray-500 truncate'>
-                        {renderAccountInfo(
-                          userState.user?.wechat_id,
-                          t('微信 ID'),
-                        )}
+                        {!status.wechat_login
+                          ? t('未启用')
+                          : isBound(userState.user?.wechat_id)
+                            ? t('已绑定')
+                            : t('未绑定')}
                       </div>
                     </div>
                   </div>
@@ -180,7 +185,7 @@ const AccountManagement = ({
                       disabled={!status.wechat_login}
                       onClick={() => setShowWeChatBindModal(true)}
                     >
-                      {userState.user && userState.user?.wechat_id
+                      {isBound(userState.user?.wechat_id)
                         ? t('修改绑定')
                         : status.wechat_login
                           ? t('绑定')
@@ -221,8 +226,7 @@ const AccountManagement = ({
                         onGitHubOAuthClicked(status.github_client_id)
                       }
                       disabled={
-                        (userState.user && userState.user.github_id !== '') ||
-                        !status.github_oauth
+                        isBound(userState.user?.github_id) || !status.github_oauth
                       }
                     >
                       {status.github_oauth ? t('绑定') : t('未启用')}
@@ -265,8 +269,7 @@ const AccountManagement = ({
                         )
                       }
                       disabled={
-                        (userState.user && userState.user.oidc_id !== '') ||
-                        !status.oidc_enabled
+                        isBound(userState.user?.oidc_id) || !status.oidc_enabled
                       }
                     >
                       {status.oidc_enabled ? t('绑定') : t('未启用')}
@@ -299,26 +302,56 @@ const AccountManagement = ({
                   </div>
                   <div className='flex-shrink-0'>
                     {status.telegram_oauth ? (
-                      userState.user?.telegram_id ? (
-                        <Button disabled={true} size='small'>
+                      isBound(userState.user?.telegram_id) ? (
+                        <Button
+                          disabled
+                          size='small'
+                          type='primary'
+                          theme='outline'
+                        >
                           {t('已绑定')}
                         </Button>
                       ) : (
-                        <div className='scale-75'>
-                          <TelegramLoginButton
-                            dataAuthUrl='/api/oauth/telegram/bind'
-                            botName={status.telegram_bot_name}
-                          />
-                        </div>
+                        <Button
+                          type='primary'
+                          theme='outline'
+                          size='small'
+                          onClick={() => setShowTelegramBindModal(true)}
+                        >
+                          {t('绑定')}
+                        </Button>
                       )
                     ) : (
-                      <Button disabled={true} size='small'>
+                      <Button
+                        disabled
+                        size='small'
+                        type='primary'
+                        theme='outline'
+                      >
                         {t('未启用')}
                       </Button>
                     )}
                   </div>
                 </div>
               </Card>
+              <Modal
+                title={t('绑定 Telegram')}
+                visible={showTelegramBindModal}
+                onCancel={() => setShowTelegramBindModal(false)}
+                footer={null}
+              >
+                <div className='my-3 text-sm text-gray-600'>
+                  {t('点击下方按钮通过 Telegram 完成绑定')}
+                </div>
+                <div className='flex justify-center'>
+                  <div className='scale-90'>
+                    <TelegramLoginButton
+                      dataAuthUrl='/api/oauth/telegram/bind'
+                      botName={status.telegram_bot_name}
+                    />
+                  </div>
+                </div>
+              </Modal>
 
               {/* LinuxDO绑定 */}
               <Card className='!rounded-xl'>
@@ -351,8 +384,7 @@ const AccountManagement = ({
                         onLinuxDOOAuthClicked(status.linuxdo_client_id)
                       }
                       disabled={
-                        (userState.user && userState.user.linux_do_id !== '') ||
-                        !status.linuxdo_oauth
+                        isBound(userState.user?.linux_do_id) || !status.linuxdo_oauth
                       }
                     >
                       {status.linuxdo_oauth ? t('绑定') : t('未启用')}