Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
何 广一
chatroom
Commits
496f7668
Commit
496f7668
authored
Aug 26, 2025
by
何 广一
Browse files
prisma & auth api
parent
35afac8f
Changes
26
Hide whitespace changes
Inline
Side-by-side
chat-room/src/app/api/tmp-state.tsx
deleted
100644 → 0
View file @
35afac8f
import
*
as
Ty
from
'
../interface
'
export
interface
RoomInfo
extends
Ty
.
RoomPreviewInfo
{
creator
:
string
;
}
export
let
State
=
{
maxRoomId
:
2
as
number
,
maxMessageId
:
3
as
number
,
fakeRooms
:
[
{
roomId
:
1
,
roomName
:
'
房间1
'
,
lastMessage
:
null
,
creator
:
'
用户1
'
},
{
roomId
:
2
,
roomName
:
'
房间2
'
,
lastMessage
:
null
,
creator
:
'
用户2
'
}
]
as
RoomInfo
[],
fakeMsg
:
[
{
messageId
:
1
,
roomId
:
1
,
sender
:
'
用户1
'
,
content
:
'
你好
'
,
time
:
Date
.
now
()
},
{
messageId
:
2
,
roomId
:
1
,
sender
:
'
用户2
'
,
content
:
'
你好啊
'
,
time
:
Date
.
now
()
},
{
messageId
:
3
,
roomId
:
2
,
sender
:
'
用户1
'
,
content
:
'
房间2的消息
'
,
time
:
Date
.
now
()
}
]
as
Ty
.
Message
[],
}
chat-room/src/app/chat/room-list.tsx
View file @
496f7668
...
...
@@ -2,6 +2,7 @@
import
{
useState
,
useEffect
}
from
'
react
'
import
*
as
Ty
from
'
../interface
'
import
{
useRoom
}
from
'
./room-context
'
import
{
notAuthorizedToDeleteRoomErrorCode
}
from
'
../api/login-interface
'
export
default
function
RoomListProvider
()
{
...
...
@@ -119,13 +120,13 @@ export default function RoomListProvider() {
/*
Error : You are not the creator. deletion failed.
{
"code": 2
,
"code":
notAuthorizedToDeleteRoomErrorCode,// code=
2
"message": "auth error: invalid identity",
"data": null
}
*/
if
(
data
.
code
===
2
)
{
if
(
data
.
code
===
notAuthorizedToDeleteRoomErrorCode
)
{
triggerErrorModal
(
'
你不是房间创建者,无法删除房间。
'
);
return
false
;
}
...
...
chat-room/src/app/chat/send-message.tsx
View file @
496f7668
...
...
@@ -14,6 +14,7 @@ export default function SendMessageProvider() {
}
=
useRoom
()
const
[
text
,
setText
]
=
useState
(
''
);
const
[
sendError
,
setSendError
]
=
useState
<
string
|
null
>
(
null
);
const
[
pending
,
setPending
]
=
useState
(
false
);
/*
- url: /api/message/add
...
...
@@ -27,9 +28,13 @@ export default function SendMessageProvider() {
- response: null (只要code为0即为成功)
*/
const
sendMessage
=
async
()
=>
{
if
(
pending
)
return
;
setPending
(
true
);
setSendError
(
null
);
if
(
!
currentRoomInfo
)
return
;
if
(
!
text
.
trim
())
return
;
const
Args
:
Ty
.
MessageAddArgs
=
{
roomId
:
currentRoomInfo
.
roomId
,
...
...
@@ -54,10 +59,12 @@ export default function SendMessageProvider() {
}
else
{
throw
new
Error
(
`发送失败:
${
data
.
message
}
`
);
}
}
catch
(
error
)
{
}
catch
(
error
:
any
)
{
setSendError
(
error
.
message
);
}
finally
{
setPending
(
false
);
}
}
}
useEffect
(()
=>
{
if
(
sendError
)
{
...
...
@@ -78,7 +85,7 @@ export default function SendMessageProvider() {
/>
<
button
className
=
{
currentRoomInfo
&&
text
.
length
?
currentRoomInfo
&&
text
.
length
&&
!
pending
?
"
bg-blue-500 text-white py-2 px-4 mt-2 w-full hover:bg-blue-600 active:bg-blue-700
"
:
"
bg-gray-300 text-gray-500 py-2 px-4 mt-2 w-full cursor-not-allowed
"
}
...
...
chat-room/src/app/interface.tsx
View file @
496f7668
...
...
@@ -4,7 +4,7 @@
===============后端选择===============
======================================
*/
const
useLocalBackend
:
boolean
=
true
;
export
const
useLocalBackend
:
boolean
=
true
;
/*
======================================
======================================
...
...
chat-room/src/app/page.tsx
View file @
496f7668
'
use client
'
;
import
{
useState
}
from
'
react
'
;
import
{
useEffect
,
useState
}
from
'
react
'
;
import
{
useRouter
}
from
'
next/navigation
'
;
import
{
useLocalBackend
}
from
'
./interface
'
;
import
*
as
Auth
from
'
./api/login-interface
'
;
import
*
as
Ty
from
'
./interface
'
;
export
default
function
Home
()
{
function
Home_Local
(){
const
[
isLogin
,
setIsLogin
]
=
useState
(
true
);
const
[
username
,
setUsername
]
=
useState
(
''
);
const
[
password
,
setPassword
]
=
useState
(
''
);
const
[
confirmPassword
,
setConfirmPassword
]
=
useState
(
''
);
const
[
session
,
setSession
]
=
useState
<
Auth
.
Session
|
null
>
(
null
);
const
[
pageError
,
setPageError
]
=
useState
<
string
|
null
>
(
null
);
const
[
isErrorInstant
,
setIsErrorInstant
]
=
useState
(
false
);
const
[
pending
,
setPending
]
=
useState
(
false
);
const
router
=
useRouter
();
const
initSession
=
async
()
=>
{
//url : /api/auth/startSession
setPageError
(
null
);
setIsErrorInstant
(
false
);
try
{
const
res
=
await
fetch
(
`
${
Ty
.
urlPrefix
}
/api/auth/startSession`
,
{
method
:
'
GET
'
,
});
if
(
!
res
.
ok
)
{
throw
new
Error
(
'
无法获取会话信息,错误码:
'
+
res
.
status
);
}
const
data
:
Ty
.
BackendResponse
<
Auth
.
Session
>
=
await
res
.
json
();
if
(
data
.
code
!==
0
)
{
throw
new
Error
(
'
会话获取错误,错误信息:
'
+
data
.
message
);
}
setSession
(
data
.
data
);
}
catch
(
error
)
{
console
.
error
(
error
);
setSession
(
null
);
setPageError
(
'
无法初始化会话,请重试
'
);
setIsErrorInstant
(
true
);
}
}
useEffect
(()
=>
{
initSession
();
},
[]);
const
updatePassword
=
(
value
:
string
,
confirm
:
string
|
null
)
=>
{
setPassword
(
value
);
if
(
value
.
length
<
Auth
.
minPasswordLength
)
{
setPageError
(
`密码长度不能少于
${
Auth
.
minPasswordLength
}
个字符`
);
setIsErrorInstant
(
true
);
}
else
if
(
value
.
length
>
Auth
.
maxPasswordLength
)
{
setPageError
(
`密码长度不能超过
${
Auth
.
maxPasswordLength
}
个字符`
);
setIsErrorInstant
(
true
);
}
else
{
setPageError
(
null
);
setIsErrorInstant
(
false
);
}
if
(
confirm
)
{
if
(
value
!==
confirm
)
{
setPageError
(
'
两次输入的密码不一致
'
);
setIsErrorInstant
(
true
);
}
else
{
setPageError
(
null
);
setIsErrorInstant
(
false
);
}
}
}
const
updateConfirmPassword
=
(
password
:
string
,
value
:
string
)
=>
{
setConfirmPassword
(
value
);
if
(
password
!==
value
)
{
setPageError
(
'
两次输入的密码不一致
'
);
setIsErrorInstant
(
true
);
}
else
{
setPageError
(
null
);
setIsErrorInstant
(
false
);
}
};
const
handleSubmit
=
async
(
asLogin
:
boolean
)
=>
{
setPending
(
true
);
setPageError
(
null
);
setIsErrorInstant
(
false
);
const
Args
:
Auth
.
AuthArgs
=
{
sessionId
:
session
?.
sessionId
||
''
,
username
,
password
,
userAgent
:
navigator
.
userAgent
,
userIP
:
null
// 服务端填充
};
try
{
if
(
asLogin
)
{
//URL : /api/auth/login
const
res
=
await
fetch
(
`
${
Ty
.
urlPrefix
}
/api/auth/login`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
},
body
:
JSON
.
stringify
(
Args
),
});
if
(
!
res
.
ok
)
{
throw
new
Error
(
'
登录失败,错误码:
'
+
res
.
status
);
}
const
data
:
Ty
.
BackendResponse
<
Auth
.
LoginResult
>
=
await
res
.
json
();
if
(
data
.
code
!==
0
)
{
throw
new
Error
(
'
登录失败,错误信息:
'
+
data
.
message
);
}
router
.
push
(
`/chat?username=
${
encodeURIComponent
(
username
)}
`
);
}
else
{
//URL : /api/auth/register
const
res
=
await
fetch
(
`
${
Ty
.
urlPrefix
}
/api/auth/register`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
},
body
:
JSON
.
stringify
(
Args
),
});
if
(
!
res
.
ok
)
{
throw
new
Error
(
'
注册失败,错误码:
'
+
res
.
status
);
}
const
data
:
Ty
.
BackendResponse
<
Auth
.
RegisterResult
>
=
await
res
.
json
();
if
(
data
.
code
!==
0
)
{
throw
new
Error
(
'
注册失败,错误信息:
'
+
data
.
message
);
}
router
.
refresh
();
}
}
catch
(
error
)
{
console
.
error
(
error
);
setPageError
((
asLogin
?
'
登录错误:
'
:
'
注册错误:
'
)
+
error
.
message
);
setIsErrorInstant
(
false
);
}
finally
{
setPending
(
false
);
}
};
return
(
<
div
className
=
"bg-gray-100 h-screen flex flex-col items-center justify-center relative"
>
<
h1
className
=
"text-2xl mb-4"
>
请输入用户名
</
h1
>
<
input
className
=
"border border-gray-300 rounded px-3 py-2 mb-4"
type
=
"text"
placeholder
=
"用户名"
value
=
{
username
}
onChange
=
{
(
e
)
=>
setUsername
(
e
.
target
.
value
.
trim
())
}
required
/>
<
input
className
=
"border border-gray-300 rounded px-3 py-2 mb-4"
type
=
"password"
value
=
{
password
}
placeholder
=
"密码"
onChange
=
{
(
e
)
=>
updatePassword
(
e
.
target
.
value
.
trim
(),
isLogin
?
null
:
confirmPassword
)
}
required
/>
{
!
isLogin
&&
<
input
className
=
"border border-gray-300 rounded px-3 py-2 mb-4"
type
=
"password"
value
=
{
confirmPassword
}
placeholder
=
"确认密码"
onChange
=
{
(
e
)
=>
updateConfirmPassword
(
password
,
e
.
target
.
value
.
trim
())
}
required
/>
}
<
p
className
=
"text-red-500 mb-4"
>
{
pageError
!==
null
&&
pageError
}
</
p
>
<
div
className
=
"flex flex-row"
>
<
button
className
=
{
username
.
length
&&
password
.
length
&&
session
&&
!
pending
&&
!
(
pageError
&&
isErrorInstant
)
?
"
bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 active:bg-blue-700
"
:
"
bg-gray-300 text-gray-500 px-4 py-2 rounded cursor-not-allowed
"
}
onClick
=
{
()
=>
handleSubmit
(
isLogin
)
}
>
{
isLogin
?
'
登录
'
:
'
注册
'
}
</
button
>
<
button
className
=
"
bg-blue-500
text-white
px-4
py-2
rounded
ml-4
hover
:
bg-blue-600
active
:
bg-blue-700
"
onClick
=
{
()
=>
setIsLogin
(
v
=>
!
v
)
}
>
{
isLogin
?
'
切换到注册
'
:
'
切换到登录
'
}
</
button
>
</
div
>
</
div
>
);
}
function
Home_External
()
{
const
[
username
,
setUsername
]
=
useState
(
''
);
const
router
=
useRouter
();
const
handleSubmit
=
()
=>
{
if
(
!
username
.
trim
())
return
;
// 简单校验
/* 这段后面换成登录 */
if
(
!
username
.
trim
())
return
;
router
.
push
(
`/chat?username=
${
encodeURIComponent
(
username
)}
`
);
};
...
...
@@ -35,4 +233,10 @@ export default function Home() {
</
button
>
</
div
>
);
}
const
HomeFn
=
useLocalBackend
?
Home_Local
:
Home_External
;
export
default
function
Home
()
{
return
HomeFn
();
}
\ No newline at end of file
chat-room/src/lib/db.ts
0 → 100644
View file @
496f7668
import
{
PrismaClient
}
from
"
@/generated/prisma
"
;
import
{
createClient
,
RedisClientType
}
from
'
redis
'
;
import
{
RateLimiterRedis
}
from
'
rate-limiter-flexible
'
;
declare
global
{
var
prisma
:
PrismaClient
|
undefined
;
var
redisClient
:
RedisClientType
|
undefined
;
var
rateLimiter
:
RateLimiterRedis
|
undefined
;
}
const
connectRedisClient
=
async
()
=>
{
const
client
=
createClient
();
client
.
on
(
'
error
'
,
(
err
)
=>
console
.
error
(
'
Redis Client Error
'
,
err
));
await
client
.
connect
();
return
client
;
};
const
getRateLimiter
=
()
=>
{
const
rateLimiter
=
new
RateLimiterRedis
({
storeClient
:
redis
,
keyPrefix
:
'
rateLimiter
'
,
points
:
5
,
// 5 points
duration
:
1
,
// per second
});
return
rateLimiter
;
};
export
const
prisma
=
global
.
prisma
||
new
PrismaClient
();
export
const
redis
=
global
.
redisClient
||
await
connectRedisClient
();
export
const
rateLimiter
=
global
.
rateLimiter
||
getRateLimiter
();
if
(
process
.
env
.
NODE_ENV
!==
'
production
'
){
global
.
prisma
=
prisma
;
}
\ No newline at end of file
Prev
1
2
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new 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