feat(channel): 添加2FA验证后查看渠道密钥功能

- 新增接口通过2FA验证后获取渠道密钥
- 统一实现2FA验证码和备用码的验证逻辑
- 记录用户查看密钥的操作日志
- 编辑渠道弹窗新增查看密钥按钮,触发2FA验证模态框
- 使用TwoFactorAuthModal进行验证码输入及验证
- 验证成功后弹出渠道密钥展示窗口
- 对渠道编辑模态框的状态进行了统一重置优化
- 添加相关国际化文案支持密钥查看功能
This commit is contained in:
AAEE86
2025-08-25 14:45:48 +08:00
parent 79c7d8f477
commit 6033d4318e
6 changed files with 635 additions and 19 deletions

View File

@@ -45,10 +45,13 @@ import {
Row,
Col,
Highlight,
Input,
} from '@douyinfe/semi-ui';
import { getChannelModels, copy, getChannelIcon, getModelCategories, selectFilter } from '../../../../helpers';
import ModelSelectModal from './ModelSelectModal';
import JSONEditor from '../../../common/ui/JSONEditor';
import TwoFactorAuthModal from '../../../common/modals/TwoFactorAuthModal';
import ChannelKeyDisplay from '../../../common/ui/ChannelKeyDisplay';
import {
IconSave,
IconClose,
@@ -158,6 +161,44 @@ const EditChannelModal = (props) => {
const [channelSearchValue, setChannelSearchValue] = useState('');
const [useManualInput, setUseManualInput] = useState(false); // 是否使用手动输入模式
const [keyMode, setKeyMode] = useState('append'); // 密钥模式replace覆盖或 append追加
// 2FA验证查看密钥相关状态
const [twoFAState, setTwoFAState] = useState({
showModal: false,
code: '',
loading: false,
showKey: false,
keyData: ''
});
// 专门的2FA验证状态用于TwoFactorAuthModal
const [show2FAVerifyModal, setShow2FAVerifyModal] = useState(false);
const [verifyCode, setVerifyCode] = useState('');
const [verifyLoading, setVerifyLoading] = useState(false);
// 2FA状态更新辅助函数
const updateTwoFAState = (updates) => {
setTwoFAState(prev => ({ ...prev, ...updates }));
};
// 重置2FA状态
const resetTwoFAState = () => {
setTwoFAState({
showModal: false,
code: '',
loading: false,
showKey: false,
keyData: ''
});
};
// 重置2FA验证状态
const reset2FAVerifyState = () => {
setShow2FAVerifyModal(false);
setVerifyCode('');
setVerifyLoading(false);
};
// 渠道额外设置状态
const [channelSettings, setChannelSettings] = useState({
force_format: false,
@@ -500,6 +541,42 @@ const EditChannelModal = (props) => {
}
};
// 使用TwoFactorAuthModal的验证函数
const handleVerify2FA = async () => {
if (!verifyCode) {
showError(t('请输入验证码或备用码'));
return;
}
setVerifyLoading(true);
try {
const res = await API.post(`/api/channel/${channelId}/key`, {
code: verifyCode
});
if (res.data.success) {
// 验证成功,显示密钥
updateTwoFAState({
showModal: true,
showKey: true,
keyData: res.data.data.key
});
reset2FAVerifyState();
showSuccess(t('验证成功'));
} else {
showError(res.data.message);
}
} catch (error) {
showError(t('获取密钥失败'));
} finally {
setVerifyLoading(false);
}
};
// 显示2FA验证模态框 - 使用TwoFactorAuthModal
const handleShow2FAModal = () => {
setShow2FAVerifyModal(true);
};
useEffect(() => {
const modelMap = new Map();
@@ -576,27 +653,37 @@ const EditChannelModal = (props) => {
// 重置手动输入模式状态
setUseManualInput(false);
} else {
formApiRef.current?.reset();
// 重置渠道设置状态
setChannelSettings({
force_format: false,
thinking_to_content: false,
proxy: '',
pass_through_body_enabled: false,
system_prompt: '',
system_prompt_override: false,
});
// 重置密钥模式状态
setKeyMode('append');
// 清空表单中的key_mode字段
if (formApiRef.current) {
formApiRef.current.setValue('key_mode', undefined);
}
// 重置本地输入,避免下次打开残留上一次的 JSON 字段值
setInputs(getInitValues());
// 统一的模态框关闭重置逻辑
resetModalState();
}
}, [props.visible, channelId]);
// 统一的模态框重置函数
const resetModalState = () => {
formApiRef.current?.reset();
// 重置渠道设置状态
setChannelSettings({
force_format: false,
thinking_to_content: false,
proxy: '',
pass_through_body_enabled: false,
system_prompt: '',
system_prompt_override: false,
});
// 重置密钥模式状态
setKeyMode('append');
// 清空表单中的key_mode字段
if (formApiRef.current) {
formApiRef.current.setValue('key_mode', undefined);
}
// 重置本地输入,避免下次打开残留上一次的 JSON 字段值
setInputs(getInitValues());
// 重置2FA状态
resetTwoFAState();
// 重置2FA验证状态
reset2FAVerifyState();
};
const handleVertexUploadChange = ({ fileList }) => {
vertexErroredNames.current.clear();
(async () => {
@@ -1080,6 +1167,16 @@ const EditChannelModal = (props) => {
{t('追加模式:新密钥将添加到现有密钥列表的末尾')}
</Text>
)}
{isEdit && (
<Button
size="small"
type="primary"
theme="outline"
onClick={handleShow2FAModal}
>
{t('查看密钥')}
</Button>
)}
{batchExtra}
</div>
}
@@ -1154,6 +1251,16 @@ const EditChannelModal = (props) => {
{t('追加模式:新密钥将添加到现有密钥列表的末尾')}
</Text>
)}
{isEdit && (
<Button
size="small"
type="primary"
theme="outline"
onClick={handleShow2FAModal}
>
{t('查看密钥')}
</Button>
)}
{batchExtra}
</div>
}
@@ -1194,6 +1301,16 @@ const EditChannelModal = (props) => {
{t('追加模式:新密钥将添加到现有密钥列表的末尾')}
</Text>
)}
{isEdit && (
<Button
size="small"
type="primary"
theme="outline"
onClick={handleShow2FAModal}
>
{t('查看密钥')}
</Button>
)}
{batchExtra}
</div>
}
@@ -1846,6 +1963,53 @@ const EditChannelModal = (props) => {
onVisibleChange={(visible) => setIsModalOpenurl(visible)}
/>
</SideSheet>
{/* 使用TwoFactorAuthModal组件进行2FA验证 */}
<TwoFactorAuthModal
visible={show2FAVerifyModal}
code={verifyCode}
loading={verifyLoading}
onCodeChange={setVerifyCode}
onVerify={handleVerify2FA}
onCancel={reset2FAVerifyState}
title={t('查看渠道密钥')}
description={t('为了保护账户安全,请验证您的两步验证码。')}
placeholder={t('请输入验证码或备用码')}
/>
{/* 使用ChannelKeyDisplay组件显示密钥 */}
<Modal
title={
<div className="flex items-center">
<div className="w-8 h-8 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center mr-3">
<svg className="w-4 h-4 text-green-600 dark:text-green-400" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd" />
</svg>
</div>
{t('渠道密钥信息')}
</div>
}
visible={twoFAState.showModal && twoFAState.showKey}
onCancel={resetTwoFAState}
footer={
<Button
type="primary"
onClick={resetTwoFAState}
>
{t('完成')}
</Button>
}
width={700}
style={{ maxWidth: '90vw' }}
>
<ChannelKeyDisplay
keyData={twoFAState.keyData}
showSuccessIcon={true}
successText={t('密钥获取成功')}
showWarning={true}
warningText={t('请妥善保管密钥信息,不要泄露给他人。如有安全疑虑,请及时更换密钥。')}
/>
</Modal>
<ModelSelectModal
visible={modelModalVisible}
models={fetchedModels}