Spring WebFlux를 이용한 chat 프로그램 - 다듬기

2025. 5. 12. 11:15JAVA/Spring Boot

우선 앞서 진행했던 것들 중 몇가지를 수정했다.

1. localhost:5173/ 로 접근했을 때 Login 화면으로 이동 처리

App.jsx 부분 수정

import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';

return (
    <Router>
      <Routes>
        <Route path="/" element={<Navigate to="/login" replace />} />
        {/* 3. Route 목록을 Loof 돌리면서 route의 componentName에 해당하는 component를 조회한다. */}
        {routeList.length === 0 ? (
          <Route path="*" element={<div>라우트 불러오는 중...</div>} />
        ) : (
          routeList.map((route, index) => {
            const Component = components[route.componentName];
            if (!Component) return null;

            return (
              <Route
                key={index}
                path={route.path}
                element={<Component />}
              />
            );
          })
        )}
      </Routes>
    </Router>
  );

<Route path="/" element={<Navigate to="/login" replace />} /> 이 부분에서 자동으로 login 페이지로 리디렉션 수행한다.


2. 채팅 시 사용자 nickname 처리 방식 및 좌측 채팅방 이동 처리

ChatMessage.jsx 부분 수정

export default function ChatMessage() {
  ...
  
  // userState('myUsername') -> userState({})로 변경
  // 로그인한 user 객체를 담아야 하기 때문에 변경
  const [currentUser, setCurrentUser] = useState({}); 
  
  ...
  
  /**
   * 채팅방 목록을 조회한다.
   */
  async function loadRooms() {
    ...
    
    // 로그인한 사용자 정보 추가
    const user = localStorage.getItem('user')
    setCurrentUser(JSON.parse(user))
    
    // 채팅방에 입장 중인 참여자 정보를 조회한다.
    const participantsRes = await chatRoomService.selectParticipants(selected.id)
    if (participantsRes.data.success) {
      /*
       * 기존에는
       * const names = participantsRes.data.data.map(u => u.nickname); // 또는 username
       * 이런 식으로 nickname만 저장했었지만 nickname은 바뀔 수 있기 때문에 
       * 사용자의 id로 변경하도록 처리 하기 위해 user Object로 변경
       */
      const participants = participantsRes.data.data; // 참여자 목록
      setParticipants(participants); 
    }
      
    ...
  }
  
  // 메시지를 전송한다.
  const handleSend = () => {
    if (input.trim()) {
      const msg = {
        sender: currentUser.id, //currentUser에서 currentUser.id로 변경
        content: input,
        avatar: '/avatar.png',
      };

      socketRef.current.send(JSON.stringify(msg));
      setInput('');
    }
  };
  
  // sender id로 nickname 찾기 함수 추가
  const getNickname = (senderId) => {
    const found = participants.find(p => p.id === senderId);
    return found ? found.nickname : `사용자(${senderId})`;
  };
  
  ...
  
  return (
      ...
      
      {/* 메시지 영역 */}
      <div className="flex flex-col flex-1">
        ...
        
          {messages.map((msg, idx) => {
            // msg.sender === currentUser  -> msg.sender === currentUser.id로 변경
            const isMine = msg.sender == currentUser.id; // 메시지의 발신자가 현재 사용자와 같은지 확인
            return (
              <div key={idx} className={`flex ${isMine ? 'justify-end' : 'justify-start'}`}>
                <div className={`flex items-end gap-2 ${isMine ? 'flex-row-reverse' : ''}`}>
                  <img src={msg.avatar} alt={msg.sender} className="w-8 h-8 rounded-full border shadow" />
                  <div className="flex flex-col max-w-[66%]">
                    <p className={`
                        text-xs text-gray-500 mb-1 
                        ${isMine ? 'text-right' : 'text-left'}`}
                    >{getNickname(msg.sender)}</p> {/* nickname 조회를 위해 getNickname 처리*/}
                    <div
                      className={`
                        px-4 py-2 rounded-xl shadow inline-block min-w-[10rem] break-words whitespace-normal 
                        ${isMine ? 'bg-blue-500 text-white self-end' : 'bg-gray-100 text-black self-start'}
                      `}
                    >
                      {msg.content}
                    </div>
                  </div>
                </div>
              </div>
            );
          })}
 
      ...
 )

기존에 myUsername이라고 테스트 했던 항목을 실제로 사용하는 사용자들의 id를 통해 nickname를 가져오도록 처리 했다.

이렇게 하면 나중에 nickname가 변경되더라도 변경된 nickname를 보여 줄 수 있다.


3. 좌측 채팅방 클릭 시 이동 처리

ChatMessage.jsx 부분 수정

import { useNavigate, useParams } from 'react-router-dom'; //useNavigate 추가

export default function ChatMessage() {
  ...
  
  const navigate = useNavigate()
  
  ...
  
  // 채팅방 이동 처리 함수 추가
  const handlerMoveRoom = (roomId) => {
    navigate(`/chat/${roomId}`)
  }
  
  return (
    <div className="flex h-screen">
      {/* 채팅방 목록 */}
      <div className="w-1/5 border-r bg-gray-100 p-4 overflow-y-auto">
        <h2 className="text-lg font-bold mb-4">채팅방</h2>
        <ul className="space-y-2">
          {rooms.map((room) => (
            <li 
              key={room.id} 
              className="p-2 rounded bg-white shadow" 
              onClick={() => handlerMoveRoom(room.id)} {/* 채팅방 클릭 시 이동 */}
            >
              🗨️ {room.name}
            </li>
          ))}
        </ul>
      </div>
      
      ...
  )

4. 방 이동 시 메시지 깜빡 거리는 현상

이유는 WebSocket가 이전 채팅방의 메시지를 계속 수신하고 있는 상태에서 새로운 채팅방으로 이동했기 때문에 발생하는 현상이다. 이런 현상을 방지하기 위해 수정해야 한다.

ChatMessage.jsx 부분 수정

/**
   * WebSocket 연결을 초기화한다.
   */
  async function initWebSocket() {

    // 이전 WebSocket 연결이 있다면 닫도록 추가한다.
    if (socketRef.current) {
      socketRef.current.close();
    }    

    // WebSocket 연결을 초기화한다.
    const socket = new WebSocket(`ws://localhost:8080/ws/chat/${roomId}`);
    socketRef.current = socket;
    socket.onopen = () => {
      console.log('WebSocket 연결 성공');
    };

    // WebSocket 메시지 전달 받은 경우
    socket.onmessage = (e) => {
      const msg = JSON.parse(e.data);

       // roomId가 없는 경우 무시하도록 추가
      if( !msg.roomId ) return;

      // 메시지 수신 시 메시지 목록에 추가한다.
      if (msg.roomId.toString() != roomId.toString()) return;

      setMessages((prev) => [...prev, msg]);
    };
  }
  
  useEffect(() => {

    if (!roomId) return;

    // 🔁 채팅방이 바뀔 때 메시지 초기화
    setMessages([]);
    
    ...

  }, [roomId]);

 

여기까지...

이제 방장의 경우 사용자를 관리할 수 있는 기능을 만들어야 겠다.