npm install class-validator
npm install class-transformer // class-validator 사용하기 위해 설치해야함.
sockets.model
{
_id : (ObjectId),
id : socekt.id,
username : 사용자 이름
}
---------------------------------------------
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { SchemaOptions } from 'mongoose';
import { IsNotEmpty, IsString } from 'class-validator';
const options: SchemaOptions = {
// 기본적으로 id: true로 되어있는데 이 경우는 _id와 그냥 id가 같도록 만들어진다. 그래서 id: username 필드를 따로 생성해주자 .
id: false, // mongooes에서 만들어줄 때 _id라고 ObjectId type으로 유니크한 키를 자동으로 만들어준다.(프라이머리 키라고 생각하면된다.)
collection: 'sockets',
timestamps: true,
};
@Schema(options)
export class Socket extends Document {
@Prop({
unique: true,
required: true,
})
@IsNotEmpty()
@IsString()
id: string;
@Prop({
required: true,
})
@IsNotEmpty()
@IsString()
username: string;
}
export const SocketSchema = SchemaFactory.createForClass(Socket);
chattings.model
{
_id : (ObjectId),
user : {
_id : (ObjectId),
id : socekt.id,
username : 사용자 이름
},
chat : 채팅 메세지
}
-----------------------------------------------------------
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { IsNotEmpty, IsString } from 'class-validator';
import { SchemaOptions, Types } from 'mongoose';
import { Socket as SocketModel } from './sockets.model';
const options: SchemaOptions = {
collection: 'chattings', // 콜랙션의 이름을 임의로 설졍할 수 있다.
timestamps: true, // update At create를 자동으로 찍어준다. 언제 db가 만들어지고 db가 언제 수정됬는지 로깅해주는 것임.
};
@Schema(options)
export class Chatting extends Document {
@Prop({
type: {
_id: { type: Types.ObjectId, required: true, ref: 'sockets' }, // Types.ObjectId는 socket.model에서 자동으로 생성되는 _id를 여기에 넣은것임
id: { type: String }, // 의미하는것은 socket.id
username: { type: String, required: true },
},
})
@IsNotEmpty()
@IsString()
user: SocketModel;
@Prop({
required: true,
})
@IsNotEmpty()
@IsString()
chat: string; // 적은 메세지
}
export const ChattingSchema = SchemaFactory.createForClass(Chatting);
chats.module
import { Module } from '@nestjs/common';
import { ChatsGateway } from './chats.gateway';
import { MongooseModule } from '@nestjs/mongoose';
import { Chatting, ChattingSchema } from './models/chattings.model';
import { SocketSchema, Socket as SocketModel } from './models/sockets.model';
@Module({
imports: [ <<<< // 추가
MongooseModule.forFeature([
{ name: Chatting.name, schema: ChattingSchema },
{ name: SocketModel.name, schema: SocketSchema },
// SocketModel 이라고 했는데 socket.io랑 헷갈릴까봐 변경함
]),
],
providers: [ChatsGateway],
})
export class ChatsModule {}
chats.gateway.ts
import { Logger, Delete } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import {
ConnectedSocket,
MessageBody,
OnGatewayConnection,
OnGatewayDisconnect,
OnGatewayInit,
SubscribeMessage,
WebSocketGateway,
} from '@nestjs/websockets';
import { Model } from 'mongoose';
import { Socket } from 'socket.io';
import { Chatting } from './models/chattings.model';
import { Socket as SocketModel } from './models/sockets.model';
@WebSocketGateway({ namespace: 'chattings' }) // 네임스페이스를 옵션에서 설정가능하다.
export class ChatsGateway
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
private logger = new Logger('chat');
constructor(
// db에 핸들링 할 수 있게 의존성 주입해주기
@InjectModel(Chatting.name) private readonly chattingModel: Model<Chatting>,
@InjectModel(SocketModel.name)
private readonly socketModel: Model<SocketModel>,
) {
this.logger.log('constructor');
}
// desconnected
// 1) socket.id를 받고 해당하는 sockets.model에 id === socket.id 면 user를 가져온다.
// 2) 해당하는 user를 제거한다.
// client 와 연결이 끊길 때
async handleDisconnect(@ConnectedSocket() socket: Socket) {
// 해당하는 유저정보를 찾기
const user = await this.socketModel.findOne({ id: socket.id });
// 해당하는 유저가 나갔다고 알려주고 db에 지우기.
if (user) {
socket.broadcast.emit('disconnect_user', user.username);
await user.deleteOne();
}
this.logger.log(`disconnected : ${socket.id} ${socket.nsp.name}`);
}
// client와 연결됬을 때
handleConnection(@ConnectedSocket() socket: Socket) {
this.logger.log(`connected : ${socket.id} ${socket.nsp.name}`);
}
// constructor 다음에 실행하는 함수
afterInit() {
this.logger.log('init'); // gateway 가 실행될때 가장 먼저 실행되는 함수이다.
}
// new_user
// 1) username을 받고 sockets.model에 username이 있는 중복체크하기
// 2) 중복되었다면 랜덤한 숫자를 추가하여 username으로 저장한다. id에는 socket.id를 저장한다.
// 2) 중복되지 않았다면 그대로 username으로 저장한다. id에는 socket.id를 저장한다.
@SubscribeMessage('new_user')
async handleNewUser(
@MessageBody() username: string,
@ConnectedSocket() socket: Socket,
) {
// socketModel.exists() 해당하는 필드에서 이미 있는지 없는지 true false로 제공해줌.
// 해당하는 username이 존재하는지 존재하면 true / 안하면 false이다.
const exist = await this.socketModel.exists({ username: username });
// 많은 유저가 들어온다면 이것 또한 중복이 될 수 있음.
if (exist) {
username = `${username}_${Math.floor(Math.random() * 100)}`;
// 100사이의 있는 특정한 rondom 수를 넣어주기.
await this.socketModel.create({
id: socket.id,
username,
});
} else {
await this.socketModel.create({
id: socket.id,
username,
});
}
socket.broadcast.emit('user_connected', username);
return username;
}
// submit_user
// 1) socket.id를 받고 해당하는 sockets.model에 id === socket.id면 user를 가져온다.
// 2) chattings.model에 해당하는 user와 chat을 저장한다.
@SubscribeMessage('submit_chat')
async handleSubmitChat(
@MessageBody() chat: string,
@ConnectedSocket() socket: Socket,
) {
const socketObject = await this.socketModel.findOne({ id: socket.id });
if (socketObject) {
await this.chattingModel.create({
user: socketObject,
chat: chat,
});
}
socket.broadcast.emit('new_chat', {
chat,
username: socketObject.username,
});
}
}
script.js
const socket = io('/chattings'); // io 가 socket.io.min.js 여기에 있는 하나의 요소(메소드)이다.
const getElementById = (id) => document.getElementById(id) || null;
// document.getElementById(id) 해당 id를 통해서 dom요소를 잡아오는 것이다.
//* get DOM element
const helloStrangerElement = getElementById('hello_stranger');
const chattingBoxElement = getElementById('chatting_box');
const formElement = getElementById('chat_form');
// global socket handler
socket.on('user_connected', (username) => {
// console.log(`${username} connected!`);
drawNewChat(`${username} connected`);
}); // 이런식으로 on으로 이벤트를 받는다.
socket.on('new_chat', (data) => {
const { chat, username } = data;
drawNewChat(`${username}: ${chat}`);
});
// disconnect_user 를 클라이언트와 연결
socket.on('disconnect_user', (username) => { <<<
drawNewChat(` ${username} : bye....`);
});