Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3bd5503
refactor: `room-socket` 서브 모듈을 `websocket` 모듈로 분리
blu3fishez Nov 25, 2024
5f0ae21
refactor: 시그널링 서버의 로그 삭제
blu3fishez Nov 25, 2024
e2322b0
fix: 스터디 세션 나가기 로직 오류 수정
blu3fishez Nov 25, 2024
5477d1e
refactor: 스터디 세션 입장, 생성 로직 분리
blu3fishez Nov 25, 2024
a9c1897
fix: 여러 WAS에서 스터디세션을 잘못 생성하는 버그 수정
blu3fishez Nov 25, 2024
b9fe581
fix: `create_room` 시, `join` emit까지 받는 버그 수정
blu3fishez Nov 25, 2024
ca2f744
fix: 마지막 사용자가 방 퇴장 시 삭제되지 않던 오류 수정
blu3fishez Nov 25, 2024
99ee909
chore: docker compose 파일 업데이트
blu3fishez Nov 25, 2024
ef38c09
Merge pull request #222 from boostcampwm-2024/fix/generate-session-id
twalla26 Nov 25, 2024
da6e3ab
fix: docker-compose.yml 파일 수정
blu3fishez Nov 25, 2024
6006b87
Merge pull request #224 from boostcampwm-2024/dev
ShipFriend0516 Nov 25, 2024
12a9e83
fix: docker-compose.yml 파일 수정
blu3fishez Nov 25, 2024
da80e7a
fix: select props 전달 오류 해결
ShipFriend0516 Nov 25, 2024
ba85b80
Merge pull request #225 from ShipFriend0516/fix/select-component-props
ShipFriend0516 Nov 25, 2024
8f8ad59
Merge remote-tracking branch 'upstream/main' into dev
ShipFriend0516 Nov 25, 2024
a94fb92
chore: constraints -> constants 폴더이름 변경
ShipFriend0516 Nov 25, 2024
ce12b47
refactor: 변경된 소켓 이벤트명 분리
ShipFriend0516 Nov 25, 2024
6a15988
refactor: 세션아이디 없을 때 토스트 메시지
ShipFriend0516 Nov 26, 2024
fc6bf02
refactor: 변경된 웹소켓 API 이벤트명으로 변경
ShipFriend0516 Nov 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,62 +1,39 @@
import {
WebSocketGateway,
WebSocketServer,
OnGatewayConnection,
OnGatewayDisconnect,
SubscribeMessage,
OnGatewayInit,
MessageBody,
ConnectedSocket,
} from "@nestjs/websockets";
import "dotenv/config";
import { Server, Socket } from "socket.io";
import { RoomService } from "../services/room.service";
import { Socket } from "socket.io";
import { RoomService } from "./services/room.service";
import { Logger, UsePipes, ValidationPipe } from "@nestjs/common";
import { EMIT_EVENT, LISTEN_EVENT } from "@/room/socket/room-socket.events";
import { EMIT_EVENT, LISTEN_EVENT } from "@/room/room.events";
import { CreateRoomDto } from "@/room/dto/create-room.dto";
import { RoomSocketService } from "@/room/socket/room-socket.service";
import { WebsocketService } from "@/websocket/websocket.service";
import { JoinRoomDto } from "@/room/dto/join-room.dto";
import { RoomRepository } from "@/room/room.repository";
import { ReactionDto } from "@/room/dto/reaction.dto";
import { RoomSocketRepository } from "@/room/socket/room-socket.repository";
import Redis from "ioredis";
import { createAdapter } from "@socket.io/redis-adapter";
import { RoomLeaveService } from "@/room/services/room-leave.service";
import { RoomCreateService } from "@/room/services/room-create.service";
import { RoomJoinService } from "@/room/services/room-join.service";

@WebSocketGateway()
export class RoomSocketGateway implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit {
@WebSocketServer()
private server: Server;
export class RoomGateway implements OnGatewayDisconnect {
private logger: Logger = new Logger("Room Gateway");

public constructor(
private readonly roomService: RoomService,
private readonly socketService: RoomSocketService,
private readonly socketRepository: RoomSocketRepository,
private readonly roomLeaveService: RoomLeaveService,
private readonly roomCreateService: RoomCreateService,
private readonly roomJoinService: RoomJoinService,
private readonly socketService: WebsocketService,
private readonly roomRepository: RoomRepository
) {}

public async handleConnection(client: Socket) {
Logger.log(`Client connected: ${client.id}`);
await this.socketRepository.register(client);
}

public async handleDisconnect(client: Socket) {
Logger.log(`Client disconnected: ${client.id}`);
await this.handleLeaveRoom(client);
await this.socketRepository.clean(client);
}

public async afterInit() {
const pubClient = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT),
});

