Skip to content

Commit 39a4f9b

Browse files
committed
fix: 反代后流式输出意外停止的问题
fix: 反代后无法发送图片和文件的问题 feat: 后台登录支持2FA
1 parent 0e66f22 commit 39a4f9b

File tree

18 files changed

+339
-114
lines changed

18 files changed

+339
-114
lines changed

api/v1/errors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ var (
1313
// more biz errors
1414
ErrEmailAlreadyUse = newError(1001, "The email is already in use.")
1515
ErrCannotRefresh = newError(1002, "Can not refresh account.")
16+
Err2FARequired = newError(1003, "2FA is required.")
17+
Err2FACode = newError(1004, "2FA code error.")
1618
)

api/v1/user.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ type RegisterRequest struct {
66
}
77

88
type LoginRequest struct {
9-
Password string `json:"password" binding:"required" example:"123456"`
9+
Password string `json:"password" binding:"required" example:"123456"`
10+
ValidateCode string `json:"validateCode" example:"123456"`
1011
}
1112
type LoginResponseData struct {
1213
AccessToken string `json:"accessToken"`

data/config.json

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,59 @@
11
{
2-
"security": {
3-
"admin_password": ""
2+
"database": {
3+
"driver": "sqlite",
4+
"dsn": "./data/data.db"
45
},
56
"http": {
67
"host": "0.0.0.0",
78
"port": 9000,
89
"proxy-pass": {
9-
"oaifree": {
10-
"enable": true,
11-
"host": "https://new.oaifree.com",
12-
"port": 9001
13-
},
1410
"fuclaude": {
1511
"enable": true,
1612
"host": "https://demo.fuclaude.com",
1713
"port": 9002
14+
},
15+
"oaifree": {
16+
"enable": true,
17+
"host": "https://new.oaifree.com",
18+
"port": 9001
1819
}
1920
},
20-
"title": "Pandora",
21-
"rate": 1000
21+
"rate": 1000,
22+
"title": "Pandora"
2223
},
23-
"database": {
24-
"driver": "sqlite",
25-
"dsn": "./data/data.db"
24+
"log": {
25+
"compress": true,
26+
"encoding": "console",
27+
"level": "info",
28+
"log_file_name": "./logs/server.log",
29+
"max_age": 7,
30+
"max_backups": 30,
31+
"max_size": 1024,
32+
"output": "console"
2633
},
27-
"share": {
28-
"random": true,
29-
"custom": true
34+
"moderation": {
35+
"apikey": "",
36+
"apiurl": "https://api-proxy.oaipro.com/v1/moderations",
37+
"message": "您的消息包含不当内容,请修改后重试!"
38+
},
39+
"oneapi": {
40+
"domain": "",
41+
"token": ""
3042
},
3143
"pandora": {
3244
"domain": {
3345
"chat": "https://chat.oaifree.com",
34-
"token": "https://token.oaifree.com",
35-
"index": "https://new.oaifree.com",
36-
"claude": "https://demo.fuclaude.com"
46+
"claude": "http://localhost:9002",
47+
"index": "http://localhost:9001",
48+
"token": "https://token.oaifree.com"
3749
}
3850
},
39-
"moderation": {
40-
"apiKey": "",
41-
"apiUrl": "https://api-proxy.oaipro.com/v1/moderations",
42-
"message": "您的消息包含不当内容,请修改后重试!"
43-
},
44-
"oneapi": {
45-
"token": "",
46-
"domain": ""
51+
"security": {
52+
"2fa_secret": "",
53+
"admin_password": ""
4754
},
48-
"log": {
49-
"level": "info",
50-
"encoding": "console",
51-
"output": "console",
52-
"log_file_name": "./logs/server.log",
53-
"max_backups": 30,
54-
"max_age": 7,
55-
"max_size": 1024,
56-
"compress": true
55+
"share": {
56+
"custom": true,
57+
"random": true
5758
}
5859
}

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"react-icons": "^4.11.0",
5555
"react-markdown": "^8.0.7",
5656
"react-organizational-chart": "^2.2.1",
57+
"react-qrcode-logo": "^3.0.0",
5758
"react-quill": "^2.0.0",
5859
"react-router-dom": "^6.16.0",
5960
"react-use": "^17.4.0",

frontend/pnpm-lock.yaml

Lines changed: 31 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/api/services/sysService.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import apiClient from '../apiClient';
33
export enum SysApi {
44
pandoraUsage = '/sys_info/usage',
55
setting = '/sys_info/info',
6+
getMfaSecret = '/user/2fa_secret',
7+
verifyMfa = '/user/2fa_verify',
68
}
79

810
export type PandoraUsage = {
@@ -11,10 +13,14 @@ export type PandoraUsage = {
1113
ttl: number;
1214
};
1315

16+
const getMfaSecretUrl = () => apiClient.get({ url: SysApi.getMfaSecret });
17+
const verifyMfa = (code: string,secret: string) => apiClient.post({ url: SysApi.verifyMfa, params: { code, secret } });
1418
const getPandoraUsage = () => apiClient.get<PandoraUsage>({ url: SysApi.pandoraUsage });
1519
const getSetting = () => apiClient.get({ url: SysApi.setting });
1620

1721
export default {
1822
getPandoraUsage,
1923
getSetting,
24+
getMfaSecretUrl,
25+
verifyMfa
2026
};

frontend/src/api/services/userService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {UserInfo} from "#/entity.ts";
55

66
export interface SignInReq {
77
password: string;
8+
validateCode?: string;
89
token?: string;
910
}
1011

@@ -16,11 +17,10 @@ export type SignInRes = {
1617

1718
export enum UserApi {
1819
SignIn = 'login',
19-
// SignIn = '/auth/signin',
2020
Logout = '/auth/logout',
2121
}
2222

23-
const signin = (data: SignInReq) => apiClient.post<SignInRes>({ url: UserApi.SignIn, data });
23+
const signin = (data: SignInReq) => apiClient.post({ url: UserApi.SignIn, data });
2424
const logout = () => apiClient.get({ url: UserApi.Logout });
2525

2626
export default {

frontend/src/layouts/_common/account-dropdown.tsx

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { Divider, MenuProps } from 'antd';
22
import Dropdown, { DropdownProps } from 'antd/es/dropdown/dropdown';
3-
import React from 'react';
3+
import React, {useState} from 'react';
44
import { useTranslation } from 'react-i18next';
5-
import { NavLink } from 'react-router-dom';
65

76
import { IconButton } from '@/components/icon';
87
import { useLoginStateContext } from '@/pages/sys/login/providers/LoginStateProvider';
98
import { useRouter } from '@/router/hooks';
109
import { useUserInfo, useUserActions } from '@/store/userStore';
1110
import { useThemeToken } from '@/theme/hooks';
1211
import avatar from '@/assets/images/qinshihuang.jpg';
12+
import MfaBindingModal from "@/layouts/_common/mfa-modal.tsx";
13+
import sysService from "@/api/services/sysService.ts";
1314

14-
const { VITE_APP_HOMEPAGE: HOMEPAGE } = import.meta.env;
1515

1616
/**
1717
* Account Dropdown
@@ -24,8 +24,6 @@ export default function AccountDropdown() {
2424
const { t } = useTranslation();
2525
const logout = () => {
2626
try {
27-
// todo const logoutMutation = useMutation(userService.logout);
28-
// todo logoutMutation.mutateAsync();
2927
clearUserInfoAndToken();
3028
backToLogin();
3129
} catch (error) {
@@ -35,6 +33,7 @@ export default function AccountDropdown() {
3533
}
3634
};
3735
const { colorBgElevated, borderRadiusLG, boxShadowSecondary } = useThemeToken();
36+
const [mfaModalVisible, setMfaModalVisible] = useState(false);
3837

3938
const contentStyle: React.CSSProperties = {
4039
backgroundColor: colorBgElevated,
@@ -58,14 +57,10 @@ export default function AccountDropdown() {
5857
);
5958

6059
const items: MenuProps['items'] = [
61-
{ label: <NavLink to={HOMEPAGE}>{t('sys.menu.dashboard')}</NavLink>, key: '0' },
6260
{
63-
label: <NavLink to="/management/user/profile">{t('sys.menu.user.profile')}</NavLink>,
64-
key: '1',
65-
},
66-
{
67-
label: <NavLink to="/management/user/account">{t('sys.menu.user.account')}</NavLink>,
61+
label: <button>2FA认证</button>,
6862
key: '2',
63+
onClick: () => setMfaModalVisible(true),
6964
},
7065
{ type: 'divider' },
7166
{
@@ -75,11 +70,21 @@ export default function AccountDropdown() {
7570
},
7671
];
7772

73+
const handleVerify = (mfaCode: string, secret: string) => {
74+
sysService.verifyMfa(mfaCode, secret).then(res => {
75+
console.log(res);
76+
setMfaModalVisible(false);
77+
});
78+
}
79+
7880
return (
79-
<Dropdown menu={{ items }} trigger={['click']} dropdownRender={dropdownRender}>
80-
<IconButton className="h-10 w-10 transform-none px-0 hover:scale-105">
81-
<img className="h-8 w-8 rounded-full" src={avatar} alt="" />
82-
</IconButton>
83-
</Dropdown>
81+
<>
82+
<Dropdown menu={{ items }} trigger={['click']} dropdownRender={dropdownRender}>
83+
<IconButton className="h-10 w-10 transform-none px-0 hover:scale-105">
84+
<img className="h-8 w-8 rounded-full" src={avatar} alt="" />
85+
</IconButton>
86+
</Dropdown>
87+
<MfaBindingModal isOpen={mfaModalVisible} onVerify={handleVerify} onClose={() => setMfaModalVisible(false)}/>
88+
</>
8489
);
8590
}

0 commit comments

Comments
 (0)