지금 구현하는 서비스에서 채팅 기능을 구현하고 있는데, 내 부족한 머리로 느끼기에는 꽤 프로세스가 장황해서 기록해두려 한다.
이번에 채팅을 구현하는 이유는 문의사항을 유저와 어드민이 주고받기 위해서이다.
필요한 기능을 정리하면 다음과 같다.
- 1대1로 유저는 항상 어드민에게만 가능하도록 수신자 지정이 되어 있어야 함.
- 서로를 제외한 나머지 다른 유저는 대화내용을 볼 수 없어야 함.
- 대화 내용 저장 및 불러오기
다음 스크린샷은 어드민으로 로그인했을 때의 ui다.
앞서 필요한 기능에 대해 정리했듯 유저는 항상 어드민에게 가능하기 때문에 위처럼 수신자를 선택할 수는 없고, 조건을 어드민으로 로그인했을 때만 저렇게 보이도록 설정했다. 그리고 저 목록은 유저가 먼저 메세지를 보내게 되면 목록이 업데이트된다.
아이디를 검색하거나 찾아서 원하는 사용자에게 채팅을 할 수 있도록 할까도 고민했지만, 타이트하게 용도에 맞게 가능하도록 제한했다.
고민했던 부분
레디스는 기본적으로 key, value 기준으로 데이터를 저장할 수 있다. 물론 기본적으로 이다.
db에 세션만 저장하다가 채팅 구현을 구상하면서 데이터를 어떻게 저장해야할까 고민을 하게 되었다.
그 이유는 일반적으로 대화는 주고 받는 형식이 되어야 할텐데, 기본적인 레디스의 저장방식인 key,value로 저장하게 된다면 유저1 : 대화내용으로만 저장할 수 있는건가 싶어서 막막했다.
만약 그렇게 그대로 저장한다면 서로의 대화를 매칭할 수가 없다.
또한 나중에 채팅내용을 조회해야 할 때 주고 받은 내용을 한꺼번에 조회할 수가 없었다.
결론적으로 key:value 형식으로 저장할 때 유저1: 대화내용으로 저장하면 일방적으로 한 개의 대화만 조회가 될 것이다.
어찌저찌 밸류에 대화를 전부 집어넣는다고 해도 둘이서 한 대화를 키에 어떤 기준을 삼을 것인지도 애매했다.
{key1: {유저1:대화내용1, 유저2:대화내용2}} 이런 식으로 말이다.
key1의 자리에 한 명의 유저만 기록하게 되면 일시적인 대화는 어떻게든 할 수 있을지라도 대화를 저장하고 key에 이름이 기록되지 않은 사용자는 대화를 불러올 수가 없다.
그렇다면 어떻게 저장할 수 있을까?
정답에 가까운 해답은 아직 모르겠지만, 내가 생각한 방법으로 일단 구현 자체는 되었다.
const { sender, receiver, message } = data;
const chatKey1 = `${sender}_${receiver}`;
const [cursor1, keys1] = await redisClient.scan(
0,
"MATCH",
`chat:*_${chatKey1}`
);
프론트에서 socket.io 통해 const { sender, receiver, message } = data; 로 받았을 때,
항상 유저가 어드민에게 먼저 메세지를 보낼 수 있기 때문에
채팅방 이름을 chatKey1로 선언하고 sender_receiver의 구조로 만들어주었다.
채팅방을 검색해서 존재유무를 파악하고, 존재할 경우에만 대화내용을 불러오게 하고 존재하지 않을 경우 새롭게 채팅방을 만들도록 구현했다. 아래와 같이 저장하게 된다.
export const createChatToRedis = async (
roomName: string,
messageData: string,
expirationInSeconds: number
) => {
const chatId = await redisClient.incr(MESSAGE_COUNTER_KEY);
const chatKey = `chat:${chatId}_${roomName}`;
await redisClient.rpush(chatKey, messageData);
await redisClient.expire(chatKey, expirationInSeconds);
};
export const saveChatUserToRedis = async (username: string) => {
await redisClient.rpush("chatUsers", username);
await redisClient.expire("chatUsers", 30 * 24 * 60 * 60);
};
export const getAllChatUsersFromRedis = async () => {
const users = await redisClient.lrange("chatUsers", 0, -1);
return users;
};
roomName에 아까 chatKey1이 들어가게 되면 고유넘버와 함께 레디스에 저장되는데, rpush를 통해 저장하였다. 기본 key:value형태의 저장방식은 set으로 하게 되는데, 채팅방은 한 key에 value를 누적해서 넣을 수 있고, 그렇기 때문에 messageData에는 sender, receiver, message를 전부 담아서 누가 누구에게 무엇을 보냈는지를 알 수 있도록 하였다. 기본 구현 이후 여기에 보낸 시각을 같이 넣어주려 한다.
또한 별도로 chatKey1을 저장해서 위 스크린샷에 있는 채팅요청 온 사용자 목록을 불러올 수 있도록 하였다.
getAllChatUsersFromRedis로 이를 전부 불러와서 user1_admin 이런식으로 배열에 전부 담길텐데, 파싱해서 유저이름을 소켓에 담아 클라이언트에게 전달하면 그대로 띄워주는 것이다.
이제 본론이다. 다음 포스팅으로 채팅이 오고가는 과정에 대해 담아보려 한다.
'개발 > library, framework' 카테고리의 다른 글
[NEST] custom repository (0) | 2023.08.30 |
---|---|
[vue3] scroll 관련 이벤트가 작동하지 않을 때 확인할 것 (feat. scrollBehavior) (0) | 2023.08.15 |
[axios] 데이터 통신시 갑자기 request에 문제가 생기는 경우 (0) | 2023.08.06 |
클라이언트에게 특정 상태에만 상태를 전송하는 방법...state.isAdmin (feat.Vue) (0) | 2023.08.03 |
mongoDB [unable to connect: connect ECONNREFUSED 127.0.0.1:27017] 에러 (0) | 2023.04.05 |