본문 바로가기

STUDY/Spring

Spring Boot | Kafka를 이용한 채팅 (3) 메시지 주고받기 + ReactJS

1. Controller작성

✔️kafkaTemplate.send메서드를 통해 메시지가 전송됨

@Slf4j
@CrossOrigin
@RestController
@RequestMapping(value = "/kafka")
public class ChatController {
    @Autowired
    private KafkaTemplate<String, Message> kafkaTemplate;

    @PostMapping(value = "/publish")
    public void sendMessage(@RequestBody Message message) {
        log.info("Produce message : " + message.toString());
        message.setTimestamp(LocalDateTime.now().toString());
        try {
            kafkaTemplate.send(KafkaConstants.KAFKA_TOPIC, message).get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @MessageMapping("/sendMessage")
    @SendTo("/topic/group")
    public Message broadcastGroupMessage(@Payload Message message) {
        return message;
    }

}

 

2. React 프로젝트 생성

create-react-app을 이용해 만들어주었음

추가로 설치해야 할 모듈

npm install axios react-stomp sockjs

 

3. 컴포넌트 작성

✔️pages폴더 - view 담당.. 원래는 components폴더를 따로 나누고 해야되는데 지금은 생략

✔️services폴더 - api와 소통하는 서비스 코드들을 작성할 곳

3-1. Login.js

간단히 사용자 이름만 입력받는 형식

import React, { useState } from "react";

function Login({ handleOnSubmit }) {
  const [name, setName] = useState("");

  const handleOnChange = (e) => {
    setName(e.target.value);
  };

  const handleSubmit = () => {
    handleOnSubmit(name);
  };
  
  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          placeholder="사용할 닉네임을 입력하세요."
          value={name}
          onChange={handleOnChange}
        />
        <button type="submit">Go!</button>
      </form>
    </div>
  );
}

export default Login;

 

3-2. Chat.js, Input.js

침착하게 작성해줍니다.. 👇

더보기
import React from "react";

function Chat({ messages, currentUser }) {

  const formattingTimestamp = (timestamp) => {
    const date = new Date(timestamp);
    let hour = date.getHours() < 10 ? `0${date.getHours()}` : date.getHours();
    let min =
      date.getMinutes() < 10 ? `0${date.getMinutes()}` : date.getMinutes();
    return `${hour}:${min}`;
  };

  return (
    <div className="chat-middle">
      {messages.map((msg) => (
        <li
          className={`chat-bubble ${
            msg.author === currentUser.name ? "send" : "receive"
          }`}
        >
          <span>{msg.author}</span>
          <p>{msg.content}</p>
          <span>{formattingTimestamp(msg.timestamp)}</span>
        </li>
      ))}
    </div>
  );
}

export default Chat;
import React, { useState } from "react";

function Input({ handleOnSubmit }) {
  const [msg, setMsg] = useState("");

  const handleOnChange = (e) => {
    setMsg(e.target.value);
  };
  const handleSubmit = async (e) => {
    e.preventDefault();
    handleOnSubmit(msg);
    setMsg("");
  };

  return (
    <div className="chat-bottom">
      <form onSubmit={handleSubmit}>
        <input
          placeholder="내용을 입력하세요."
          value={msg}
          onChange={handleOnChange}
          onKeyPress={(e) => {
            if (e.key === "Enter") {
              handleSubmit(e);
            }
          }}
        />
        <button type="submit">전송</button>
      </form>
    </div>
  );
}

export default Input;

 

3-3. App.js

일단 import

import React, { useState } from "react";
import SockJsClient from "react-stomp";
import chatApi from "./services/chatapi";

messages는 대화 목록이 담길 리스트, user는 말그대로 유저..

const [messages, setMessages] = useState([]);
const [user, setUser] = useState(null);

 

onMessageReceived는 메시지가 도착하면 messages에 도착한 값을 더해줌 (concat을 이용해 뒤에 계속 추가됨)

handleMessageSubmit은 Input.js에 넘겨주는 함수... 서버로 전송

