Skip to content

Commit b29759e

Browse files
authored
Merge pull request #7 from boostcampwm-2024/dev-be
[Deploy] dev-be to prod-be
2 parents 55c6b44 + e332e41 commit b29759e

14 files changed

+156
-119
lines changed

backend/chatServer/src/app.module.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { Module } from '@nestjs/common';
22
import { ChatModule } from './chat/chat.module';
33
import { RoomModule } from './room/room.module';
4+
import { UserModule } from './user/user.module';
5+
import { RedisModule } from './redis/redis.module';
46

57
@Module({
6-
imports: [ChatModule, RoomModule],
8+
imports: [ChatModule, RoomModule, UserModule, RedisModule],
79
providers: [],
810
})
911
export class AppModule {

backend/chatServer/src/chat/chat.gateway.ts

+12-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
} from '../event/dto/IncomingMessage.dto';
2020
import { JoiningRoomDto } from '../event/dto/JoiningRoom.dto';
2121
import { RoomService } from '../room/room.service';
22-
import { createAdapter } from '@socket.io/redis-adapter';
2322
import { BlacklistGuard, HostGuard, MessageGuard } from './chat.guard';
2423
import { LeavingRoomDto } from '../event/dto/LeavingRoom.dto';
2524
import {
@@ -29,18 +28,21 @@ import {
2928
} from '../event/dto/OutgoingMessage.dto';
3029
import { QuestionDto } from '../event/dto/Question.dto';
3130
import { ChatException, CHATTING_SOCKET_ERROR } from './chat.error';
31+
import { RedisAdapterFactory } from '../redis/redis-adapter.factory';
3232

3333
@WebSocketGateway({
3434
cors: true,
3535
pingInterval: 30000,
3636
pingTimeout: 10000,
3737
})
3838
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
39-
constructor(private roomService: RoomService) {};
39+
constructor(
40+
private roomService: RoomService,
41+
private adapterFactory: RedisAdapterFactory) {};
4042

41-
async afterInit() {
42-
const { pubClient, subClient } = this.roomService.getPubSubClients();
43-
const redisAdapter = createAdapter(pubClient, subClient);
43+
afterInit() {
44+
// const adapterConstructor = this.adapterFactory.createAdapter();
45+
const redisAdapter = this.adapterFactory.createAdapter();
4446
this.server.adapter(redisAdapter);
4547
console.log('Redis adapter set');
4648
}
@@ -51,7 +53,7 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
5153

5254
async handleConnection(client: Socket) {
5355
console.log(`Client connected: ${client.id}`);
54-
const user = await this.roomService.createUser(client);
56+
const user = await this.roomService.addUser(client);
5557
console.log(user);
5658

5759
/*
@@ -121,6 +123,10 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
121123
} else {
122124
// 나를 제외한, 방에 있는 모든 사람에게는 owner : user 로 보내고
123125
console.log('people', this.server.sockets.adapter.rooms.get(roomId));
126+
127+
// TODO: redis-adapter + redis-cluster 특성 상,
128+
// redis 모든 노드에게 이벤트가 전파될 것이므로 이벤트를 두 번에 나누어서 보내는 것은 부하가 2배가 될 것 같다.
129+
124130
client.emit(CHATTING_SOCKET_RECEIVE_EVENT.NORMAL, { ...normalOutgoingMessage, owner: 'me' });
125131
client.broadcast.to(roomId).emit(CHATTING_SOCKET_RECEIVE_EVENT.NORMAL, {
126132
...normalOutgoingMessage,

backend/chatServer/src/chat/chat.module.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
22
import { ChatGateway } from './chat.gateway';
33
import { RoomModule } from '../room/room.module';
44
import { BlacklistGuard, HostGuard, MessageGuard } from './chat.guard';
5+
import { RedisModule } from '../redis/redis.module';
56

67
@Module({
7-
imports: [RoomModule],
8+
imports: [RoomModule, RedisModule],
89
providers: [ChatGateway, MessageGuard, BlacklistGuard, HostGuard],
910
})
1011
export class ChatModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { RedisClientFactory } from './redis-client.factory';
3+
import { createAdapter } from '@socket.io/redis-adapter';
4+
5+
@Injectable()
6+
export class RedisAdapterFactory {
7+
constructor(private readonly redisClient: RedisClientFactory) {}
8+
9+
createAdapter() {
10+
const pubClient = this.redisClient.createClient();
11+
const subClient = this.redisClient.createClient();
12+
return createAdapter(pubClient, subClient);
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Injectable, OnModuleDestroy } from '@nestjs/common';
2+
import { Cluster, Redis } from 'ioredis';
3+
import dotenv from 'dotenv';
4+
import path from 'path';
5+
import { fileURLToPath } from 'url';
6+
import { dirname } from 'path';
7+
8+
// 현재 파일의 URL을 파일 경로로 변환
9+
const __filename = fileURLToPath(import.meta.url);
10+
const __dirname = dirname(__filename);
11+
12+
// 상위 디렉터리의 .env 파일을 불러오기
13+
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
14+
const REDIS_CONFIG = JSON.parse(process.env.REDIS_CONFIG!);
15+
16+
17+
@Injectable()
18+
export class RedisClientFactory implements OnModuleDestroy{
19+
redisClientList: Cluster[] = [];
20+
21+
async onModuleDestroy(){
22+
if (this.redisClientList) {
23+
const quitPromise = this.redisClientList.map((redisClient) =>
24+
redisClient.quit().catch((err) => console.error(err)));
25+
await Promise.all(quitPromise);
26+
console.log('Redis connection closed');
27+
}
28+
}
29+
30+
createClient() {
31+
const newRedisClient = new Redis.Cluster(REDIS_CONFIG);
32+
this.redisClientList.push(newRedisClient);
33+
return newRedisClient;
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Module } from '@nestjs/common';
2+
import { RedisAdapterFactory } from './redis-adapter.factory';
3+
import { RedisClientFactory } from './redis-client.factory';
4+
5+
@Module({
6+
providers: [RedisAdapterFactory, RedisClientFactory],
7+
exports: [RedisAdapterFactory, RedisClientFactory],
8+
})
9+
export class RedisModule {}

backend/chatServer/src/room/room.interface.ts

-10
This file was deleted.

backend/chatServer/src/room/room.module.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { Module } from '@nestjs/common';
22
import { RoomService } from './room.service';
33
import { RoomRepository } from './room.repository';
4+
import { UserModule } from '../user/user.module';
5+
import { RedisModule } from '../redis/redis.module';
46

57
@Module({
8+
imports: [UserModule, RedisModule],
69
providers: [RoomService, RoomRepository],
710
exports: [RoomService, RoomRepository],
811
})

backend/chatServer/src/room/room.repository.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
22
import { Cluster } from 'ioredis';
33
import { QuestionDto } from '../event/dto/Question.dto';
44
import { ChatException, CHATTING_SOCKET_ERROR } from '../chat/chat.error';
5-
import { User } from './user.interface';
5+
import { User } from '../user/user.dto';
66

77
type USER_AGENT = string;
88

+36-91
Original file line numberDiff line numberDiff line change
@@ -1,186 +1,131 @@
1-
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
2-
import { Cluster, Redis } from 'ioredis';
3-
import { createAdapter } from '@socket.io/redis-adapter';
4-
import { User } from './user.interface';
5-
import { getRandomAdjective, getRandomBrightColor, getRandomNoun } from '../utils/random';
1+
import { Injectable, OnModuleInit } from '@nestjs/common';
2+
import { Cluster } from 'ioredis';
63
import { RoomRepository } from './room.repository';
74
import { QuestionDto } from '../event/dto/Question.dto';
85

9-
import dotenv from 'dotenv';
10-
import path from 'path';
11-
import { fileURLToPath } from 'url';
12-
import { dirname } from 'path';
136
import { ChatException, CHATTING_SOCKET_ERROR } from '../chat/chat.error';
147
import { Socket } from 'socket.io';
15-
16-
// 현재 파일의 URL을 파일 경로로 변환
17-
const __filename = fileURLToPath(import.meta.url);
18-
const __dirname = dirname(__filename);
19-
20-
// 상위 디렉터리의 .env 파일을 불러오기
21-
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
22-
const REDIS_CONFIG = JSON.parse(process.env.REDIS_CONFIG!);
23-
24-
function createRandomNickname(){
25-
return `${getRandomAdjective()} ${getRandomNoun()}`;
26-
}
27-
28-
function createRandomUserInstance(address: string, userAgent: string): User {
29-
return {
30-
address,
31-
userAgent,
32-
nickname: createRandomNickname(),
33-
color: getRandomBrightColor(),
34-
entryTime: new Date().toISOString()
35-
};
36-
}
8+
import { UserFactory } from '../user/user.factory';
9+
import { RedisClientFactory } from '../redis/redis-client.factory';
3710

3811
@Injectable()
39-
export class RoomService implements OnModuleInit, OnModuleDestroy {
40-
redisAdapter: ReturnType<typeof createAdapter>;
41-
redisClient: Cluster;
42-
43-
constructor(private redisRepository: RoomRepository) {
44-
this.redisClient = new Redis.Cluster(REDIS_CONFIG);
45-
this.redisAdapter = createAdapter(this.redisClient, this.redisClient);
46-
}
47-
48-
async onModuleInit(){
49-
const redisClient = new Redis.Cluster(REDIS_CONFIG);
50-
this.redisRepository.injectClient(redisClient);
51-
}
52-
53-
async onModuleDestroy(){
54-
if (this.redisClient) {
55-
await this.redisClient.quit();
56-
console.log('Redis connection closed');
57-
}
58-
}
12+
export class RoomService implements OnModuleInit {
13+
redisClient: Cluster | undefined;
14+
constructor(private readonly redisClientFactory: RedisClientFactory, private roomRepository: RoomRepository, private userFactory: UserFactory) {}
5915

60-
getClient(): Cluster {
61-
return this.redisClient;
16+
async onModuleInit() {
17+
this.redisClient = this.redisClientFactory.createClient();
18+
this.roomRepository.injectClient(this.redisClient);
6219
}
6320

64-
getPubSubClients(): { pubClient: Cluster; subClient: Cluster } {
65-
const pubClient = this.redisClient.duplicate();
66-
const subClient = this.redisClient.duplicate();
67-
68-
return { pubClient, subClient };
69-
}
70-
71-
get adapter(): ReturnType<typeof createAdapter> {
72-
return this.redisAdapter;
73-
}
74-
75-
7621
// 방 생성
7722
async createRoom(roomId: string, hostId: string) {
78-
const roomExists = await this.redisRepository.isRoomExisted(roomId);
23+
const roomExists = await this.roomRepository.isRoomExisted(roomId);
7924
if (roomExists) return false;
8025

8126
// roomId: hostId 로, room 에 hostId 지정
82-
await this.redisRepository.createNewRoom(roomId, hostId);
27+
await this.roomRepository.createNewRoom(roomId, hostId);
8328
console.log(`${hostId} 가 room: ${roomId} 을 만들었습니다.`);
8429
return true;
8530
}
8631

8732
// 방 삭제
8833
async deleteRoom(roomId: string) {
89-
const roomExists = await this.redisRepository.isRoomExisted(roomId);
34+
const roomExists = await this.roomRepository.isRoomExisted(roomId);
9035
if (!roomExists) throw new ChatException(CHATTING_SOCKET_ERROR.ROOM_EMPTY, roomId);
91-
await this.redisRepository.deleteRoom(roomId);
36+
await this.roomRepository.deleteRoom(roomId);
9237
}
9338

9439
async addQuestion(roomId: string, question: Omit<QuestionDto, 'questionId'>){
95-
const roomExists = await this.redisRepository.isRoomExisted(roomId);
40+
const roomExists = await this.roomRepository.isRoomExisted(roomId);
9641
if (!roomExists) throw new ChatException(CHATTING_SOCKET_ERROR.ROOM_EMPTY, roomId);
9742

98-
return await this.redisRepository.addQuestionToRoom(roomId, question);
43+
return await this.roomRepository.addQuestionToRoom(roomId, question);
9944
}
10045

10146
// 질문 추가
10247
async addQuestionAndReturnQuestion(
10348
roomId: string,
10449
question: Omit<QuestionDto, 'questionId'>,
10550
): Promise<QuestionDto> {
106-
const roomExists = await this.redisRepository.isRoomExisted(roomId);
51+
const roomExists = await this.roomRepository.isRoomExisted(roomId);
10752
if (!roomExists) throw new ChatException(CHATTING_SOCKET_ERROR.ROOM_EMPTY, roomId);
10853

109-
return await this.redisRepository.addQuestionToRoom(roomId, question);
54+
return await this.roomRepository.addQuestionToRoom(roomId, question);
11055
}
11156

11257
// 특정 질문 완료 처리
11358
async markQuestionAsDone(roomId: string, questionId: number) {
114-
const roomExists = await this.redisRepository.isRoomExisted(roomId);
59+
const roomExists = await this.roomRepository.isRoomExisted(roomId);
11560
if (!roomExists) throw new ChatException(CHATTING_SOCKET_ERROR.ROOM_EMPTY, roomId);
11661

117-
const markedQuestion = await this.redisRepository.markQuestionAsDone(roomId, questionId);
62+
const markedQuestion = await this.roomRepository.markQuestionAsDone(roomId, questionId);
11863
if (!markedQuestion) throw new ChatException(CHATTING_SOCKET_ERROR.QUESTION_EMPTY, roomId);
11964
return markedQuestion;
12065
}
12166

12267
// 방에 속한 모든 질문 조회
12368
async getQuestions(roomId: string): Promise<QuestionDto[]> {
124-
const roomExists = await this.redisRepository.isRoomExisted(roomId);
69+
const roomExists = await this.roomRepository.isRoomExisted(roomId);
12570
if (!roomExists) throw new ChatException(CHATTING_SOCKET_ERROR.ROOM_EMPTY, roomId);
12671

127-
return this.redisRepository.getQuestionsAll(roomId);
72+
return this.roomRepository.getQuestionsAll(roomId);
12873
}
12974

13075
async getQuestionsNotDone(roomId: string): Promise<QuestionDto[]> {
131-
const roomExists = await this.redisRepository.isRoomExisted(roomId);
76+
const roomExists = await this.roomRepository.isRoomExisted(roomId);
13277
if (!roomExists) throw new ChatException(CHATTING_SOCKET_ERROR.ROOM_EMPTY, roomId);
13378

134-
return this.redisRepository.getQuestionsUnmarked(roomId);
79+
return this.roomRepository.getQuestionsUnmarked(roomId);
13580
}
13681

13782
// 특정 질문 조회
13883
async getQuestion(roomId: string, questionId: number): Promise<QuestionDto> {
139-
const roomExists = await this.redisRepository.isRoomExisted(roomId);
84+
const roomExists = await this.roomRepository.isRoomExisted(roomId);
14085
if (!roomExists) throw new ChatException(CHATTING_SOCKET_ERROR.ROOM_EMPTY, roomId);
141-
return this.redisRepository.getQuestion(roomId, questionId);
86+
return this.roomRepository.getQuestion(roomId, questionId);
14287
}
14388

14489
// 유저 생성
145-
async createUser(socket: Socket) {
90+
async addUser(socket: Socket) {
14691
const clientId = socket.id;
14792
const address = socket.handshake.address.replaceAll('::ffff:', '');
14893
const userAgent = socket.handshake.headers['user-agent'];
14994

15095
if(!address || !userAgent) throw new ChatException(CHATTING_SOCKET_ERROR.INVALID_USER);
15196

152-
const newUser = createRandomUserInstance(address, userAgent);
153-
const isCreatedDone = await this.redisRepository.createUser(clientId, newUser);
97+
const newUser = this.userFactory.createUserInstance(address, userAgent);
98+
const isCreatedDone = await this.roomRepository.createUser(clientId, newUser);
15499
if(!isCreatedDone) throw new ChatException(CHATTING_SOCKET_ERROR.INVALID_USER);
155100
console.log(newUser);
156101
return newUser;
157102
}
158103

159104
// 유저 삭제
160105
async deleteUser(clientId: string) {
161-
return await this.redisRepository.deleteUser(clientId);
106+
return await this.roomRepository.deleteUser(clientId);
162107
}
163108

164109
// 특정 유저 조회
165110
async getUserByClientId(clientId: string) {
166-
const user = this.redisRepository.getUser(clientId);
111+
const user = this.roomRepository.getUser(clientId);
167112
return user;
168113
}
169114

170115
async getHostOfRoom(roomId: string) {
171-
const roomExists = await this.redisRepository.isRoomExisted(roomId);
116+
const roomExists = await this.roomRepository.isRoomExisted(roomId);
172117
if (!roomExists) throw new ChatException(CHATTING_SOCKET_ERROR.ROOM_EMPTY, roomId);
173-
return await this.redisRepository.getHost(roomId);
118+
return await this.roomRepository.getHost(roomId);
174119
}
175120

176121
async getUserBlacklist(roomId: string, address: string) {
177-
const roomExists = await this.redisRepository.isRoomExisted(roomId);
122+
const roomExists = await this.roomRepository.isRoomExisted(roomId);
178123
if (!roomExists) return [];
179124

180-
return await this.redisRepository.getUserBlacklist(roomId, address);
125+
return await this.roomRepository.getUserBlacklist(roomId, address);
181126
}
182127

183128
async addUserToBlacklist(roomId: string, address: string, userAgent: string){
184-
return await this.redisRepository.addUserBlacklistToRoom(roomId, address, userAgent);
129+
return await this.roomRepository.addUserBlacklistToRoom(roomId, address, userAgent);
185130
}
186131
}

0 commit comments

Comments
 (0)