프로젝트(개인)
[Spring , react] 실시간 채팅 구현하기
그zi운아이
2024. 3. 19. 18:04
웹 기반 실시간 채팅의 핵심 기술 소개
WebSocket: 실시간 양방향 통신의 기반
- 정의: 웹 애플리케이션과 서버 간에 실시간, 양방향, 풀 듀플렉스 통신을 가능하게 하는 고급 통신 프로토콜입니다.
- 장점: 지속적인 연결을 통해 실시간 데이터 교환이 가능, HTTP 폴링에 비해 훨씬 효율적인 네트워크 자원 사용.
- 사용 예: 실시간 게임, 채팅 애플리케이션, 금융 시장 데이터 스트리밍.
STOMP (Simple Text Oriented Messaging Protocol): 메시지 교환 프로토콜
- 정의: 간단한 텍스트 기반의 메시징 프로토콜로, 웹소켓 위에서 더 고급 메시징 기능을 제공합니다.
- 특징: 헤더와 바디를 포함하는 단순한 텍스트 메시지 형식을 사용. 구독/발행(pub-sub) 모델을 통해 특정 주제에 대한 메시지를 구독하고, 서버로부터 메시지를 받거나 전송할 수 있습니다.
- 적용: 복잡한 메시징 요구사항을 가진 애플리케이션, 예를 들어, 대규모 채팅 시스템, 브로커를 통한 메시지 분배가 필요한 경우.
SockJS: 광범위한 호환성을 위한 라이브러리
- 정의: 웹소켓을 지원하지 않는 브라우저에서도 웹소켓과 유사한 기능을 에뮬레이트하여, 개발자가 일관된 프로그래밍 모델을 사용할 수 있게 해주는 JavaScript 라이브러리입니다.
- 장점: 폴백 옵션을 제공하여, 웹소켓이 불가능한 환경에서도 연결을 유지할 수 있습니다. 이를 통해 거의 모든 브라우저에서 실시간 통신 기능을 사용할 수 있게 됩니다.
- 적용 사례: 다양한 브라우저 환경에서 안정적인 실시간 애플리케이션을 구현하고자 할 때.
구현 단계 및 방법
백엔드 구현: 실시간 메시지 처리
의존성 주입
implementation 'org.springframework.boot:spring-boot-starter-security'
웹소켓 설정 (WebSocketConfig 클래스)
package com.example.fitconnect.config.webSocket;
import lombok.extern.slf4j.Slf4j;
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
@Slf4j
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws/{chatRoomId}").setAllowedOrigins("도메인주소")
.withSockJS()
.setInterceptors(new CustomHandshakeInterceptor());;
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
log.info("Configuring message broker {}", registry);
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/app");
}
}
웹소켓 설정 (WebSocketConfig 클래스)
- 엔드포인트 등록: 클라이언트가 서버와 웹소켓 연결을 시작할 수 있는 엔드포인트(/ws/{chatRoomId})를 정의합니다.
- CORS(Cross-Origin Resource Sharing) 설정: setAllowedOrigins("")를 사용하여 특정 출처에서의 웹소켓 연결을 허용합니다. 이는 보안을 강화하며, 지정된 출처에서만 리소스 접근이 가능하게 합니다.
- SockJS 사용: 웹소켓을 지원하지 않는 브라우저에 대한 호환성을 제공합니다.
- 메시지 브로커 구성: 클라이언트와 서버 간 메시지 교환을 위한 설정을 포함합니다. /app로 시작하는 목적지 주소를 가진 메시지는 애플리케이션으로 라우팅되고, /topic으로 시작하는 주소를 구독하는 클라이언트에게 메시지가 발행됩니다.
도메인 모델 (ChatMessage 및 ChatRoom 클래스)
@Entity
@Getter
@Table(name = "chat_messages")
public class ChatMessage extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 1000)
private String content;
@ManyToOne(fetch = FetchType.LAZY)
private ChatRoom chatRoom;
@ManyToOne(fetch = FetchType.LAZY)
private User sender;
public ChatMessage() {
}
public ChatMessage(String content,ChatRoom chatRoom,User user) {
this.content = content;
this.chatRoom = chatRoom;
this.sender = user;
}
@Entity
@Getter
@Table(name = "chat_rooms")
public class ChatRoom extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
@Size(min = 1 ,max = 100)
private String title;
@ManyToOne(fetch = FetchType.LAZY)
private ExerciseEvent exerciseEvent;
@ManyToOne(fetch = FetchType.LAZY)
private User creator;
@ManyToOne(fetch = FetchType.LAZY)
private User participant;
public ChatRoom(String title,ExerciseEvent exerciseEvent,User creator,User participant) {
this.title = title;
this.exerciseEvent = exerciseEvent;
this.creator = creator;
this.participant = participant;
}
- ChatMessage: 채팅 메시지의 내용, 송신자, 속한 채팅방 등을 정의합니다. 메시지 수정 및 삭제 권한 검증을 포함합니다.
- ChatRoom: 채팅방의 제목, 참여자, 생성자 등을 정의합니다. 채팅방 수정 권한 검증을 포함합니다.
ChatController
@Controller
@RequiredArgsConstructor
@Slf4j
public class ChatController {
private final ChatMessageCreationService chatMessageCreationService;
@MessageMapping("/chat/{chatRoomId}/sendMessage")
@SendTo("/topic/{chatRoomId}")
public ResponseEntity<ChatMessageResponseDto> sendMessage(
SimpMessageHeaderAccessor headerAccessor,
ChatMessageRegistrationDto dto, @DestinationVariable Long chatRoomId) {
Long userId = (Long) headerAccessor.getSessionAttributes().get("userId");
ChatMessageResponseDto chatMessageResponseDto = chatMessageCreationService.createChatMessage(
dto.getContent(), chatRoomId, userId);
return ResponseEntity.ok().body(chatMessageResponseDto);
}
}
- ChatController에서 /chat/{chatRoomId}/sendMessage 엔드포인트를 통해 메시지 송신 요청을 처리할 때, ChatMessageCreationService를 사용하여 실제 메시지 처리 로직을 수행합니다. 이 과정에서 메시지는 생성, 저장되며 추후에 대화 내용을 확인할 수 있습니다.
메시지 저장 로직 (ChatMessageCreationService 클래스)
@Service
@RequiredArgsConstructor
public class ChatMessageCreationService {
private final ChatMessageRepository chatMessageRepository;
private final ChatRoomFindService chatRoomFindService;
private final UserFindService userFindService;
@Transactional
public ChatMessageResponseDto createChatMessage(String message, Long chatRoomId, Long userId) {
ChatRoom chatRoom = findChatRoom(message, chatRoomId);
User sender = findUser(userId);
ChatMessage chatMessage = new ChatMessage(message, chatRoom, sender);
ChatMessage saveChatMessage = chatMessageRepository.save(chatMessage);
return ChatMessageResponseDto.toDto(saveChatMessage);
}
- 메시지 생성 및 저장: 사용자가 보낸 메시지(message), 채팅방 ID(chatRoomId), 사용자 ID(userId)를 기반으로 새로운 ChatMessage 인스턴스를 생성하고, 이를 데이터베이스에 저장합니다.
프론트 구현: 실시간 메시지 처리
의존성 주입
"sockjs-client": "^1.6.1",
"stompjs": "^2.3.3",
웹소켓 연결과 메시지 구독
const socket = new SockJS('/ws');
const stompClient = Stomp.over(socket);
stompClient.connect({}, () => {
setClient(stompClient);
stompClient.subscribe(`/topic/${chatRoomId}`, (msg) => {
const newMessage = JSON.parse(msg.body);
setMessages(prevMessages => [...prevMessages, newMessage.body]);
});
});
- SockJS와 Stomp를 사용하여 웹소켓 연결을 설정하고, 특정 채팅방의 메시지를 구독
메시지 전송 로직
if (message.trim() && client) {
await client.send(`/app/chat/${chatRoomId}/sendMessage`, {}, JSON.stringify({ content: message.trim() }));
setMessage('');
}
- 사용자가 메시지를 전송하는 이벤트 핸들러