Notice
Recent Posts
Recent Comments
Link
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Archives
Today
Total
관리 메뉴

JinHee's Board

Project - Socket과 ElasticSearch 를 활용한 채팅 사이트 개발일지 [4] 본문

공부한 내용정리/기타

Project - Socket과 ElasticSearch 를 활용한 채팅 사이트 개발일지 [4]

JinHee Han 2022. 5. 22. 14:25

 

 

 Project - Socket과 ElasticSearch 를 활용한 채팅 사이트 개발일지 [3]

 

 

구현 결과

  • 로그인시 현재 참여중인 채팅방 목록 표시
  • 사용자간의 채팅 구현

채팅방 목록 표시

 

Elastic Index 생성 ( 채팅방 목록 )

채팅방 구분에 필요한 키와 그 외의 데이터를 담을 인덱스를 생성한다. 

인덱스 생성에 필요한 매핑을 설정한다. ( 필드 타입 지정 등.. )

  PUT chat_room
  {
    "mappings" : {
      "properties" : {
        "room_date" : {
          "type" : "date",
          "ignore_malformed" : true
        },
        "room_id" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword"
            }
          }
        },
        "room_name" : {
          "type" : "text"
        },
        "room_users" : {
          "type" : "text"
        }
      }
    }
  }

room_id : 채팅방 구분 키 , room_name : 채팅방 이름

room_users : 채팅방 참여자 id 목록 (구분자 ,) , room_date : 채팅방 생성일

 

삽입된 데이터 예시) 

 

 

서버(Back) 단 코드 수정

서버에서는 화면에서 로그인시 ElasticSearch 서버에 로그인한 id가 포함되어있는 채팅방 목록 검색을 실행하고 

그 결과를 리턴할 소켓을 생성한다. ( 채팅방 목록 검색시 query_string 을 사용했다 )

 

App.js 에 아래 코드 추가

    socket.on('loadRoom',(user) => {
        console.log("Call Room Lists");
        let callList = esService.search("chat_room", {
            "query": {
                "query_string": {
                    "query": "room_users : *"+user+"*"
                }
            }
        });

        callList.then(function (result) {
            socket.emit('renderlist', result.hits.hits); // 클라이언트(화면) 에게 결과 전송           
        })
    });

 

 

화면(Front) 단 코드 수정

 

로그인에 성공하면 histroy.push 를 실행하여 채팅방 목록이 있는 페이지로 이동한다.

    socket.on('checkLogin', (user) => {
        history.push({
          pathname : '/roomList/' + user,
          user_id : user
        })
    })

위의 pathname에서 /roomList/ 경로로 이동하기 위해서 App.js에 라우터를 추가시킨다.

 

 

App.js 수정

import Login from "./routes/Login"
import RoomList from './routes/RoomList';

function App() {
  return <Router>
    <Switch>
      <Route path="/roomList/:id" component={RoomList}/>
      <Route path="/" component={Login}/>    
    </Switch>
  </Router>;
}

그다음 routes 디렉토리 하위에 RoomList.js파일을 추가한다.

RoomList에는 아래와 같은 기능들이 필요하다.

  • 페이지에 이동하자마자 채팅방 목록 출력
  • 채팅방 목록중 하나를 클릭하면 채팅방으로 입장

 

채팅방 목록 출력의 경우 페이지에 이동후 서버에게 한번만 요청해야 하므로 useEffect 를 사용하고 서버로부터 결과를 받아야 한다.

    const [room, setRoom] = useState([]);
    
    useEffect(()=>{
        socket.emit('loadRoom', location.user_id); 
    }, []);

    socket.on('renderlist', (room) => {
        setRoom(room);
    });

 

또한 채팅방 목록에서 각 채팅방을 클릭하면 페이지 이동이 필요하다.

    const connect_room = (roomInfo) => {
        history.push({
            pathname : '/room/' + roomInfo.target.id,
            room_id : roomInfo.target.id,
            user_id : location.user_id
          })
    }

 

RoomList.js 생성

import React from 'react';
import { useState, useEffect } from "react";
import io from "socket.io-client";
import "../css/Room_List.css";
const socket = io("http://localhost:3001/");  //3001 Back단 서버포트

function RoomList({location, history}) {
    
    const [room, setRoom] = useState([]);
    
    useEffect(()=>{
        socket.emit('loadRoom', location.user_id); 
    }, []);

    socket.on('renderlist', (room) => {
        setRoom(room);
    });


    const connect_room = (roomInfo) => {
        history.push({
            pathname : '/room/' + roomInfo.target.id,
            room_id : roomInfo.target.id,
            user_id : location.user_id
          })
    }

    return <div>
        <div id="roomList">
            {location.user_id}
        </div>
        <div className='room_container'>
            {
                room.map((ele) => 
                <div className="room_element" key={ele._source.room_id} id={ele._source.room_id} onClick={connect_room}>
                    {ele._source.room_name}
                </div>
                )
            }
        </div>
    </div>
}

export default RoomList

 

채팅방 목록 구현 결과

(좌) 서버 터미널에 출력되는 채팅방 목록 로그 , (우) 로그인시 나타나는 채팅방 목록


채팅 구현

각 방에 참여되어 있는 사용자간의 채팅을 구현한다.

예를 들어 1번방에 A,와 B가 참여하고 있고 2번방에는 C와 D가 참여중이라면 A와 B의 채팅내용을 C와 D는 볼수 없도록 한다.

 

 

서버(Back) 단 코드 수정

 

먼저 채팅방에 입장할 때 해당 채팅방의 id 정보를 전달받을 소캣을 생성한다.

