diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/bus/ChatRedisPublisher.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/bus/ChatRedisPublisher.java index 28164b1..97c6ab1 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/bus/ChatRedisPublisher.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/bus/ChatRedisPublisher.java @@ -8,7 +8,7 @@ import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; -//@Component // Redis 방식 비활성화 +@Component @RequiredArgsConstructor public class ChatRedisPublisher { diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/bus/ChatRedisSubscriber.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/bus/ChatRedisSubscriber.java index 5de21cd..26b9c86 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/bus/ChatRedisSubscriber.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/bus/ChatRedisSubscriber.java @@ -9,7 +9,7 @@ import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Component; -//@Component // Redis 방식 비활성화 +@Component @RequiredArgsConstructor public class ChatRedisSubscriber implements MessageListener { diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/bus/RedisPubSubConfig.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/bus/RedisPubSubConfig.java index 442bce9..955d43b 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/bus/RedisPubSubConfig.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/bus/RedisPubSubConfig.java @@ -7,7 +7,7 @@ import org.springframework.data.redis.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; -//@Configuration // Redis 방식 비활성화 +@Configuration @RequiredArgsConstructor public class RedisPubSubConfig { diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatHistoryController.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatHistoryController.java index 50f0821..b79620a 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatHistoryController.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatHistoryController.java @@ -7,6 +7,13 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + +@Tag(name = "Chat History", description = "채팅 히스토리 조회 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/chat-rooms") @@ -14,20 +21,29 @@ public class ChatHistoryController { private final ChatHistoryService chatHistoryService; - /** 최신 30개 */ + @Operation(summary = "최신 채팅 메시지 조회", description = "채팅방의 최신 메시지들을 지정된 개수만큼 조회합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "메시지 조회 성공"), + @ApiResponse(responseCode = "404", description = "채팅방을 찾을 수 없음") + }) @GetMapping("/{roomId}/messages") public ResponseEntity latest( - @PathVariable UUID roomId, - @RequestParam(defaultValue = "30") int limit) { + @Parameter(description = "채팅방 ID", required = true) @PathVariable UUID roomId, + @Parameter(description = "조회할 메시지 개수 (기본값: 30)") @RequestParam(defaultValue = "30") int limit) { return ResponseEntity.ok(chatHistoryService.loadLatest(roomId, limit)); } - /** 이전 페이지(커서 기반) */ + @Operation(summary = "이전 채팅 메시지 조회", description = "커서 기반으로 이전 메시지들을 조회합니다 (무한스크롤용)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "메시지 조회 성공"), + @ApiResponse(responseCode = "400", description = "잘못된 커서 값"), + @ApiResponse(responseCode = "404", description = "채팅방을 찾을 수 없음") + }) @GetMapping("/{roomId}/messages/before") public ResponseEntity before( - @PathVariable UUID roomId, - @RequestParam String cursor, - @RequestParam(defaultValue = "30") int limit) { + @Parameter(description = "채팅방 ID", required = true) @PathVariable UUID roomId, + @Parameter(description = "페이지 커서 (이전 조회의 nextCursor 값)", required = true) @RequestParam String cursor, + @Parameter(description = "조회할 메시지 개수 (기본값: 30)") @RequestParam(defaultValue = "30") int limit) { return ResponseEntity.ok(chatHistoryService.loadBefore(roomId, cursor, limit)); } } \ No newline at end of file diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatMessagingController.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatMessagingController.java index 800c2eb..1fe339c 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatMessagingController.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatMessagingController.java @@ -4,7 +4,7 @@ import java.security.Principal; import java.util.Map; import java.util.UUID; -//import likelion.mlb.backendProject.domain.chat.bus.ChatRedisPublisher; +import likelion.mlb.backendProject.domain.chat.bus.ChatRedisPublisher; import likelion.mlb.backendProject.domain.chat.dto.ChatSendRequest; import likelion.mlb.backendProject.domain.chat.repository.ChatMembershipRepository; import lombok.RequiredArgsConstructor; @@ -23,7 +23,7 @@ public class ChatMessagingController { private final ChatMessageService chatMessageService; private final SimpMessagingTemplate messagingTemplate; private final ChatMembershipRepository membershipRepository; - //private final ChatRedisPublisher chatRedisPublisher; + private final ChatRedisPublisher chatRedisPublisher; @MessageMapping("/chat/{roomId}/send") @@ -55,11 +55,7 @@ public void send(@DestinationVariable UUID roomId, "createdAt", saved.getCreatedAt().toString() ); - // Redis 대신 직접 WebSocket으로 전송 (즉시 전달) - String topic = "/topic/chat/" + roomId; - messagingTemplate.convertAndSend(topic, payload); - - // Redis 방식 (주석처리 - 지연 발생) - //chatRedisPublisher.publishToRoom(roomId, new java.util.HashMap<>(payload)); + // Redis pub/sub 방식으로 전송 + chatRedisPublisher.publishToRoom(roomId, new java.util.HashMap<>(payload)); } } diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatNotificationController.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatNotificationController.java index e8e1481..faeb9b4 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatNotificationController.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatNotificationController.java @@ -5,7 +5,7 @@ * */ import java.util.Map; import java.util.UUID; -//import likelion.mlb.backendProject.domain.chat.bus.ChatRedisPublisher; +import likelion.mlb.backendProject.domain.chat.bus.ChatRedisPublisher; import likelion.mlb.backendProject.domain.chat.service.ChatMessageService; import lombok.Data; import lombok.RequiredArgsConstructor; @@ -14,6 +14,13 @@ import org.springframework.web.bind.annotation.*; import likelion.mlb.backendProject.domain.chat.service.ChatNotificationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + +@Tag(name = "Chat Notification", description = "채팅 알림 및 테스트 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/notify") @@ -21,12 +28,17 @@ public class ChatNotificationController { private final SimpMessagingTemplate messagingTemplate; - //private final ChatRedisPublisher chatRedisPublisher; // Redis 방식 비활성화 + private final ChatRedisPublisher chatRedisPublisher; private final ChatNotificationService notificationService; private final ChatMessageService chatMessageService; // 디버그용 + @Operation(summary = "직접 알림 전송", description = "플레이어 이벤트 알림을 직접 전송합니다 (테스트용)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "알림 전송 성공"), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터") + }) @PostMapping("/raw") - public ResponseEntity raw(@RequestBody RawNotify req) { + public ResponseEntity raw(@Parameter(description = "알림 데이터") @RequestBody RawNotify req) { notificationService.sendMatchAlert( req.getPlayerId(), req.getFixtureId(), req.getEventType(), req.getMinute(), req.getPoint(), req.getText() @@ -34,14 +46,19 @@ public ResponseEntity raw(@RequestBody RawNotify req) { return ResponseEntity.ok().build(); } + @Operation(summary = "채팅방 알림 전송", description = "특정 채팅방에 시스템 알림 메시지를 전송합니다 (테스트용)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "알림 전송 성공"), + @ApiResponse(responseCode = "404", description = "채팅방을 찾을 수 없음") + }) @GetMapping("/room/{roomId}") public ResponseEntity> roomAlert( - @PathVariable UUID roomId, - @RequestParam String text) { + @Parameter(description = "채팅방 ID", required = true) @PathVariable UUID roomId, + @Parameter(description = "전송할 메시지 내용", required = true) @RequestParam String text) { var saved = chatMessageService.saveSystemAlert(roomId, text); // DB 저장 - // ✅ 직접 WebSocket으로 브로드캐스트 (즉시 전송) + // ✅ STOMP 브로드캐스트 + Redis fan-out Map payload = Map.of( "id", saved.getId().toString(), "chatRoomId", roomId.toString(), @@ -50,9 +67,7 @@ public ResponseEntity> roomAlert( "createdAt", saved.getCreatedAt().toString() ); messagingTemplate.convertAndSend("/topic/chat/" + roomId, payload); - - // Redis 방식 (주석처리 - 지연 발생) - //chatRedisPublisher.publishToRoom(roomId, new java.util.HashMap<>(payload)); + chatRedisPublisher.publishToRoom(roomId, new java.util.HashMap<>(payload)); return ResponseEntity.ok(Map.of("ok", true, "id", saved.getId().toString())); } diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatReadController.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatReadController.java index a30493d..280cd97 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatReadController.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatReadController.java @@ -11,6 +11,13 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +@Tag(name = "Chat Read State", description = "채팅 만음 상태 관리 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/chat-rooms") @@ -20,8 +27,16 @@ public class ChatReadController { private final ChatReadService chatReadService; private final ChatMembershipRepository membershipRepository; + @Operation(summary = "릶음 상태 조회", description = "사용자의 마지막 읽은 메시지와 미읽 개수를 조회합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "읽음 상태 조회 성공"), + @ApiResponse(responseCode = "401", description = "인증되지 않은 사용자"), + @ApiResponse(responseCode = "403", description = "채팅방 멤버가 아님"), + @ApiResponse(responseCode = "500", description = "내부 서버 오류") + }) @GetMapping("/{roomId}/read-state") - public ResponseEntity getReadState(@PathVariable UUID roomId, + public ResponseEntity getReadState( + @Parameter(description = "채팅방 ID", required = true) @PathVariable UUID roomId, @AuthenticationPrincipal CustomUserDetails cud) { if (cud == null || cud.getUser() == null) { log.debug("[read-state] 401: no principal"); @@ -46,9 +61,18 @@ public ResponseEntity getReadState(@PathVariable UUID roomId, } } + @Operation(summary = "메시지 읽음 처리", description = "지정된 메시지까지 읽음으로 표시합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "읽음 처리 성공"), + @ApiResponse(responseCode = "400", description = "잘못된 메시지 ID"), + @ApiResponse(responseCode = "401", description = "인증되지 않은 사용자"), + @ApiResponse(responseCode = "403", description = "채팅방 멤버가 아님"), + @ApiResponse(responseCode = "500", description = "내부 서버 오류") + }) @PostMapping("/{roomId}/read-state") - public ResponseEntity markRead(@PathVariable UUID roomId, - @RequestBody(required = false) Map body, + public ResponseEntity markRead( + @Parameter(description = "채팅방 ID", required = true) @PathVariable UUID roomId, + @Parameter(description = "읽음 요청 데이터 (messageId 포함)") @RequestBody(required = false) Map body, @AuthenticationPrincipal CustomUserDetails cud) { if (cud == null || cud.getUser() == null) { log.debug("[mark-read] 401: no principal"); diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatRoomQueryController.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatRoomQueryController.java index 46ed403..d55e015 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatRoomQueryController.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/controller/ChatRoomQueryController.java @@ -12,6 +12,13 @@ import likelion.mlb.backendProject.domain.chat.service.ChatRoomQueryService; import likelion.mlb.backendProject.domain.chat.service.ChatRoomService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + +@Tag(name = "Chat Room", description = "채팅방 조회 및 관리 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/chat-rooms") @@ -21,36 +28,46 @@ public class ChatRoomQueryController { private final ChatRoomService chatRoomService; private final ChatRoomRepository chatRoomRepository; - /** - * 방 스코어보드 (4명, 점수/랭크) - */ + @Operation(summary = "채팅방 스코어보드 조회", description = "채팅방 참가자들의 점수와 순위를 조회합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "스코어보드 조회 성공"), + @ApiResponse(responseCode = "404", description = "채팅방을 찾을 수 없음") + }) @GetMapping("/{roomId}/scoreboard") - public List scoreboard(@PathVariable UUID roomId) { + public List scoreboard(@Parameter(description = "채팅방 ID", required = true) @PathVariable UUID roomId) { return queryService.getScoreboard(roomId); } - /** - * 특정 참가자의 로스터(11인) + 포메이션 - */ + @Operation(summary = "참가자 로스터 조회", description = "특정 참가자의 11명 로스터와 포메이션 정보를 조회합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "로스터 조회 성공"), + @ApiResponse(responseCode = "404", description = "참가자를 찾을 수 없음") + }) @GetMapping("/{roomId}/participants/{participantId}/roster") - public RosterResponse roster(@PathVariable UUID roomId, @PathVariable UUID participantId) { + public RosterResponse roster( + @Parameter(description = "채팅방 ID", required = true) @PathVariable UUID roomId, + @Parameter(description = "참가자 ID", required = true) @PathVariable UUID participantId) { return queryService.getRoster(roomId, participantId); } - /** - * 드래프트로 채팅방 조회 - */ + @Operation(summary = "드래프트 채팅방 조회", description = "드래프트 ID로 해당 채팅방을 조회합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "채팅방 조회 성공"), + @ApiResponse(responseCode = "404", description = "드래프트에 해당하는 채팅방을 찾을 수 없음") + }) @GetMapping("/by-draft/{draftId}") - public ChatRoom getChatRoomByDraft(@PathVariable UUID draftId) { + public ChatRoom getChatRoomByDraft(@Parameter(description = "드래프트 ID", required = true) @PathVariable UUID draftId) { return chatRoomRepository.findByDraftId(draftId) .orElseThrow(() -> new IllegalArgumentException("ChatRoom not found for draft " + draftId)); } - /** - * 채팅방 생성 (드래프트용) - */ + @Operation(summary = "채팅방 생성", description = "드래프트를 위한 새 채팅방을 생성합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "채팅방 생성 성공"), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터") + }) @PostMapping - public ChatRoom createChatRoom(@RequestBody CreateChatRoomRequest request) { + public ChatRoom createChatRoom(@Parameter(description = "채팅방 생성 요청") @RequestBody CreateChatRoomRequest request) { return chatRoomService.createForDraft(request.getDraftId()); } diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/elasticsearch/ChatReindexController.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/elasticsearch/ChatReindexController.java index 6aee0a1..f6b0a0d 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/elasticsearch/ChatReindexController.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/elasticsearch/ChatReindexController.java @@ -8,6 +8,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + +@Tag(name = "Chat Search Management", description = "채팅 검색 인덱스 관리 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/chat-rooms") @@ -15,8 +22,14 @@ public class ChatReindexController { private final ChatMessageRepository repo; private final ChatSearchIndexer indexer; + @Operation(summary = "채팅 메시지 재인덱싱", description = "지정된 채팅방의 모든 메시지를 Elasticsearch에 재인덱싱합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "재인덱싱 성공"), + @ApiResponse(responseCode = "404", description = "채팅방을 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "내부 서버 오류") + }) @PostMapping("/{roomId}/reindex") - public void reindex(@PathVariable UUID roomId) { + public void reindex(@Parameter(description = "채팅방 ID", required = true) @PathVariable UUID roomId) { // 대용량이면 Page로 분할 처리 repo.findAllByChatRoomIdOrderByCreatedAtAsc(roomId).forEach(indexer::index); } diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/elasticsearch/controller/ChatSearchController.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/elasticsearch/controller/ChatSearchController.java index aabbf2b..de873e6 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/elasticsearch/controller/ChatSearchController.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/elasticsearch/controller/ChatSearchController.java @@ -11,6 +11,13 @@ import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + +@Tag(name = "Chat Search", description = "채팅 메시지 검색 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/chat-rooms") @@ -19,12 +26,18 @@ public class ChatSearchController { private final ChatSearchService chatSearchService; private final ChatMembershipRepository membershipRepository; + @Operation(summary = "채팅 메시지 검색", description = "지정된 채팅방에서 키워드로 메시지를 검색합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "검색 성공"), + @ApiResponse(responseCode = "403", description = "채팅방 멤버가 아님"), + @ApiResponse(responseCode = "500", description = "내부 서버 오류") + }) @GetMapping("/{roomId}/search") public ResponseEntity search( - @PathVariable UUID roomId, - @RequestParam(required = false, defaultValue = "") String q, - @RequestParam(required = false) String cursor, - @RequestParam(defaultValue = "20") int limit, + @Parameter(description = "채팅방 ID", required = true) @PathVariable UUID roomId, + @Parameter(description = "검색 키워드") @RequestParam(required = false, defaultValue = "") String q, + @Parameter(description = "페이지 커서 (이전 조회의 nextCursor 값)") @RequestParam(required = false) String cursor, + @Parameter(description = "조회할 메시지 개수 (1-50, 기본값: 20)") @RequestParam(defaultValue = "20") int limit, Authentication authentication) { // 권한 체크 diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/service/Impl/ChatNotificationServiceImpl.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/service/Impl/ChatNotificationServiceImpl.java index 2ca18b2..3a3832a 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/service/Impl/ChatNotificationServiceImpl.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/service/Impl/ChatNotificationServiceImpl.java @@ -6,7 +6,7 @@ import java.util.Map; import java.util.UUID; -//import likelion.mlb.backendProject.domain.chat.bus.ChatRedisPublisher; +import likelion.mlb.backendProject.domain.chat.bus.ChatRedisPublisher; import likelion.mlb.backendProject.domain.chat.entity.ChatMessage; import likelion.mlb.backendProject.domain.chat.repository.AlertRoutingRepository; import likelion.mlb.backendProject.domain.chat.repository.ChatMessageRepository; @@ -28,7 +28,7 @@ public class ChatNotificationServiceImpl implements ChatNotificationService { private final ChatMessageRepository chatMessageRepository; private final ChatMessageService chatMessageService; private final SimpMessagingTemplate messagingTemplate; - //private final ChatRedisPublisher chatRedisPublisher; // Redis 방식 비활성화 + private final ChatRedisPublisher chatRedisPublisher; /** * LiveDataService에서 바로 호출 (현재 트랜잭션 커밋 후에 실행되도록 지연) @@ -77,7 +77,6 @@ protected void doDispatch(UUID playerId, // ✅ 서비스 경유 저장: AFTER_COMMIT 색인(ES)까지 자동 수행 var saved = chatMessageService.saveSystemAlert(roomId, msgText); - // ✅ 직접 WebSocket으로 전송 (즉시 전달) var payload = Map.of( "id", saved.getId().toString(), "chatRoomId", roomId.toString(), @@ -86,9 +85,7 @@ protected void doDispatch(UUID playerId, "createdAt", saved.getCreatedAt().toString() ); messagingTemplate.convertAndSend("/topic/chat/" + roomId, payload); - - // Redis 방식 (주석처리 - 지연 발생) - //chatRedisPublisher.publishToRoom(roomId, new java.util.HashMap<>(payload)); + chatRedisPublisher.publishToRoom(roomId, new java.util.HashMap<>(payload)); } } diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/draft/controller/DraftController.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/draft/controller/DraftController.java index 5d0d1cd..f492ee6 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/draft/controller/DraftController.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/draft/controller/DraftController.java @@ -17,12 +17,19 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + import java.security.Principal; import java.util.*; /** * 드래프트 관련 controller * */ +@Tag(name = "Draft", description = "드래프트 관리 API") @Controller @RequiredArgsConstructor @RequestMapping("/api/draft") @@ -69,43 +76,54 @@ public void selectRandomPlayer(@Payload DraftRequest draftRequest, Principal pri } } - /* - * 참여자(participant)클릭 시 해당 참여자가 선택한 선수 리스트 가져오기 - * */ + @Operation(summary = "참여자별 선수 목록 조회", description = "특정 참여자가 선택한 선수 리스트를 조회합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "선수 목록 조회 성공"), + @ApiResponse(responseCode = "404", description = "참여자를 찾을 수 없음") + }) @GetMapping("/{participantId}/players") @ResponseBody - public List getPlayersByParticipantId(@PathVariable("participantId") UUID participantId) { + public List getPlayersByParticipantId( + @Parameter(description = "참여자 ID", required = true) @PathVariable("participantId") UUID participantId) { return draftService.getPlayersByParticipantId(participantId); } - - /* - * 드래프트 방 입장 시 해당 드래프트 방에 속해 있는 4명의 참여자 리스트 가져오기 - */ + @Operation(summary = "드래프트 참여자 목록 조회", description = "드래프트 방에 속해있는 참여자 목록을 조회합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "참여자 목록 조회 성공"), + @ApiResponse(responseCode = "404", description = "드래프트를 찾을 수 없음") + }) @GetMapping("/{draftId}/participants") @ResponseBody - public List getParticipantsByDraftId(@PathVariable("draftId") UUID draftId - , @AuthenticationPrincipal CustomUserDetails userDetails) { + public List getParticipantsByDraftId( + @Parameter(description = "드래프트 ID", required = true) @PathVariable("draftId") UUID draftId, + @Parameter(description = "인증된 사용자 정보", hidden = true) @AuthenticationPrincipal CustomUserDetails userDetails) { String userEmail = userDetails.getUser().getEmail(); return draftService.getParticipantsByDraftId(draftId, userEmail); } - /* - * 해당 드래프트 방에서 선택 된 선수 리스트 가져오기 - */ + @Operation(summary = "드래프트 전체 선수 목록 조회", description = "드래프트에서 선택된 모든 선수 목록을 조회합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "선수 목록 조회 성공"), + @ApiResponse(responseCode = "404", description = "드래프트를 찾을 수 없음") + }) @GetMapping("/{draftId}/allPlayers") @ResponseBody - public List getAllPlayersByDraftId(@PathVariable("draftId") UUID draftId) { + public List getAllPlayersByDraftId( + @Parameter(description = "드래프트 ID", required = true) @PathVariable("draftId") UUID draftId) { return draftService.getAllPlayersByDraftId(draftId); } - /* - * isWithinSquadLimits테스트용 - */ + @Operation(summary = "스쿼드 제한 검사", description = "참여자의 스쿼드 제한을 검사합니다 (테스트용)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "검사 완료"), + @ApiResponse(responseCode = "400", description = "잘못된 요청 파라미터") + }) @GetMapping("squadLimit") @ResponseBody - public boolean isWithinSquadLimits(@RequestParam(value="participantId") UUID participantId, - @RequestParam(value="participantId")UUID elementTypeId) { + public boolean isWithinSquadLimits( + @Parameter(description = "참여자 ID", required = true) @RequestParam(value="participantId") UUID participantId, + @Parameter(description = "엘리먼트 타입 ID", required = true) @RequestParam(value="elementTypeId")UUID elementTypeId) { return draftService.isWithinSquadLimitsTest(participantId, elementTypeId); } -} +} \ No newline at end of file diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/player/cache/controller/PlayerCacheController.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/player/cache/controller/PlayerCacheController.java index e141869..9e67e7d 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/player/cache/controller/PlayerCacheController.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/player/cache/controller/PlayerCacheController.java @@ -11,8 +11,14 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + import java.util.*; +@Tag(name = "Player Cache", description = "선수 캐시 관리 API") @RestController @RequestMapping("/api/playerCache") @RequiredArgsConstructor @@ -20,14 +26,22 @@ public class PlayerCacheController { private final PlayerCacheService playerCacheService; - // DB → Redis 저장 (캐시 초기화) + @Operation(summary = "선수 데이터 캐시링", description = "DB의 선수 데이터를 Redis 캐시로 로드합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "캐시링 성공"), + @ApiResponse(responseCode = "500", description = "내부 서버 오류") + }) @PostMapping("/cache") public ResponseEntity cachePlayerList() { playerCacheService.loadPlayersToRedis(); return ResponseEntity.ok("Player list cached to Redis."); } - // Redis → 조회 (캐시 미스 시 자동 로드) + @Operation(summary = "선수 목록 조회", description = "Redis 캐시에서 선수 목록을 조회합니다 (캐시 미스 시 DB에서 자동 로드)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "선수 목록 조회 성공"), + @ApiResponse(responseCode = "500", description = "내부 서버 오류") + }) @GetMapping public ResponseEntity> getPlayerList() { List playerList = playerCacheService.getPlayersFromRedis(); diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/domain/player/elasticsearch/controller/PlayerEsController.java b/backendProject/src/main/java/likelion/mlb/backendProject/domain/player/elasticsearch/controller/PlayerEsController.java index 7a851d8..497b685 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/domain/player/elasticsearch/controller/PlayerEsController.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/domain/player/elasticsearch/controller/PlayerEsController.java @@ -11,10 +11,17 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; +@Tag(name = "Player Search", description = "선수 검색 API (Elasticsearch)") @RestController @RequiredArgsConstructor @RequestMapping("api/playerEs") @@ -22,11 +29,16 @@ public class PlayerEsController { private final PlayerEsService playerEsService; - // elasticsearch 검색 결과를 json List로 반환 + @Operation(summary = "선수 검색", description = "키워드와 엘리먼트 타입으로 선수를 검색합니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "검색 성공"), + @ApiResponse(responseCode = "400", description = "잘못된 검색 파라미터"), + @ApiResponse(responseCode = "500", description = "내부 서버 오류") + }) @GetMapping("/search") public ResponseEntity> searchWithKeyword( - @RequestParam(value = "keyword", defaultValue = "") String keyword - , @RequestParam(value = "elementTypeId", defaultValue = "") String elementTypeId + @Parameter(description = "검색 키워드 (선수명 등)") @RequestParam(value = "keyword", defaultValue = "") String keyword, + @Parameter(description = "엘리먼트 타입 ID") @RequestParam(value = "elementTypeId", defaultValue = "") String elementTypeId ) { return ResponseEntity.ok(playerEsService.search(keyword, elementTypeId)); diff --git a/backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/OpenApiConfig.java b/backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/OpenApiConfig.java index 4bd2387..9cebbd8 100644 --- a/backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/OpenApiConfig.java +++ b/backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/OpenApiConfig.java @@ -5,6 +5,7 @@ import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springdoc.core.models.GroupedOpenApi; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,4 +35,94 @@ public OpenAPI openAPI() { .components(components); } + @Bean + public GroupedOpenApi chatApi() { + return GroupedOpenApi.builder() + .group("Chat API") + .pathsToMatch("/api/chat-rooms/**", "/api/notify/**") + .packagesToScan("likelion.mlb.backendProject.domain.chat") + .build(); + } + + @Bean + public GroupedOpenApi chatSearchApi() { + return GroupedOpenApi.builder() + .group("Chat Search API") + .pathsToMatch("/api/chat-rooms/*/search/**", "/api/chat-rooms/*/reindex/**") + .packagesToScan("likelion.mlb.backendProject.domain.chat.elasticsearch") + .build(); + } + + @Bean + public GroupedOpenApi userApi() { + return GroupedOpenApi.builder() + .group("User API") + .pathsToMatch("/api/user/**", "/api/auth/**") + .packagesToScan("likelion.mlb.backendProject.domain.user") + .build(); + } + + @Bean + public GroupedOpenApi draftApi() { + return GroupedOpenApi.builder() + .group("Draft API") + .pathsToMatch("/api/draft/**") + .packagesToScan("likelion.mlb.backendProject.domain.draft") + .build(); + } + + @Bean + public GroupedOpenApi playerApi() { + return GroupedOpenApi.builder() + .group("Player API") + .pathsToMatch("/api/player/**", "/api/elementType/**") + .packagesToScan("likelion.mlb.backendProject.domain.player") + .build(); + } + + @Bean + public GroupedOpenApi playerCacheApi() { + return GroupedOpenApi.builder() + .group("Player Cache API") + .pathsToMatch("/api/playerCache/**") + .packagesToScan("likelion.mlb.backendProject.domain.player.cache") + .build(); + } + + @Bean + public GroupedOpenApi playerEsApi() { + return GroupedOpenApi.builder() + .group("Player Search API") + .pathsToMatch("/api/playerEs/**") + .packagesToScan("likelion.mlb.backendProject.domain.player.elasticsearch") + .build(); + } + + @Bean + public GroupedOpenApi matchApi() { + return GroupedOpenApi.builder() + .group("Match API") + .pathsToMatch("/api/match/**") + .packagesToScan("likelion.mlb.backendProject.domain.match") + .build(); + } + + @Bean + public GroupedOpenApi teamApi() { + return GroupedOpenApi.builder() + .group("Team API") + .pathsToMatch("/api/team/**") + .packagesToScan("likelion.mlb.backendProject.domain.team") + .build(); + } + + @Bean + public GroupedOpenApi roundApi() { + return GroupedOpenApi.builder() + .group("Round API") + .pathsToMatch("/api/round/**") + .packagesToScan("likelion.mlb.backendProject.domain.round") + .build(); + } + } \ No newline at end of file