[webSocket] STOMP 사용해서 채팅프로그램 만들기

첸첸·2021년 11월 17일
1

webSocket in java

목록 보기
9/9

STOMP가 뭔지는 다른 블로그에서도 잘 설명하고 있으니...그냥 바로 코드로..다른 블로그를 참고했는데...링크는 맨 아래에 올려놓을게요

porm.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>stomp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>stomp</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>



    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

config

package com.example.stomp.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //클라이언트로 메세지를 응답 해 줄 때 prefix 정의 - 클라이언트가 메세지를 받을 때
        registry.enableSimpleBroker("/sub"); //ex) stomp.subscribe("/sub/chat/room/",function(){})
        //클라이언트에서 메세지 송신 시 붙일 prefix 정의 - 클라이언트가 메세지를 보낼때
        registry.setApplicationDestinationPrefixes("/pub"); //ex) stomp.send("/sub/chat/room/",function(){})
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //the url is for Websocket handshake
        registry.addEndpoint("/stomp/chat") //handshake가 될 endpoint지정
                .setAllowedOrigins("*") //현재 구동되고 있는 서버와 다른 도메인에서도 접근 가능하게
                .withSockJS(); //SockJS사용
    }
}

VO

채팅방VO

package com.example.stomp.vo;

import lombok.Data;
import org.springframework.web.socket.WebSocketSession;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

@Data
public class ChatRoom {

    private String roomId;
    private String name;
    private Set<WebSocketSession> sessions = new HashSet<>();

    public static ChatRoom createChatRoom (String name) {
        ChatRoom chatRoom = new ChatRoom();
        chatRoom.roomId = UUID.randomUUID().toString();
        chatRoom.name = name;
        return chatRoom;
    }
}

Message VO

package com.example.stomp.vo;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ChatMessage {

    private String roomId;
    private String writer;
    private String message;
}

controller

ChatRoomController

package com.example.stomp.controller;

import com.example.stomp.repository.ChatRoomRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@Controller
@RequiredArgsConstructor
@RequestMapping("/chat")
public class ChatRoomController {
    private final ChatRoomRepository chatRoomRepository;

    //채팅방 목록 조회
    @GetMapping(value = "/rooms")
    public ModelAndView rooms(){

        ModelAndView mv = new ModelAndView("chat/rooms");

        mv.addObject("list", chatRoomRepository.findAllRooms());

        return mv;
    }

    //채팅방 개설
    @PostMapping(value = "/room")
    public String create(@RequestParam String name, RedirectAttributes rttr){

        rttr.addFlashAttribute("roomName", chatRoomRepository.createChatRoom(name));
        return "redirect:/chat/rooms";
    }

    //채팅방 조회
    @GetMapping("/room")
    public ModelAndView getRoom(@RequestParam(value = "roomId") String roomId){
        ModelAndView mv = new ModelAndView("chat/room");
        mv.addObject("room", chatRoomRepository.findByRoomId(roomId));
        return mv;
    }
}

StompController

package com.example.stomp.controller;

import com.example.stomp.vo.ChatMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;

@Controller
@RequiredArgsConstructor
public class StompChatClient {
    private final SimpMessagingTemplate messagingTemplate;

    @MessageMapping(value = "/chat/enter")
    public void enter(ChatMessage chatMessage) {
        System.out.println("연결성공");
        chatMessage.setMessage(chatMessage.getWriter() + "님이 채팅방에 참여하셨습니다.");
        messagingTemplate.convertAndSend("/sub/chat/room/" + chatMessage.getRoomId(), chatMessage);
    }

    @MessageMapping(value = "/chat/message")
    public void message(ChatMessage chatMessage) {
        messagingTemplate.convertAndSend("/sub/chat/room/"+chatMessage.getRoomId(),chatMessage);
    }
}

view

rooms.html : 채팅방 생성 및 채팅방 리스트확인

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
  <script src="https://code.jquery.com/jquery-2.2.1.min.js"></script>
</head>
<body>
  <div class="container">
    <div>
      <ul th:each="room : ${list}">
        <li><a th:href="@{/chat/room(roomId=${room.roomId})}">[[${room.name}]]</a></li>
      </ul>
    </div>
  </div>
  <form th:action="@{/chat/room}" method="post">
    <input type="text" name="name" class="form-control">
    <button class="btn btn-secondary">개설하기</button>
  </form>
</body>


<script th:inline="javascript">
  $(document).ready(function(){

    var roomName = /*[[${roomName}]]*/;

    if(roomName != null)
      alert( roomName.name+ "방이 개설되었습니다.");

    $(".btn-create").on("click", function (e){
      e.preventDefault();

      var name = $("input[name='name']").val();

      if(name == "")
        alert("Please write the name.")
      else
        $("form").submit();
    });

  });
</script>

</html>

room.html : 채팅방

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <!-- 합쳐지고 최소화된 최신 CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">

  <!-- 부가적인 테마 -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">

  <!-- 합쳐지고 최소화된 최신 자바스크립트 -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
  <script src="https://code.jquery.com/jquery-2.2.1.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
</head>
<body>
  <div class="container">
    <div class="col-6">
      <h1>[[${room.name}]]</h1>
    </div>
    <div>
      <div id="msgArea" class="col"></div>
      <div class="col-6">
        <div class="input-group mb-3">
          <input type="text" id="msg" class="form-control">
          <div class="input-group-append">
            <button class="btn btn-outline-secondary" type="button" id="button-send">전송</button>
          </div>
        </div>
      </div>
    </div>
    <div class="col-6"></div>
  </div>
</body>

<script th:inline="javascript">
  /*<![CDATA[*/
  $(document).ready(function(){

    var roomName = /*[[${room.name}]]*/;
    var roomId = /*[[${room.roomId}]]*/;
    var username = prompt("what's your name?");

    console.log(roomName + ", " + roomId + ", " + username);

    var sockJs = new SockJS("/stomp/chat");
    //1. SockJS를 내부에 들고있는 stomp를 내어줌
    var stomp = Stomp.over(sockJs);

    //2. connection이 맺어지면 실행
    stomp.connect({}, function (){
      console.log("STOMP Connection")

      //4. subscribe(path, callback)으로 메세지를 받을 수 있음
      stomp.subscribe("/sub/chat/room/" + roomId, function (chat) {
        var content = JSON.parse(chat.body);

        var writer = content.writer;
        var message = content.message;
        var str = '';

        if(writer === username){
          str = "<div class='col-6'>";
          str += "<div class='alert alert-secondary'>";
          str += "<b>" + writer + " : " + message + "</b>";
          str += "</div></div>";
        }
        else{
          str = "<div class='col-6'>";
          str += "<div class='alert alert-warning'>";
          str += "<b>" + writer + " : " + message + "</b>";
          str += "</div></div>";
        }
        $("#msgArea").append(str);
      });

      //3. send(path, header, message)로 메세지를 보낼 수 있음
      stomp.send('/pub/chat/enter', {}, JSON.stringify({roomId: roomId, writer: username}))
    });

    $("#button-send").on("click", function(e){
      var msg = document.getElementById("msg");

      console.log(username + ":" + msg.value);
      stomp.send('/pub/chat/message', {}, JSON.stringify({roomId: roomId, message: msg.value, writer: username}));
      msg.value = '';
    });
  });
  /*]]>*/
</script>
</html>

참고
https://dev-gorany.tistory.com/235?category=901854

0개의 댓글