Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
ce729dff
authored
Aug 07, 2023
by
archer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: git login
parent
206eb81b
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
311 additions
and
32 deletions
+311
-32
client/data/config.json
+3
-1
client/src/api/user.ts
+1
-0
client/src/components/Icon/icons/fill/git.svg
+2
-0
client/src/components/Icon/index.tsx
+1
-0
client/src/components/Layout/auth.tsx
+1
-0
client/src/components/Layout/index.tsx
+2
-0
client/src/pages/api/user/account/gitLogin.ts
+116
-0
client/src/pages/login/components/LoginForm.tsx
+30
-0
client/src/pages/login/provider.tsx
+74
-0
client/src/store/global.ts
+49
-31
client/src/types/index.d.ts
+2
-0
client/src/utils/tools.ts
+30
-0
No files found.
client/data/config.json
View file @
ce729dff
...
@@ -7,9 +7,11 @@
...
@@ -7,9 +7,11 @@
"show_git"
:
true
,
"show_git"
:
true
,
"beianText"
:
""
,
"beianText"
:
""
,
"systemTitle"
:
"FastAI"
,
"systemTitle"
:
"FastAI"
,
"authorText"
:
"Made by FastAI Team."
"authorText"
:
"Made by FastAI Team."
,
"gitLoginKey"
:
""
},
},
"SystemParams"
:
{
"SystemParams"
:
{
"gitLoginSecret"
:
""
,
"vectorMaxProcess"
:
15
,
"vectorMaxProcess"
:
15
,
"qaMaxProcess"
:
15
,
"qaMaxProcess"
:
15
,
"pgIvfflatProbe"
:
20
"pgIvfflatProbe"
:
20
...
...
client/src/api/user.ts
View file @
ce729dff
...
@@ -13,6 +13,7 @@ export const sendAuthCode = (data: {
...
@@ -13,6 +13,7 @@ export const sendAuthCode = (data: {
})
=>
POST
(
'/user/sendAuthCode'
,
data
);
})
=>
POST
(
'/user/sendAuthCode'
,
data
);
export
const
getTokenLogin
=
()
=>
GET
<
UserType
>
(
'/user/account/tokenLogin'
);
export
const
getTokenLogin
=
()
=>
GET
<
UserType
>
(
'/user/account/tokenLogin'
);
export
const
gitLogin
=
(
code
:
string
)
=>
GET
<
ResLogin
>
(
'/user/account/gitLogin'
,
{
code
});
export
const
postRegister
=
({
export
const
postRegister
=
({
username
,
username
,
...
...
client/src/components/Icon/icons/fill/git.svg
0 → 100644
View file @
ce729dff
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg
t=
"1691391466028"
class=
"icon"
viewBox=
"0 0 1024 1024"
version=
"1.1"
xmlns=
"http://www.w3.org/2000/svg"
p-id=
"4715"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"64"
height=
"64"
><path
d=
"M511.957333 21.333333C241.024 21.333333 21.333333 240.981333 21.333333 512c0 216.832 140.544 400.725333 335.573334 465.664 24.490667 4.394667 32.256-10.069333 32.256-23.082667 0-11.690667 0.256-44.245333 0-85.205333-136.448 29.610667-164.736-64.64-164.736-64.64-22.314667-56.704-54.4-71.765333-54.4-71.765333-44.586667-30.464 3.285333-29.824 3.285333-29.824 49.194667 3.413333 75.178667 50.517333 75.178667 50.517333 43.776 75.008 114.816 53.333333 142.762666 40.789333 4.522667-31.658667 17.152-53.376 31.189334-65.536-108.970667-12.458667-223.488-54.485333-223.488-242.602666 0-53.546667 19.114667-97.322667 50.517333-131.669334-5.034667-12.330667-21.930667-62.293333 4.778667-129.834666 0 0 41.258667-13.184 134.912 50.346666a469.802667 469.802667 0 0 1 122.88-16.554666c41.642667 0.213333 83.626667 5.632 122.88 16.554666 93.653333-63.488 134.784-50.346667 134.784-50.346666 26.752 67.541333 9.898667 117.504 4.864 129.834666 31.402667 34.346667 50.474667 78.122667 50.474666 131.669334 0 188.586667-114.730667 230.016-224.042666 242.090666 17.578667 15.232 33.578667 44.672 33.578666 90.453334v135.850666c0 13.141333 7.936 27.605333 32.853334 22.869334C862.250667 912.597333 1002.666667 728.746667 1002.666667 512 1002.666667 240.981333 783.018667 21.333333 511.957333 21.333333z"
p-id=
"4716"
></path></svg>
\ No newline at end of file
client/src/components/Icon/index.tsx
View file @
ce729dff
...
@@ -23,6 +23,7 @@ const map = {
...
@@ -23,6 +23,7 @@ const map = {
wx
:
require
(
'./icons/wx.svg'
).
default
,
wx
:
require
(
'./icons/wx.svg'
).
default
,
out
:
require
(
'./icons/out.svg'
).
default
,
out
:
require
(
'./icons/out.svg'
).
default
,
git
:
require
(
'./icons/git.svg'
).
default
,
git
:
require
(
'./icons/git.svg'
).
default
,
gitFill
:
require
(
'./icons/fill/git.svg'
).
default
,
menu
:
require
(
'./icons/menu.svg'
).
default
,
menu
:
require
(
'./icons/menu.svg'
).
default
,
edit
:
require
(
'./icons/edit.svg'
).
default
,
edit
:
require
(
'./icons/edit.svg'
).
default
,
inform
:
require
(
'./icons/inform.svg'
).
default
,
inform
:
require
(
'./icons/inform.svg'
).
default
,
...
...
client/src/components/Layout/auth.tsx
View file @
ce729dff
...
@@ -7,6 +7,7 @@ import { useQuery } from '@tanstack/react-query';
...
@@ -7,6 +7,7 @@ import { useQuery } from '@tanstack/react-query';
const
unAuthPage
:
{
[
key
:
string
]:
boolean
}
=
{
const
unAuthPage
:
{
[
key
:
string
]:
boolean
}
=
{
'/'
:
true
,
'/'
:
true
,
'/login'
:
true
,
'/login'
:
true
,
'/login/provider'
:
true
,
'/appStore'
:
true
,
'/appStore'
:
true
,
'/chat/share'
:
true
'/chat/share'
:
true
};
};
...
...
client/src/components/Layout/index.tsx
View file @
ce729dff
...
@@ -14,6 +14,7 @@ import { getUnreadCount } from '@/api/user';
...
@@ -14,6 +14,7 @@ import { getUnreadCount } from '@/api/user';
const
pcUnShowLayoutRoute
:
Record
<
string
,
boolean
>
=
{
const
pcUnShowLayoutRoute
:
Record
<
string
,
boolean
>
=
{
'/'
:
true
,
'/'
:
true
,
'/login'
:
true
,
'/login'
:
true
,
'/login/provider'
:
true
,
'/chat/share'
:
true
,
'/chat/share'
:
true
,
'/app/edit'
:
true
,
'/app/edit'
:
true
,
'/chat'
:
true
'/chat'
:
true
...
@@ -21,6 +22,7 @@ const pcUnShowLayoutRoute: Record<string, boolean> = {
...
@@ -21,6 +22,7 @@ const pcUnShowLayoutRoute: Record<string, boolean> = {
const
phoneUnShowLayoutRoute
:
Record
<
string
,
boolean
>
=
{
const
phoneUnShowLayoutRoute
:
Record
<
string
,
boolean
>
=
{
'/'
:
true
,
'/'
:
true
,
'/login'
:
true
,
'/login'
:
true
,
'/login/provider'
:
true
,
'/chat/share'
:
true
'/chat/share'
:
true
};
};
...
...
client/src/pages/api/user/account/gitLogin.ts
0 → 100644
View file @
ce729dff
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import
type
{
NextApiRequest
,
NextApiResponse
}
from
'next'
;
import
{
jsonRes
}
from
'@/service/response'
;
import
{
User
}
from
'@/service/models/user'
;
import
{
generateToken
,
setCookie
}
from
'@/service/utils/tools'
;
import
axios
from
'axios'
;
import
{
parseQueryString
}
from
'@/utils/tools'
;
import
{
customAlphabet
}
from
'nanoid'
;
const
nanoid
=
customAlphabet
(
'abcdefghijklmnopqrstuvwxyz1234567890'
,
8
);
type
GithubAccessTokenType
=
{
access_token
:
string
;
expires_in
:
number
;
refresh_token
:
string
;
refresh_token_expires_in
:
number
;
token_type
:
'bearer'
;
scope
:
string
;
};
type
GithubUserType
=
{
email
:
string
;
avatar_url
:
string
;
};
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
<
any
>
)
{
try
{
const
{
code
}
=
req
.
query
as
{
code
:
string
};
const
{
data
:
gitAccessToken
}
=
await
axios
.
post
<
string
>
(
`https://github.com/login/oauth/access_token?client_id=
${
global
.
feConfigs
.
gitLoginKey
}
&client_secret=
${
global
.
systemEnv
.
gitLoginSecret
}
&code=
${
code
}
`
);
const
jsonGitAccessToken
=
parseQueryString
(
gitAccessToken
)
as
GithubAccessTokenType
;
const
access_token
=
jsonGitAccessToken
?.
access_token
;
if
(
!
access_token
)
{
throw
new
Error
(
'access_token is null'
);
}
const
{
data
:
{
email
,
avatar_url
}
}
=
await
axios
.
get
<
GithubUserType
>
(
'https://api.github.com/user'
,
{
headers
:
{
Authorization
:
`Bearer
${
access_token
}
`
}
});
try
{
jsonRes
(
res
,
{
data
:
await
loginByUsername
({
username
:
email
,
res
})
});
}
catch
(
err
:
any
)
{
if
(
err
?.
code
===
500
)
{
jsonRes
(
res
,
{
data
:
await
registerUser
({
username
:
email
,
avatar
:
avatar_url
,
res
})
});
}
throw
new
Error
(
err
);
}
}
catch
(
err
)
{
jsonRes
(
res
,
{
code
:
500
,
error
:
err
});
}
}
export
async
function
loginByUsername
({
username
,
res
}:
{
username
:
string
;
res
:
NextApiResponse
;
})
{
const
user
=
await
User
.
findOne
({
username
});
console
.
log
(
user
,
username
);
if
(
!
user
)
{
return
Promise
.
reject
({
code
:
500
});
}
const
token
=
generateToken
(
user
.
_id
);
setCookie
(
res
,
token
);
return
{
user
,
token
};
}
export
async
function
registerUser
({
username
,
avatar
,
res
}:
{
username
:
string
;
avatar
?:
string
;
res
:
NextApiResponse
;
})
{
const
response
=
await
User
.
create
({
username
,
avatar
,
password
:
nanoid
()
});
// 根据 id 获取用户信息
const
user
=
await
User
.
findById
(
response
.
_id
);
if
(
!
user
)
{
throw
new
Error
(
'获取用户信息异常'
);
}
const
token
=
generateToken
(
user
.
_id
);
setCookie
(
res
,
token
);
return
{
user
,
token
};
}
client/src/pages/login/components/LoginForm.tsx
View file @
ce729dff
import
React
,
{
useState
,
Dispatch
,
useCallback
}
from
'react'
;
import
React
,
{
useState
,
Dispatch
,
useCallback
}
from
'react'
;
import
{
FormControl
,
Flex
,
Input
,
Button
,
FormErrorMessage
,
Box
}
from
'@chakra-ui/react'
;
import
{
FormControl
,
Flex
,
Input
,
Button
,
FormErrorMessage
,
Box
}
from
'@chakra-ui/react'
;
import
{
useForm
}
from
'react-hook-form'
;
import
{
useForm
}
from
'react-hook-form'
;
import
{
useRouter
}
from
'next/router'
;
import
{
PageTypeEnum
}
from
'@/constants/user'
;
import
{
PageTypeEnum
}
from
'@/constants/user'
;
import
{
postLogin
}
from
'@/api/user'
;
import
{
postLogin
}
from
'@/api/user'
;
import
type
{
ResLogin
}
from
'@/api/response/user'
;
import
type
{
ResLogin
}
from
'@/api/response/user'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
feConfigs
}
from
'@/store/static'
;
import
{
feConfigs
}
from
'@/store/static'
;
import
{
useGlobalStore
}
from
'@/store/global'
;
import
MyIcon
from
'@/components/Icon'
;
interface
Props
{
interface
Props
{
setPageType
:
Dispatch
<
`
${
PageTypeEnum
}
`
>
;
setPageType
:
Dispatch
<
`
${
PageTypeEnum
}
`
>
;
...
@@ -18,7 +21,10 @@ interface LoginFormType {
...
@@ -18,7 +21,10 @@ interface LoginFormType {
}
}
const
LoginForm
=
({
setPageType
,
loginSuccess
}:
Props
)
=>
{
const
LoginForm
=
({
setPageType
,
loginSuccess
}:
Props
)
=>
{
const
router
=
useRouter
();
const
{
lastRoute
=
'/app/list'
}
=
router
.
query
as
{
lastRoute
:
string
};
const
{
toast
}
=
useToast
();
const
{
toast
}
=
useToast
();
const
{
setLoginStore
}
=
useGlobalStore
();
const
{
const
{
register
,
register
,
handleSubmit
,
handleSubmit
,
...
@@ -52,6 +58,19 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
...
@@ -52,6 +58,19 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
[
loginSuccess
,
toast
]
[
loginSuccess
,
toast
]
);
);
const
onclickGit
=
useCallback
(()
=>
{
setLoginStore
({
provider
:
'git'
,
lastRoute
});
router
.
replace
(
`https://github.com/login/oauth/authorize?client_id=
${
feConfigs
?.
gitLoginKey
}
&
redirect_uri
=
$
{
`
${
location
.
origin
}
/login/provider`
}
&
scope
=
user
:
email
%
20
read
:
user
`,
'_self'
);
}, [lastRoute, setLoginStore]);
return (
return (
<>
<>
<Box fontWeight={'bold'} fontSize={'2xl'} textAlign={'center'}>
<Box fontWeight={'bold'} fontSize={'2xl'} textAlign={'center'}>
...
@@ -117,6 +136,17 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
...
@@ -117,6 +136,17 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
>
>
登录
登录
</Button>
</Button>
{feConfigs?.show_register && (
<Flex mt={10} justifyContent={'center'} alignItems={'center'}>
<MyIcon
name="gitFill"
w={'34px'}
cursor={'pointer'}
color={'myGray.800'}
onClick={onclickGit}
/>
</Flex>
)}
</form>
</form>
</>
</>
);
);
...
...
client/src/pages/login/provider.tsx
0 → 100644
View file @
ce729dff
import
React
,
{
useCallback
,
useEffect
}
from
'react'
;
import
{
useRouter
}
from
'next/router'
;
import
{
useGlobalStore
}
from
'@/store/global'
;
import
{
ResLogin
}
from
'@/api/response/user'
;
import
{
useChatStore
}
from
'@/store/chat'
;
import
{
useUserStore
}
from
'@/store/user'
;
import
{
setToken
}
from
'@/utils/user'
;
import
{
gitLogin
}
from
'@/api/user'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
Loading
from
'@/components/Loading'
;
const
provider
=
()
=>
{
const
{
loginStore
}
=
useGlobalStore
();
const
{
setLastChatId
,
setLastChatAppId
}
=
useChatStore
();
const
{
setUserInfo
}
=
useUserStore
();
const
router
=
useRouter
();
const
{
toast
}
=
useToast
();
const
{
code
}
=
router
.
query
as
{
code
?:
string
};
const
loginSuccess
=
useCallback
(
(
res
:
ResLogin
)
=>
{
// init store
setLastChatId
(
''
);
setLastChatAppId
(
''
);
setUserInfo
(
res
.
user
);
setToken
(
res
.
token
);
setTimeout
(()
=>
{
router
.
push
(
loginStore
?.
lastRoute
?
decodeURIComponent
(
loginStore
?.
lastRoute
)
:
'/app/list'
);
},
100
);
},
[
setLastChatId
,
setLastChatAppId
,
setUserInfo
,
router
,
loginStore
?.
lastRoute
]
);
const
authCode
=
useCallback
(
async
()
=>
{
if
(
!
code
)
return
;
if
(
!
loginStore
)
{
router
.
replace
(
'/login'
);
return
;
}
try
{
const
res
=
await
(
async
()
=>
{
if
(
loginStore
.
provider
===
'git'
)
{
return
gitLogin
(
code
);
}
return
null
;
})();
if
(
!
res
)
{
toast
({
status
:
'warning'
,
title
:
'登录异常'
});
return
router
.
replace
(
'/login'
);
}
loginSuccess
(
res
);
}
catch
(
error
)
{
toast
({
status
:
'warning'
,
title
:
'登录异常'
});
router
.
replace
(
'/login'
);
}
},
[
code
,
loginStore
,
loginSuccess
]);
useEffect
(()
=>
{
authCode
();
},
[
authCode
]);
return
<
Loading
/>;
};
export
default
provider
;
client/src/store/global.ts
View file @
ce729dff
import
{
create
}
from
'zustand'
;
import
{
create
}
from
'zustand'
;
import
{
devtools
}
from
'zustand/middleware'
;
import
{
devtools
,
persist
}
from
'zustand/middleware'
;
import
{
immer
}
from
'zustand/middleware/immer'
;
import
{
immer
}
from
'zustand/middleware/immer'
;
import
axios
from
'axios'
;
import
axios
from
'axios'
;
type
LoginStoreType
=
{
provider
:
'git'
;
lastRoute
:
string
};
type
State
=
{
type
State
=
{
loginStore
?:
LoginStoreType
;
setLoginStore
:
(
e
:
LoginStoreType
)
=>
void
;
loading
:
boolean
;
loading
:
boolean
;
setLoading
:
(
val
:
boolean
)
=>
null
;
setLoading
:
(
val
:
boolean
)
=>
null
;
screenWidth
:
number
;
screenWidth
:
number
;
...
@@ -16,39 +20,53 @@ type State = {
...
@@ -16,39 +20,53 @@ type State = {
export
const
useGlobalStore
=
create
<
State
>
()(
export
const
useGlobalStore
=
create
<
State
>
()(
devtools
(
devtools
(
immer
((
set
,
get
)
=>
({
persist
(
loading
:
false
,
immer
((
set
,
get
)
=>
({
setLoading
:
(
val
:
boolean
)
=>
{
loginStore
:
undefined
,
set
((
state
)
=>
{
setLoginStore
(
e
)
{
state
.
loading
=
val
;
set
((
state
)
=>
{
});
state
.
loginStore
=
e
;
return
null
;
});
},
},
screenWidth
:
600
,
loading
:
false
,
setScreenWidth
(
val
:
number
)
{
setLoading
:
(
val
:
boolean
)
=>
{
set
((
state
)
=>
{
set
((
state
)
=>
{
state
.
screenWidth
=
val
;
state
.
loading
=
val
;
state
.
isPc
=
val
<
900
?
false
:
true
;
});
});
return
null
;
},
},
isPc
:
undefined
,
screenWidth
:
600
,
initIsPc
(
val
:
boolean
)
{
setScreenWidth
(
val
:
number
)
{
if
(
get
().
isPc
!==
undefined
)
return
;
set
((
state
)
=>
{
state
.
screenWidth
=
val
;
set
((
state
)
=>
{
state
.
isPc
=
val
<
900
?
false
:
true
;
state
.
isPc
=
val
;
});
});
},
},
isPc
:
undefined
,
gitStar
:
2700
,
initIsPc
(
val
:
boolean
)
{
async
loadGitStar
()
{
if
(
get
().
isPc
!==
undefined
)
return
;
try
{
const
{
data
:
git
}
=
await
axios
.
get
(
'https://api.github.com/repos/labring/FastGPT'
);
set
((
state
)
=>
{
set
((
state
)
=>
{
state
.
gitStar
=
git
.
stargazers_count
;
state
.
isPc
=
val
;
});
});
}
catch
(
error
)
{}
},
gitStar
:
2700
,
async
loadGitStar
()
{
try
{
const
{
data
:
git
}
=
await
axios
.
get
(
'https://api.github.com/repos/labring/FastGPT'
);
set
((
state
)
=>
{
state
.
gitStar
=
git
.
stargazers_count
;
});
}
catch
(
error
)
{}
}
})),
{
name
:
'globalStore'
,
partialize
:
(
state
)
=>
({
loginStore
:
state
.
loginStore
})
}
}
})
)
)
)
)
);
);
client/src/types/index.d.ts
View file @
ce729dff
...
@@ -24,9 +24,11 @@ export type FeConfigsType = {
...
@@ -24,9 +24,11 @@ export type FeConfigsType = {
beianText
?:
string
;
beianText
?:
string
;
googleClientVerKey
?:
string
;
googleClientVerKey
?:
string
;
baiduTongjiUrl
?:
string
;
baiduTongjiUrl
?:
string
;
gitLoginKey
?:
string
;
};
};
export
type
SystemEnvType
=
{
export
type
SystemEnvType
=
{
googleServiceVerKey
?:
string
;
googleServiceVerKey
?:
string
;
gitLoginSecret
?:
string
;
vectorMaxProcess
:
number
;
vectorMaxProcess
:
number
;
qaMaxProcess
:
number
;
qaMaxProcess
:
number
;
pgIvfflatProbe
:
number
;
pgIvfflatProbe
:
number
;
...
...
client/src/utils/tools.ts
View file @
ce729dff
...
@@ -53,6 +53,36 @@ export const Obj2Query = (obj: Record<string, string | number>) => {
...
@@ -53,6 +53,36 @@ export const Obj2Query = (obj: Record<string, string | number>) => {
return
queryParams
.
toString
();
return
queryParams
.
toString
();
};
};
export
const
parseQueryString
=
(
str
:
string
)
=>
{
const
queryObject
:
Record
<
string
,
any
>
=
{};
const
splitStr
=
str
.
split
(
'?'
);
str
=
splitStr
[
1
]
||
splitStr
[
0
];
// 将字符串按照 '&' 分割成键值对数组
const
keyValuePairs
=
str
.
split
(
'&'
);
// 遍历键值对数组,将每个键值对解析为对象的属性和值
keyValuePairs
.
forEach
(
function
(
keyValuePair
)
{
const
pair
=
keyValuePair
.
split
(
'='
);
const
key
=
decodeURIComponent
(
pair
[
0
]);
const
value
=
decodeURIComponent
(
pair
[
1
]
||
''
);
// 如果对象中已经存在该属性,则将值转换为数组
if
(
queryObject
.
hasOwnProperty
(
key
))
{
if
(
!
Array
.
isArray
(
queryObject
[
key
]))
{
queryObject
[
key
]
=
[
queryObject
[
key
]];
}
queryObject
[
key
].
push
(
value
);
}
else
{
queryObject
[
key
]
=
value
;
}
});
return
queryObject
;
};
/**
/**
* 格式化时间成聊天格式
* 格式化时间成聊天格式
*/
*/
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment