[TIL] 210710

2K1·2021년 7월 15일
0

TIL

목록 보기
25/31
post-thumbnail

대략 기본적인 채팅기능은 완성된거같다.

import { Server } from 'socket.io'
import server from './index.js'

const io = new Server(server, {
  cors: { origin: "*" }
});

일단 cors는 열어두기만하고 잘 연결되는지 체크하고 수정해야겠다.

📂연결 & 접속자 리스트

현재 접속자를 표시하기위해 CurrentOn 배열안에 프론트에서 닉네임을 가져와서 넣고 누가 들어거나 나갈때 io.emit으로 현재접속자 리스트를 업데이트 해주는 형식으로 짰다.
currentOnUserInfo 배열을 따로 만들어 둔 이유는 refresh 할때마다 socket.id가 바뀌기 때문에 나중 특정 socket.id에게 보내는 귓속말이나 1:1 채팅이나 알람같은 이벤트를 위해 해당 닉네임의 socket.id를 업데이트 해주기위해서 묶어놨다.

const currentOn = [];
const currentOnUserInfo=[];

io.on("connection", (socket) => {

  io.emit('currentOn', currentOn); // (현재 접속자 리스트) refresh 할일이 많아서 처음에 넣어줌.

  socket.on("join", ({ nickname }) => {
      const userNickname = nickname
      const userSocketId = {      // 특정 닉네임에게만 보내는 이벤트를 위한 socket.id저장
        nickname : userNickname,
        socketId : socket.id
      }
      if (currentOn.indexOf(userNickname) === -1) {  //현재 접속자에 유저아이디가 없으면 추가
        currentOnUserInfo.push(userSocketId)
        currentOn.push(userNickname);
        io.emit('currentOn', currentOn); // 현재 접속자 리스트 업데이트
      }else{                      // refresh 할때마다 socket.id가 바뀌므로 같이 업데이트 해주는작업
        for (let i in currentOnUserInfo){
          if (currentOnUserInfo[i].nickname === userNickname){
            currentOnUserInfo[i].socketId = userSocketId.socketId
          }
        }
      }
    });
 
 // 나갈때
 socket.on("disconnect", () => {
    // 현재 socket.id랑 연결되어있는 닉네임이 있는 배열에서 제거
    for (let i in currentOnUserInfo){
      if (currentOnUserInfo[i].socketId === socket.id){
        currentOn.splice(currentOn.indexOf(currentOnUserInfo[i].nickname), 1);
        currentOnUserInfo.splice(currentOnUserInfo[i],1)
      }
    }
    io.emit('currentOn', currentOn);
  });
}); 

📂채팅

채팅은 그냥 닉네임과 메세지 들어온거 그대로 emit 해주면 된다. 하지만 이것또한 refresh하면 띄워놨던 채팅들이 날라가기 때문에 DB에 따로 저장하게 만들었다.

//메세지 주고받기(프론트: 보내는 유저info, 보내는 메세지)
  socket.on("sendMsg", async ({message, nickname, date}) => {
    try{
      const maxOrder = await Chat.findOne({}).sort('-order').exec();
      let order = 1;

      if (maxOrder) {
        order = maxOrder.order + 1;
      }
      await Chat.create({ order, message, nickname, date })
      io.emit("receiveMsg", {nickname, message, date});

    }catch (err) {
      console.error(err);
    };
  });

📂채팅기록 100개이상시 반 삭제

채팅이 전체채팅을 기록하기때문에 사람이 많아서 아마 DB가 터질거 같아 기록이 100개가 넘어가면 50개 삭제해주는 기능을 만들었다.
이 변수를 유저가 connect 했을때 맨 처음에 달아줬는데 들어올때는 로딩할 데이터들이 많아서 효율적이지 않은거같다. 다음부턴 disconnect 부분에 넣어주는게 좀더 효율적일거같다.
하지만! 좀더 생각해보니 유저수가 많으면 삭제API 주기가 많아서 모르겠지만 유저수가 적으면 채팅을 보내다 보면 200개 300개 보다 많아 질텐데 그거의 반만 삭제하면 계속 차오를수 밖에 없는 구조다.
차라리 현재 collection에 있는 전체 document 갯수가져오고 50개를 뺀 갯수를 삭제해주면 항상 50개가 남게 해주는게 좀더 좋을거같다.(기호에 따라서 숫자 50만 바꿔주면 된다.)

// 채팅 기록 삭제 Upgrade.ver
const allCount = Chat.count() - 50
Chat.find({}, {_id : 1})
    .limit(allCount)
    .sort({timestamp:-1})
    .toArray()
    .map(function(doc) { return doc._id; }); 
    
Chat.remove({_id: {$in: removeIdsArray}})
//채팅 기록 100개 이상시 반 삭제
const deleteMaxChat = async () => {
  try{
    const chats = await Chat.find().sort('-order').exec()
    if(chats.length > 100) {
      const maxi = chats[0].order
      const mini = chats[chats.length - 1].order
      const halfOrder = (maxi + mini) / 2
      await Chat.deleteMany({ order: { $lte : halfOrder } }).exec()
    }
  }catch (err) {
    console.error(err);
  };
};

📂채팅기록 나타내기

여기서 좀더 나아가서 유저가 온라인인 상태만에서의 전체 채팅기록만 개개인에게 기록하게 할려면,
1. 전체채팅 메세지를 쓰고 보낼때 현재 온라인인 유저에게만 보내지게 해야한다.
2. 그럼 현재 온라인인 모든 유저의 socket.id를 하나씩 다 따는것보다 전체채팅 room을 하나 만들어서 룸 전체에게 메세지를 쏘는게 더 효율적일것이다.
3. 메세지를 받으면 유저 개개인 아이디 DB에 chat 필드를 따로 만들어서 저장해준다.
4. 로그인했을때 해당 유저 아이디에 저장되어있는 chat 필드를 해당 유저에게만 emit 해준다.

하지만 여기선 전체 채팅방밖에 없기 때문에 그냥 chat document있은걸 다 모아와서 차례대로만 보여주면 된다.

const showChatLog = async () => {
  try{
    const chats = await Chat.find().sort('-order').exec()
    io.emit('chatLog', chats)
  }catch (err) {
    console.error(err);
  };
};

------📝Theory------

여기서 부터는 실제 프로젝트에서 배제된 기능들이다.
프론트가 할일도 많고 안그래도 지금 백엔드가 기능 만드는 속도가 빠른데 백엔드 숫자가 더 많아서 기본 CRUD 구현에 먼저 초점을 맞추기로 했다. 그래서 지금 이것들은 실험을 해보지 못한 Theory( 이론 )적인 나의 뇌피셜들이다 ㅎㅎ..

📂귓속말

귓속말은 전체채팅방에서 해당 유저를 누르고 "귓속말보내기" 탭을 클릭하고 메세지칸에 메세지를 보내면 보내는 유저의 정보, 보내는 메세지, 받는 유저의 정보 이 세가지를 받는다. 받는 유저의 socket.id를 찾고 받은 내용들을 그 socket.id에게 전송하면 된다.
date는 원래 서버에서 보내주려고 했지만 실제 프로젝트에서는 프론트에서 다뤄주기로 결정해서 위에는 date가 없지만 여기에는 있다. 누가하든 상관없다. 단지 한쪽이 쪼금 편해진다.

//귓속말(프론트: 보내는 유저info, 보내는 메세지, 받는 유저info)
  socket.on("writingComment", (send, receive) => {
    const sendUser = {
        nickname : send.nickname,
        sendMessage: send.message,
        date: currentDate
    };
    for (let i in currentOnUserInfo){
      if (currentOnUserInfo[i].nickname === receive.nickname){
        const socketId = currentOnUserInfo[i].socketId;
        socket.to(socketId).emit("whisper", sendUser) //특정 socketid에게만 전송
      }
    }
  });

📂게시물 포스팅 알람

이 게시물 포스팅 알람은 전체 유저에게 보내는거라 post의 정보만 넣어주면 된다. 만약 특정 유저에게만 알람을 보낼려면 socket.id를 찾아서 따로 보내주면 될거같다.

 // 게시물 포스팅 알람(프론트: 게시물 작성한 유저info)
  socket.on("posting", (giveNickname) => {
    const post = {
      nickname: giveNickname,
      date: currentDate,
    };
    io.emit("postNotification", post);
  });

📂댓글 작성 알람

해당 게시물을 쓴 유저에게만 가는 댓글이 달렸다는 알람 기능이다. 귓속말과 비슷하게 게시물을 쓴 유저의 정보와 댓글을단 유저의 정보와 만약 댓글내용을 보여주고 싶으면 댓글 내용 아니면 클릭했을때 댓글을 쓴 게시물에 리다렉트를 해줄려면 게시물 url 정보를 주면 될거같다.

//댓글 작성 알람(프론트: 댓글 단 유저info, 댓글내용, 해당 게시물 유저info)
  socket.on("writingComment", (post, comment) => {
      const postUser = {
          nickname : post.nickname
      };
      const commentUser = {
        comment : comment.comment,  
        nickname : comment.nickname,
        date: currentDate
      };
      for (let i in currentOnUserInfo){
        if (currentOnUserInfo[i].nickname === receive.nickname){
          const socketId = currentOnUserInfo[i].socketId;
          socket.broadcast.to(socketId).emit("commentNotification", postUser, commentUser) //특정 socketid에게만 전송
      }
    }
  });
profile
📌dev_log

0개의 댓글