docs: 문서 주석 및 리팩토링 - #67
Conversation
Controller, DTO에 Swagger 주석, 문서 주석 추가 및 리팩토링 진행
설정 파일에 문서 주석 추가
디렉토리 구조 변경 및 코드 리팩토링
리팩토링 및 문서주석 완료
리팩토링 및 문서주석 완료
Walkthrough매칭 도메인 전반을 이벤트/스케줄 기반으로 재구성했다. REST/WebSocket 핸들러를 교체·정리하고, 상태 브로드캐스트, 세션 레지스트리, Redis 락, 스케줄러/부트스트랩퍼, 이벤트(큐 입장/취소, 상태변경)와 리스너를 추가했다. DTO에 Swagger/검증 메타데이터를 부여했고, 일부 컨트롤러·서비스·구 DTO/WS 핸들러를 제거했다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant WS as WebSocket(MatchHandler)
participant SR as SessionRegistry
participant MS as MatchService
participant MB as MatchBroadcaster
Client->>WS: CONNECT /ws/match
WS->>SR: add(userId, session)
WS->>MB: sendUserId(session, userId)
WS->>MS: getCurrentStatus()
MS-->>WS: MatchStatusResponse
WS->>MB: sendStatusTo(session, status, null)
Client->>WS: {"type":"JOIN"}
WS->>MS: joinMatch(userId)
MS->>MS: publish MatchQueueJoinedEvent
sequenceDiagram
participant E as MatchEventsListener
participant MS as MatchService
participant SC as StatusCache
participant MB as MatchBroadcaster
MS-->>E: MatchQueueJoinedEvent / CanceledEvent
E->>MS: getCurrentStatus()
MS-->>E: MatchStatusResponse
E->>SC: updateIfChanged(status)?
alt changed
E->>MB: sendStatusToAll(status)
end
sequenceDiagram
participant S as MatchScheduler
participant PUB as AppEventPublisher
participant E as MatchEventsListener
participant RL as RedisLock
participant TP as MatchTimeoutProcessor
participant MB as MatchBroadcaster
participant MS as MatchService
S->>PUB: publish MatchStateChangedEvent(OPEN)
S->>PUB: publish MatchStateChangedEvent(LOCKED, roundId)
PUB-->>E: MatchStateChangedEvent(LOCKED, roundId)
E->>RL: withLock("lock:match:round:{id}", 60s)
alt acquired
E->>TP: processTimeoutAndInsert(roundId)
TP-->>E: Map<userId, AssignDto>
E->>MB: sendDraftStart(userId, assign) (for each)
E->>MS: getNextRoundWindowAfterOrNull(roundId)
MS-->>E: nextRound?
alt exists
E->>S: scheduleFor(nextRound)
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 14
🔭 Outside diff range comments (1)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchService.java (1)
75-98: 라운드 미확정 시 getCurrentStatus가 예외를 던져 브로드캐스트 흐름을 깨뜨릴 수 있음현재
getCurrentStatus()는 라운드가 없을 때getNextDraftWindowOrThrow()예외가 전파됩니다.MatchEventsListener.broadcastIfChanged()등 호출부가 예외를 처리하지 않아 이벤트 루프에 영향을 줄 수 있습니다. 라운드 부재를 정상 상태로 취급해 응답하세요.다음과 같이 null-세이프하게 변경을 제안합니다:
- RoundInfo round = draftTimingService.getNextDraftWindowOrThrow(); - LocalDateTime now = LocalDateTime.now(KST); - - LocalDateTime openAt = LocalDateTime.parse(round.getOpenAt()); - LocalDateTime lockAt = LocalDateTime.parse(round.getLockAt()); + LocalDateTime now = LocalDateTime.now(KST); + RoundInfo round = getNextRoundWindowOrNull(); + if (round == null) { + // 라운드 미확정: 대기열 카운트만 포함하여 LOCKED로 응답 + return new MatchStatusResponse(userCount, "LOCKED", "00:00:00", null); + } + LocalDateTime openAt = LocalDateTime.parse(round.getOpenAt()); + LocalDateTime lockAt = LocalDateTime.parse(round.getLockAt());참고:
MatchHandler.afterConnectionEstablished()는 try-catch로 보호되지만,MatchEventsListener.broadcastIfChanged()는 보호되지 않습니다.
🧹 Nitpick comments (28)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/repository/ParticipantRepository.java (2)
16-16: FIXME 주석 정리 필요의미 없는 FIXME가 남아 있습니다. 불필요한 주석은 제거하고, 리포지토리 수준의 JavaDoc으로 의도를 명확히 해주세요.
다음과 같이 정리하는 것을 제안합니다:
-//FIXME 주석 추가할 것 +/** + * 매칭 도메인에서 드래프트 참가자(Participant)에 대한 조회/검증을 제공하는 리포지토리. + * - 중복 참가 여부 검사 + * - 라운드/유저 기반 배정 조회 + */ +// @Repository 사용 여부는 코드베이스 일관성에 맞춰 결정
17-17: @repository 애노테이션 일관성동일 코드베이스의 다른 Repository(예: ParticipantPlayerRepository)는 @repository를 명시하고 있습니다. Spring Data JPA에서는 생략해도 동작하지만, 프로젝트 내 일관성을 위해 명시 사용 여부를 통일하는 것을 권장합니다.
다음과 같이 추가할 수 있습니다:
+import org.springframework.stereotype.Repository; @@ -public interface ParticipantRepository extends JpaRepository<Participant, UUID> { +@Repository +public interface ParticipantRepository extends JpaRepository<Participant, UUID> {backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchKeys.java (1)
3-6: 단일 책임의 키 상수화 LGTMRedis 키의 전역 상수화가 명확하고 재사용성/일관성 측면에서 좋습니다. 향후 사용할 다른 키들(예: 상태 캐시, 락 키 등)도 이 클래스로 집중 관리하면 유지보수성이 올라갑니다.
추가 제안:
- 키 네임스페이스 규칙을 문서화하고, 관련 키(예: "match:status", "match:lock:*")도 함께 상수화하면 좋습니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchQueueCanceledEvent.java (1)
3-9: userId 타입 일관성 점검(추천: UUID 사용)도메인 전반에서 User의 식별자가 UUID라면 이벤트 필드도 UUID로 통일하는 것이 타입 안정성과 후속 처리(파싱/검증) 측면에서 유리합니다. 현재 WebSocket 핸들러는 문자열을 전달하지만, 서비스 계층에서 UUID로 변환 후 이벤트를 발행하는 편이 안전합니다.
제안:
+import java.util.UUID; @@ -public record MatchQueueCanceledEvent(String userId) { } +public record MatchQueueCanceledEvent(UUID userId) { }확인 포인트:
- MatchService.cancelMatch(...)의 파라미터 및 내부 발행 코드 타입
- MatchEventsListener.onQueueCanceled(...)의 파라미터 타입
- 세션 속성(userId) 문자열 → UUID 변환 지점 통일
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchQueueJoinedEvent.java (1)
3-9: userId 타입 일관성 점검(추천: UUID 사용)Canceled 이벤트와 동일하게 userId를 UUID로 통일하는 것을 권장합니다. 이벤트 간 타입 불일치가 생기면 핸들러/리스너에서 중복 변환 코드가 발생할 수 있습니다.
제안:
+import java.util.UUID; @@ -public record MatchQueueJoinedEvent(String userId) { } +public record MatchQueueJoinedEvent(UUID userId) { }backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/SchedulerConfig.java (2)
13-19: 수명주기 관리: initialize()는 생략 가능ThreadPoolTaskScheduler는 빈 등록 시 컨테이너가 initialize를 호출합니다. 수명주기 중복 관리를 피하려면 명시적 s.initialize() 호출을 제거해도 됩니다.
다음과 같이 정리:
- s.initialize(); return s;
13-18: 운영 설정 외부화 및 에러 핸들러 추가 권장
- poolSize, threadNamePrefix는 설정 파일로 외부화하면 환경별(로컬/스테이징/운영) 조정이 용이합니다.
- 스케줄러 작업 중 발생하는 예외 로깅을 위해 ErrorHandler를 지정하는 것을 권장합니다.
예시:
-import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.support.TaskUtils; @@ - public ThreadPoolTaskScheduler matchTaskScheduler() { + public ThreadPoolTaskScheduler matchTaskScheduler() { ThreadPoolTaskScheduler s = new ThreadPoolTaskScheduler(); - s.setPoolSize(2); - s.setThreadNamePrefix("match-scheduler-"); + s.setPoolSize(2); // TODO: application.yml로 외부화 + s.setThreadNamePrefix("match-scheduler-"); // TODO: 외부화 s.setRemoveOnCancelPolicy(true); s.setWaitForTasksToCompleteOnShutdown(true); + s.setErrorHandler(TaskUtils.getDefaultErrorHandler(true)); - s.initialize(); return s; }또는 반환 타입을 TaskScheduler로 좁히면 의존성 역전이 좋아집니다.
-import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; @@ - public ThreadPoolTaskScheduler matchTaskScheduler() { + public TaskScheduler matchTaskScheduler() { ThreadPoolTaskScheduler s = new ThreadPoolTaskScheduler(); ... return s; }backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchStateChangedEvent.java (1)
5-17: 상태 타입을 enum으로 이행 고려문자열 대신 enum(예: MatchState { BEFORE_OPEN, OPEN, LOCKED })를 사용하면 컴파일 타임 안전성과 IDE 지원(오타 방지)을 확보할 수 있습니다. Listener/Service/DTO 정합성 영향이 크므로 단계적 이행(이벤트/스케줄러/리스너부터)도 가능합니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/UserIdMessage.java (1)
29-31: UUID 직렬화 일관성 검토(선택): String 대신 UUID 필드 사용 고려현재 예제/사용처가 UUID 문자열임이 명확하므로, 타입 안정성을 위해
private final UUID userId;로 두고 직렬화 시 문자열로 출력하는 접근도 가능합니다. 클라이언트 스펙 합의가 있다면 현 상태 유지도 무방합니다.backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/RedisLock.java (2)
49-55: unlock 결과 피드백 보강(토큰 불일치/결과 null 로깅)현재 unlock은 예외만 로깅하고, 스크립트 실행 결과(0/1/null)에 대한 로깅이 없습니다. 운영 관점에서 토큰 불일치(0)와 드문 null 결과를 구분해 관측하면 트러블슈팅에 유용합니다.
다음 패치를 적용해 주세요:
public void unlock(String key, String token) { try { - redis.execute(UNLOCK_SCRIPT, Collections.singletonList(key), token); + Long res = redis.execute(UNLOCK_SCRIPT, Collections.singletonList(key), token); + if (res == null) { + log.warn("unlock 결과 null key={}", key); + } else if (res == 0L) { + // 토큰 불일치(이미 만료/타 노드가 소유 변경 등) + log.debug("unlock: token 불일치로 미삭제 key={}", key); + } } catch (Exception e) { log.warn("unlock 실패 key={}, cause={}", key, e.toString()); } }
60-69: withLock의 예외 전파 동작 명시(문서화 제안)
withLock은 작업 실행 중 예외가 발생하면 false를 반환하지 않고 예외를 그대로 전파합니다(해제는 finally로 보장). Javadoc에 해당 동작을 명시하면 호출부 설계가 명확해집니다.backendProject/src/main/java/likelion/mlb/backendProject/domain/match/scheduler/MatchBootstrapper.java (1)
31-43: (선택) 초기화 시 중복 예약 방지 로그 추가 제안실제
scheduleFor가 동일 라운드 중복등록을 내부적으로 방지하더라도, 이미 동일 라운드가 예약된 상태에서 bootstrap을 다시 호출한 경우(재시작 시나리오) 스킵 로그를 남기면 원인 추적이 쉬워집니다. 필요 시MatchScheduler가 반환 값/상태를 제공하면 여기서도 분기 로깅 가능.backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/StatusMessage.java (2)
43-45: remainingTime 포맷 표기 일관화(대문자 MM → 소문자 mm) 및 오해 소지 제거문서상 "HH:MM:SS" 표기는 시간 포맷에서 월(MM)과 분(mm)이 혼동될 수 있습니다. 일반적으로 "HH:mm:ss" 표기가 관례입니다. 아래처럼 문구만 정정해 주세요.
-@Schema(description = "남은 시간(HH:MM:SS)", example = "00:42:10") +@Schema(description = "남은 시간(HH:mm:ss)", example = "00:42:10")
46-52: state 문자열 상수 대신 enum 도입 고려"BEFORE_OPEN", "OPEN", "LOCKED", "LOCKED_HOLD"의 오타/불일치 방지를 위해 enum(예: WsMatchState)을 도입하면 안전합니다. REST DTO(MatchStatusResponse)와 WS DTO(StatusMessage)가 상이한 value set을 갖는다면, 각각 별도 enum을 유지하거나 한 enum에 SUPPORTED_FOR_WS/REST 메타를 둘 수 있습니다.
원하시면 enum 설계(직렬화 값, Swagger 문서 자동화 포함) 초안을 드리겠습니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/RoundInfo.java (1)
17-21: 시간 필드의 타입 강화를 검토(선택): String → LocalDateTime/OffsetDateTime현재 openAt/lockAt은 String이며 서비스에서 LocalDateTime.parse로 파싱합니다. DTO 단계에서 LocalDateTime(또는 OffsetDateTime)으로 엄격히 타입을 지정하면:
- 파싱 오류를 컴파일/런타임 초기에 발견
- Swagger에서 날짜-시간 스키마를 더 정확히 표현
- 직렬화 시 표준 ISO-8601 포맷을 일관되게 유지
예시(선택):
+import com.fasterxml.jackson.annotation.JsonFormat; +import java.time.LocalDateTime; @@ - private String openAt; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime openAt; @@ - private String lockAt; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime lockAt;서비스/리포지토리 계층의 생성/파싱 코드도 함께 정리되어야 하므로, 변경 영향도를 고려해 결정하면 좋겠습니다.
backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/MatchWebSocketConfig.java (1)
42-45: Origin 값 정규화 필요 — FRONTEND_HTTP_URL 값 확인 및 패턴 사용 검토application-prod.properties에서 frontend.http.url = ${FRONTEND_HTTP_URL}로 설정되어 있고 MatchWebSocketConfig에서 그대로 .setAllowedOrigins(frontendHttpUrl)를 사용합니다. 런타임 환경변수에 슬래시(/)나 경로가 포함되면 Origin 매칭이 실패할 수 있으므로 값 정규화가 필요하고, 멀티 도메인/와일드카드가 필요하면 setAllowedOriginPatterns 전환도 고려하세요.
주의할 위치:
- backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/MatchWebSocketConfig.java (약 42-45행) — .setAllowedOrigins(frontendHttpUrl)
- backendProject/src/main/resources/application-prod.properties (52행) — frontend.http.url = ${FRONTEND_HTTP_URL}
권장 수정(선택):
- .setAllowedOrigins(frontendHttpUrl); // 3) CORS: 지정 Origin만 허용 + .setAllowedOrigins(originOf(frontendHttpUrl)); // 3) CORS: 지정 Origin만 허용유틸(예시):
private static String originOf(String url) { try { var u = java.net.URI.create(url); String scheme = (u.getScheme() != null) ? u.getScheme() : "http"; String host = (u.getHost() != null) ? u.getHost() : url.replaceAll("^https?://", "").replaceAll("/.*$", ""); int port = u.getPort(); String origin = scheme + "://" + host + (port > 0 ? (":" + port) : ""); return origin.replaceAll("/+$", ""); } catch (Exception e) { return url.replaceAll("/+$", ""); } }요청: FRONTEND_HTTP_URL(또는 application-prod.properties에 실제 설정된 값)이 오리진(예: https://app.example.com)만 포함하는지 확인해 주세요. 현재 리포지토리에서는 런타임 ENV 값은 확인되지 않습니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/MatchHandler.java (1)
88-99: 클라이언트 피드백(ACK/ERROR) 응답 추가를 고려현재 JOIN/CANCEL 수신 시 서버 동작만 수행하고, 클라이언트에 결과를 돌려주지 않습니다. 경우에 따라 간단한 ACK 메시지(예: {"type":"ACK","for":"JOIN"})나 에러 응답을 보내면 UX 개선 및 클라이언트 상태 동기화가 쉬워집니다. 실패 시(예: 내부 예외) 에러 타입을 내려주는 것도 유용합니다.
원하시면 WS 메시지 스키마(ACK/ERROR)와 전송 유틸을 추가한 패치를 제안드리겠습니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/AssignDto.java (1)
12-23: 스키마/검증 메타데이터 추가 적절 — 응답 DTO에 대한 의도 명확화 권장
- OpenAPI 문서화와 필드 제약이 명확합니다. REST 응답용 DTO라서 Bean Validation은 런타임 검증에 사용되지는 않지만, 문서/스키마 목적으로는 충분히 유용합니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/DraftStartMessage.java (2)
56-59: 미사용 파라미터 제거fromAssign에서 userId는 사용되지 않습니다. 혼선을 줄이기 위해 시그니처를 단순화하세요.
- public static DraftStartMessage fromAssign(String userId, AssignDto a) { - return of(a.getDraftId(), a.getUserNumber()); - } + public static DraftStartMessage fromAssign(AssignDto a) { + return of(a.getDraftId(), a.getUserNumber()); + }
35-38: type은 상수 취급 — 빌더에서 수동 지정 대신 팩토리 사용 권장현재 빌더로 type을 임의로 바꿀 수 있습니다. 외부 사용처(예: MatchBroadcaster)에서는
of/fromAssign를 사용해 상수화를 보장하는 편이 안전합니다.backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchEventsListener.java (2)
111-139: 분산 락 TTL과 재진입 보호 전략 점검
- TTL을 고정 60s로 둔 상태에서 처리 시간이 길어지면, TTL 만료 후 타 노드가 재획득/중복 실행할 수 있습니다. 배치 규모가 늘어날 수 있으므로 TTL을 설정값으로 외부화하고(예: application.yml), 처리 시간 상한보다 충분히 크게 가져가세요.
- 처리 완료 여부를 DB/캐시(예: “processed:round:{roundId}” 마커)로 한 번 더 방어하면 장애/락만료 케이스에서도 중복 삽입을 줄일 수 있습니다.
101-105: 중복 코드 제거: broadcastIfChanged 재사용 가능상태 브로드캐스트 로직이
broadcastIfChanged()와 중복입니다. 동일 메서드를 호출하도록 정리하면 가독성과 유지보수가 좋아집니다.backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/DraftTimingService.java (2)
21-25: Javadoc 정책 시간(LOCK) 불일치주석은 lockAt을 14:35로 표기하지만, 상수는 17:05입니다. 혼선을 방지하기 위해 주석/상수 중 하나로 일치시켜 주세요. 비즈니스 의도가 17:05라면 Javadoc 수정이 맞습니다.
- * - `lockAt = draftDay 14:35 KST` + * - `lockAt = draftDay 17:05 KST`
80-96: 시간대 변환 로직 OK — 경계 케이스 로깅만으로 충분한지 재검토
- UTC → KST 변환 및 draftDay 계산(2일 전) 로직은 명확합니다.
- open < lock 위반 시 경고만 로그를 남기는데, 스케줄러에서는 위반 시 즉시 LOCKED를 발사합니다. 여기서도 동일한 폴백(예: 예외 또는 보정/강제 LOCKED 반환)을 고려하면 상위 호출부에서의 방어가 쉬워집니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchTimeoutProcessor.java (1)
124-130: SREM 호출 시 varargs 생성 방식 개선(미세 최적화)미미하지만,
toArray(new String[0])대신 메서드 레퍼런스를 쓰면 불필요한 빈 배열 할당을 피할 수 있습니다.- redisTemplate.opsForSet().remove( - MatchKeys.QUEUE_KEY, - usedRealUserIds.toArray(new String[0]) // varargs 타입 안전 - ); + redisTemplate.opsForSet().remove( + MatchKeys.QUEUE_KEY, + usedRealUserIds.toArray(String[]::new) + );backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchService.java (1)
45-51: 중복 JOIN 이벤트 소음 최소화(선택)이미 세트에 존재하는 경우에도 매번 이벤트를 발행합니다. 상태 캐시로 브로드캐스트는 억제되겠지만, 이벤트 수를 줄이려면 SADD 결과를 확인해 변경 시에만 발행할 수 있습니다.
예시:
Long added = redisTemplate.opsForSet().add(MatchKeys.QUEUE_KEY, userId); if (added != null && added > 0) { publisher.publishEvent(new MatchQueueJoinedEvent(userId)); }backendProject/src/main/java/likelion/mlb/backendProject/domain/match/scheduler/MatchScheduler.java (1)
141-160: 이벤트 prev 상태의 정합성 향상 제안(선택)둘 다 과거인 경우 OPEN을 생략하고 LOCKED만 발사합니다. 리스너는 next만 사용하지만, 로깅/감사 관점에선 prev를 실제 전이 경로에 맞추면 좋습니다(예: OPEN 미발사 시 prev="BEFORE_OPEN" → "LOCKED").
간단히
fireLocked(UUID roundId)에서 prev를 파라미터로 받아 분기하거나,scheduleFor에서 now/open/lock 비교로 prev 값을 결정해 전달하세요.backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchBroadcaster.java (1)
96-113: DraftStart 메시지 생성: 팩토리 메서드 일관 사용 제안(선택)이미
DraftStartMessage.fromAssign(userId, assign)유틸이 있다면 이를 사용해 메시지 포맷 일관성을 높이세요.- var msg = DraftStartMessage.builder() - .type("DRAFT_START") - .draftId(assign.getDraftId()) - .userNumber(assign.getUserNumber()) - .build(); + var msg = DraftStartMessage.fromAssign(userId, assign);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these settings in your CodeRabbit configuration.
📒 Files selected for processing (30)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/controller/AssignmentController.java(0 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/controller/MatchRestController.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/controller/WellKnownIgnoreController.java(0 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/AssignDto.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/DraftStartMessage.java(0 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/MatchStatusResponse.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/RoundInfo.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchEventsListener.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchQueueCanceledEvent.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchQueueJoinedEvent.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchStateChangedEvent.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/handler/MatchHandler.java(0 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchBroadcaster.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchKeys.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/RedisLock.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/SessionRegistry.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/StatusCache.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/repository/ParticipantRepository.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/scheduler/MatchBootstrapper.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/scheduler/MatchScheduler.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/AssignmentService.java(0 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/DraftTimingService.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchService.java(2 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchTimeoutProcessor.java(5 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/MatchHandler.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/DraftStartMessage.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/StatusMessage.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/UserIdMessage.java(1 hunks)backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/MatchWebSocketConfig.java(2 hunks)backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/SchedulerConfig.java(1 hunks)
💤 Files with no reviewable changes (5)
- backendProject/src/main/java/likelion/mlb/backendProject/domain/match/handler/MatchHandler.java
- backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/DraftStartMessage.java
- backendProject/src/main/java/likelion/mlb/backendProject/domain/match/controller/AssignmentController.java
- backendProject/src/main/java/likelion/mlb/backendProject/domain/match/controller/WellKnownIgnoreController.java
- backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/AssignmentService.java
🧰 Additional context used
🧬 Code Graph Analysis (19)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchQueueCanceledEvent.java (4)
backendProject/src/main/java/likelion/mlb/backendProject/domain/player/entity/live/MatchEvent.java (1)
Entity(13-43)backendProject/src/main/java/likelion/mlb/backendProject/domain/player/repository/MatchEventRepository.java (1)
MatchEventRepository(8-9)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/handler/MatchHandler.java (2)
Override(95-118)Override(120-133)backendProject/src/main/java/likelion/mlb/backendProject/global/scheduler/service/LiveDataService.java (1)
createMatchEvent(302-315)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchQueueJoinedEvent.java (5)
backendProject/src/main/java/likelion/mlb/backendProject/domain/player/entity/live/MatchEvent.java (1)
Entity(13-43)backendProject/src/main/java/likelion/mlb/backendProject/domain/player/repository/MatchEventRepository.java (1)
MatchEventRepository(8-9)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/handler/MatchHandler.java (1)
Override(95-118)backendProject/src/main/java/likelion/mlb/backendProject/global/scheduler/service/LiveDataService.java (1)
createMatchEvent(302-315)backendProject/src/main/java/likelion/mlb/backendProject/domain/chat/event/ChatMessageEvent.java (1)
Getter(10-34)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/repository/ParticipantRepository.java (2)
backendProject/src/main/java/likelion/mlb/backendProject/domain/draft/repository/ParticipantPlayerRepository.java (3)
Repository(14-37)Query(26-36)findByParticipant(21-21)backendProject/src/main/java/likelion/mlb/backendProject/domain/draft/repository/DraftRepository.java (1)
Repository(14-26)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchStateChangedEvent.java (1)
backendProject/src/main/java/likelion/mlb/backendProject/domain/player/repository/MatchEventRepository.java (1)
MatchEventRepository(8-9)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchEventsListener.java (8)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchBroadcaster.java (1)
Slf4j(37-151)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/RedisLock.java (1)
Slf4j(19-70)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/SessionRegistry.java (1)
Slf4j(35-149)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/scheduler/MatchScheduler.java (1)
Slf4j(38-161)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchService.java (1)
Slf4j(31-130)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchTimeoutProcessor.java (1)
Slf4j(33-164)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/MatchHandler.java (1)
Slf4j(34-126)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/StatusCache.java (1)
Component(16-70)
backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/SchedulerConfig.java (4)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/handler/MatchHandler.java (2)
Scheduled(136-214)Slf4j(24-230)backendProject/src/main/java/likelion/mlb/backendProject/global/scheduler/LiveDataScheduler.java (2)
Component(35-49)Scheduled(42-48)backendProject/src/main/java/likelion/mlb/backendProject/global/scheduler/RoundSettlementScheduler.java (1)
Component(26-40)backendProject/src/main/java/likelion/mlb/backendProject/global/aop/SchedulerLog.java (1)
Target(9-13)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/RedisLock.java (5)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchEventsListener.java (1)
Slf4j(53-147)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchService.java (1)
Slf4j(31-130)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/StatusCache.java (1)
Component(16-70)backendProject/src/main/java/likelion/mlb/backendProject/global/redis/RedisPublisher.java (1)
RequiredArgsConstructor(8-19)backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/RedisConfig.java (1)
Configuration(19-67)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/UserIdMessage.java (3)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/DraftStartMessage.java (1)
Schema(18-60)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/StatusMessage.java (1)
Schema(14-58)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/handler/MatchHandler.java (4)
Override(95-118)uid(192-205)notifyDraftStart(216-229)extractUserId(84-93)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/DraftStartMessage.java (3)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/UserIdMessage.java (1)
Schema(12-32)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/DraftStartMessage.java (1)
Getter(7-16)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/handler/MatchHandler.java (1)
notifyDraftStart(216-229)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/AssignDto.java (5)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/MatchStatusResponse.java (1)
Schema(9-23)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/RoundInfo.java (1)
Getter(9-22)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/AssignmentService.java (1)
Service(14-36)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/DraftStartMessage.java (1)
Getter(7-16)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/controller/AssignmentController.java (1)
RestController(13-30)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/DraftTimingService.java (4)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/scheduler/MatchScheduler.java (1)
Slf4j(38-161)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchService.java (1)
Slf4j(31-130)backendProject/src/main/java/likelion/mlb/backendProject/global/scheduler/service/DataUpdaterService.java (2)
setRoundTime(197-218)Service(32-220)backendProject/src/main/java/likelion/mlb/backendProject/domain/draft/entity/Draft.java (1)
Entity(12-38)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/MatchHandler.java (4)
backendProject/src/main/java/likelion/mlb/backendProject/global/security/dto/CustomUserDetails.java (1)
CustomUserDetails(10-41)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchBroadcaster.java (1)
Slf4j(37-151)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/SessionRegistry.java (1)
Slf4j(35-149)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchService.java (1)
Slf4j(31-130)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchBroadcaster.java (3)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchEventsListener.java (1)
Slf4j(53-147)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/SessionRegistry.java (1)
Slf4j(35-149)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/MatchHandler.java (1)
Slf4j(34-126)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/StatusCache.java (1)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/handler/MatchHandler.java (1)
Scheduled(136-214)
backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/MatchWebSocketConfig.java (3)
backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/AuthHandshakeHandler.java (1)
RequiredArgsConstructor(16-48)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/handler/MatchHandler.java (4)
Slf4j(24-230)Override(120-133)Override(38-82)Override(95-118)backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/WebSocketDraftConfig.java (2)
Override(45-162)Override(37-42)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/StatusMessage.java (2)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/DraftStartMessage.java (1)
Schema(18-60)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/UserIdMessage.java (1)
Schema(12-32)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchTimeoutProcessor.java (5)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchKeys.java (1)
MatchKeys(3-6)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/DraftTimingService.java (1)
Slf4j(30-105)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchEventsListener.java (1)
Slf4j(53-147)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/RedisLock.java (1)
Slf4j(19-70)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/scheduler/MatchScheduler.java (1)
Slf4j(38-161)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/RoundInfo.java (1)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/MatchStatusResponse.java (1)
Schema(9-23)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchService.java (4)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchKeys.java (1)
MatchKeys(3-6)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/DraftTimingService.java (1)
Slf4j(30-105)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchEventsListener.java (1)
Slf4j(53-147)backendProject/src/main/java/likelion/mlb/backendProject/domain/match/scheduler/MatchBootstrapper.java (1)
Slf4j(26-48)
🔇 Additional comments (11)
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/repository/ParticipantRepository.java (1)
21-30: AssignDto 반환을 Optional로 변경 권장 — 호출부/유일성 확인 필요레포 내에서
findAssignment호출부를 검색(사용한 스크립트: rg)했으나 정의 파일 외에 호출부를 찾지 못했습니다. 호출부가 존재하지 않거나 다른 모듈에서 사용 중일 수 있으니 변경 영향 범위를 직접 확인해 주세요.요약 및 제안:
- 문제: 현재 반환이 단일 DTO(AssignDto)라 결과 없음 -> null, 2건 이상 -> IncorrectResultSizeDataAccessException 발생 가능.
- 권장 변경: 호출부에서 null/예외 처리를 강제하는 대신 Optional 반환으로 안전하게 처리.
- DB 유일성: userId + roundId 조합이 논리적으로 유일해야 하므로 DB 측 Unique 제약(인덱스/constraint)으로 보장되어 있는지 확인. 없다면 마이그레이션으로 추가 권장.
- 호출부 영향: 리포지토리 변경 시 다른 모듈/서비스(또는 테스트)에서 컴파일/런타임 영향을 받을 수 있으므로 전체 검색(특히 멀티모듈, 외부 의존) 후 변경하세요.
수정 예시:
- AssignDto findAssignment(@Param("userId") UUID userId, - @Param("roundId") UUID roundId); + Optional<AssignDto> findAssignment(@Param("userId") UUID userId, + @Param("roundId") UUID roundId);점검이 필요한 위치:
- backendProject/src/main/java/likelion/mlb/backendProject/domain/match/repository/ParticipantRepository.java (해당 메서드)
- Participant 관련 엔티티 및 DB 스키마(유일성 제약 추가 여부)
- 전체 레포/다른 모듈에서의
findAssignment호출 여부(외부 모듈 포함)검증 상태: 호출부 검색 결과가 정의 파일 외에 없음 — 변경 영향 범위 확인이 필요합니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/scheduler/MatchBootstrapper.java (1)
34-47: 기동 시 부트스트랩 로직 적절 — 실패 허용과 재예약 책임 분리가 명확합니다다음 라운드가 없을 때 예외를 삼키고 정보 로그만 남기는 처리, 실제 예약/보정 책임을 MatchScheduler로 위임한 구조가 명확하고 안전합니다. 운영 시 초기 부팅 안정성에 도움이 됩니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/RoundInfo.java (1)
9-22: Swagger 메타데이터 보강 LGTM필드별 @Schema 추가와 클래스 어노테이션 구성 일관성이 좋습니다. 기존 동작에 영향 없이 문서 품질을 높였습니다.
backendProject/src/main/java/likelion/mlb/backendProject/global/configuration/MatchWebSocketConfig.java (1)
3-3: 새 위치로의 MatchHandler import 교체 적절WS 핸들러의 패키지 이동에 맞춘 import 정정이 정확합니다. 구성 동작에는 변화가 없습니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/MatchHandler.java (1)
43-68: 연결 처리 흐름 및 초기 브로드캐스트 설계 LGTM인증 검증 → 최신 세션 유지 → 초기 USER_ID/STATUS 전송의 흐름이 명확합니다. 전송 실패를 예외로 삼지 않고 워닝 로그 후 진행하는 결정도 운영 친화적입니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/DraftTimingService.java (1)
55-59: 검증 완료 — BaseException.ROUND_NOT_FOUND가 정의되어 있어 컴파일 오류 아님리포지토리 검사 결과 해당 상수가 정의되어 있고 현재 코드에서 사용 중임이 확인되었습니다. 원래 제안(throw BaseException.ROUND_NOT_FOUND → throw new BaseException(ErrorCode.ROUND_NOT_FOUND))은 필수 수정이 아닙니다. 다만 예외 생성 방식의 혼재는 발견되어 통일을 권장합니다.
문서화된 위치:
- backendProject/src/main/java/likelion/mlb/backendProject/global/exception/BaseException.java:21
- public static final BaseException ROUND_NOT_FOUND = new BaseException(ErrorCode.ROUND_NOT_FOUND);
- backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/DraftTimingService.java:56
- throw BaseException.ROUND_NOT_FOUND;
- backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchTimeoutProcessor.java:78
- .orElseThrow(() -> new BaseException(ErrorCode.ROUND_NOT_FOUND));
- backendProject/src/main/java/likelion/mlb/backendProject/global/exception/ErrorCode.java:28
- ROUND_NOT_FOUND enum 정의
조치 권고: 컴파일 오류 우려는 해소되므로 제안된 diff는 적용 불필요 — 다만 팀 컨벤션에 따라 "상수 사용" 또는 "new 생성" 중 하나로 통일하시길 권장합니다.
Likely an incorrect or invalid review comment.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchTimeoutProcessor.java (1)
69-74: 설계 확인: 더미로 패딩된 그룹도 Draft/Participant 생성마지막 그룹이 더미로 패딩되어도 무조건 Draft를 생성합니다. 의도된 정책이면 OK입니다. 다만 “실유저가 한 명도 없는 그룹”은 현재 경로상 존재하지 않지만(빈 큐는 사전 리턴), 향후 정책 변경 시 방어 로직을 고려하세요.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchService.java (1)
120-129: formatRemaining: 경계값 처리 OK미래가 아닌 경우 "00:00:00"을 반환하는 방어 로직이 명확하고 간결합니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/SessionRegistry.java (1)
118-135: pruneClosed(): 설계 및 로깅 적절닫힌 세션을 일괄 정리하고, per-user 매핑까지 함께 제거하는 로직이 명확합니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/scheduler/MatchScheduler.java (1)
72-118: 스케줄링/즉시 발사 흐름 명료하고 동시성 보호 적절
- open/lock 파싱과 KST → Instant 변환이 일관적입니다.
- 동일 라운드·동일 시각 스킵, 과거 시각 즉시 발사, 취소-재등록 순서가 명확합니다.
- synchronized와 volatile로 상태 경합을 적절히 제어합니다.
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchBroadcaster.java (1)
64-72: STATUS 브로드캐스트 시 단일 직렬화 전략 적절JSON을 한 번 직렬화해 재사용하는 전략이 효율적입니다. 예외 처리와 세션 정리도 적절합니다.
| /** | ||
| * 현재 매칭 상태 요약 조회. | ||
| * - state: BEFORE_OPEN | OPEN | LOCKED | ||
| * - remainingTime: "mm:ss" (LOCKED 시 "00:00") | ||
| * - count: 현재 대기(세션) 사용자 수 | ||
| * - round: 라운드 식별자/번호/오픈·락 시각(KST 문자열, ISO_LOCAL_DATE_TIME) | ||
| */ | ||
| @Operation( | ||
| summary = "현재 매칭 상태 조회", | ||
| description = "대기 인원수(count), 상태(state), 남은 시간(remainingTime, mm:ss), 라운드 정보(round)를 반환합니다." |
There was a problem hiding this comment.
API 문서의 시간 포맷 설명이 구현과 불일치(HH:MM:SS vs mm:ss)
MatchService.formatRemaining은 "HH:MM:SS" 형식을 반환합니다. 그러나 본 주석/Operation 설명은 "mm:ss"로 표기되어 있어 클라이언트 혼선을 야기합니다. 문서를 구현에 맞춰 "HH:MM:SS"로 정정해 주세요.
다음 패치를 적용해 주세요:
- * - remainingTime: "mm:ss" (LOCKED 시 "00:00")
+ * - remainingTime: "HH:MM:SS" (LOCKED 시 "00:00:00")
@@
- description = "대기 인원수(count), 상태(state), 남은 시간(remainingTime, mm:ss), 라운드 정보(round)를 반환합니다."
+ description = "대기 인원수(count), 상태(state), 남은 시간(remainingTime, HH:MM:SS), 라운드 정보(round)를 반환합니다."📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * 현재 매칭 상태 요약 조회. | |
| * - state: BEFORE_OPEN | OPEN | LOCKED | |
| * - remainingTime: "mm:ss" (LOCKED 시 "00:00") | |
| * - count: 현재 대기(세션) 사용자 수 | |
| * - round: 라운드 식별자/번호/오픈·락 시각(KST 문자열, ISO_LOCAL_DATE_TIME) | |
| */ | |
| @Operation( | |
| summary = "현재 매칭 상태 조회", | |
| description = "대기 인원수(count), 상태(state), 남은 시간(remainingTime, mm:ss), 라운드 정보(round)를 반환합니다." | |
| /** | |
| * 현재 매칭 상태 요약 조회. | |
| * - state: BEFORE_OPEN | OPEN | LOCKED | |
| * - remainingTime: "HH:MM:SS" (LOCKED 시 "00:00:00") | |
| * - count: 현재 대기(세션) 사용자 수 | |
| * - round: 라운드 식별자/번호/오픈·락 시각(KST 문자열, ISO_LOCAL_DATE_TIME) | |
| */ | |
| @Operation( | |
| summary = "현재 매칭 상태 조회", | |
| description = "대기 인원수(count), 상태(state), 남은 시간(remainingTime, HH:MM:SS), 라운드 정보(round)를 반환합니다." |
🤖 Prompt for AI Agents
In
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/controller/MatchRestController.java
around lines 22 to 31, the Javadoc/Operation description documents remainingTime
as "mm:ss" but the implementation MatchService.formatRemaining returns
"HH:MM:SS"; update the Operation/Javadoc text to state "HH:MM:SS" (or explicitly
"HH:mm:ss" for Java DateTime patterns) so the API docs match the implementation
and avoid client confusion.
| @ExampleObject(name = "BEFORE_OPEN", value = """ | ||
| { | ||
| "count": 27, | ||
| "state": "BEFORE_OPEN", | ||
| "remainingTime": "12:34", | ||
| "round": { | ||
| "id": "11111111-1111-1111-1111-111111111111", | ||
| "no": 3, | ||
| "openAt": "2025-08-18T20:50:00", | ||
| "lockAt": "2025-08-20T20:50:00" | ||
| } | ||
| } | ||
| """), | ||
| @ExampleObject(name = "OPEN", value = """ | ||
| { | ||
| "count": 31, | ||
| "state": "OPEN", | ||
| "remainingTime": "05:10", | ||
| "round": { | ||
| "id": "11111111-1111-1111-1111-111111111111", | ||
| "no": 3, | ||
| "openAt": "2025-08-18T20:50:00", | ||
| "lockAt": "2025-08-20T20:50:00" | ||
| } | ||
| } | ||
| """), | ||
| @ExampleObject(name = "LOCKED", value = """ | ||
| { | ||
| "count": 0, | ||
| "state": "LOCKED", | ||
| "remainingTime": "00:00", | ||
| "round": { | ||
| "id": "11111111-1111-1111-1111-111111111111", | ||
| "no": 3, | ||
| "openAt": "2025-08-18T20:50:00", | ||
| "lockAt": "2025-08-20T20:50:00" | ||
| } | ||
| } | ||
| """) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
예시 payload의 remainingTime 값 형식 수정 필요
예시 값이 "12:34", "05:10", "00:00"으로 표기되어 있으나 실제 응답은 "HH:MM:SS"입니다. 예시를 "00:12:34", "00:05:10", "00:00:00"으로 수정해 주세요.
적용 패치:
- "remainingTime": "12:34",
+ "remainingTime": "00:12:34",
@@
- "remainingTime": "05:10",
+ "remainingTime": "00:05:10",
@@
- "remainingTime": "00:00",
+ "remainingTime": "00:00:00",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @ExampleObject(name = "BEFORE_OPEN", value = """ | |
| { | |
| "count": 27, | |
| "state": "BEFORE_OPEN", | |
| "remainingTime": "12:34", | |
| "round": { | |
| "id": "11111111-1111-1111-1111-111111111111", | |
| "no": 3, | |
| "openAt": "2025-08-18T20:50:00", | |
| "lockAt": "2025-08-20T20:50:00" | |
| } | |
| } | |
| """), | |
| @ExampleObject(name = "OPEN", value = """ | |
| { | |
| "count": 31, | |
| "state": "OPEN", | |
| "remainingTime": "05:10", | |
| "round": { | |
| "id": "11111111-1111-1111-1111-111111111111", | |
| "no": 3, | |
| "openAt": "2025-08-18T20:50:00", | |
| "lockAt": "2025-08-20T20:50:00" | |
| } | |
| } | |
| """), | |
| @ExampleObject(name = "LOCKED", value = """ | |
| { | |
| "count": 0, | |
| "state": "LOCKED", | |
| "remainingTime": "00:00", | |
| "round": { | |
| "id": "11111111-1111-1111-1111-111111111111", | |
| "no": 3, | |
| "openAt": "2025-08-18T20:50:00", | |
| "lockAt": "2025-08-20T20:50:00" | |
| } | |
| } | |
| """) | |
| @ExampleObject(name = "BEFORE_OPEN", value = """ | |
| { | |
| "count": 27, | |
| "state": "BEFORE_OPEN", | |
| "remainingTime": "00:12:34", | |
| "round": { | |
| "id": "11111111-1111-1111-1111-111111111111", | |
| "no": 3, | |
| "openAt": "2025-08-18T20:50:00", | |
| "lockAt": "2025-08-20T20:50:00" | |
| } | |
| } | |
| """), | |
| @ExampleObject(name = "OPEN", value = """ | |
| { | |
| "count": 31, | |
| "state": "OPEN", | |
| "remainingTime": "00:05:10", | |
| "round": { | |
| "id": "11111111-1111-1111-1111-111111111111", | |
| "no": 3, | |
| "openAt": "2025-08-18T20:50:00", | |
| "lockAt": "2025-08-20T20:50:00" | |
| } | |
| } | |
| """), | |
| @ExampleObject(name = "LOCKED", value = """ | |
| { | |
| "count": 0, | |
| "state": "LOCKED", | |
| "remainingTime": "00:00:00", | |
| "round": { | |
| "id": "11111111-1111-1111-1111-111111111111", | |
| "no": 3, | |
| "openAt": "2025-08-18T20:50:00", | |
| "lockAt": "2025-08-20T20:50:00" | |
| } | |
| } | |
| """) |
🤖 Prompt for AI Agents
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/controller/MatchRestController.java
around lines 39 to 77: the ExampleObject payloads use remainingTime values in
"MM:SS" format ("12:34", "05:10", "00:00") but the actual API returns
"HH:MM:SS"; update the three examples to use full "HH:MM:SS" format (e.g.,
"00:12:34", "00:05:10", "00:00:00") so examples match the real response format.
| @Schema(description = "다음 상태까지 남은 시간 (mm:ss). LOCKED 시 00:00", example = "05:10") | ||
| private String remainingTime; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
remainingTime 문서/예시가 구현과 불일치(HH:mm:ss vs mm:ss, 00:00:00 vs 00:00)
MatchService.formatRemaining은 "HH:mm:ss" 형식이며 LOCKED일 때 "00:00:00"을 반환합니다. 문서/예시를 아래처럼 맞춰 주세요.
- @Schema(description = "다음 상태까지 남은 시간 (mm:ss). LOCKED 시 00:00", example = "05:10")
+ @Schema(description = "다음 상태까지 남은 시간 (HH:mm:ss). LOCKED 시 00:00:00", example = "00:42:10")혹시 REST 응답을 소비하는 프론트엔드에서 "mm:ss"를 기대하고 있지 않은지 확인이 필요합니다. 사용처를 점검해 주세요:
🏁 Script executed:
#!/bin/bash
rg -n "remainingTime" -A 3Length of output: 6848
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "==== Searching for formatRemaining occurrences ===="
rg -n "formatRemaining" -S || true
echo
echo "==== Searching for MatchService class files ===="
rg -n "class\s+MatchService" -S || true
rg -n "MatchService" -S || true
echo
echo "==== Searching for MatchStatusResponse usages ===="
rg -n "MatchStatusResponse" -S || true
echo
echo "==== Searching for constructors / builders / assignments related to remainingTime ===="
rg -n "new\s+MatchStatusResponse" -S || true
rg -n "MatchStatusResponse\(" -S || true
rg -n "remainingTime\s*=" -S || true
rg -n "setRemainingTime" -S || trueLength of output: 6312
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- MatchService.java ----"
nl -ba backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchService.java | sed -n '1,220p'
echo
echo "---- MatchStatusResponse.java ----"
nl -ba backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/MatchStatusResponse.java | sed -n '1,200p'
echo
echo "---- MatchRestController.java ----"
nl -ba backendProject/src/main/java/likelion/mlb/backendProject/domain/match/controller/MatchRestController.java | sed -n '1,220p'
echo
echo "---- StatusMessage.java ----"
nl -ba backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/StatusMessage.java | sed -n '1,220p'
echo
echo "---- MatchBroadcaster.java ----"
nl -ba backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchBroadcaster.java | sed -n '1,220p'Length of output: 298
🏁 Script executed:
#!/bin/bash
set -euo pipefail
files=(
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchService.java
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/MatchStatusResponse.java
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/controller/MatchRestController.java
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/StatusMessage.java
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchBroadcaster.java
)
for f in "${files[@]}"; do
echo "---- $f ----"
if [ -f "$f" ]; then
sed -n '1,240p' "$f"
else
echo "File not found: $f"
fi
echo
doneLength of output: 18578
remainingTime 문서/예시가 구현(HH:mm:ss)과 불일치 — REST DTO/컨트롤러 문서 수정 필요
MatchService.formatRemaining은 "HH:mm:ss" 포맷을 반환하고 LOCKED 시 "00:00:00"을 사용합니다. 현재 REST 쪽 DTO와 컨트롤러 문서/예시에는 "mm:ss"/"00:00"으로 되어 있어 불일치합니다. 다음 파일들을 수정해 주세요.
- backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/MatchStatusResponse.java
- backendProject/src/main/java/likelion/mlb/backendProject/domain/match/controller/MatchRestController.java
참고: backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/StatusMessage.java 및 MatchBroadcaster.java는 이미 HH:MM:SS로 문서화되어 있어 변경 불필요합니다.
권장 변경(diff 예시):
- MatchStatusResponse.java
- @Schema(description = "다음 상태까지 남은 시간 (mm:ss). LOCKED 시 00:00", example = "05:10")
+ @Schema(description = "다음 상태까지 남은 시간 (HH:mm:ss). LOCKED 시 00:00:00", example = "00:05:10")- MatchRestController.java (주요 변경: 설명과 Examples의 remainingTime 값)
- * - remainingTime: "mm:ss" (LOCKED 시 "00:00")
+ * - remainingTime: "HH:mm:ss" (LOCKED 시 "00:00:00")
...
- description = "대기 인원수(count), 상태(state), 남은 시간(remainingTime, mm:ss), 라운드 정보(round)를 반환합니다."
+ description = "대기 인원수(count), 상태(state), 남은 시간(remainingTime, HH:mm:ss), 라운드 정보(round)를 반환합니다."
...
- "remainingTime": "12:34",
+ "remainingTime": "00:12:34",
...
- "remainingTime": "05:10",
+ "remainingTime": "00:05:10",
...
- "remainingTime": "00:00",
+ "remainingTime": "00:00:00",추가 권고: 프론트엔드(또는 외부 소비자)가 이미 mm:ss를 기대하고 있는지 확인하세요. 리포지토리 검색 결과(WS는 HH:MM:SS, REST 문서는 mm:ss)로 보아 REST 문서만 불일치하므로 문서/예시를 구현에 맞춰 변경하는 것이 안전합니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Schema(description = "다음 상태까지 남은 시간 (mm:ss). LOCKED 시 00:00", example = "05:10") | |
| private String remainingTime; | |
| @Schema(description = "다음 상태까지 남은 시간 (HH:mm:ss). LOCKED 시 00:00:00", example = "00:05:10") | |
| private String remainingTime; |
🤖 Prompt for AI Agents
In
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/dto/MatchStatusResponse.java
around lines 18-19 and
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/controller/MatchRestController.java
update the remainingTime documentation and examples to match the implementation
(MatchService.formatRemaining) which returns "HH:mm:ss" and uses "00:00:00" for
LOCKED; change the @Schema description/example in MatchStatusResponse from
"mm:ss"/"00:00" to "HH:mm:ss"/"00:00:00" and update any controller-level
OpenAPI/Swagger descriptions or example responses in MatchRestController
accordingly so REST docs/examples align with the actual HH:mm:ss format.
| public record MatchStateChangedEvent( | ||
| String prev, // 이전 상태 | ||
| String next, // 다음 상태 | ||
| UUID roundId // 대상 라운드 ID | ||
| ) { } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
문자열 상태 값에 대한 유효성 보장 추가(압축 생성자에서 null/허용값 검증 및 대문자 정규화 제안)
이 이벤트는 상태 전이의 핵심 트리거라 prev/next의 무결성이 중요합니다. 현재는 자유 문자열이므로 오타/대소문자 혼용/허용값 외 입력으로 인한 런타임 버그 가능성이 있습니다. record의 압축 생성자에서 null 체크, 허용값 검증, 대문자 정규화를 추가하는 것을 권장합니다.
다음 패치를 적용해 주세요:
-public record MatchStateChangedEvent(
- String prev, // 이전 상태
- String next, // 다음 상태
- UUID roundId // 대상 라운드 ID
-) { }
+public record MatchStateChangedEvent(
+ String prev, // 이전 상태
+ String next, // 다음 상태
+ UUID roundId // 대상 라운드 ID
+) {
+ // 허용 상태: UI 전용 "LOCKED_HOLD"는 제외 (도메인 상태만)
+ private static final java.util.Set<String> ALLOWED =
+ java.util.Set.of("BEFORE_OPEN", "OPEN", "LOCKED");
+
+ public MatchStateChangedEvent {
+ java.util.Objects.requireNonNull(prev, "prev");
+ java.util.Objects.requireNonNull(next, "next");
+ java.util.Objects.requireNonNull(roundId, "roundId");
+ // 대소문자 정규화
+ prev = prev.toUpperCase();
+ next = next.toUpperCase();
+ if (!ALLOWED.contains(prev) || !ALLOWED.contains(next)) {
+ throw new IllegalArgumentException(
+ "prev/next must be one of " + ALLOWED + " (prev=" + prev + ", next=" + next + ")"
+ );
+ }
+ }
+}참고: 외부 import 추가 없이 FQCN을 사용해 패치했습니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public record MatchStateChangedEvent( | |
| String prev, // 이전 상태 | |
| String next, // 다음 상태 | |
| UUID roundId // 대상 라운드 ID | |
| ) { } | |
| public record MatchStateChangedEvent( | |
| String prev, // 이전 상태 | |
| String next, // 다음 상태 | |
| UUID roundId // 대상 라운드 ID | |
| ) { | |
| // 허용 상태: UI 전용 "LOCKED_HOLD"는 제외 (도메인 상태만) | |
| private static final java.util.Set<String> ALLOWED = | |
| java.util.Set.of("BEFORE_OPEN", "OPEN", "LOCKED"); | |
| public MatchStateChangedEvent { | |
| java.util.Objects.requireNonNull(prev, "prev"); | |
| java.util.Objects.requireNonNull(next, "next"); | |
| java.util.Objects.requireNonNull(roundId, "roundId"); | |
| // 대소문자 정규화 | |
| prev = prev.toUpperCase(); | |
| next = next.toUpperCase(); | |
| if (!ALLOWED.contains(prev) || !ALLOWED.contains(next)) { | |
| throw new IllegalArgumentException( | |
| "prev/next must be one of " + ALLOWED + " (prev=" + prev + ", next=" + next + ")" | |
| ); | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/event/MatchStateChangedEvent.java
lines 18-22: the record currently accepts free-form prev/next strings; add a
compact record constructor that validates prev and next are non-null, normalizes
them to uppercase, and enforces they are one of the allowed values (throw new
java.lang.IllegalArgumentException on invalid input); keep roundId as-is and
perform null check if desired; implement checks using FQCNs (e.g.,
java.util.Set.of(...)) so no new imports are required.
| private void broadcastRaw(String json) { | ||
| for (var s : sessionRegistry.all()) { | ||
| if (s == null) continue; | ||
| if (!s.isOpen()) { | ||
| sessionRegistry.remove(s); | ||
| continue; | ||
| } | ||
| try { | ||
| s.sendMessage(new TextMessage(json)); | ||
| } catch (IOException e) { | ||
| log.warn("STATUS 브로드캐스트 실패: {}", e.getMessage()); | ||
| sessionRegistry.remove(s); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
브로드캐스트 시 열린 세션 스냅샷 사용 권장
sessionRegistry.all() 순회 중 제거 대신, 레지스트리의 allOpenSnapshot()을 사용해 폐세션 정리를 위임하면 단순하고 안전합니다.
- private void broadcastRaw(String json) {
- for (var s : sessionRegistry.all()) {
- if (s == null) continue;
- if (!s.isOpen()) {
- sessionRegistry.remove(s);
- continue;
- }
- try {
- s.sendMessage(new TextMessage(json));
- } catch (IOException e) {
- log.warn("STATUS 브로드캐스트 실패: {}", e.getMessage());
- sessionRegistry.remove(s);
- }
- }
- }
+ private void broadcastRaw(String json) {
+ for (var s : sessionRegistry.allOpenSnapshot()) {
+ if (s == null) continue;
+ try {
+ s.sendMessage(new TextMessage(json));
+ } catch (IOException e) {
+ log.warn("STATUS 브로드캐스트 실패: {}", e.getMessage());
+ sessionRegistry.remove(s);
+ }
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private void broadcastRaw(String json) { | |
| for (var s : sessionRegistry.all()) { | |
| if (s == null) continue; | |
| if (!s.isOpen()) { | |
| sessionRegistry.remove(s); | |
| continue; | |
| } | |
| try { | |
| s.sendMessage(new TextMessage(json)); | |
| } catch (IOException e) { | |
| log.warn("STATUS 브로드캐스트 실패: {}", e.getMessage()); | |
| sessionRegistry.remove(s); | |
| } | |
| } | |
| } | |
| private void broadcastRaw(String json) { | |
| for (var s : sessionRegistry.allOpenSnapshot()) { | |
| if (s == null) continue; | |
| try { | |
| s.sendMessage(new TextMessage(json)); | |
| } catch (IOException e) { | |
| log.warn("STATUS 브로드캐스트 실패: {}", e.getMessage()); | |
| sessionRegistry.remove(s); | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchBroadcaster.java
around lines 117 to 131, the code iterates sessionRegistry.all() and removes
sessions during iteration; change to iterate sessionRegistry.allOpenSnapshot()
so closed-session cleanup is delegated to the registry, remove the null and
isOpen() checks (they become unnecessary), keep the try/catch around
s.sendMessage(new TextMessage(json)) and still call sessionRegistry.remove(s)
only when an IOException occurs, and ensure no modifications happen to the
snapshot during iteration.
| public Map<String, AssignDto> processTimeoutAndInsert(UUID roundId) { | ||
| // 1) 현재 대기 인원 스냅샷 |
There was a problem hiding this comment.
roundId null 방어 로직 추가 필요
LOCKED 이벤트에서 roundId가 항상 온다는 전제라도, 서비스 경계에서는 방어하는 것이 안전합니다. NPE 방지 및 조기 실패를 위해 즉시 검증하세요.
다음과 같이 추가를 제안합니다:
@Transactional
public Map<String, AssignDto> processTimeoutAndInsert(UUID roundId) {
+ if (roundId == null) {
+ throw new IllegalArgumentException("roundId must not be null");
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public Map<String, AssignDto> processTimeoutAndInsert(UUID roundId) { | |
| // 1) 현재 대기 인원 스냅샷 | |
| public Map<String, AssignDto> processTimeoutAndInsert(UUID roundId) { | |
| if (roundId == null) { | |
| throw new IllegalArgumentException("roundId must not be null"); | |
| } | |
| // 1) 현재 대기 인원 스냅샷 |
🤖 Prompt for AI Agents
In
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchTimeoutProcessor.java
around lines 54-55, add a defensive null-check for the incoming roundId at the
start of processTimeoutAndInsert to avoid NPEs and fail fast; validate roundId
(e.g., Objects.requireNonNull or an explicit if-check) and throw an appropriate
unchecked exception with a clear message (IllegalArgumentException or
NullPointerException) or return an empty result as per service contract before
proceeding with processing.
| // 필요 시 채팅방 생성(멀티 트랜잭션 고려 시 내부에서 별도 보호) | ||
| chatRoomService.createRoom(draft.getId()); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
트랜잭션 롤백 시 채팅방 생성 불일치 가능성(사이드 이펙트 분리 권장)
Draft/Participant 저장 트랜잭션 내에서 채팅방을 즉시 생성하면, 이후 롤백 시 채팅방만 남는 불일치가 발생할 수 있습니다. afterCommit 훅으로 전환하거나 ChatRoomService를 멱등 처리하세요.
아래처럼 커밋 이후에 생성하도록 변경을 권장합니다:
- // 필요 시 채팅방 생성(멀티 트랜잭션 고려 시 내부에서 별도 보호)
- chatRoomService.createRoom(draft.getId());
+ // 커밋 후 생성하여 불일치 방지
+ TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+ @Override public void afterCommit() {
+ try {
+ chatRoomService.createRoom(draft.getId());
+ } catch (Exception e) {
+ log.warn("chat room creation failed post-commit. draftId={}, cause={}", draft.getId(), e.toString());
+ }
+ }
+ });추가 필요 import:
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;🤖 Prompt for AI Agents
In
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchTimeoutProcessor.java
around lines 93 to 95, creating the chat room inside the Draft/Participant DB
transaction can leave an orphaned chat room if the transaction rolls back;
change to create the room after commit by registering a
TransactionSynchronization.afterCommit callback (use
TransactionSynchronizationManager.registerSynchronization) or alternatively make
ChatRoomService.createRoom idempotent and safe to call within the transaction;
also add the recommended imports
(org.springframework.transaction.support.TransactionSynchronization and
org.springframework.transaction.support.TransactionSynchronizationManager) when
implementing the afterCommit registration.
| if (uid.startsWith(DUMMY_PREFIX)) { | ||
| p.setDummy(true); | ||
| p.setUser(null); | ||
| } else { | ||
| p.setDummy(false); | ||
| User userRef = new User(); // 프록시 참조만 세팅 | ||
| userRef.setId(UUID.fromString(uid)); | ||
| // ✅ 영속성 컨텍스트의 reference 사용(Transient 오류 방지) | ||
| UUID userUuid = UUID.fromString(uid); | ||
| User userRef = em.getReference(User.class, userUuid); | ||
| p.setUser(userRef); | ||
|
|
||
| // 결과 매핑 | ||
| result.put(uid, new AssignDto(draft.getId(), (short) (p.getUserNumber()))); | ||
| result.put(uid, new AssignDto(draft.getId(), (short) p.getUserNumber())); | ||
| usedRealUserIds.add(uid); | ||
| } |
There was a problem hiding this comment.
Redis 큐의 비정상 userId 처리(IllegalArgumentException) 누락
Redis Set에 UUID 형식이 아닌 값이 섞일 경우 UUID.fromString(uid)에서 런타임 예외가 발생해 전체 배치가 실패합니다. 비정상 id는 더미로 취급하여 안전하게 스킵하세요.
다음과 같이 예외를 흡수하고 더미로 처리하는 수정안을 제안합니다:
- if (uid.startsWith(DUMMY_PREFIX)) {
- p.setDummy(true);
- p.setUser(null);
- } else {
- p.setDummy(false);
- // ✅ 영속성 컨텍스트의 reference 사용(Transient 오류 방지)
- UUID userUuid = UUID.fromString(uid);
- User userRef = em.getReference(User.class, userUuid);
- p.setUser(userRef);
-
- // 결과 매핑
- result.put(uid, new AssignDto(draft.getId(), (short) p.getUserNumber()));
- usedRealUserIds.add(uid);
- }
+ if (uid.startsWith(DUMMY_PREFIX)) {
+ p.setDummy(true);
+ p.setUser(null);
+ } else {
+ try {
+ UUID userUuid = UUID.fromString(uid);
+ User userRef = em.getReference(User.class, userUuid);
+ p.setDummy(false);
+ p.setUser(userRef);
+ // 결과 매핑
+ result.put(uid, new AssignDto(draft.getId(), (short) p.getUserNumber()));
+ usedRealUserIds.add(uid);
+ } catch (IllegalArgumentException ex) {
+ // 잘못된 값은 더미로 취급하여 안전하게 진행
+ p.setDummy(true);
+ p.setUser(null);
+ log.warn("Invalid userId in Redis queue. treated as dummy. value={}", uid);
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (uid.startsWith(DUMMY_PREFIX)) { | |
| p.setDummy(true); | |
| p.setUser(null); | |
| } else { | |
| p.setDummy(false); | |
| User userRef = new User(); // 프록시 참조만 세팅 | |
| userRef.setId(UUID.fromString(uid)); | |
| // ✅ 영속성 컨텍스트의 reference 사용(Transient 오류 방지) | |
| UUID userUuid = UUID.fromString(uid); | |
| User userRef = em.getReference(User.class, userUuid); | |
| p.setUser(userRef); | |
| // 결과 매핑 | |
| result.put(uid, new AssignDto(draft.getId(), (short) (p.getUserNumber()))); | |
| result.put(uid, new AssignDto(draft.getId(), (short) p.getUserNumber())); | |
| usedRealUserIds.add(uid); | |
| } | |
| if (uid.startsWith(DUMMY_PREFIX)) { | |
| p.setDummy(true); | |
| p.setUser(null); | |
| } else { | |
| try { | |
| UUID userUuid = UUID.fromString(uid); | |
| User userRef = em.getReference(User.class, userUuid); | |
| p.setDummy(false); | |
| p.setUser(userRef); | |
| // 결과 매핑 | |
| result.put(uid, new AssignDto(draft.getId(), (short) p.getUserNumber())); | |
| usedRealUserIds.add(uid); | |
| } catch (IllegalArgumentException ex) { | |
| // 잘못된 값은 더미로 취급하여 안전하게 진행 | |
| p.setDummy(true); | |
| p.setUser(null); | |
| log.warn("Invalid userId in Redis queue. treated as dummy. value={}", uid); | |
| } | |
| } |
🤖 Prompt for AI Agents
In
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/service/MatchTimeoutProcessor.java
around lines 105 to 118, wrap the UUID.fromString(uid) and
em.getReference(User.class, userUuid) call in a try/catch for
IllegalArgumentException (and optionally NullPointerException) so
malformed/non-UUID values from Redis are treated as dummies: in the catch set
p.setDummy(true), p.setUser(null), skip creating a User reference, and do NOT
add the uid to usedRealUserIds or the result mapping; only perform
em.getReference and result.put when parsing succeeds.
| @AllArgsConstructor | ||
| @JsonInclude(JsonInclude.Include.NON_NULL) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
STATUS 메시지의 고정 type 필드와 @AllArgsConstructor 충돌로 생성자 시그니처 불일치 (컴파일/런타임 오류 가능)
type이 final 상수지만 @AllArgsConstructor는 모든 필드를 파라미터로 포함합니다. 그 결과 생성자 첫 번째 파라미터가 type이 되며, 현재 호출부(MatchBroadcaster.sendStatusTo/ToAll/ToUsers)는 5개 인자(count, remainingTime, state, round, serverTime)만 넘기고 있어 시그니처가 맞지 않습니다. 의도대로라면 type은 생성자 인자에서 제외되어야 하므로 @RequiredArgsConstructor로 교체해야 합니다. 동일 패턴의 UserIdMessage도 영향이 있습니다.
수정 제안:
-import lombok.AllArgsConstructor;
+import lombok.RequiredArgsConstructor;
import lombok.Getter;
@@
-@AllArgsConstructor
+@RequiredArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class StatusMessage {참고: 관련 호출부
- backendProject/.../infra/MatchBroadcaster.java: new StatusMessage(s.getCount(), s.getRemainingTime(), s.getState(), s.getRound(), ...)
- backendProject/.../ws/message/UserIdMessage.java도 동일 패턴(@AllArgsConstructor + final type 상수)으로, 사용처(new UserIdMessage(userId))를 고려하면
@RequiredArgsConstructor로 교체가 필요합니다.
다음 스크립트로 현재 생성자 사용과 주석의 불일치를 빠르게 확인할 수 있습니다.
Also applies to: 6-8, 37-58
🏁 Script executed:
#!/bin/bash
# StatusMessage 생성자 호출 인자 개수와 선언부 확인
rg -n "new\s+StatusMessage\s*\(" -A 1
rg -n "@AllArgsConstructor|@RequiredArgsConstructor" backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/StatusMessage.java
# UserIdMessage도 동일 패턴 확인
rg -n "@AllArgsConstructor|@RequiredArgsConstructor" backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/UserIdMessage.java
rg -n "new\s+UserIdMessage\s*\(" -A 1Length of output: 1578
수정 필요 — @AllArgsConstructor가 final 상수 type을 생성자 인자로 포함해 호출부와 시그니처 불일치
요약: 실행한 grep 결과 StatusMessage와 UserIdMessage가 @AllArgsConstructor를 사용 중이고 MatchBroadcaster에서 각각 파라미터 수가 다른 상태로 호출되고 있어 컴파일 오류가 발생합니다(검증 완료). 의도대로라면 final로 고정된 type은 생성자 인자에서 제외되어야 하므로 @requiredargsconstructor로 교체해야 합니다.
영향 파일(확인 위치)
- backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/StatusMessage.java — 33: @AllArgsConstructor
- backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/UserIdMessage.java — 22: @AllArgsConstructor
- backendProject/src/main/java/likelion/mlb/backendProject/domain/match/infra/MatchBroadcaster.java — new UserIdMessage(...) at line ~49, new StatusMessage(...) at lines ~54, ~66, ~76
제안된 수정(diff)
StatusMessage.java:
-import lombok.AllArgsConstructor;
+import lombok.RequiredArgsConstructor;
import lombok.Getter;
@@
-@AllArgsConstructor
+@RequiredArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class StatusMessage {UserIdMessage.java:
-import lombok.AllArgsConstructor;
+import lombok.RequiredArgsConstructor;
import lombok.Getter;
@@
-@AllArgsConstructor
+@RequiredArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UserIdMessage {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @AllArgsConstructor | |
| @JsonInclude(JsonInclude.Include.NON_NULL) | |
| import lombok.RequiredArgsConstructor; | |
| import lombok.Getter; | |
| import com.fasterxml.jackson.annotation.JsonInclude; | |
| @RequiredArgsConstructor | |
| @JsonInclude(JsonInclude.Include.NON_NULL) | |
| public class StatusMessage { | |
| // existing fields and methods... | |
| } |
| @AllArgsConstructor | |
| @JsonInclude(JsonInclude.Include.NON_NULL) | |
| import lombok.RequiredArgsConstructor; | |
| import lombok.Getter; | |
| import com.fasterxml.jackson.annotation.JsonInclude; | |
| @RequiredArgsConstructor | |
| @JsonInclude(JsonInclude.Include.NON_NULL) | |
| public class UserIdMessage { | |
| // existing fields and methods... | |
| } |
🤖 Prompt for AI Agents
In
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/StatusMessage.java
around line 33 and
backendProject/src/main/java/likelion/mlb/backendProject/domain/match/ws/message/UserIdMessage.java
around line 22, @AllArgsConstructor is generating constructors that include the
final field `type`, causing constructor/signature mismatches where
MatchBroadcaster calls these constructors with fewer args; replace
@AllArgsConstructor with Lombok's @RequiredArgsConstructor on both classes so
the fixed final `type` is excluded from the generated constructor and existing
call sites in MatchBroadcaster compile cleanly.
| @Getter | ||
| @AllArgsConstructor | ||
| @JsonInclude(JsonInclude.Include.NON_NULL) | ||
| public class UserIdMessage { | ||
|
|
||
| @Schema(description = "메시지 타입", example = "USER_ID", allowableValues = { "USER_ID" }) | ||
| private final String type = "USER_ID"; | ||
|
|
||
| @Schema(description = "서버가 판단한 로그인 사용자 ID(일반적으로 UUID 문자열)", example = "969a6b7d-2a24-41ca-9f46-d1d2f8012844") | ||
| @NotBlank | ||
| private final String userId; |
There was a problem hiding this comment.
@AllArgsConstructor 사용으로 인한 컴파일/불변성 문제: final 필드 초기화 충돌
Line 27의 final 필드(type)가 선언 시점에서 이미 초기화되었는데, Line 22의 @AllArgsConstructor는 해당 필드에 대한 파라미터/대입을 생성합니다. 이는 다음 문제를 유발합니다:
- final 필드에 대한 중복 대입으로 컴파일 오류가 발생할 수 있습니다.
- 설령 컴파일을 통과하더라도 "USER_ID" 불변성을 외부에서 깨뜨릴 여지가 생깁니다.
@AllArgsConstructor를 제거하고 userId만 받는 명시적 생성자를 추가해 주세요.
다음 패치를 적용해 주세요:
import jakarta.validation.constraints.NotBlank;
-import lombok.AllArgsConstructor;
import lombok.Getter;
@@
-@Getter
-@AllArgsConstructor
-@JsonInclude(JsonInclude.Include.NON_NULL)
-public class UserIdMessage {
+@Getter
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class UserIdMessage {
@@
@NotBlank
private final String userId;
+
+ public UserIdMessage(String userId) {
+ this.userId = userId;
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Getter | |
| @AllArgsConstructor | |
| @JsonInclude(JsonInclude.Include.NON_NULL) | |
| public class UserIdMessage { | |
| @Schema(description = "메시지 타입", example = "USER_ID", allowableValues = { "USER_ID" }) | |
| private final String type = "USER_ID"; | |
| @Schema(description = "서버가 판단한 로그인 사용자 ID(일반적으로 UUID 문자열)", example = "969a6b7d-2a24-41ca-9f46-d1d2f8012844") | |
| @NotBlank | |
| private final String userId; | |
| @Getter | |
| @JsonInclude(JsonInclude.Include.NON_NULL) | |
| public class UserIdMessage { | |
| @Schema(description = "메시지 타입", example = "USER_ID", allowableValues = { "USER_ID" }) | |
| private final String type = "USER_ID"; | |
| @Schema(description = "서버가 판단한 로그인 사용자 ID(일반적으로 UUID 문자열)", example = "969a6b7d-2a24-41ca-9f46-d1d2f8012844") | |
| @NotBlank | |
| private final String userId; | |
| public UserIdMessage(String userId) { | |
| this.userId = userId; | |
| } | |
| } |
Summary by CodeRabbit