const subClient = pubClient.duplicate();

const redisAdapter = createAdapter(pubClient, subClient);
this.server.adapter(redisAdapter);

this.roomService.setServer(this.server);
}

@SubscribeMessage(LISTEN_EVENT.CREATE)
Expand All @@ -65,7 +42,8 @@ export class RoomSocketGateway implements OnGatewayConnection, OnGatewayDisconne
@ConnectedSocket() client: Socket,
@MessageBody() dto: CreateRoomDto
) {
await this.roomService.createRoom({ ...dto, socketId: client.id });
// TODO: try - catch 로 에러 핸들링을 통해 이벤트 Emit 을 여기서 하기
await this.roomCreateService.createRoom({ ...dto, socketId: client.id });
}

@SubscribeMessage(LISTEN_EVENT.JOIN)
Expand All @@ -74,12 +52,12 @@ export class RoomSocketGateway implements OnGatewayConnection, OnGatewayDisconne
@ConnectedSocket() client: Socket,
@MessageBody() dto: JoinRoomDto
) {
await this.roomService.joinRoom({ ...dto, socketId: client.id });
await this.roomJoinService.joinRoom({ ...dto, socketId: client.id });
}

@SubscribeMessage(LISTEN_EVENT.LEAVE)
public async handleLeaveRoom(client: Socket) {
await this.roomService.leaveRoom(client);
await this.roomLeaveService.leaveRoom(client);
}

@SubscribeMessage(LISTEN_EVENT.FINISH)
Expand Down
16 changes: 8 additions & 8 deletions backend/src/room/room.module.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { Module } from "@nestjs/common";
import { RoomService } from "./services/room.service";
import { RoomSocketGateway } from "./socket/room-socket.gateway";
import { RoomGateway } from "./room.gateway";
import { RoomRepository } from "./room.repository";
import { RoomController } from "./room.controller";
import { RedisOmModule } from "nestjs-redis-om";
import { RoomEntity } from "./room.entity";
import { RoomLeaveService } from "@/room/services/room-leave.service";
import { RoomSocketService } from "@/room/socket/room-socket.service";
import { RoomHostService } from "@/room/services/room-host.service";
import { RoomSocketRepository } from "@/room/socket/room-socket.repository";
import { RoomSocketEntity } from "@/room/socket/room-socket.entity";
import { QuestionListRepository } from "@/question-list/question-list.repository";
import { WebsocketModule } from "@/websocket/websocket.module";
import { RoomCreateService } from "@/room/services/room-create.service";
import { RoomJoinService } from "@/room/services/room-join.service";

@Module({
imports: [RedisOmModule.forFeature([RoomEntity, RoomSocketEntity])],
imports: [RedisOmModule.forFeature([RoomEntity]), WebsocketModule],
providers: [
RoomService,
RoomSocketGateway,
RoomGateway,
RoomRepository,
RoomCreateService,
RoomJoinService,
RoomLeaveService,
RoomSocketService,
RoomHostService,
RoomSocketRepository,
QuestionListRepository,
],
controllers: [RoomController],
Expand Down
63 changes: 63 additions & 0 deletions backend/src/room/services/room-create.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Injectable } from "@nestjs/common";
import { CreateRoomInternalDto } from "@/room/dto/create-room.dto";
import { EMIT_EVENT } from "@/room/room.events";
import { WebsocketService } from "@/websocket/websocket.service";
import { RoomRepository } from "@/room/room.repository";
import { QuestionListRepository } from "@/question-list/question-list.repository";
import { RoomJoinService } from "@/room/services/room-join.service";
import { createHash } from "node:crypto";
import "dotenv/config";

@Injectable()
export class RoomCreateService {
private static ROOM_ID_CREATE_KEY = "room_id";

public constructor(
private readonly roomRepository: RoomRepository,
private readonly socketService: WebsocketService,
private readonly questionListRepository: QuestionListRepository,
private readonly roomJoinService: RoomJoinService
) {}

public async createRoom(dto: CreateRoomInternalDto) {
const { socketId, nickname } = dto;
const roomId = await this.generateRoomId();
const currentTime = Date.now();
const questionListContents = await this.questionListRepository.getContentsByQuestionListId(
dto.questionListId
);

const roomDto = {
...dto,
roomId,
connectionList: [],
questionListContents,
createdAt: currentTime,
host: dto.socketId,
};

await this.roomRepository.setRoom(roomDto);

await this.roomJoinService.joinRoom({ roomId, socketId, nickname }, true);

this.socketService.emitToRoom(roomId, EMIT_EVENT.CREATE, roomDto);
}

// TODO: 동시성 고려해봐야하지 않을까?
private async generateRoomId() {
const client = this.socketService.getRedisClient();

const idString = await client.get(RoomCreateService.ROOM_ID_CREATE_KEY);

let id: number;
if (idString && !isNaN(parseInt(idString))) {
id = await client.incr(RoomCreateService.ROOM_ID_CREATE_KEY);
} else {
id = parseInt(await client.set(RoomCreateService.ROOM_ID_CREATE_KEY, "1"));
}

return createHash("sha256")
.update(id + process.env.SESSION_HASH)
.digest("hex");
}
}
46 changes: 46 additions & 0 deletions backend/src/room/services/room-join.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Injectable } from "@nestjs/common";
import { EMIT_EVENT } from "@/room/room.events";
import { WebsocketService } from "@/websocket/websocket.service";
import { RoomRepository } from "@/room/room.repository";
import { RoomDto } from "@/room/dto/room.dto";
import { JoinRoomInternalDto } from "@/room/dto/join-room.dto";
import { WebsocketRepository } from "@/websocket/websocket.repository";

