From c68fd36ee1a92ec76945145e265f0e3c71982ff1 Mon Sep 17 00:00:00 2001 From: RedwindA Date: Sat, 27 Sep 2025 22:18:39 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E5=AE=A2=E6=88=B7=E7=AB=AF=E7=BC=93=E5=AD=98=E5=92=8C?= =?UTF-8?q?=E9=87=8D=E7=BD=AE=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20HTTP=20=E5=AE=A2=E6=88=B7=E7=AB=AF=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service/http_client.go | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/service/http_client.go b/service/http_client.go index b191ddd78..dfed9b543 100644 --- a/service/http_client.go +++ b/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,15 @@ 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 + } + proxyClientLock.Lock() + proxyClients[proxyURL] = client + proxyClientLock.Unlock() + return client, nil case "socks5", "socks5h": // 获取认证信息 @@ -67,13 +95,17 @@ 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 + } + proxyClientLock.Lock() + proxyClients[proxyURL] = client + proxyClientLock.Unlock() + return client, nil default: return nil, fmt.Errorf("unsupported proxy scheme: %s", parsedURL.Scheme) From 486c828df0d20d9aede54cf980bc8022d2f9c92d Mon Sep 17 00:00:00 2001 From: RedwindA Date: Sat, 27 Sep 2025 22:18:46 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E5=9C=A8=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=92=8C=E6=9B=B4=E6=96=B0=E6=B8=A0=E9=81=93=E6=97=B6=E9=87=8D?= =?UTF-8?q?=E7=BD=AE=E4=BB=A3=E7=90=86=E5=AE=A2=E6=88=B7=E7=AB=AF=E7=BC=93?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/channel.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/controller/channel.go b/controller/channel.go index 480d5b4f3..5d075f3c5 100644 --- a/controller/channel.go +++ b/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{ From 466d19c33d88636388c1da5237989a8f597b40e4 Mon Sep 17 00:00:00 2001 From: RedwindA Date: Sat, 27 Sep 2025 22:29:27 +0800 Subject: [PATCH 3/5] fix: add missing timeout --- service/http_client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/service/http_client.go b/service/http_client.go index dfed9b543..d8fcfae01 100644 --- a/service/http_client.go +++ b/service/http_client.go @@ -70,6 +70,7 @@ func NewProxyHttpClient(proxyURL string) (*http.Client, error) { Proxy: http.ProxyURL(parsedURL), }, } + client.Timeout = time.Duration(common.RelayTimeout) * time.Second proxyClientLock.Lock() proxyClients[proxyURL] = client proxyClientLock.Unlock() @@ -102,6 +103,7 @@ func NewProxyHttpClient(proxyURL string) (*http.Client, error) { }, }, } + client.Timeout = time.Duration(common.RelayTimeout) * time.Second proxyClientLock.Lock() proxyClients[proxyURL] = client proxyClientLock.Unlock() From b08f1889e8627cf83573857ecf894cb4332b8d07 Mon Sep 17 00:00:00 2001 From: RedwindA Date: Sun, 28 Sep 2025 17:31:38 +0800 Subject: [PATCH 4/5] fix(settings): correct third-party binding states and unify Telegram button styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Show “Not enabled” for WeChat when status.wechat_login is false. - Refresh /api/status on PersonalSetting mount and persist via setStatusData to avoid stale flags, enabling binding after admin turns on OAuth. - Unify Telegram button styling with other providers; open a modal to render TelegramLoginButton for binding; align disabled “Not enabled” and “Bound” states. - Introduce isBound helper and reuse across providers (email/GitHub/OIDC/ LinuxDO/WeChat) to simplify checks and prevent falsy-ID issues. --- .../components/settings/PersonalSetting.jsx | 43 ++++++++--- .../personal/cards/AccountManagement.jsx | 73 ++++++++++++++----- 2 files changed, 86 insertions(+), 30 deletions(-) diff --git a/web/src/components/settings/PersonalSetting.jsx b/web/src/components/settings/PersonalSetting.jsx index 3ba8dcfd3..d86a810d3 100644 --- a/web/src/components/settings/PersonalSetting.jsx +++ b/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,34 @@ 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); } } - 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); + } + } + } catch (e) { + // ignore and keep local status + } + })(); + + getUserData(); }, []); useEffect(() => { diff --git a/web/src/components/settings/personal/cards/AccountManagement.jsx b/web/src/components/settings/personal/cards/AccountManagement.jsx index 515a5c191..017e7c1e6 100644 --- a/web/src/components/settings/personal/cards/AccountManagement.jsx +++ b/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 = ({ ); }; + const isBound = (accountId) => Boolean(accountId); + const [showTelegramBindModal, setShowTelegramBindModal] = React.useState(false); + return ( {/* 卡片头部 */} @@ -142,7 +146,7 @@ const AccountManagement = ({ size='small' onClick={() => setShowEmailBindModal(true)} > - {userState.user && userState.user.email !== '' + {isBound(userState.user?.email) ? t('修改绑定') : t('绑定')} @@ -165,9 +169,11 @@ const AccountManagement = ({ {t('微信')}
- {userState.user && userState.user.wechat_id !== '' - ? t('已绑定') - : t('未绑定')} + {!status.wechat_login + ? t('未启用') + : isBound(userState.user?.wechat_id) + ? t('已绑定') + : t('未绑定')}
@@ -179,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('绑定') @@ -220,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('未启用')} @@ -264,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('未启用')} @@ -298,26 +302,56 @@ const AccountManagement = ({
{status.telegram_oauth ? ( - userState.user.telegram_id !== '' ? ( - ) : ( -
- -
+ ) ) : ( - )}
+ setShowTelegramBindModal(false)} + footer={null} + > +
+ {t('点击下方按钮通过 Telegram 完成绑定')} +
+
+
+ +
+
+
{/* LinuxDO绑定 */} @@ -350,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('未启用')} From e2798fa62f128c94438b04aa00ddb0451fa77578 Mon Sep 17 00:00:00 2001 From: RedwindA Date: Sun, 28 Sep 2025 17:38:56 +0800 Subject: [PATCH 5/5] fix(settings): ensure turnstile settings are reset when disabled --- web/src/components/settings/PersonalSetting.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/src/components/settings/PersonalSetting.jsx b/web/src/components/settings/PersonalSetting.jsx index d86a810d3..15dfbd973 100644 --- a/web/src/components/settings/PersonalSetting.jsx +++ b/web/src/components/settings/PersonalSetting.jsx @@ -85,6 +85,9 @@ const PersonalSetting = () => { if (parsed.turnstile_check) { setTurnstileEnabled(true); setTurnstileSiteKey(parsed.turnstile_site_key); + } else { + setTurnstileEnabled(false); + setTurnstileSiteKey(''); } } // Always refresh status from server to avoid stale flags (e.g., admin just enabled OAuth) @@ -98,6 +101,9 @@ const PersonalSetting = () => { if (data.turnstile_check) { setTurnstileEnabled(true); setTurnstileSiteKey(data.turnstile_site_key); + } else { + setTurnstileEnabled(false); + setTurnstileSiteKey(''); } } } catch (e) {