1. 서론
채팅 기능 개발을 하던 도중 문득 그런 생각이 들었다.
현재 플로우는 사용자가 공방에 제작 의뢰를 하면 DB에 채팅방이 생성되고 생성된 고유 id 값을 STOMP 채팅 시의 구독경로로 넣어야 한다.
여기서 의문점.
- 그럼 채팅을 보낼때마다 DB에서 조회를 해야 하는가?
- 그럼 채팅을 보낼때마다 DB에 채팅 내역을 저장해야 하는가?
2. 본론
1번 해결
일단 1번은 확실히 아닌 것 같다.
DB 조회 연산이 많아지면 서버가 느려지는것 정도는 어느정도 생각했고, 지금이야 테스트 하고 있어서 돌아가겠지만 사용자가 4명일 때 채팅만 해도 한번 채팅 주고 받을 때 조회 4번, 저장 4번 총 8번이 생긴다.
나의 생각은 이랬다.
'소켓을 연결할 때 커스텀 헤더에서 토큰 값을 추출하는데, 어짜피 채팅을 한번 하고 끊고 한번 하고 끊을 게 아니라면 소켓 연결 시에만 DB 조회를 해서 세션에 {user : ok} 이런식으로 저장해두고 세션에 user 키 유무만 확인하면 되지 않을까?'
StompHandler.java
@Slf4j
@RequiredArgsConstructor
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 99) // 스프링의 빈 순서를 지정하는 애노테이션으로, StompHandler 의 우선순위를 설정한다.
public class StompHandler implements ChannelInterceptor {
private final JwtTokenProvider jwtTokenProvider;
/**
* preSend 는 메시지가 채널로 전송되기 전에 호출되는 메소드.
* @param message payload + Headers
* @param channel
* @return
*/
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
// 메세지의 STOMP 헤더를 쉽게 접근하기 위한 유틸리티 클래스
// websocket 연결시 헤더의 jwt token 유효성 검증
if(StompCommand.CONNECT == accessor.getCommand()){
log.info("[stomphandler] extract header");
String token = accessor.getFirstNativeHeader("Authorization");
log.info("[stomphandler] token : {}",token);
if(!token.isEmpty()){
token = token.replace("Bearer ","").trim();
String nickName = jwtTokenProvider.getUserNickName(token);
log.info("[stomphandler] nickname : {}",nickName);
accessor.getSessionAttributes().put("senderNickname", nickName); // 기존 헤더에 nickname 정보를 추가해 저장한다.
}else{
throw new TokenNotFoundException("토큰이 필요합니다.");
}
}
return message;
}
}
accessor.getSessionAttributes().put("senderNickname", nickName); 이 부분에서 소켓 연결을 할 때 STOMP 세션 속성에 메세지 송신자의 닉네임을 저장해둔다.
ChatController.java
@MessageMapping("/gagu-chat/{roomNumber}") // mapping ex)/pub/gagu-chat/{roomNumber}
public void sendMessage(RequestChatContentsDto message,
SimpMessageHeaderAccessor accessor,
@DestinationVariable Long roomNumber) throws Exception {
log.info("[chat] room id : {}",roomNumber);
String nickname = (String) accessor.getSessionAttributes().get("senderNickname");
log.info("[chat] check memeber....");
if (nickname == null) {
throw new IllegalArgumentException("세션에 닉네임이 없습니다.");
}
Long sessionRoomId = (Long) accessor.getSessionAttributes().get("chatRoomId");
if(sessionRoomId==null){ // 처음 메세지 보내는 경우
log.info("[chat] chatting session is empty room id : {}, nickname : {}", roomNumber,nickname);
User user = userRepository.findByNickName(nickname);
if(user == null){
throw new NotFoundUserException();
}
Optional<ChatRoom> foundChatRoomList = chatRoomRepository.findById(roomNumber);
if(!foundChatRoomList.isEmpty()){
ChatRoom chatRoom = foundChatRoomList.get();
if(checkMember(chatRoom, user)){ // 채팅방 권한이 있다면
accessor.getSessionAttributes().putIfAbsent("chatRoomId", roomNumber);// 세션에 저장되어 있지 않을때, 세션에 저장
log.info("[chat] successfully put room id to session");
}
log.info("[chat] complete check member");
Thread.sleep(1000); // 비동기적으로 메시지를 처리하기 위해서 1초 지연(옵션)
log.info("[chat] question : {}", message.getContents());
ResponseChatDto responseChatDto = chatService.sendContents(message,roomNumber,nickname);
template.convertAndSend("/sub/chatroom/"+roomNumber,responseChatDto); // 구독하고 있는 채팅방에 전송
}else{
throw new ChatRoomNotFoundException();
}
}else{
log.info("[chat] complete check member");
Thread.sleep(1000); // 비동기적으로 메시지를 처리하기 위해서 1초 지연(옵션)
log.info("[chat] question : {}", message.getContents());
ResponseChatDto responseChatDto = chatService.sendContents(message,roomNumber,nickname);
template.convertAndSend("/sub/chatroom/"+roomNumber,responseChatDto); // 구독하고 있는 채팅방에 전송
}
}
- accessor.getSessionAttributes().get("senderNickname"); 를 통해 소켓 연결 시 미리 넣어 두었던 송신자 닉네임을 확인한다.
- 메세지를 처음 보내는 경우에는 DB 조회를 해서 메세지 송신자가 채팅방에 속해 있는지 확인한다. 만약, 정상적으로 확인되면 {nickname : 방번호} 형식으로 세션에 넣고 만약 이미 세션에 닉네임을 키로 가진 채팅방 번호가 있을 때 DB 조회 없이 메세지를 전송한다.
이렇게 관리 하니 처음보다 훨씬 DB 조회 연산이 줄었다!
'programming > ts' 카테고리의 다른 글
| [TS] 3D-랜더링 시 텍스처 파일 분리 (1) | 2026.04.16 |
|---|---|
| [TS] 3.x.x maven 빌더 Lombok cannot find symbol (2) | 2024.12.12 |