Skip to content

Commit

Permalink
[feat #95] 채팅 전송 웹소켓 API (#96)
Browse files Browse the repository at this point in the history
* [chore] : application.yml 환경별 분리

* [chore] : 웹소켓 라이브러리 추가

* [feat] : 웹소켓 설정 파일 추가

* [feat] : ws cors 허용

* [feat] : chatMessage 엔티티 내 mediaUrl 필드 삭제

* [test] : mediaUrl 필드 삭제 반영

* [feat] : 메시지 전송 임시 로직 삭제

* [feat] : 채팅 메시지 저장하는 비즈니스 로직 추가

* [feat] : 웹소켓 통신 로직 추가 - 인메모리 브로커 사용
hyun2371 authored Sep 1, 2024
1 parent 33774aa commit e00ff80
Showing 14 changed files with 101 additions and 46 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -39,3 +39,5 @@ out/
.DS_Store
/src/test/resources/application.yml
/src/main/generated/
/src/main/resources/application-local.yml
/src/main/resources/application-prod.yml
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -144,6 +144,9 @@ dependencies {

// MongoDB
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb:3.1.8'

// WebSocket
implementation 'org.springframework.boot:spring-boot-starter-websocket'
}

// Querydsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.dnd.gongmuin.chat.controller;

import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

import com.dnd.gongmuin.chat.dto.ChatMessageRequest;
import com.dnd.gongmuin.chat.dto.ChatMessageResponse;
import com.dnd.gongmuin.chat.service.ChatMessageService;

import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
public class ChatMessageController {

private final ChatMessageService chatMessageService;

@MessageMapping("/chat-rooms/{chatRoomId}")
@SendTo("/sub/chat-rooms/{chatRoomId}")
public ChatMessageResponse sendMessage(
@DestinationVariable("chatRoomId") Long chatRoomId,
@Payload ChatMessageRequest request
) {
return chatMessageService.saveChatMessage(request, chatRoomId);
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
package com.dnd.gongmuin.chat.controller;

import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.dnd.gongmuin.chat.dto.ChatMessageRequest;
import com.dnd.gongmuin.chat.dto.ChatMessageResponse;
import com.dnd.gongmuin.chat.service.ChatRoomService;
import com.dnd.gongmuin.common.dto.PageResponse;
import com.dnd.gongmuin.member.domain.Member;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Tag(name = "채팅방 API")
@@ -28,17 +21,6 @@ public class ChatRoomController {

private final ChatRoomService chatRoomService;

@Operation(summary = "채팅방 메시지 등록 임시 API", description = "웹소켓 연결 전 임시 API")
@PostMapping("/api/chat-messages/{chatRoomId}")
public ResponseEntity<Void> registerChatMessage(
@PathVariable Long chatRoomId,
@Valid @RequestBody ChatMessageRequest request,
@AuthenticationPrincipal Member member
) {
chatRoomService.saveChatMessage(request, chatRoomId, member.getId());
return new ResponseEntity<>(HttpStatus.CREATED);
}

@Operation(summary = "채팅방 메시지 조회 API", description = "채팅방 메시지를 최신순으로 페이징한다.")
@GetMapping("/api/chat-messages/{chatRoomId}")
public ResponseEntity<PageResponse<ChatMessageResponse>> getChatMessages(
7 changes: 1 addition & 6 deletions src/main/java/com/dnd/gongmuin/chat/domain/ChatMessage.java
Original file line number Diff line number Diff line change
@@ -26,8 +26,6 @@ public class ChatMessage {

private Boolean isRead;

private String mediaUrl;

private MessageType type;

private LocalDateTime createdAt;
@@ -36,13 +34,11 @@ private ChatMessage(
String content,
long chatRoomId,
long memberId,
String mediaUrl,
MessageType type
) {
this.content = content;
this.chatRoomId = chatRoomId;
this.memberId = memberId;
this.mediaUrl = mediaUrl;
this.type = type;
this.isRead = false;
this.createdAt = LocalDateTime.now();
@@ -52,9 +48,8 @@ public static ChatMessage of(
String content,
long chatRoomId,
long memberId,
String mediaUrl,
MessageType type
) {
return new ChatMessage(content, chatRoomId, memberId, mediaUrl, type);
return new ChatMessage(content, chatRoomId, memberId, type);
}
}
4 changes: 1 addition & 3 deletions src/main/java/com/dnd/gongmuin/chat/dto/ChatMapper.java
Original file line number Diff line number Diff line change
@@ -16,8 +16,7 @@ public static ChatMessageResponse toChatMessageResponse(
chatMessage.getMemberId(),
chatMessage.getChatRoomId(),
chatMessage.getContent(),
chatMessage.getType().getLabel(),
chatMessage.getMediaUrl()
chatMessage.getType().getLabel()
);
}

@@ -30,7 +29,6 @@ public static ChatMessage toChatMessage(
request.content(),
chatRoomId,
memberId,
request.mediaUrl(),
MessageType.of(request.type())
);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.dnd.gongmuin.chat.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record ChatMessageRequest(
@NotBlank(message = "채팅 내용을 입력해주세요.")
String content,
@NotBlank(message = "채팅 타입을 입력해주세요.")
String type,
String mediaUrl

@NotNull(message = "회원 아이디를 입력해주세요.")
Long memberId
) {
}
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ public record ChatMessageResponse(
Long memberId,
Long chatRoomId,
String content,
String type,
String mediaUrl
String type
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.dnd.gongmuin.chat.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.dnd.gongmuin.chat.domain.ChatMessage;
import com.dnd.gongmuin.chat.dto.ChatMapper;
import com.dnd.gongmuin.chat.dto.ChatMessageRequest;
import com.dnd.gongmuin.chat.dto.ChatMessageResponse;
import com.dnd.gongmuin.chat.repository.ChatMessageRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@RequiredArgsConstructor
@Slf4j
public class ChatMessageService {

private final ChatMessageRepository chatMessageRepository;

@Transactional
public ChatMessageResponse saveChatMessage(
ChatMessageRequest request,
Long chatRoomId
) {
Long memberId = request.memberId();
ChatMessage chatMessage = chatMessageRepository.save(ChatMapper.toChatMessage(request, chatRoomId, memberId));
log.info("chatRoomId = {}, memberId= {}, chatMessageId= {}", chatRoomId, memberId, chatMessage.getId());
return ChatMapper.toChatMessageResponse(chatMessage);
}
}
11 changes: 0 additions & 11 deletions src/main/java/com/dnd/gongmuin/chat/service/ChatRoomService.java
Original file line number Diff line number Diff line change
@@ -6,13 +6,11 @@
import org.springframework.transaction.annotation.Transactional;

import com.dnd.gongmuin.chat.dto.ChatMapper;
import com.dnd.gongmuin.chat.dto.ChatMessageRequest;
import com.dnd.gongmuin.chat.dto.ChatMessageResponse;
import com.dnd.gongmuin.chat.repository.ChatMessageRepository;
import com.dnd.gongmuin.common.dto.PageMapper;
import com.dnd.gongmuin.common.dto.PageResponse;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Service
@@ -21,15 +19,6 @@ public class ChatRoomService {

private final ChatMessageRepository chatMessageRepository;

@Transactional
public void saveChatMessage(
@Valid ChatMessageRequest request,
Long chatRoomId,
Long memberId
) {
chatMessageRepository.save(ChatMapper.toChatMessage(request, chatRoomId, memberId));
}

@Transactional(readOnly = true)
public PageResponse<ChatMessageResponse> getChatMessages(Long chatRoomId, Pageable pageable) {
Slice<ChatMessageResponse> responsePage = chatMessageRepository
25 changes: 25 additions & 0 deletions src/main/java/com/dnd/gongmuin/common/config/WebSocketConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.dnd.gongmuin.common.config;

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
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/sub"); // 구독 경로
registry.setApplicationDestinationPrefixes("/pub"); // 발행 경로
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws") // 웹소켓 연결 엔드포인트
.setAllowedOriginPatterns("*")
.withSockJS();
}
}
Original file line number Diff line number Diff line change
@@ -81,7 +81,7 @@ public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring()
.requestMatchers(
"/error", "/favicon.ico", "/api/auth/temp-signup", "/api/auth/temp-signin",
"/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html"
"/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html", "/ws/**"
);
}

@@ -91,7 +91,7 @@ public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();

configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000", "https://gongmuin.netlify.app",
"https://gongmuin.site/", "http://localhost:8080"));
"https://gongmuin.site/", "http://localhost:8080", "/ws/**"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setExposedHeaders(Arrays.asList("Set-Cookie", "Authorization"));
Original file line number Diff line number Diff line change
@@ -34,7 +34,6 @@ void getChatMessages() throws Exception {
.andExpect(jsonPath("$.content[0].memberId").value(chatMessages.get(0).getMemberId()))
.andExpect(jsonPath("$.content[0].chatRoomId").value(chatMessages.get(0).getChatRoomId()))
.andExpect(jsonPath("$.content[0].content").value(chatMessages.get(0).getContent()))
.andExpect(jsonPath("$.content[0].type").value(chatMessages.get(0).getType().getLabel()))
.andExpect(jsonPath("$.content[0].mediaUrl").value(chatMessages.get(0).getMediaUrl()));
.andExpect(jsonPath("$.content[0].type").value(chatMessages.get(0).getType().getLabel()));
}
}
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@ public static ChatMessage chatMessage() {
"하하",
1L,
1L,
null,
MessageType.TEXT
);
}

0 comments on commit e00ff80

Please sign in to comment.