지난번에 취미(?)로 VS에서 node.js를 사용한 채팅을 구현해봤는데, 인텔리제이에서는 '웹소켓'으로 구현할 수 있다! 여러번 시도했지만 인텔리제이에서 노드를 쓰는 것보다 그냥 웹소켓을 사용하는 게 더 나은 것 같다.
Transport protocol의 일종으로 서버와 클라이언트 간의 효율적인 양방향 통신을 실현하기 위한 구조이다.
웹소켓 단순한 API로 구성되어있으며, 웹소켓을 이용하면 하나의 HTTP 접속으로 양방향 메시지를 자유롭게 주고받을 수 있다.
서버와 클라이언트간의 웹소켓 연결을 HTTP프로토콜을 통해 이루진다.
연결이 정상적으로 이루어진다면 서버와 클라이언트 간에 웹소켓 연결(TCP/IP기반)이 이루어지고 일정 시간이 지나면 HTTP연결은 자동으로 끊어진다.
기본적으로 웹소켓 API는 아주 간단한 기능들만을 제공하기 때문에 대부분의 경우 SockJS나 Socket.IO같은 오픈 소스 라이브러리를 많이 사용하고 있으며 메시지 포맷 또한 STOMP같은 프로토콜을 같이 이용한다.
출처: https://choseongho93.tistory.com/266 [TROLL]
기본적으로 STOMP에 대해서는 개발하는 고라니님이 잘 설명해주셔서 정말 감사했다.
https://dev-gorany.tistory.com/235
일단 Entity 설계부터
채팅방, 채팅이력을 저장하는 db를 설계하였고,
채팅방의 id를 채팅이력 테이블이 참조한다.
package com.phl.cocolo.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Setter
@Table(name = "chatRoom_table")
public class ChatRoomEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "chatRoom_id")
private Long id;
@Column
private String roomId;
@Column
private String roomName;
@Column
private String chatMentor;
@OneToMany(mappedBy = "chatRoomEntity", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<ChatMessageEntity> chatMessageEntityList = new ArrayList<>();
public static ChatRoomEntity toChatRoomEntity(String roomName, String roomId, String chatMentor){
ChatRoomEntity chatRoomEntity = new ChatRoomEntity();
chatRoomEntity.setRoomName(roomName);
chatRoomEntity.setRoomId(roomId);
chatRoomEntity.setChatMentor(chatMentor);
return chatRoomEntity;
}
}
package com.phl.cocolo.entity;
import com.phl.cocolo.dto.ChatMessageSaveDTO;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.springframework.data.annotation.CreatedDate;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Getter
@Setter
@Table(name = "chat_table")
public class ChatMessageEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "chat_id")
private Long id;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "chatRoom_id")
private ChatRoomEntity chatRoomEntity;
private String writer;
@Column
private String message;
@CreationTimestamp
@Column(updatable = false)
private LocalDateTime sendDate;
public static ChatMessageEntity toChatEntity(ChatMessageSaveDTO chatMessageSaveDTO, ChatRoomEntity chatRoomEntity){
ChatMessageEntity chatMessageEntity = new ChatMessageEntity();
chatMessageEntity.setChatRoomEntity(chatRoomEntity);
chatMessageEntity.setWriter(chatMessageSaveDTO.getWriter());
chatMessageEntity.setMessage(chatMessageSaveDTO.getMessage());
return chatMessageEntity;
}
}
package com.phl.cocolo.dto;
import lombok.*;
import org.springframework.web.socket.WebSocketSession;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChatRoomDTO {
private String chatMentor;
private String roomId;
private String name;
private Set<WebSocketSession> sessions = new HashSet<>();
//WebSocketSession 은 Spring 에서 Websocket Connection 이 맺어진 세션
public static ChatRoomDTO create(String name){
ChatRoomDTO room = new ChatRoomDTO();
room.roomId = UUID.randomUUID().toString();
room.name = name;
return room;
}
}
package com.phl.cocolo.dto;
import com.phl.cocolo.entity.ChatRoomEntity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChatRoomDetailDTO {
private Long chatRoomId;
private String chatMentor;
private String roomId;
private String name;
public static ChatRoomDetailDTO toChatRoomDetailDTO(ChatRoomEntity chatRoomEntity){
ChatRoomDetailDTO chatRoomDetailDTO = new ChatRoomDetailDTO();
chatRoomDetailDTO.setChatRoomId(chatRoomEntity.getId());
chatRoomDetailDTO.setChatMentor(chatRoomEntity.getChatMentor());
chatRoomDetailDTO.setRoomId(chatRoomEntity.getRoomId());
chatRoomDetailDTO.setName(chatRoomEntity.getRoomName());
return chatRoomDetailDTO;
}
}
package com.phl.cocolo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChatMessageSaveDTO {
private String roomId;
private String writer;
private String message;
}
package com.phl.cocolo.dto;
import com.phl.cocolo.entity.ChatMessageEntity;
import lombok.*;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChatMessageDetailDTO {
private Long chatId;
private Long chatRoomId;
private String roomId;
private String writer;
private String message;
public static ChatMessageDetailDTO toChatMessageDetailDTO(ChatMessageEntity chatMessageEntity){
ChatMessageDetailDTO chatMessageDetailDTO = new ChatMessageDetailDTO();
chatMessageDetailDTO.setChatId(chatMessageEntity.getId());
chatMessageDetailDTO.setChatRoomId(chatMessageEntity.getChatRoomEntity().getId());
chatMessageDetailDTO.setRoomId(chatMessageEntity.getChatRoomEntity().getRoomId());
chatMessageDetailDTO.setWriter(chatMessageEntity.getWriter());
chatMessageDetailDTO.setMessage(chatMessageEntity.getMessage());
return chatMessageDetailDTO;
}
}