시작하면서
오랜만에 돌아온 자바 갖고놀기 프로젝트!!
이번에는 예전부터 정말정말정말 해보고 싶었던 spring 과 웹소켓을 이용한 채팅 프로그램 구현을 해보려고 합니다.
오늘 뼈대 만들기를 시작해서 stomp 를 사용한 채팅 구현, 파일 업로드, DB 와 연결 등등 여러가지를 더해서 만들겠습니다!
추가로 아래 코드 및 설명에서 등장하는 새로 공부하게된 어노테이션과 개념들은 따로 정리하도록 하겠습니다
그럼 시작하겠습니다
필수 라이브러리 임포트
- gradle 에 websocket 임포트
// WebSocket
implementation 'org.springframework.boot:spring-boot-starter-websocket'
websocket Handler 작성
- 웹 소켓 클라이언트로부터 채팅 메시지를 전달받아 채팅 메시지 객체로 변환
- 전달받은 메시지에 담긴 채팅방 Id 로 발송 대상 채팅방 정보를 조회
- 해당 채팅방에 입장해 있는 모든 클라이언트(Websocket Session) 에게 타입에 따른 메시지 발송
package webChat.Handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import webChat.dto.ChatDTO;
import webChat.dto.ChatRoom;
import webChat.service.ChatService;
@Slf4j
@Component
@RequiredArgsConstructor
public class WebSocketChatHandler extends TextWebSocketHandler {
private final ObjectMapper mapper;
private final ChatService service;
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
log.info("payload {}", payload);
// TextMessage textMessage = new TextMessage("Welcome Chatting Server");
// session.sendMessage(textMessage);
ChatDTO chatMessage = mapper.readValue(payload, ChatDTO.class);
log.info("session {}", chatMessage.toString());
ChatRoom room = service.findRoomById(chatMessage.getRoomId());
log.info("room {}", room.toString());
room.handleAction(session, chatMessage, service);
}
}
config 설정
package webChat;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class SpringConfig implements WebSocketConfigurer {
// WebSocketHandler 에 관한 생성자 추가
private final WebSocketHandler webSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// endpoint 설정 : /ws/chat
// 이를 통해서 ws://localhost:8080/ws/chat 으로 요청이 들어오면 websocket 통신을 진행한다.
registry.addHandler(webSocketHandler, "/ws/chat").setAllowedOrigins("*");
}
}
채팅 메시지 클래스 : ChatDTO
- 채팅 메시지에 대한 정보를 담는 클래스 : 일종의 채팅 내용에 대한 DTO
- 채팅 내용은 크게 들어오는 사람에 대한 환영 메시지에 대한 ENTER 과 방에 있는 사람들이 채팅을 칠 때 사용하는 TALK 두 가지로 메시지 타입을 나눈다. 이때 타입은 ENUM 으로 선언한다.
- 다음으로 어떤 방에서 채팅이 오가는지 확인하기 위한 방번호, 채팅 보낸 사람, 메시지, 채팅 발송 시간 등을 변수로 선언한다.
- 여기서 더 나가면 ENTER, TALK 뿐만 아니라 OUT 으로 메시지 타입을 추가해서 나가는 사람에 대한 메시지를 전달해도 좋을듯!
package webChat.dto;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class ChatDTO {
// 메시지 타입 : 입장, 채팅
public enum MessageType{
ENTER, TALK
}
private MessageType type; // 메시지 타입
private String roomId; // 방 번호
private String sender; // 채팅을 보낸 사람
private String message; // 메시지
private String time; // 채팅 발송 시간간
}
채팅방 클래스 : ChatRoom
- 채팅방에 대한 정보를 담는 클래스 : 역시나 채팅방 DTO 로 생각하면 편하다!
- 채팅방 클래스는 해당 채팅방에 어떤 사람이 있는지에 대한 정보를 갖고 있어야 한다. 즉 채팅방에 입장한 클라이언트에 대한 내용, 즉 클라이언트별 세션을 갖고 있어야한다.
- 이를 위해서 클라이언트별로 세션을 저장하기 위한 sessions 라는 이름의 HashSet 을 만든다.
- 다음으로 채팅방의 아이디, 채팅방의 이름을 변수로 갖는다.
- 메서드는 총 2가지를 선언한다.
- handleAction : message type 에 따라서 session(클라이언트)에게 메시지를 전달하기 위한 메서드이다. type 이 ENTER 인 경우 채팅방에 “환영합니다”를 띄우고 TALK 인 경우 채팅방에 클라이언트가 발송한 채팅내용(message) 내용을 그대로 채팅방에 반환한다.
- sendMessage 는 sessions 에 담긴 모든 session 에 handleAction 으로 부터 넘어온 message 를 전달할 수 있도록 하는 메서드이다.
package webChat.dto;
import lombok.Builder;
import lombok.Data;
import org.springframework.web.socket.WebSocketSession;
import webChat.service.ChatService;
import java.util.HashSet;
import java.util.Set;
@Data
public class ChatRoom {
private String roomId; // 채팅방 아이디
private String name; // 채팅방 이름
private Set<WebSocketSession> sessions = new HashSet<>();
@Builder
public ChatRoom(String roomId, String name){
this.roomId = roomId;
this.name = name;
}
public void handleAction(WebSocketSession session, ChatDTO message, ChatService service) {
// message 에 담긴 타입을 확인한다.
// 이때 message 에서 getType 으로 가져온 내용이
// ChatDTO 의 열거형인 MessageType 안에 있는 ENTER 과 동일한 값이라면
if (message.getType().equals(ChatDTO.MessageType.ENTER)) {
// sessions 에 넘어온 session 을 담고,
sessions.add(session);
// message 에는 입장하였다는 메시지를 띄운다
message.setMessage(message.getSender() + " 님이 입장하셨습니다");
sendMessage(message, service);
} else if (message.getType().equals(ChatDTO.MessageType.TALK)) {
message.setMessage(message.getMessage());
sendMessage(message, service);
}
}
public <T> void sendMessage(T message, ChatService service) {
sessions.parallelStream().forEach(session -> service.sendMessage(session, message));
}
}
채팅 서비스 : ChatService
- 채팅 서비스 클래스 : 여기서 사용되는 findAllRoom, createRoom, findRoomById 등은 사실상 DB 와 연결되는 순간 DAO 로 넘어가야한다.
- 지금은 DB 와 연결없이 만들 예정이기 때문에 일단 Service 클래스에 다 때려박아두었다.
- DB 와 연결이 없기 때문에 일단 채팅방 정보가 HashMap 안에 저장되어 있다.
- createRoom : UUID 를 통해 랜던으로 생성된 UUID 값으로 채팅방 아이디를 정하고, NAME으로 채팅방 이름을 정해서 채팅방을 생성한다.
- sendMessage : 지정된 세션에 메시지를 발송한다. 여기서 사용되는 메서드들은 아래에 따로 정리 예정!
- find~~~Room : roomId 를 기준으로 map 에 담긴 채팅방 정보를 조회한다.
package webChat.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import webChat.dto.ChatRoom;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.*;
@Slf4j
@Data
@Service
public class ChatService {
private final ObjectMapper mapper;
private Map<String, ChatRoom> chatRooms;
@PostConstruct
private void init() {
chatRooms = new LinkedHashMap<>();
}
public List<ChatRoom> findAllRoom(){
return new ArrayList<>(chatRooms.values());
}
public ChatRoom findRoomById(String roomId){
return chatRooms.get(roomId);
}
public ChatRoom createRoom(String name) {
String roomId = UUID.randomUUID().toString(); // 랜덤한 방 아이디 생성
// Builder 를 이용해서 ChatRoom 을 Building
ChatRoom room = ChatRoom.builder()
.roomId(roomId)
.name(name)
.build();
chatRooms.put(roomId, room); // 랜덤 아이디와 room 정보를 Map 에 저장
return room;
}
public <T> void sendMessage(WebSocketSession session, T message) {
try{
session.sendMessage(new TextMessage(mapper.writeValueAsString(message)));
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
Controller : RestController
- 컨트롤러는 RestController 로 구현한다.
- 이는 메시지를 받을때도 보낼때도 json 형식을 사용하기 때문!!
package webChat.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import webChat.dto.ChatRoom;
import webChat.service.ChatService;
import java.util.List;
@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/chat")
public class ChatController {
private final ChatService service;
@PostMapping
public ChatRoom createRoom(@RequestParam String name){
return service.createRoom(name);
}
@GetMapping
public List<ChatRoom> findAllRooms(){
return service.findAllRoom();
}
}
코드 구현 확인하기
- 크롬 확장 프로그램인 talend api tster 와 socket test client 를 사용해서 채팅 방을 만들고, 채팅을 시작하도록 하겠습니다.
- 아래처럼 http://localhost:8080/chat?name=채팅방명 으로 요청을 보내면 서버에서 roomId, name, session 을 보내준다
- 받아온 roodId 를 이용해서 아래처럼 채팅구현이 가능하다.
- roomId 를 일종의 채팅방으로 생각하면 되고, talk 와 enter 를 작성해가면서 채팅을 보내게 된다.
- Reference
https://chrome.google.com/webstore/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm
https://chrome.google.com/webstore/detail/websocket-test-client/fgponpodhbmadfljofbimhhlengambbn
https://daddyprogrammer.org/post/4077/spring-websocket-chatting/
https://dev-gorany.tistory.com/3
'토이 프로젝트 > Spring&Java 갖고놀기' 카테고리의 다른 글
Spring Boot Web Chatting : 스프링 부트로 실시간 채팅 만들기(2) chatDTO, DAO, Socket.js 코드 알아보기 (17) | 2022.09.01 |
---|---|
Spring Boot Web Chatting : 스프링 부트로 실시간 채팅 만들기(1) stomp, socketjs, websocket (8) | 2022.08.29 |
DataPlay Project - 2 : 이미지 텍스트 번역기 (2) 코드 정리 (0) | 2022.06.11 |
DataPlay Project - 2 : 이미지 텍스트 번역기 (1) 파파고 API & Tesseract OCR (0) | 2022.06.01 |
DataPlay project - 1 : word cloud 생성기 코드 정리 (0) | 2022.05.10 |
댓글