  const onMessageReceived = (msg) => {
    console.log("New Message Received!!", msg);
    setMessages(messages.concat(msg));
  };

  const handleLoginSubmit = (name) => {
    setUser({ name: name, color: randomColor() });
  };

  const handleMessageSubmit = (msg) => {
    chatApi
      .sendMessage(user.name, msg)
      .then((res) => {
        console.log("sent", res);
      })
      .catch((e) => {
        console.log(e);
      });
  };

 

user값이 null이면 로그인을 먼저 하게 되어있음..

SockJsClient를 이용해서 소켓통신을 하게되는 것

✔️url - 서버의 웹 소켓 통신 url Spring Boot에서 WebSocketConfiguration작성할 때 적었던 endpoint값을 적어주면 됨

*포트번호를 임의로 수정하였음. 기본 포트는 8080

✔️onConnect / onDisconnect - 소켓 연결, 연결해제되었을 때 실행되는 콜백함수

✔️onMessage - 메시지를 받음

  return (
    <>
      {user !== null ? (
        <div className="chat-container">
          <SockJsClient
            url={"http://localhost:6002/my-chat/"}
            topics={["/topic/group"]}
            onConnect={console.log("connected!")}
            onDisconnect={console.log("disconnected!")}
            onMessage={(msg) => onMessageReceived(msg)}
            debug={false}
          />
          <Chat messages={messages} currentUser={user} />
          <Input handleOnSubmit={handleMessageSubmit} />
        </div>
      ) : (
        <Login handleOnSubmit={handleLoginSubmit} />
      )}
    </>
  );

 

전체코드👇

더보기
import React, { useState } from "react";
import SockJsClient from "react-stomp";
import chatApi from "./services/chatapi";

import "./styles.css";

import Chat from "./pages/Chat";
import Input from "./pages/Input";
import Login from "./pages/Login";

function App() {
  const [messages, setMessages] = useState([]);
  const [user, setUser] = useState(null);

  const onMessageReceived = (msg) => {
    console.log("New Message Received!!", msg);
    setMessages(messages.concat(msg));
  };

  const handleLoginSubmit = (name) => {
    setUser({ name: name, color: randomColor() });
  };

  const handleMessageSubmit = (msg) => {
    chatApi
      .sendMessage(user.name, msg)
      .then((res) => {
        console.log("sent", res);
      })
      .catch((e) => {
        console.log(e);
      });
  };

  const randomColor = () => {
    return "#" + Math.floor(Math.random() * 0xffffff).toString(16);
  };

  return (
    <>
      {user !== null ? (
        <div className="chat-container">
          <SockJsClient
            url={"http://localhost:6002/my-chat/"}
            topics={["/topic/group"]}
            onConnect={console.log("connected!")}
            onDisconnect={console.log("disconnected!")}
            onMessage={(msg) => onMessageReceived(msg)}
            debug={false}
          />
          <Chat messages={messages} currentUser={user} />
          <Input handleOnSubmit={handleMessageSubmit} />
        </div>
      ) : (
        <Login handleOnSubmit={handleLoginSubmit} />
      )}
    </>
  );
}

export default App;

 

4. chatapi.js

baseURL.. kafka/~~~로 가게됩니다..

sendMessage가 /kafka/publish로 전송(produce)됨

import Axios from "axios";

const api = Axios.create({
  baseURL: "http://localhost:6002/kafka",
});

const chatAPI = {
  getMessages: (groupId) => {
    console.log("Calling get messages from API");
    return api.get(`/messages/${groupId}`);
  },

  sendMessage: (username, text) => {
    let msg = {
      author: username,
      content: text,
    };
    return api.post(`/publish`, msg, {
      headers: { "Content-Type": "application/json" },
    });
  },
};

export default chatAPI;

 

5. 채팅!!!

뭔가 설명을 놓친게 있는 기분인데... 혹시 빠진게 있는 것 같다믄 댓글주세요...