diff --git a/chat-room-backend/.vscode/launch.json b/chat-room-backend/.vscode/launch.json new file mode 100644 index 0000000..36a2139 --- /dev/null +++ b/chat-room-backend/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "debug nest", + "request": "launch", + "runtimeExecutable": "npm", + "runtimeArgs": [ + "run", + "start:dev" + ], + "console": "integratedTerminal", + "type": "node" + } + ] +} \ No newline at end of file diff --git a/chat-room-backend/package.json b/chat-room-backend/package.json index 1482b38..cae7445 100644 --- a/chat-room-backend/package.json +++ b/chat-room-backend/package.json @@ -25,13 +25,17 @@ "@nestjs/jwt": "^10.2.0", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^10.0.0", + "@nestjs/platform-socket.io": "^10.3.10", + "@nestjs/websockets": "^10.3.10", "@prisma/client": "^5.12.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "minio": "^8.0.1", "nodemailer": "^6.9.13", "redis": "^4.6.13", "reflect-metadata": "^0.1.13", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "socket.io": "^4.7.5" }, "devDependencies": { "@nestjs/cli": "^10.0.0", diff --git a/chat-room-backend/prisma/migrations/20240802135008_chat_history/migration.sql b/chat-room-backend/prisma/migrations/20240802135008_chat_history/migration.sql new file mode 100644 index 0000000..4536b25 --- /dev/null +++ b/chat-room-backend/prisma/migrations/20240802135008_chat_history/migration.sql @@ -0,0 +1,12 @@ +-- CreateTable +CREATE TABLE `ChatHistory` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `content` VARCHAR(500) NOT NULL, + `type` INTEGER NOT NULL, + `chatroomId` INTEGER NOT NULL, + `senderId` INTEGER NOT NULL, + `createTime` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updateTime` DATETIME(3) NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/chat-room-backend/prisma/schema.prisma b/chat-room-backend/prisma/schema.prisma index c5d17a3..f44dfd6 100644 --- a/chat-room-backend/prisma/schema.prisma +++ b/chat-room-backend/prisma/schema.prisma @@ -66,3 +66,14 @@ model UserChatroom { @@id([userId, chatroomId]) } + +model ChatHistory { + id Int @id @default(autoincrement()) + content String @db.VarChar(500) + //聊天记录类型 text:0、image:1、file:2 + type Int + chatroomId Int + senderId Int + createTime DateTime @default(now()) + updateTime DateTime @updatedAt +} \ No newline at end of file diff --git a/chat-room-backend/src/app.module.ts b/chat-room-backend/src/app.module.ts index 3707d2b..e941a28 100644 --- a/chat-room-backend/src/app.module.ts +++ b/chat-room-backend/src/app.module.ts @@ -11,6 +11,9 @@ import { APP_GUARD } from '@nestjs/core'; import { AuthGuard } from './auth.guard'; import { FriendshipModule } from './friendship/friendship.module'; import { ChatroomModule } from './chatroom/chatroom.module'; +import { MinioModule } from './minio/minio.module'; +import { ChatModule } from './chat/chat.module'; +import { ChatHistoryModule } from './chat-history/chat-history.module'; @Module({ imports: [ @@ -31,6 +34,9 @@ import { ChatroomModule } from './chatroom/chatroom.module'; }), FriendshipModule, ChatroomModule, + MinioModule, + ChatModule, + ChatHistoryModule, ], controllers: [AppController], providers: [ diff --git a/chat-room-backend/src/chat-history/chat-history.controller.ts b/chat-room-backend/src/chat-history/chat-history.controller.ts new file mode 100644 index 0000000..55ea6b3 --- /dev/null +++ b/chat-room-backend/src/chat-history/chat-history.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get, Query, Res } from '@nestjs/common'; +import { ChatHistoryService } from './chat-history.service'; + +@Controller('chat-history') +export class ChatHistoryController { + constructor(private readonly chatHistoryService: ChatHistoryService) {} + + @Get('list') + async list(@Query('chatroomId') chatroomId: string) { + return this.chatHistoryService.list(+chatroomId); + } +} diff --git a/chat-room-backend/src/chat-history/chat-history.module.ts b/chat-room-backend/src/chat-history/chat-history.module.ts new file mode 100644 index 0000000..da5bf27 --- /dev/null +++ b/chat-room-backend/src/chat-history/chat-history.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ChatHistoryService } from './chat-history.service'; +import { ChatHistoryController } from './chat-history.controller'; + +@Module({ + controllers: [ChatHistoryController], + providers: [ChatHistoryService], + exports: [ChatHistoryService], +}) +export class ChatHistoryModule {} diff --git a/chat-room-backend/src/chat-history/chat-history.service.ts b/chat-room-backend/src/chat-history/chat-history.service.ts new file mode 100644 index 0000000..4e103df --- /dev/null +++ b/chat-room-backend/src/chat-history/chat-history.service.ts @@ -0,0 +1,26 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ChatHistory } from '@prisma/client'; +import { PrismaService } from 'src/prisma/prisma.service'; + +export type HistoryDto = Pick; + +@Injectable() +export class ChatHistoryService { + @Inject(PrismaService) + private prismaService: PrismaService; + + async list(chatroomId: number) { + return this.prismaService.chatHistory.findMany({ + where: { + chatroomId + } + }); + } + + async add(chatroomId: number, history: HistoryDto) { + return this.prismaService.chatHistory.create({ + data: history + }); + } + +} diff --git a/chat-room-backend/src/chat/chat.gateway.ts b/chat-room-backend/src/chat/chat.gateway.ts new file mode 100644 index 0000000..07e9070 --- /dev/null +++ b/chat-room-backend/src/chat/chat.gateway.ts @@ -0,0 +1,60 @@ +import { ChatHistory } from '@prisma/client'; +import { MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer } from '@nestjs/websockets'; +import { ChatService } from './chat.service'; +import { Server, Socket } from 'socket.io'; +import { ChatHistoryService } from 'src/chat-history/chat-history.service'; +import { Inject } from '@nestjs/common'; + +interface JoinRoomPayload { + chatroomId: number + userId: number +} + +interface SendMessagePayload { + sendUserId: number; + chatroomId: number; + message: { + type: 'text' | 'image', + content: string + } +} + +@WebSocketGateway({cors: { origin: '*' }}) +export class ChatGateway { + constructor(private readonly chatService: ChatService) {} + + @WebSocketServer() server: Server; + + @SubscribeMessage('joinRoom') + joinRoom(client: Socket, payload: JoinRoomPayload): void { + const roomName = payload.chatroomId.toString(); + + client.join(roomName) + + this.server.to(roomName).emit('message', { + type: 'joinRoom', + userId: payload.userId + }); + } + + @Inject(ChatHistoryService) + private chatHistoryService: ChatHistoryService + + @SubscribeMessage('sendMessage') + async sendMessage(@MessageBody() payload: SendMessagePayload) { + const roomName = payload.chatroomId.toString(); + + await this.chatHistoryService.add(payload.chatroomId, { + content: payload.message.content, + type: payload.message.type === 'image' ? 1 : 0, + chatroomId: payload.chatroomId, + senderId: payload.sendUserId + }); + + this.server.to(roomName).emit('message', { + type: 'sendMessage', + userId: payload.sendUserId, + message: payload.message + }); + } +} diff --git a/chat-room-backend/src/chat/chat.module.ts b/chat-room-backend/src/chat/chat.module.ts new file mode 100644 index 0000000..58f205f --- /dev/null +++ b/chat-room-backend/src/chat/chat.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ChatService } from './chat.service'; +import { ChatGateway } from './chat.gateway'; +import { ChatHistoryModule } from 'src/chat-history/chat-history.module'; + +@Module({ + imports: [ChatHistoryModule], + providers: [ChatGateway, ChatService], +}) +export class ChatModule {} diff --git a/chat-room-backend/src/chat/chat.service.ts b/chat-room-backend/src/chat/chat.service.ts new file mode 100644 index 0000000..408edcc --- /dev/null +++ b/chat-room-backend/src/chat/chat.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class ChatService {} diff --git a/chat-room-backend/src/chatroom/chatroom.controller.ts b/chat-room-backend/src/chatroom/chatroom.controller.ts index 002a6f2..7cb12a7 100644 --- a/chat-room-backend/src/chatroom/chatroom.controller.ts +++ b/chat-room-backend/src/chatroom/chatroom.controller.ts @@ -21,11 +21,11 @@ export class ChatroomController { } @Get('list') - async list(@UserInfo('userId') userId: number) { + async list(@UserInfo('userId') userId: number, @Query('name') name: string) { if(!userId) { throw new BadRequestException('userId 不能为空') } - return this.chatroomService.list(userId); + return this.chatroomService.list(userId, name); } @Get('members') diff --git a/chat-room-backend/src/chatroom/chatroom.service.ts b/chat-room-backend/src/chatroom/chatroom.service.ts index cfe3738..cfcf573 100644 --- a/chat-room-backend/src/chatroom/chatroom.service.ts +++ b/chat-room-backend/src/chatroom/chatroom.service.ts @@ -49,7 +49,7 @@ export class ChatroomService { return '创建成功' } - async list(userId: number) { + async list(userId: number, name: string) { const chatroomIds = await this.prismaService.userChatroom.findMany({ where: { userId @@ -62,6 +62,9 @@ export class ChatroomService { where: { id: { in: chatroomIds.map(item => item.chatroomId) + }, + name: { + contains: name } }, select: { diff --git a/chat-room-backend/src/friendship/dto/friend-add.dto.ts b/chat-room-backend/src/friendship/dto/friend-add.dto.ts index 0ab4e4a..e318d54 100644 --- a/chat-room-backend/src/friendship/dto/friend-add.dto.ts +++ b/chat-room-backend/src/friendship/dto/friend-add.dto.ts @@ -3,9 +3,9 @@ import { IsNotEmpty } from "class-validator"; export class FriendAddDto { @IsNotEmpty({ - message: "添加的好友 id 不能为空" + message: "添加好友的 username 不能为空" }) - friendId: number; + username: string; reason: string; } diff --git a/chat-room-backend/src/friendship/friendship.controller.ts b/chat-room-backend/src/friendship/friendship.controller.ts index 6c24e8f..8f90513 100644 --- a/chat-room-backend/src/friendship/friendship.controller.ts +++ b/chat-room-backend/src/friendship/friendship.controller.ts @@ -35,8 +35,8 @@ export class FriendshipController { } @Get('list') - async friendship(@UserInfo('userId') userId: number) { - return this.friendshipService.getFriendship(userId); + async friendship(@UserInfo('userId') userId: number, @Query('name') name: string) { + return this.friendshipService.getFriendship(userId, name); } @Get('remove/:id') diff --git a/chat-room-backend/src/friendship/friendship.service.ts b/chat-room-backend/src/friendship/friendship.service.ts index edc4e02..c99e28a 100644 --- a/chat-room-backend/src/friendship/friendship.service.ts +++ b/chat-room-backend/src/friendship/friendship.service.ts @@ -1,5 +1,5 @@ import { FriendAddDto } from './dto/friend-add.dto'; -import { Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { PrismaService } from 'src/prisma/prisma.service'; @Injectable() @@ -9,10 +9,35 @@ export class FriendshipService { private prismaService: PrismaService; async add(friendAddDto: FriendAddDto, userId: number) { + const friend = await this.prismaService.user.findUnique({ + where: { + username: friendAddDto.username + } + }); + + if(!friend) { + throw new BadRequestException('要添加的 username 不存在'); + } + + if(friend.id === userId) { + throw new BadRequestException('不能添加自己为好友'); + } + + const found = await this.prismaService.friendship.findMany({ + where: { + userId, + friendId: friend.id + } + }) + + if(found.length) { + throw new BadRequestException('该好友已经添加过'); + } + return await this.prismaService.friendRequest.create({ data: { fromUserId: userId, - toUserId: friendAddDto.friendId, + toUserId: friend.id, reason: friendAddDto.reason, status: 0 } @@ -20,38 +45,101 @@ export class FriendshipService { } async list(userId: number) { - return this.prismaService.friendRequest.findMany({ + const fromMeRequest = await this.prismaService.friendRequest.findMany({ where: { fromUserId: userId } }) + + const toMeRequest = await this.prismaService.friendRequest.findMany({ + where: { + toUserId: userId + } + }) + + const res = { + toMe: [], + fromMe: [] + } + + for (let i = 0; i < fromMeRequest.length; i++) { + const user = await this.prismaService.user.findUnique({ + where: { + id: fromMeRequest[i].toUserId + }, + select: { + id: true, + username: true, + nickName: true, + email: true, + headPic: true, + createTime: true + } + }) + res.fromMe.push({ + ...fromMeRequest[i], + toUser: user + }) + } + + for (let i = 0; i < toMeRequest.length; i++) { + const user = await this.prismaService.user.findUnique({ + where: { + id: toMeRequest[i].fromUserId + }, + select: { + id: true, + username: true, + nickName: true, + email: true, + headPic: true, + createTime: true + } + }) + res.toMe.push({ + ...toMeRequest[i], + fromUser: user + }) + } + + return res; } async agree(friendId: number, userId: number) { await this.prismaService.friendRequest.updateMany({ where: { - fromUserId: userId, - toUserId: friendId, + fromUserId: friendId, + toUserId: userId, status: 0 }, data: { status: 1 } }) - await this.prismaService.friendship.create({ - data: { + + const res = await this.prismaService.friendship.findMany({ + where: { userId, friendId } }) + + if(!res.length) { + await this.prismaService.friendship.create({ + data: { + userId, + friendId + } + }) + } return '添加成功' } async reject(friendId: number, userId: number) { await this.prismaService.friendRequest.updateMany({ where: { - fromUserId: userId, - toUserId: friendId, + fromUserId: friendId, + toUserId: userId, status: 0 }, data: { @@ -61,7 +149,7 @@ export class FriendshipService { return '已拒绝' } - async getFriendship(userId: number) { + async getFriendship(userId: number, name: string) { const foundUser = await this.prismaService.user.findUnique({ where: { id: userId @@ -74,7 +162,10 @@ export class FriendshipService { return this.prismaService.user.findMany({ where: { id: { - in: foundUser.friends.map(item => item.friendId) + in: foundUser.friends.map(item => item.friendId) + }, + nickName: { + contains: name } }, select: { diff --git a/chat-room-backend/src/main.ts b/chat-room-backend/src/main.ts index 1576d34..dd5effa 100644 --- a/chat-room-backend/src/main.ts +++ b/chat-room-backend/src/main.ts @@ -7,6 +7,9 @@ async function bootstrap() { app.useGlobalPipes(new ValidationPipe({ transform: true})); - await app.listen(3000); + app.enableCors({ + exposedHeaders: ['Token'] + }); + await app.listen(3005); } bootstrap(); diff --git a/chat-room-backend/src/minio/minio.controller.ts b/chat-room-backend/src/minio/minio.controller.ts new file mode 100644 index 0000000..17672fa --- /dev/null +++ b/chat-room-backend/src/minio/minio.controller.ts @@ -0,0 +1,14 @@ +import { Controller, Get, Inject, Query } from '@nestjs/common'; +import * as Minio from 'minio'; + +@Controller('minio') +export class MinioController { + + @Inject('MINIO_CLIENT') + private minioClient: Minio.Client; + + @Get('presignedUrl') + presignedPutObject(@Query('name') name: string) { + return this.minioClient.presignedPutObject('chat-room', name, 3600); + } +} diff --git a/chat-room-backend/src/minio/minio.module.ts b/chat-room-backend/src/minio/minio.module.ts new file mode 100644 index 0000000..65b8679 --- /dev/null +++ b/chat-room-backend/src/minio/minio.module.ts @@ -0,0 +1,25 @@ +import { Global, Module } from '@nestjs/common'; +import { MinioController } from './minio.controller'; +import * as Minio from 'minio'; + +@Global() +@Module({ + providers: [ + { + provide: 'MINIO_CLIENT', + async useFactory() { + const client = new Minio.Client({ + endPoint: 'localhost', + port: 9000, + useSSL: false, + accessKey: '2Dby02mwgiYZ53HfxqdF', + secretKey: 'lpRC0lMDAtYTBieut43zYm5OscJkD0m05r0yVFqS' + }) + return client; + } + } + ], + exports: ['MINIO_CLIENT'], + controllers: [MinioController] +}) +export class MinioModule {} \ No newline at end of file diff --git a/chat-room-backend/src/user/user.controller.ts b/chat-room-backend/src/user/user.controller.ts index 11ca564..095d069 100644 --- a/chat-room-backend/src/user/user.controller.ts +++ b/chat-room-backend/src/user/user.controller.ts @@ -94,10 +94,10 @@ export class UserController { } @Get('update/captcha') - async updateCaptcha(@Query('address') address: string) { - if(!address) { - throw new BadRequestException('邮箱地址不能为空'); - } + @RequireLogin() + async updateCaptcha(@UserInfo('userId') userId: number) { + const { email: address } = await this.userService.findUserDetailById(userId); + const code = Math.random().toString().slice(2,8); await this.redisService.set(`update_user_captcha_${address}`, code, 10 * 60); diff --git a/chat-room-frontend/package.json b/chat-room-frontend/package.json index 0491899..fe70463 100644 --- a/chat-room-frontend/package.json +++ b/chat-room-frontend/package.json @@ -10,11 +10,13 @@ "preview": "vite preview" }, "dependencies": { + "@ant-design/icons": "^5.4.0", "antd": "^5.19.4", "axios": "^1.7.2", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router-dom": "^6.25.1" + "react-router-dom": "^6.25.1", + "socket.io-client": "^4.7.5" }, "devDependencies": { "@types/react": "^18.3.3", diff --git a/chat-room-frontend/src/App.css b/chat-room-frontend/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/chat-room-frontend/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/chat-room-frontend/src/App.tsx b/chat-room-frontend/src/App.tsx deleted file mode 100644 index afe48ac..0000000 --- a/chat-room-frontend/src/App.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' - -function App() { - const [count, setCount] = useState(0) - - return ( - <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ) -} - -export default App diff --git a/chat-room-frontend/src/index.css b/chat-room-frontend/src/index.css deleted file mode 100644 index 6119ad9..0000000 --- a/chat-room-frontend/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/chat-room-frontend/src/interfaces/index.ts b/chat-room-frontend/src/interfaces/index.ts index 5959799..5b9f6d2 100644 --- a/chat-room-frontend/src/interfaces/index.ts +++ b/chat-room-frontend/src/interfaces/index.ts @@ -1,11 +1,48 @@ import axios from "axios"; import { RegisterUser } from "../pages/Register"; +import { UpdatePassword } from "../pages/UpdatePassword"; +import { UserInfo } from "../pages/UpdateInfo"; +import { message } from "antd"; +import { AddFriend } from "../pages/Friendship/AddFriendModal"; const axiosInstance = axios.create({ baseURL: 'http://localhost:3005/', timeout: 3000 }); +axiosInstance.interceptors.request.use(function (config) { + const accessToken = localStorage.getItem('token'); + + if(accessToken) { + config.headers.authorization = 'Bearer ' + accessToken; + } + return config; +}) + +axiosInstance.interceptors.response.use( + (response) => { + const newToken = response.headers['token']; + if(newToken) { + localStorage.setItem('token', newToken); + } + return response; + }, async (error) => { + if(!error.response) { + return Promise.reject(error); + } + let { data } = error.response; + if (data.statusCode === 401) { + message.error(data.message); + + setTimeout(() => { + window.location.href = '/login'; + }, 1500); + } else { + return Promise.reject(error); + } + } +) + export async function login(username: string, password: string) { return await axiosInstance.post('/user/login', { username, password @@ -22,4 +59,57 @@ export async function registerCaptcha(email: string) { export async function register(registerUser: RegisterUser) { return await axiosInstance.post('/user/register', registerUser); +} + +export async function updatePasswordCaptcha(email: string) { + return await axiosInstance.get('/user/update_password/captcha', { + params: { + address: email + } + }); +} + +export async function updatePassword(data: UpdatePassword) { + return await axiosInstance.post('/user/update_password', data); +} + +export async function getUserInfo() { + return await axiosInstance.get('/user/info'); +} + +export async function updateInfo(data: UserInfo) { + return await axiosInstance.post('/user/update', data); +} + +export async function updateUserInfoCaptcha() { + return await axiosInstance.get('/user/update/captcha'); +} + +export async function presignedUrl(fileName: string) { + return axiosInstance.get(`/minio/presignedUrl?name=${fileName}`); +} + + +export async function friendshipList(name: string) { + return axiosInstance.get(`/friendship/list?name=${name}`); +} + +export async function chatroomList(name: string) { + return axiosInstance.get(`/chatroom/list?name=${name}`); +} + +export async function friendAdd(data: AddFriend) { + return axiosInstance.post('/friendship/add', data); +} + +export async function friendRequestList() { + return axiosInstance.get('/friendship/request_list'); +} + +export async function agreeFriendRequest(id: number) { + return axiosInstance.get(`/friendship/agree/${id}`); +} + +export async function rejectFriendRequest(id: number) { + return axiosInstance.get(`/friendship/reject/${id}`); } \ No newline at end of file diff --git a/chat-room-frontend/src/main.tsx b/chat-room-frontend/src/main.tsx index 8f0f79f..d0d6d3a 100644 --- a/chat-room-frontend/src/main.tsx +++ b/chat-room-frontend/src/main.tsx @@ -3,11 +3,51 @@ import { RouterProvider, createBrowserRouter } from 'react-router-dom'; import { Register } from './pages/Register'; import { Login } from './pages/Login'; import { UpdatePassword } from './pages/UpdatePassword'; +import { Index } from './pages/Index'; +import { UpdateInfo } from './pages/UpdateInfo'; +import { Collection } from './pages/Collection'; +import { Chat } from './pages/Chat'; +import { Notification } from './pages/Notification'; +import { Friendship } from './pages/Friendship'; +import { Menu } from './pages/Menu'; +import { Group } from './pages/Group'; -const routes = [ +export const routes = [ { - path: "/", - element:
index
, + path: "/", + element: , + children: [ + { + path: 'update_info', + element: + }, + { + path: '/', + element: , + children: [ + { + path: '/', + element: + }, + { + path: '/group', + element: + }, + { + path: 'chat', + element: + }, + { + path: 'collection', + element: + }, + { + path: 'notification', + element: + } + ] + } + ] }, { path: "login", @@ -22,7 +62,7 @@ const routes = [ element: , } ]; -const router = createBrowserRouter(routes); +export const router = createBrowserRouter(routes); const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement diff --git a/chat-room-frontend/src/pages/Chat/index.tsx b/chat-room-frontend/src/pages/Chat/index.tsx new file mode 100644 index 0000000..fda8a5f --- /dev/null +++ b/chat-room-frontend/src/pages/Chat/index.tsx @@ -0,0 +1,85 @@ +import { Input } from "antd"; +import { useEffect, useRef, useState } from "react"; +import { io, Socket } from "socket.io-client"; + +interface JoinRoomPayload { + chatroomId: number + userId: number +} + +interface SendMessagePayload { + sendUserId: number; + chatroomId: number; + message: Message +} + +interface Message { + type: 'text' | 'image' + content: string +} + +type Reply = { + type: 'sendMessage' + userId: number + message: Message +} | { + type: 'joinRoom' + userId: number +} + +export function Chat() { + + const [messageList, setMessageList] = useState>([]); + const socketRef = useRef(); + + useEffect(() => { + const socket = socketRef.current = io('http://localhost:3005'); + socket.on('connect', function() { + + const payload: JoinRoomPayload = { + chatroomId: 1, + userId: 1 + } + + socket.emit('joinRoom', payload); + + socket.on('message', (reply: Reply) => { + if(reply.type === 'joinRoom') { + setMessageList(messageList => [...messageList, { + type: 'text', + content: '用户 ' + reply.userId + '加入聊天室' + }]) + } else { + setMessageList(messageList => [...messageList, reply.message]) + } + }); + + }); + }, []); + + function sendMessage(value: string) { + const payload2: SendMessagePayload = { + sendUserId: 1, + chatroomId: 1, + message: { + type: 'text', + content: value + } + } + + socketRef.current?.emit('sendMessage', payload2); + } + + return
+ { + sendMessage(e.target.value); + }}/> +
+ {messageList.map(item => { + return
+ {item.type === 'image' ? : item.content } +
+ })} +
+
+} \ No newline at end of file diff --git a/chat-room-frontend/src/pages/Collection/index.tsx b/chat-room-frontend/src/pages/Collection/index.tsx new file mode 100644 index 0000000..73990f6 --- /dev/null +++ b/chat-room-frontend/src/pages/Collection/index.tsx @@ -0,0 +1,3 @@ +export function Collection() { + return
Collection
+} \ No newline at end of file diff --git a/chat-room-frontend/src/pages/Friendship/AddFriendModal.tsx b/chat-room-frontend/src/pages/Friendship/AddFriendModal.tsx new file mode 100644 index 0000000..86ff3d3 --- /dev/null +++ b/chat-room-frontend/src/pages/Friendship/AddFriendModal.tsx @@ -0,0 +1,77 @@ +import { Button, Form, Input, InputNumber, Modal, message } from "antd"; +import { useForm } from "antd/es/form/Form"; +import TextArea from "antd/es/input/TextArea"; +import { useState } from "react"; +import { friendAdd } from "../../interfaces"; + +interface AddFriendModalProps { + isOpen: boolean; + handleClose: Function +} + +const layout = { + labelCol: { span: 6 }, + wrapperCol: { span: 18 } +} + +export interface AddFriend { + username: string; + reason: string; +} + +export function AddFriendModal(props: AddFriendModalProps) { + + const [form] = useForm(); + + const handleOk = async function() { + await form.validateFields(); + + const values = form.getFieldsValue(); + + try{ + const res = await friendAdd(values); + + if(res.status === 201 || res.status === 200) { + message.success('好友申请已发送'); + form.resetFields(); + props.handleClose(); + } + } catch(e: any){ + message.error(e.response?.data?.message || '系统繁忙,请稍后再试'); + } + } + + return props.handleClose()} + okText={'发送好友请求'} + cancelText={'取消'} + > +
+ + + + +