From df42a1b2f2d0345352f1e57ce1d15046d342bffa Mon Sep 17 00:00:00 2001 From: heygeeji Date: Sun, 21 Dec 2025 01:45:45 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20WebSocket=20STOMP=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/WebSocketConfig.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/main/java/com/back/web7_9_codecrete_be/global/config/WebSocketConfig.java diff --git a/src/main/java/com/back/web7_9_codecrete_be/global/config/WebSocketConfig.java b/src/main/java/com/back/web7_9_codecrete_be/global/config/WebSocketConfig.java new file mode 100644 index 00000000..eac5a9d6 --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/global/config/WebSocketConfig.java @@ -0,0 +1,25 @@ +package com.back.web7_9_codecrete_be.global.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 config) { + config.enableSimpleBroker("/topic"); + config.setApplicationDestinationPrefixes("/app"); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws-chat") + .setAllowedOriginPatterns("*"); // https://www.naeconcertbutakhae.shop + } + +} From de7e7a6ecf8379f4274090c85c9bae29d4a5a4e8 Mon Sep 17 00:00:00 2001 From: heygeeji Date: Sun, 21 Dec 2025 02:30:59 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20STOMP=20=EC=A0=84=EC=86=A1,=20=EB=B8=8C?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=EC=BA=90=EC=8A=A4=ED=8A=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ChatMessageController.java | 26 +++++++++++++++++++ .../domain/chats/dto/ChatMessageRequest.java | 17 ++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/main/java/com/back/web7_9_codecrete_be/domain/chats/controller/ChatMessageController.java create mode 100644 src/main/java/com/back/web7_9_codecrete_be/domain/chats/dto/ChatMessageRequest.java diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/chats/controller/ChatMessageController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/controller/ChatMessageController.java new file mode 100644 index 00000000..eb9c2183 --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/controller/ChatMessageController.java @@ -0,0 +1,26 @@ +package com.back.web7_9_codecrete_be.domain.chats.controller; + +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Controller; + +import com.back.web7_9_codecrete_be.domain.chats.dto.ChatMessageRequest; + +import lombok.RequiredArgsConstructor; + +@Controller +@RequiredArgsConstructor +public class ChatMessageController { + + private final SimpMessagingTemplate messagingTemplate; + + @MessageMapping("/chat/send") + public void send(ChatMessageRequest message) { + + messagingTemplate.convertAndSend( + "/topic/chat/" + message.getConcertId(), + message + ); + } +} + diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/chats/dto/ChatMessageRequest.java b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/dto/ChatMessageRequest.java new file mode 100644 index 00000000..366c527d --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/dto/ChatMessageRequest.java @@ -0,0 +1,17 @@ +package com.back.web7_9_codecrete_be.domain.chats.dto; + +import java.time.LocalDateTime; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class ChatMessageRequest { + private Long concertId; + private Long senderId; + private String content; + private LocalDateTime sentDate; +} From 2fb1d82128fb82042fa495f76a1054b86bae1e0f Mon Sep 17 00:00:00 2001 From: heygeeji Date: Tue, 23 Dec 2025 16:54:59 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20WebSocket=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EC=8B=9C=20JWT=20=EA=B8=B0=EB=B0=98=20Principal=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/WebSocketConfig.java | 19 +++++- .../global/security/SecurityConfig.java | 9 ++- .../global/websocket/JwtHandshakeHandler.java | 60 +++++++++++++++++++ 3 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/back/web7_9_codecrete_be/global/websocket/JwtHandshakeHandler.java diff --git a/src/main/java/com/back/web7_9_codecrete_be/global/config/WebSocketConfig.java b/src/main/java/com/back/web7_9_codecrete_be/global/config/WebSocketConfig.java index eac5a9d6..5a04bb5c 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/global/config/WebSocketConfig.java +++ b/src/main/java/com/back/web7_9_codecrete_be/global/config/WebSocketConfig.java @@ -6,20 +6,37 @@ import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; +import com.back.web7_9_codecrete_be.global.websocket.JwtHandshakeHandler; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j @Configuration @EnableWebSocketMessageBroker +@RequiredArgsConstructor public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + private final JwtHandshakeHandler jwtHandshakeHandler; + @Override public void configureMessageBroker(MessageBrokerRegistry config) { + config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/app"); + } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { + + log.info("WebSocket 엔드포인트 등록: /ws-chat"); + registry.addEndpoint("/ws-chat") - .setAllowedOriginPatterns("*"); // https://www.naeconcertbutakhae.shop + .setHandshakeHandler(jwtHandshakeHandler) + .setAllowedOriginPatterns("http://localhost:3000", + "https://www.naeconcertbutakhae.shop"); + } } diff --git a/src/main/java/com/back/web7_9_codecrete_be/global/security/SecurityConfig.java b/src/main/java/com/back/web7_9_codecrete_be/global/security/SecurityConfig.java index 28312cf5..2a0c3b17 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/global/security/SecurityConfig.java +++ b/src/main/java/com/back/web7_9_codecrete_be/global/security/SecurityConfig.java @@ -1,7 +1,7 @@ package com.back.web7_9_codecrete_be.global.security; -import com.back.web7_9_codecrete_be.domain.auth.service.TokenService; -import lombok.RequiredArgsConstructor; +import java.util.List; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -14,7 +14,9 @@ import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import java.util.List; +import com.back.web7_9_codecrete_be.domain.auth.service.TokenService; + +import lombok.RequiredArgsConstructor; @Configuration @RequiredArgsConstructor @@ -48,6 +50,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // Authorization 설정 .authorizeHttpRequests(auth -> auth .requestMatchers( + "/ws-chat/**", "/actuator/**", "/api/v1/auth/**", // 로그인/회원가입은 허용 "/v3/api-docs/**", // Swagger diff --git a/src/main/java/com/back/web7_9_codecrete_be/global/websocket/JwtHandshakeHandler.java b/src/main/java/com/back/web7_9_codecrete_be/global/websocket/JwtHandshakeHandler.java new file mode 100644 index 00000000..78eba1bc --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/global/websocket/JwtHandshakeHandler.java @@ -0,0 +1,60 @@ +package com.back.web7_9_codecrete_be.global.websocket; + +import java.security.Principal; +import java.util.Map; + +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.support.DefaultHandshakeHandler; + +import com.back.web7_9_codecrete_be.global.error.code.AuthErrorCode; +import com.back.web7_9_codecrete_be.global.error.exception.BusinessException; +import com.back.web7_9_codecrete_be.global.security.JwtTokenProvider; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; + +/** + * WebSocket Handshake 단계에서 쿠키에 포함된 JWT를 추출하여 + * 사용자 Authentication(Principal)을 설정하는 HandshakeHandler. + * + * HTTP 필터 체인이 적용되지 않는 WebSocket 연결 단계에서 + * 별도의 인증 처리를 담당 + */ +@Component +@RequiredArgsConstructor +public class JwtHandshakeHandler extends DefaultHandshakeHandler { + + private final JwtTokenProvider jwtTokenProvider; + + @Override + protected Principal determineUser( + ServerHttpRequest request, + WebSocketHandler wsHandler, + Map attributes) { + + if (!(request instanceof ServletServerHttpRequest servletRequest)) { + throw new BusinessException(AuthErrorCode.UNAUTHORIZED_USER); + } + + HttpServletRequest httpRequest = servletRequest.getServletRequest(); + + if (httpRequest.getCookies() == null) { + throw new BusinessException(AuthErrorCode.UNAUTHORIZED_USER); + } + + for (Cookie cookie : httpRequest.getCookies()) { + if ("ACCESS_TOKEN".equals(cookie.getName())) { + String token = cookie.getValue(); + + return jwtTokenProvider.getAuthentication(token); + } + } + + throw new BusinessException(AuthErrorCode.UNAUTHORIZED_USER); + } +} + From 8e1562ec70b12d6e94ca9e03ab039d0daba2806a Mon Sep 17 00:00:00 2001 From: heygeeji Date: Tue, 23 Dec 2025 16:56:41 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20Request/Response=20DTO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chats/dto/ChatMessageRequest.java | 9 ++++--- .../domain/chats/dto/ChatMessageResponse.java | 26 +++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/back/web7_9_codecrete_be/domain/chats/dto/ChatMessageResponse.java diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/chats/dto/ChatMessageRequest.java b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/dto/ChatMessageRequest.java index 366c527d..06bfc514 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/chats/dto/ChatMessageRequest.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/dto/ChatMessageRequest.java @@ -1,7 +1,6 @@ package com.back.web7_9_codecrete_be.domain.chats.dto; -import java.time.LocalDateTime; - +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -9,9 +8,11 @@ @Getter @NoArgsConstructor @AllArgsConstructor +@Schema(description = "채팅 메시지 전송 요청") public class ChatMessageRequest { + + @Schema(description = "공연 ID", example = "1") private Long concertId; - private Long senderId; + @Schema(description = "메시지 내용", example = "안녕하세요") private String content; - private LocalDateTime sentDate; } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/chats/dto/ChatMessageResponse.java b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/dto/ChatMessageResponse.java new file mode 100644 index 00000000..d7e7d8f4 --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/dto/ChatMessageResponse.java @@ -0,0 +1,26 @@ +package com.back.web7_9_codecrete_be.domain.chats.dto; + +import java.time.LocalDateTime; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "채팅 메시지 응답") +public class ChatMessageResponse { + + @Schema(description = "공연 ID", example = "1") + private Long concertId; + @Schema(description = "발신자 ID", example = "2") + private Long senderId; + @Schema(description = "발신자 닉네임", example = "테스트 유저") + private String senderName; + @Schema(description = "메시지 내용", example = "안녕하세요") + private String content; + @Schema(description = "전송 시각", example = "2025-12-23T16:28:07.8806432") + private LocalDateTime sentDate; +} From dc5d53b3482c9102ea9810f42a95ebb68b6162a2 Mon Sep 17 00:00:00 2001 From: heygeeji Date: Tue, 23 Dec 2025 16:57:11 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20STOMP=20=EC=B1=84=ED=8C=85=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=A0=84=EC=86=A1=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ChatMessageController.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/chats/controller/ChatMessageController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/controller/ChatMessageController.java index eb9c2183..8929b55a 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/chats/controller/ChatMessageController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/controller/ChatMessageController.java @@ -1,26 +1,31 @@ package com.back.web7_9_codecrete_be.domain.chats.controller; +import java.security.Principal; + import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Controller; import com.back.web7_9_codecrete_be.domain.chats.dto.ChatMessageRequest; +import com.back.web7_9_codecrete_be.domain.chats.service.ChatMessageService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Controller @RequiredArgsConstructor public class ChatMessageController { - private final SimpMessagingTemplate messagingTemplate; + private final ChatMessageService chatMessageService; @MessageMapping("/chat/send") - public void send(ChatMessageRequest message) { + public void send(ChatMessageRequest message, Principal principal) { + + if (principal == null) { + throw new IllegalStateException("Unauthenticated WebSocket access"); + } - messagingTemplate.convertAndSend( - "/topic/chat/" + message.getConcertId(), - message - ); + chatMessageService.sendMessage(message, principal); } } From 3cd31ff1e875dfbd31482d1e1fceee03ebc3b490 Mon Sep 17 00:00:00 2001 From: heygeeji Date: Tue, 23 Dec 2025 16:58:20 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20STOMP=20=EC=B1=84=ED=8C=85=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=A0=84=EC=86=A1,=20=EB=B8=8C?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=EC=BA=90=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chats/service/ChatMessageService.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/java/com/back/web7_9_codecrete_be/domain/chats/service/ChatMessageService.java diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/chats/service/ChatMessageService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/service/ChatMessageService.java new file mode 100644 index 00000000..77af3a29 --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/service/ChatMessageService.java @@ -0,0 +1,53 @@ +package com.back.web7_9_codecrete_be.domain.chats.service; + +import java.security.Principal; +import java.time.LocalDateTime; + +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Service; + +import com.back.web7_9_codecrete_be.domain.chats.dto.ChatMessageRequest; +import com.back.web7_9_codecrete_be.domain.chats.dto.ChatMessageResponse; +import com.back.web7_9_codecrete_be.domain.users.entity.User; +import com.back.web7_9_codecrete_be.domain.users.repository.UserRepository; +import com.back.web7_9_codecrete_be.global.error.code.AuthErrorCode; +import com.back.web7_9_codecrete_be.global.error.exception.BusinessException; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ChatMessageService { + + private final UserRepository userRepository; + private final SimpMessagingTemplate messagingTemplate; + + public void sendMessage(ChatMessageRequest message, Principal principal) { + + String email = principal.getName(); + + // TODO: 캐싱처리 + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new BusinessException(AuthErrorCode.USER_NOT_FOUND)); + + Long senderId = user.getId(); + String senderName = user.getNickname(); + + ChatMessageResponse response = new ChatMessageResponse( + message.getConcertId(), + senderId, + senderName, + message.getContent(), + LocalDateTime.now() + ); + + log.info("[SEND MESSAGE] From User ID: {}, Content: {}", senderId, message.getContent()); + + messagingTemplate.convertAndSend( + "/topic/chat/" + message.getConcertId(), + response + ); + } +} From 3d27086376946cea84d6d3f7ecf7df4491d66f7d Mon Sep 17 00:00:00 2001 From: heygeeji Date: Tue, 23 Dec 2025 16:58:51 +0900 Subject: [PATCH 7/7] =?UTF-8?q?docs:=20WebSocket=20STOMP=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=20=ED=94=84=EB=A1=9C=ED=86=A0=EC=BD=9C=20Swagger=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ChatStompDocsController.java | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/main/java/com/back/web7_9_codecrete_be/domain/chats/controller/ChatStompDocsController.java diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/chats/controller/ChatStompDocsController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/controller/ChatStompDocsController.java new file mode 100644 index 00000000..ed625105 --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/chats/controller/ChatStompDocsController.java @@ -0,0 +1,96 @@ +package com.back.web7_9_codecrete_be.domain.chats.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.back.web7_9_codecrete_be.domain.chats.dto.ChatMessageRequest; +import com.back.web7_9_codecrete_be.domain.chats.dto.ChatMessageResponse; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +@RestController +@RequestMapping("/docs/chat") +@Tag(name = "Chat STOMP", description = "WebSocket / STOMP 채팅 프로토콜 문서. 문서용 API. 사용X") +public class ChatStompDocsController { + + @Operation( + summary = "채팅 메시지 전송 (STOMP)", + description = """ + ### 📡 WebSocket STOMP 채팅 메시지 전송 + + #### 1️⃣ WebSocket Endpoint + ``` + ws://localhost:8080/ws-chat + or + wss://www.naeconcertbutakhae.shop/ws-chat + ``` + + #### 2️⃣ SEND Destination + ``` + /app/chat/send + ``` + + #### 3️⃣ SUBSCRIBE Destination + ``` + /topic/chat/{concertId} + ``` + + #### 4️⃣ SEND Payload + ```json + { + "concertId": 1, + "content": "안녕하세요!" + } + ``` + + #### 5️⃣ SUBSCRIBE Response + ```json + { + "concertId": 1, + "senderId": 10, + "senderName": "테스트 유저", + "content": "안녕하세요!", + "sentAt": "2025-12-23T15:30:00" + } + ``` + """ + ) + @GetMapping("/stomp") + public void stompChatGuide() {} + + @Operation( + summary = "STOMP 채팅 메시지 전송 규격", + description = """ + WebSocket + STOMP 기반 채팅 메시지 전송 규격입니다. + + - 실제 사용되는 HTTP API 아닙니다. + - Swagger 문서용 + """, + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "STOMP 메세지 SEND하면 전달되는 요청 데이터", + required = true, + content = @Content( + schema = @Schema(implementation = ChatMessageRequest.class) + ) + ), + responses = { + @ApiResponse( + responseCode = "200", + description = "STOMP SUBSCRIBE로 수신되는 메시지", + content = @Content( + schema = @Schema(implementation = ChatMessageResponse.class) + ) + ) + } + ) + @GetMapping("/message-schema") + public ChatMessageResponse messageSchema() { + return null; // 실제 반환 목적 X + } +} +