mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-19 08:38:38 +00:00
Merge branch 'main-upstream' into fix/volcengine_default_baseurl
# Conflicts: # web/src/components/settings/personal/cards/AccountManagement.jsx
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"one-api/constant"
|
"one-api/constant"
|
||||||
"one-api/dto"
|
"one-api/dto"
|
||||||
"one-api/model"
|
"one-api/model"
|
||||||
|
"one-api/service"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -633,6 +634,7 @@ func AddChannel(c *gin.Context) {
|
|||||||
common.ApiError(c, err)
|
common.ApiError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
service.ResetProxyClientCache()
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "",
|
"message": "",
|
||||||
@@ -894,6 +896,7 @@ func UpdateChannel(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
model.InitChannelCache()
|
model.InitChannelCache()
|
||||||
|
service.ResetProxyClientCache()
|
||||||
channel.Key = ""
|
channel.Key = ""
|
||||||
clearChannelInfo(&channel.Channel)
|
clearChannelInfo(&channel.Channel)
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
|||||||
@@ -7,12 +7,17 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"one-api/common"
|
"one-api/common"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/proxy"
|
"golang.org/x/net/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var httpClient *http.Client
|
var (
|
||||||
|
httpClient *http.Client
|
||||||
|
proxyClientLock sync.Mutex
|
||||||
|
proxyClients = make(map[string]*http.Client)
|
||||||
|
)
|
||||||
|
|
||||||
func InitHttpClient() {
|
func InitHttpClient() {
|
||||||
if common.RelayTimeout == 0 {
|
if common.RelayTimeout == 0 {
|
||||||
@@ -28,12 +33,31 @@ func GetHttpClient() *http.Client {
|
|||||||
return httpClient
|
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 客户端
|
// NewProxyHttpClient 创建支持代理的 HTTP 客户端
|
||||||
func NewProxyHttpClient(proxyURL string) (*http.Client, error) {
|
func NewProxyHttpClient(proxyURL string) (*http.Client, error) {
|
||||||
if proxyURL == "" {
|
if proxyURL == "" {
|
||||||
return http.DefaultClient, nil
|
return http.DefaultClient, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxyClientLock.Lock()
|
||||||
|
if client, ok := proxyClients[proxyURL]; ok {
|
||||||
|
proxyClientLock.Unlock()
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
proxyClientLock.Unlock()
|
||||||
|
|
||||||
parsedURL, err := url.Parse(proxyURL)
|
parsedURL, err := url.Parse(proxyURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -41,11 +65,16 @@ func NewProxyHttpClient(proxyURL string) (*http.Client, error) {
|
|||||||
|
|
||||||
switch parsedURL.Scheme {
|
switch parsedURL.Scheme {
|
||||||
case "http", "https":
|
case "http", "https":
|
||||||
return &http.Client{
|
client := &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
Proxy: http.ProxyURL(parsedURL),
|
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":
|
case "socks5", "socks5h":
|
||||||
// 获取认证信息
|
// 获取认证信息
|
||||||
@@ -67,13 +96,18 @@ func NewProxyHttpClient(proxyURL string) (*http.Client, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &http.Client{
|
client := &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return dialer.Dial(network, addr)
|
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:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported proxy scheme: %s", parsedURL.Scheme)
|
return nil, fmt.Errorf("unsupported proxy scheme: %s", parsedURL.Scheme)
|
||||||
|
|||||||
@@ -19,7 +19,14 @@ For commercial licensing, please contact support@quantumnous.com
|
|||||||
|
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 { UserContext } from '../../context/User';
|
||||||
import { Modal } from '@douyinfe/semi-ui';
|
import { Modal } from '@douyinfe/semi-ui';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -71,18 +78,40 @@ const PersonalSetting = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let status = localStorage.getItem('status');
|
let saved = localStorage.getItem('status');
|
||||||
if (status) {
|
if (saved) {
|
||||||
status = JSON.parse(status);
|
const parsed = JSON.parse(saved);
|
||||||
setStatus(status);
|
setStatus(parsed);
|
||||||
if (status.turnstile_check) {
|
if (parsed.turnstile_check) {
|
||||||
setTurnstileEnabled(true);
|
setTurnstileEnabled(true);
|
||||||
setTurnstileSiteKey(status.turnstile_site_key);
|
setTurnstileSiteKey(parsed.turnstile_site_key);
|
||||||
|
} else {
|
||||||
|
setTurnstileEnabled(false);
|
||||||
|
setTurnstileSiteKey('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getUserData().then((res) => {
|
// Always refresh status from server to avoid stale flags (e.g., admin just enabled OAuth)
|
||||||
console.log(userState);
|
(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(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
Tabs,
|
Tabs,
|
||||||
TabPane,
|
TabPane,
|
||||||
Popover,
|
Popover,
|
||||||
|
Modal,
|
||||||
} from '@douyinfe/semi-ui';
|
} from '@douyinfe/semi-ui';
|
||||||
import {
|
import {
|
||||||
IconMail,
|
IconMail,
|
||||||
@@ -83,6 +84,9 @@ const AccountManagement = ({
|
|||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
const isBound = (accountId) => Boolean(accountId);
|
||||||
|
const [showTelegramBindModal, setShowTelegramBindModal] = React.useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className='!rounded-2xl'>
|
<Card className='!rounded-2xl'>
|
||||||
{/* 卡片头部 */}
|
{/* 卡片头部 */}
|
||||||
@@ -142,7 +146,7 @@ const AccountManagement = ({
|
|||||||
size='small'
|
size='small'
|
||||||
onClick={() => setShowEmailBindModal(true)}
|
onClick={() => setShowEmailBindModal(true)}
|
||||||
>
|
>
|
||||||
{userState.user && userState.user.email !== ''
|
{isBound(userState.user?.email)
|
||||||
? t('修改绑定')
|
? t('修改绑定')
|
||||||
: t('绑定')}
|
: t('绑定')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -165,10 +169,11 @@ const AccountManagement = ({
|
|||||||
{t('微信')}
|
{t('微信')}
|
||||||
</div>
|
</div>
|
||||||
<div className='text-sm text-gray-500 truncate'>
|
<div className='text-sm text-gray-500 truncate'>
|
||||||
{renderAccountInfo(
|
{!status.wechat_login
|
||||||
userState.user?.wechat_id,
|
? t('未启用')
|
||||||
t('微信 ID'),
|
: isBound(userState.user?.wechat_id)
|
||||||
)}
|
? t('已绑定')
|
||||||
|
: t('未绑定')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -180,7 +185,7 @@ const AccountManagement = ({
|
|||||||
disabled={!status.wechat_login}
|
disabled={!status.wechat_login}
|
||||||
onClick={() => setShowWeChatBindModal(true)}
|
onClick={() => setShowWeChatBindModal(true)}
|
||||||
>
|
>
|
||||||
{userState.user && userState.user?.wechat_id
|
{isBound(userState.user?.wechat_id)
|
||||||
? t('修改绑定')
|
? t('修改绑定')
|
||||||
: status.wechat_login
|
: status.wechat_login
|
||||||
? t('绑定')
|
? t('绑定')
|
||||||
@@ -221,8 +226,7 @@ const AccountManagement = ({
|
|||||||
onGitHubOAuthClicked(status.github_client_id)
|
onGitHubOAuthClicked(status.github_client_id)
|
||||||
}
|
}
|
||||||
disabled={
|
disabled={
|
||||||
(userState.user && userState.user.github_id !== '') ||
|
isBound(userState.user?.github_id) || !status.github_oauth
|
||||||
!status.github_oauth
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{status.github_oauth ? t('绑定') : t('未启用')}
|
{status.github_oauth ? t('绑定') : t('未启用')}
|
||||||
@@ -265,8 +269,7 @@ const AccountManagement = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
disabled={
|
disabled={
|
||||||
(userState.user && userState.user.oidc_id !== '') ||
|
isBound(userState.user?.oidc_id) || !status.oidc_enabled
|
||||||
!status.oidc_enabled
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{status.oidc_enabled ? t('绑定') : t('未启用')}
|
{status.oidc_enabled ? t('绑定') : t('未启用')}
|
||||||
@@ -299,26 +302,56 @@ const AccountManagement = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className='flex-shrink-0'>
|
<div className='flex-shrink-0'>
|
||||||
{status.telegram_oauth ? (
|
{status.telegram_oauth ? (
|
||||||
userState.user?.telegram_id ? (
|
isBound(userState.user?.telegram_id) ? (
|
||||||
<Button disabled={true} size='small'>
|
<Button
|
||||||
|
disabled
|
||||||
|
size='small'
|
||||||
|
type='primary'
|
||||||
|
theme='outline'
|
||||||
|
>
|
||||||
{t('已绑定')}
|
{t('已绑定')}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<div className='scale-75'>
|
<Button
|
||||||
<TelegramLoginButton
|
type='primary'
|
||||||
dataAuthUrl='/api/oauth/telegram/bind'
|
theme='outline'
|
||||||
botName={status.telegram_bot_name}
|
size='small'
|
||||||
/>
|
onClick={() => setShowTelegramBindModal(true)}
|
||||||
</div>
|
>
|
||||||
|
{t('绑定')}
|
||||||
|
</Button>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<Button disabled={true} size='small'>
|
<Button
|
||||||
|
disabled
|
||||||
|
size='small'
|
||||||
|
type='primary'
|
||||||
|
theme='outline'
|
||||||
|
>
|
||||||
{t('未启用')}
|
{t('未启用')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</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绑定 */}
|
{/* LinuxDO绑定 */}
|
||||||
<Card className='!rounded-xl'>
|
<Card className='!rounded-xl'>
|
||||||
@@ -351,8 +384,7 @@ const AccountManagement = ({
|
|||||||
onLinuxDOOAuthClicked(status.linuxdo_client_id)
|
onLinuxDOOAuthClicked(status.linuxdo_client_id)
|
||||||
}
|
}
|
||||||
disabled={
|
disabled={
|
||||||
(userState.user && userState.user.linux_do_id !== '') ||
|
isBound(userState.user?.linux_do_id) || !status.linuxdo_oauth
|
||||||
!status.linuxdo_oauth
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{status.linuxdo_oauth ? t('绑定') : t('未启用')}
|
{status.linuxdo_oauth ? t('绑定') : t('未启用')}
|
||||||
|
|||||||
Reference in New Issue
Block a user