@Injectable()
export class RoomJoinService {
public constructor(
private readonly roomRepository: RoomRepository,
private readonly socketService: WebsocketService,
private readonly socketRepository: WebsocketRepository
) {}

public async joinRoom(dto: JoinRoomInternalDto, isCreate: boolean = false) {
const { roomId, socketId, nickname } = dto;

const room = await this.roomRepository.getRoom(roomId);
const socket = this.socketService.getSocket(socketId);

if (!socket) throw new Error("Invalid Socket");

if (room.roomId === null) throw new Error("Redis: RoomEntity Entity type error");

if (this.isFullRoom(room)) return socket.emit(EMIT_EVENT.FULL, {});

socket.join(roomId);
await this.socketRepository.joinRoom(socket.id, roomId);
room.connectionList.push({
socketId,
createAt: Date.now(),
nickname,
});

await this.roomRepository.setRoom(room);

// TODO: 성공 / 실패 여부를 전송하는데 있어서 결과에 따라 다르게 해야하는데.. 어떻게 관심 분리를 할까?
if (!isCreate) this.socketService.emitToRoom(roomId, EMIT_EVENT.JOIN, room);
}

private isFullRoom(room: RoomDto): boolean {
return room.maxParticipants <= room.connectionList.length;
}
}
28 changes: 11 additions & 17 deletions backend/src/room/services/room-leave.service.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Injectable } from "@nestjs/common";
import { RoomSocketService } from "@/room/socket/room-socket.service";
import { WebsocketService } from "@/websocket/websocket.service";
import { RoomRepository } from "@/room/room.repository";
import { RoomDto } from "@/room/dto/room.dto";
import { RoomHostService } from "@/room/services/room-host.service";
import { EMIT_EVENT } from "@/room/socket/room-socket.events";
import { EMIT_EVENT } from "@/room/room.events";
import { Socket } from "socket.io";
import { RoomSocketRepository } from "@/room/socket/room-socket.repository";
import { WebsocketRepository } from "@/websocket/websocket.repository";

@Injectable()
export class RoomLeaveService {
constructor(
private readonly socketService: RoomSocketService,
private readonly socketRepository: RoomSocketRepository,
private readonly socketService: WebsocketService,
private readonly socketRepository: WebsocketRepository,
private readonly roomRepository: RoomRepository,
private readonly roomHostService: RoomHostService
) {}
Expand All @@ -27,23 +27,17 @@ export class RoomLeaveService {
const room = await this.roomRepository.getRoom(roomId);
if (!room) return;

await this.leaveSocket(socketId, room);

if (room.host === socketId) await this.handleHostChange(socketId, room);
else this.socketService.emitToRoom(room.roomId, EMIT_EVENT.QUIT, { socketId });

if (!room.connectionList.length) await this.deleteRoom(socketId);
}

private async leaveSocket(socketId: string, room: RoomDto) {
await this.socketService.leaveRoom(socketId, room.roomId);

// TODO : 엄청 비효율적인 코드라고 생각하는데 개선할 방법 찾기
room.connectionList = room.connectionList.filter(
(connection) => connection.socketId !== socketId
);

if (!room.connectionList.length) return this.deleteRoom(room.roomId);

await this.roomRepository.setRoom(room);

if (room.host === socketId) return this.handleHostChange(socketId, room);

this.socketService.emitToRoom(room.roomId, EMIT_EVENT.QUIT, { socketId });
}

private async handleHostChange(socketId: string, room: RoomDto) {
Expand Down
Loading