채팅방의 id정보는 참여중인 채팅방 구분에 사용된다.

 

App.js 수정

    socket.on('enterRoom', (roomid) => {
        socket.join(roomid); // 방마다 구분하는 소캣 join
        console.log("방 들어감")
    })

    socket.on('reaveRoom', (roomid) => {
        socket.leave(roomid);
    })
    
    socket.on('send', (user, message, room_id) => {
        console.log('메시지전송: ' + user);

    	//채팅 메세지를 전달
        socket.broadcast.to(room_id).emit('recept_message', message, user);       
    });

채팅방 구분에 사용되는 부분은 socket.join 이다.

socket.join을 통해 어디에 join되어있는지 설정하면 send가 실행되어 채팅 메시지를 broadcast.to 를 통해 전달 할때 join되어 있는 방에만 broadcast 할 수 있다.

 

 

화면(Front) 단 코드 수정

 

RoomList 에서 histroy.push 를 통해 해당 경로로 이동하기 위해 App.js 에 router를 추가한다.

 

App.js 수정

import Login from "./routes/Login"
import RoomList from './routes/RoomList';
import Room from './routes/Room';

function App() {
  return <Router>
    <Switch>
      <Route path="/room/:roomid" component={Room}/>  
      <Route path="/roomList/:id" component={RoomList}/>
      <Route path="/" component={Login}/>    
    </Switch>
  </Router>;
}

 

Room.js에는 채팅방에 처음입장시 채팅방 정보를 서버로 전달한다.

    const [roomid, setRoomId] = useState("");
    
    useEffect(() => {
        setRoomId(location.room_id);
        socket.emit('enterRoom', location.room_id);
    }, [])

 

또한 채팅 내용을 입력할 수 있고 채팅 한 내용을 화면에 표시해야 한다.

    const [message, setMessage] = useState("");
    const [messageLog, setLog] = useState([]);
    
    useEffect(() => {
        socket.on('recept_message', (message, user) => {
            addMessage(user, message, 'other')
        });
    }, [])
    
   const addMessage = (name, message, target) => {
        let message_div = {
            "userName" : name,
            "message" : message,
            "type" : target
        }
        if(message_div.type == 'me') message_div.userName = 'me' 
        setLog((currentArray) => [ ...currentArray,message_div])
    }

    const doSend = (event) => {
        socket.emit('send', location.user_id ,message, roomid); // send 요청
        addMessage(location.user_id, message, 'me') // 화면에 채팅내용 표시
        setMessage(""); // 메세지 입력창 초기화
        event.preventDefault();
    }
    
    const onInputMessage = (event) => setMessage(event.target.value);

채팅내용을 화면에 표시할 때는 채팅한 사람이 본인인지 아니면 다른 사람인지 구분해야 한다.

 

dosend 에서는 채팅을 한사람이 본인 이기 때문에 addMessage를 실행할때 'me' 값을 같이 전달하며

다른 사람의 채팅을 전달받는 recept_message 소캣에서는 addMessage를 실행할때 'other'을 전달한다.

 

이를 통해 채팅화면에서 내가 작성한 채팅은 me : [내용] 으로 표시하고 다른 사람이 작성한 채팅은[ 작성자id ] : [내용] 으로 표시된다.

(addMessage 의 if 부분 참조)

 

 

Room.js 생성

import React from 'react';
import ReactDOM from 'react-dom/client';
import { useState, useEffect } from "react";
import io from "socket.io-client";
const socket = io("http://localhost:3001/");

function Room({ location, history }) {
    const [roomid, setRoomId] = useState("");
    const [message, setMessage] = useState("");
    const [messageLog, setLog] = useState([]);

    useEffect(() => {
        setRoomId(location.room_id);
        socket.emit('enterRoom', location.room_id);
    }, [])

    useEffect(() => {
        socket.on('recept_message', (message, user) => {
            addMessage(user, message, 'other')
        });
    }, [])

    const addMessage = (name, message, target) => {
        let message_div = {
            "userName" : name,
            "message" : message,
            "type" : target
        }
        if(message_div.type == 'me') message_div.userName = 'me'
        setLog((currentArray) => [ ...currentArray,message_div])
    }

    const onInputMessage = (event) => setMessage(event.target.value);

    const doSend = (event) => {
        socket.emit('send', location.user_id ,message, roomid); // check 요청
        addMessage(location.user_id, message, 'me')
        setMessage("");
        event.preventDefault();
    }

    const goList = (event) => {
        socket.emit('reaveRoom', location.room_id);
        
        history.push({
            pathname : '/roomList/' + location.user_id,
            user_id : location.user_id
        })
        event.preventDefault();
    }

    return <div>
        <div>
            대화내용
        </div>
        <div>
            <form onSubmit={doSend}>
                <div id="message_window">
                    {messageLog.map((message_div, index) => (
                        <div className={message_div.user} key={index}>{message_div.userName} : {message_div.message}</div>
                    ))
                    }
                </div>
                <div>
                    <input onChange={onInputMessage} type="text" value={message} id="user_message" placeholder="메시지를 입력하세요" /><br />
                </div>
                <div>
                    <button onClick={doSend} id="doSend">전송</button>
                </div>
            </form>
            <div>
            <button onClick={goList} id="goList">뒤로</button>
            </div>
        </div>
    </div>
}
export default Room

 

채팅방 구현 결과

 


 

개발일지는 여기까지만 작성하지만 차후에는 사용자 목록을 조회하고 사용자들을 초대해 채팅방을 추가하는 등 여러 기능을 추가할 예정이다.

 

 

Socket과 ElasticSearch 를 활용한 채팅 사이트 개발일지 마침-

Comments