NestJS-Socket.io(Schema)

효딩딩·2023년 7월 26일
0
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 {}

DB 연결 및 서비스 로직

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....`);
});  
profile
어제보다 나은 나의 코딩지식

0개의 댓글

Powered by GraphCDN, the GraphQL CDN