-
Notifications
You must be signed in to change notification settings - Fork 0
fix: 줌 출석 조회조건 수정 #135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
fix: 줌 출석 조회조건 수정 #135
Conversation
- 줌 나가는 이벤트와 회의실 간 이동하는 경우 동일하게 event: meeting.participant_left 발생. - 소회의실 이동할 때, 기존 소회의실을 나가며ㄹ 출첵이 완료 - 새로운 소회의실에 입장할 때, 기존 uuid로 다시 새로운 엔티티를 생성하지만 유니크 제약조건으로 오류 발생 - NotNull인 경우엔 무조건 leaveTime 업데이트 하는 방식으로 수정
Walkthrough줌 웹훅을 이벤트 기반으로 전환하고 Redis 기반 디바운싱(10초)과 참여/퇴장 이벤트 DTO 및 리스너를 추가했으며, 웹훅 서비스는 ApplicationEventPublisher로 이벤트를 발행하도록 변경했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Zoom Webhook Client
participant ZWS as ZoomWebhookService
participant PUB as ApplicationEventPublisher
participant ZEL as ZoomAttendanceEventListener
participant Redis as RedisService
participant TS as TaskScheduler
participant ZAS as ZoomAttendanceService
rect rgb(220,240,220)
Note over Client,ZAS: Participant Joined Flow
Client->>ZWS: webhook (participant joined)
ZWS->>PUB: publish(ParticipantJoined)
PUB->>ZEL: handleJoined(event)
ZEL->>Redis: hasZoomPendingLeave(uuid)?
alt pending exists (user returned)
ZEL->>Redis: deleteZoomPendingLeave(uuid)
ZEL-->>ZEL: log "continued session"
else no pending
ZEL->>Redis: setZoomJoining(uuid, 30s)
alt set succeeded
ZEL->>ZAS: handleParticipantJoined(...)
ZEL->>Redis: deleteZoomJoining(uuid)
else set failed
ZEL-->>ZEL: log "concurrent join - skip"
end
end
end
rect rgb(200,220,240)
Note over Client,ZAS: Participant Left (Debounced) Flow
Client->>ZWS: webhook (participant left)
ZWS->>PUB: publish(ParticipantLeftAttempt)
PUB->>ZEL: handleLeftAttempt(event)
ZEL->>Redis: hasZoomPendingLeave(uuid)?
alt already pending
ZEL-->>ZEL: log "duplicate left - skip"
else no pending
ZEL->>Redis: setZoomPendingLeave(uuid, token, 10s)
alt set succeeded
ZEL->>TS: schedule(10s) -> verifyFinalLeave(token)
TS-->>ZEL: callback after 10s
ZEL->>Redis: getZoomPendingLeaveToken(uuid)
alt token matches
ZEL->>ZAS: handleParticipantLeft(...)
ZEL->>Redis: deleteZoomPendingLeave(uuid)
else token missing/changed
ZEL-->>ZEL: log "user returned or newer event"
end
else set failed
ZEL-->>ZEL: log "concurrent left - skip"
end
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.java (1)
42-54: 소회의실 이동 시 leaveTime 초기화 누락주석에서 "leaveTime이 존재할 수 있음"을 명시했는데, 48-54번 라인에서 활성 세션이 존재하면 조기 반환만 하고
leaveTime을null로 초기화하지 않습니다.문제점: 사용자가 소회의실 이동으로 인해
leaveTime이 설정된 상태에서 재입장하면, 해당 세션은 여전히 "퇴장 완료" 상태로 남아있게 되어durationMinutes계산이 부정확해집니다.ZoomAttendanceRepository.java의 리뷰 코멘트에서 제안한 수정 방안 2를 참고하세요.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/repository/ZoomAttendanceRepository.javatecheerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (2)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.java (2)
108-117: 로그 레벨이 시나리오에 적합한지 검토 필요114번 라인에서 로그 레벨을
debug에서error로 변경했습니다.소회의실 이동 시나리오에서 이벤트 순서나 타이밍 문제로 인해 활성 세션을 찾지 못하는 경우가 정상적으로 발생할 수 있다면,
error레벨은 과도할 수 있습니다. 모니터링 시스템에서 불필요한 알람이 발생할 수 있습니다.
ZoomLeaveReason.isCompleteExit()로직이 소회의실 이동과 완전 퇴장을 정확히 구분하는지 확인하고, 구분이 불완전하여 정상 케이스에서도 활성 세션을 찾지 못하는 경우가 있다면 로그 레벨을warn으로 조정하는 것을 고려하세요.
120-122: 주석 업데이트 적절함소회의실 이동 시
leaveTime이 이미 설정되어 있을 수 있다는 컨텍스트를 명확히 설명하고 있습니다.
...eerzip/src/main/java/backend/techeerzip/domain/zoom/repository/ZoomAttendanceRepository.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.java (1)
42-54:findActiveSessionByParticipantUuid쿼리에서leaveTime IS NULL조건 부재로 인한 데이터 정합성 위험리포지토리 조회에서
leaveTime IS NULL조건이 제거되었으나, 이는 특정 시나리오에서 문제를 야기할 수 있습니다.실제 동작 분석:
handleParticipantLeft에서 leaveTime은ZoomLeaveReason.isCompleteExit()이true일 때만 설정됨 (완전 퇴장만)ZoomLeaveReason.isCompleteExit()는 소회의실 이동 관련 이벤트를 명시적으로 제외하므로, 소회의실 이동 시에는 leaveTime이 설정되지 않음- 따라서 원래 주석의 "소회의실 이동으로 인해 leaveTime이 존재할 수 있음"은 실제로는 발생하지 않음
잠재적 버그 시나리오:
테이블의 유니크 제약조건이(participantUuid, meetingDate)이므로:
- 사용자가 입장 →
activeSession없음, 새 레코드 생성- 사용자가 완전 퇴장 → 레코드의
leaveTime업데이트- 같은
participantUuid로 같은 날짜에 재입장 →findActiveSessionByParticipantUuid가leaveTime이 설정된 기존 레코드를 반환- 조기 반환으로 재입장 무시 → 데이터 불일치 발생
- 새 레코드 저장 시 유니크 제약조건 위반
해결 방법:
코드 주석에서 "완전히 나갔다 다시 들어오면 새로 할당됨"이라고 명시되어 있으므로, 이를 검증하고:
- Zoom API 명세에서
participantUuid재할당 보장 여부 확인- 만약 같은 날짜 내 재사용 가능성이 있다면,
findActiveSessionByParticipantUuid메서드에AND z.leaveTime IS NULL조건 추가 필요
🧹 Nitpick comments (1)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.java (1)
142-153: 시간 파싱 실패 시 fallback 로직 개선 권장파싱 실패 시
LocalDateTime.now()를 반환하는 현재 fallback 로직은 다음과 같은 문제를 야기할 수 있습니다:
- 실제 이벤트 시간이 아닌 현재 시간으로 데이터가 저장됨
- 잘못된
durationMinutes계산- 파싱 오류가 데이터 정합성 문제로 이어질 수 있음
Optional<LocalDateTime>을 반환하거나 예외를 throw하여 호출자가 명시적으로 처리하도록 하는 것이 더 안전합니다.🔎 개선된 에러 처리 방안
방안 1: Optional 반환
- private LocalDateTime parseZoomDateTime(String dateTimeStr) { + private Optional<LocalDateTime> parseZoomDateTime(String dateTimeStr) { try { if (dateTimeStr.endsWith("Z")) { dateTimeStr = dateTimeStr.substring(0, dateTimeStr.length() - 1); } - return LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME); + return Optional.of(LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME)); } catch (Exception e) { log.error("시간 파싱 오류: {}", dateTimeStr, e); - return LocalDateTime.now(); // fallback + return Optional.empty(); } }호출부에서는
Optional처리 추가:Optional<LocalDateTime> joinTimeOpt = parseZoomDateTime(joinTimeStr); if (joinTimeOpt.isEmpty()) { log.error("시간 파싱 실패로 입장 이벤트 무시: {}", participantUuid); return; } LocalDateTime joinTime = joinTimeOpt.get();방안 2: 예외 전파
private LocalDateTime parseZoomDateTime(String dateTimeStr) { - try { if (dateTimeStr.endsWith("Z")) { dateTimeStr = dateTimeStr.substring(0, dateTimeStr.length() - 1); } return LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME); - } catch (Exception e) { - log.error("시간 파싱 오류: {}", dateTimeStr, e); - return LocalDateTime.now(); // fallback - } }이 경우 외부의 try-catch에서 처리됨 (이미 존재).
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (1)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.java (1)
95-106: 디버그 로그 추가 승인 및 isCompleteExit 로직 검증 필요LeaveReason을 로깅하는 디버그 코드 추가는 좋은 개선사항입니다. 다만, 전체 로직의 정확성이
ZoomLeaveReason.isCompleteExit()메서드의 구현에 크게 의존하므로, 다음 사항들을 확인해주세요:
- 소회의실 이동 시 leaveReason 값
- 실제 완전 퇴장 시 leaveReason 값
- 대기실 이동 시 leaveReason 값
위 스크립트 결과와 함께, 실제 프로덕션 로그에서 어떤 leaveReason 값들이 나타나는지 모니터링하여
isCompleteExit로직이 모든 케이스를 정확히 처리하는지 확인하시기 바랍니다.
techeerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.java
Show resolved
Hide resolved
- PARTICIPANT_JOINED_BREAKOUT_ROOM - PARTICIPANT_LEFT_BREAKOUT_ROOM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.java (1)
116-121: 로그 레벨 수정 및 조기 반환 추가 – LGTM이전 리뷰에서 지적된 대로
log.error를log.warn으로 변경했습니다. 네트워크 지연으로 인한 이벤트 순서 이상, 중복 이벤트 등 예상 가능한 엣지 케이스에 적합한 로그 레벨입니다. 명시적인return문 추가로 제어 흐름이 더 명확해졌습니다.
🧹 Nitpick comments (4)
techeerzip/src/main/java/backend/techeerzip/infra/zoom/webhook/ZoomWebhookService.java (1)
79-83: 주석이 실제 동작과 불일치합니다.Line 79의 주석 "도메인 서비스 호출하여 출석 데이터 저장"은 이제 이벤트를 발행하는 동작과 맞지 않습니다. 주석을 업데이트해 주세요.
🔎 수정 제안
- // 도메인 서비스 호출하여 출석 데이터 저장 + // 퇴장 이벤트 발행 (디바운싱 처리를 위해 리스너에서 처리)techeerzip/src/main/java/backend/techeerzip/domain/zoom/events/ZoomAttendanceEventListener.java (1)
35-51: 매직 넘버 중복 – 상수 추출 권장10초 디바운스 시간이 Line 35 (
setZoomPendingLeave(uuid, 10L))와 Line 51 (plusSeconds(10))에 중복되어 있습니다. 상수로 추출하면 유지보수성이 향상됩니다.🔎 수정 제안
+ private static final long DEBOUNCE_SECONDS = 10L; + @EventListener public void handleLeftAttempt(ZoomAttendanceEvents.ParticipantLeftAttempt attempt) { // ... - boolean isNewPending = redisService.setZoomPendingLeave(uuid, 10L); + boolean isNewPending = redisService.setZoomPendingLeave(uuid, DEBOUNCE_SECONDS); // ... scheduler.schedule( () -> { // ... }, - Instant.now().plusSeconds(10)); + Instant.now().plusSeconds(DEBOUNCE_SECONDS)); }techeerzip/src/main/java/backend/techeerzip/infra/redis/service/RedisService.java (2)
128-130: 메서드 이름이 동작과 불일치합니다.
setZoomPendingKey는 키를 생성/반환하는 메서드이지만, 이름이set으로 시작하여 상태를 변경하는 것처럼 보입니다.getZoomPendingKey또는buildZoomPendingKey로 변경을 권장합니다.🔎 수정 제안
- private static String setZoomPendingKey(String uuid) { + private static String getZoomPendingKey(String uuid) { return ZOOM_PENDING_PREFIX + uuid; }호출부도 함께 수정:
public boolean setZoomPendingLeave(String uuid, Long ttlSeconds) { - String key = setZoomPendingKey(uuid); + String key = getZoomPendingKey(uuid); // ... } public boolean hasZoomPendingLeave(String uuid) { - String key = setZoomPendingKey(uuid); + String key = getZoomPendingKey(uuid); // ... } public boolean deleteZoomPendingLeave(String uuid) { - String key = setZoomPendingKey(uuid); + String key = getZoomPendingKey(uuid); // ... }
92-106:deleteTask반환 타입 변경 – 호출부 개선 권장
deleteTask의 반환 타입이void에서boolean으로 변경되었습니다. 현재RedisTaskCleanupListener.java26번 줄에서 반환값을 무시하고 있는데, 삭제 성공/실패 여부를 확인하여 로깅을 더 정교하게 처리하는 것을 권장합니다. 예를 들어 삭제 실패 시 경고 로그를 추가할 수 있습니다.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/events/ZoomAttendanceEventListener.javatecheerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.javatecheerzip/src/main/java/backend/techeerzip/infra/redis/service/RedisService.javatecheerzip/src/main/java/backend/techeerzip/infra/zoom/dto/ZoomAttendanceEvents.javatecheerzip/src/main/java/backend/techeerzip/infra/zoom/type/ZoomWebhookEventType.javatecheerzip/src/main/java/backend/techeerzip/infra/zoom/webhook/ZoomWebhookService.java
🧰 Additional context used
🧬 Code graph analysis (2)
techeerzip/src/main/java/backend/techeerzip/infra/zoom/webhook/ZoomWebhookService.java (1)
techeerzip/src/main/java/backend/techeerzip/infra/zoom/dto/ZoomAttendanceEvents.java (1)
ZoomAttendanceEvents(3-10)
techeerzip/src/main/java/backend/techeerzip/infra/redis/service/RedisService.java (2)
techeerzip/src/main/java/backend/techeerzip/infra/redis/AbstractRedisCompletionListener.java (1)
Slf4j(15-115)techeerzip/src/main/java/backend/techeerzip/infra/redis/RedisTaskReader.java (1)
Slf4j(20-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (9)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.java (2)
42-54: 주석 개선 및 소회의실 이동 시나리오 설명 추가 – LGTM소회의실과 메인 룸 간 이동 시
leaveTime이 존재할 수 있다는 점을 명확히 설명하는 주석 업데이트가 적절합니다. 코드 동작의 의도를 명확히 전달합니다.
95-98: 디버그 로깅 추가 – LGTM
leaveReason디버그 로그 추가는 퇴장 이벤트 문제 해결 시 유용합니다.techeerzip/src/main/java/backend/techeerzip/infra/zoom/dto/ZoomAttendanceEvents.java (1)
1-10: 이벤트 DTO 설계 – LGTMJava record를 활용한 불변 이벤트 페이로드 설계가 적절합니다. 간결하고 명확한 구조입니다.
timestamp필드가 현재ZoomAttendanceEventListener에서 사용되지 않는 것으로 보입니다. 향후 디버깅이나 이벤트 순서 추적에 활용 계획이 있다면 괜찮지만, 불필요하다면 제거를 고려해 주세요.techeerzip/src/main/java/backend/techeerzip/infra/zoom/webhook/ZoomWebhookService.java (1)
48-65: 이벤트 기반 입장 처리로 전환 – LGTM
ApplicationEventPublisher를 통한 이벤트 발행 방식으로의 전환이 적절합니다. 소회의실 입장 이벤트(isParticipantJoinedBreakoutRoom)도 동일하게 처리하여 일관성을 확보했습니다.techeerzip/src/main/java/backend/techeerzip/domain/zoom/events/ZoomAttendanceEventListener.java (2)
54-71: 입장 이벤트 핸들러 – LGTM디바운싱 로직이 올바르게 구현되었습니다. 보류 중인 퇴장이 있으면 세션을 유지하고, 없으면 새 세션을 생성하는 흐름이 명확합니다.
41-51: TaskScheduler 빈 등록과 트랜잭션 컨텍스트는 Spring Boot에 의해 자동 구성됩니다.
@EnableScheduling이TecheerzipApplication에 선언되어 있으므로 Spring Boot 3.3.1이 자동으로TaskScheduler빈을 등록합니다. 또한ZoomAttendanceService의 클래스 레벨@Transactional선언이handleParticipantLeft()메서드에 적용되므로, 스케줄러 스레드에서 호출되더라도 Spring의 프록시 패턴과 트랜잭션 관리에 의해 트랜잭션 컨텍스트가 정상적으로 적용됩니다.techeerzip/src/main/java/backend/techeerzip/infra/zoom/type/ZoomWebhookEventType.java (2)
31-37: Predicate 메서드 추가 – LGTM기존
isParticipantJoined,isParticipantLeft패턴과 일관성 있게 구현되었습니다.
9-11: 소회의실 이벤트 타입 추가 – LGTM소회의실 입장/퇴장 이벤트 상수가 올바르게 추가되었습니다. 기존 패턴과 일관성 있는 네이밍이며, Zoom API 문서에서
meeting.participant_joined_breakout_room및meeting.participant_left_breakout_room이벤트 이름이 확인되었습니다.techeerzip/src/main/java/backend/techeerzip/infra/redis/service/RedisService.java (1)
110-116: 원자적 pending 플래그 설정 – LGTM
setIfAbsent를 사용한 원자적 연산으로 동시성 문제를 올바르게 처리합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (3)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.java (3)
77-82: catch 블록에서 이미 계산된uuidShort재활용 가능
uuidShort는 try 블록 초반에 이미 계산되어 catch 블록에서도 접근 가능합니다. 중복된 substring 로직 대신uuidShort를 직접 사용하면 코드가 더 간결해집니다.🔎 리팩토링 제안
} catch (Exception e) { - log.error("[ZOOM] [JOIN] [DB] [ERROR] 입장 처리 실패 - userName={} uuid={} error={}", - participant.getUserName(), - participant.getParticipantUuid() != null ? participant.getParticipantUuid().substring(0, 8) : "N/A", - e.getMessage(), e); + log.error("[ZOOM] [JOIN] [DB] [ERROR] 입장 처리 실패 - userName={} uuid={} error={}", + userName, + uuidShort, + e.getMessage(), e); }
144-149: 동일: catch 블록에서uuidShort재활용 가능
handleParticipantLeft메서드의 catch 블록에서도 동일하게 이미 계산된uuidShort와userName을 사용할 수 있습니다.🔎 리팩토링 제안
} catch (Exception e) { - log.error("[ZOOM] [LEFT] [DB] [ERROR] 퇴장 처리 실패 - userName={} uuid={} error={}", - participant.getUserName(), - participant.getParticipantUuid() != null ? participant.getParticipantUuid().substring(0, 8) : "N/A", - e.getMessage(), e); + log.error("[ZOOM] [LEFT] [DB] [ERROR] 퇴장 처리 실패 - userName={} uuid={} error={}", + userName, + uuidShort, + e.getMessage(), e); }
160-163: 파싱 실패 시 fallback 전략 재검토 권장파싱 실패 시
LocalDateTime.now()를 반환하면 실제와 다른 시간이 DB에 저장될 수 있습니다. 기존 로직이지만, 예외를 throw하거나 Optional을 반환하여 호출자가 처리하도록 하는 것이 더 안전할 수 있습니다.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/events/ZoomAttendanceEventListener.javatecheerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.javatecheerzip/src/main/java/backend/techeerzip/infra/zoom/webhook/ZoomWebhookController.javatecheerzip/src/main/java/backend/techeerzip/infra/zoom/webhook/ZoomWebhookService.javatecheerzip/src/main/resources/logback-spring.xml
🧰 Additional context used
🧬 Code graph analysis (2)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/events/ZoomAttendanceEventListener.java (5)
techeerzip/src/main/java/backend/techeerzip/infra/zoom/dto/ZoomAttendanceEvents.java (1)
ZoomAttendanceEvents(3-10)techeerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.java (1)
Slf4j(18-165)techeerzip/src/main/java/backend/techeerzip/infra/zoom/webhook/ZoomWebhookController.java (1)
Slf4j(15-36)techeerzip/src/main/java/backend/techeerzip/infra/zoom/webhook/ZoomWebhookService.java (1)
Slf4j(15-83)techeerzip/src/main/java/backend/techeerzip/infra/redis/service/RedisService.java (1)
Slf4j(20-131)
techeerzip/src/main/java/backend/techeerzip/infra/zoom/webhook/ZoomWebhookService.java (1)
techeerzip/src/main/java/backend/techeerzip/infra/zoom/dto/ZoomAttendanceEvents.java (1)
ZoomAttendanceEvents(3-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (7)
techeerzip/src/main/resources/logback-spring.xml (1)
66-69: 외부 라이브러리 로깅 레벨 설정이 적절합니다.Lettuce Redis 클라이언트와 HikariCP 커넥션 풀의 DEBUG 로그를 억제하는 것은 표준적인 로깅 모범 사례입니다. 특히 Redis 기반 디바운싱 로직이 추가되면서 Lettuce의 상세 로그가 애플리케이션 로그를 어지럽힐 수 있으므로, INFO 레벨로 설정하는 것이 로그 가독성 향상에 도움이 됩니다.
techeerzip/src/main/java/backend/techeerzip/infra/zoom/webhook/ZoomWebhookController.java (1)
28-28: LGTM!로그 포맷이 Zoom 관련 다른 컴포넌트들과 일관되게
[ZOOM] [WEBHOOK_RECEIVED]형식으로 변경되었습니다. 구조화된 로그 형식은 모니터링 및 검색에 유용합니다.techeerzip/src/main/java/backend/techeerzip/domain/zoom/events/ZoomAttendanceEventListener.java (2)
48-58: 스케줄된 태스크의 트랜잭션 컨텍스트 확인 필요스케줄된 태스크는 원래 요청 스레드와 별도의 컨텍스트에서 실행됩니다.
ZoomAttendanceService가@Transactional로 선언되어 있어 자체 트랜잭션이 시작되므로 정상 동작할 것으로 보이나, 예외 발생 시 별도 처리가 없습니다.예외 발생 시에도 pending 상태가 삭제되지 않으면 해당 사용자의 이후 이벤트 처리에 영향을 줄 수 있습니다.
🔎 예외 처리 추가 제안
scheduler.schedule( () -> { - if (!redisService.hasZoomPendingLeave(uuid)) { - log.info("[ZOOM] [LEFT] [CANCELLED] 사용자 복귀 - userName={} uuid={}", userName, uuidShort); - return; + try { + if (!redisService.hasZoomPendingLeave(uuid)) { + log.info("[ZOOM] [LEFT] [CANCELLED] 사용자 복귀 - userName={} uuid={}", userName, uuidShort); + return; + } + zoomAttendanceService.handleParticipantLeft(participant); + log.info("[ZOOM] [LEFT] [CONFIRMED] 세션 종료 처리 완료 - userName={} uuid={}", userName, uuidShort); + } catch (Exception e) { + log.error("[ZOOM] [LEFT] [ERROR] 스케줄 처리 실패 - userName={} uuid={} error={}", + userName, uuidShort, e.getMessage(), e); + } finally { + redisService.deleteZoomPendingLeave(uuid); } - zoomAttendanceService.handleParticipantLeft(participant); - redisService.deleteZoomPendingLeave(uuid); - log.info("[ZOOM] [LEFT] [CONFIRMED] 세션 종료 처리 완료 - userName={} uuid={}", userName, uuidShort); }, Instant.now().plusSeconds(10));
28-36:uuid가 null일 경우 NPE 및 Redis 키 오류 발생 가능
participantUuid가 null인 경우, 라인 29에서uuid.substring(0, 8)호출 시NullPointerException이 발생합니다. 또한 라인 33, 39에서 null uuid로 Redis 작업을 수행하게 됩니다.
uuidShort는 null 체크가 되어 있지만,uuid변수 자체는 null일 수 있습니다.🔎 null 체크 추가 제안
public void handleLeftAttempt(ZoomAttendanceEvents.ParticipantLeftAttempt attempt) { WebhookParticipant participant = attempt.participant(); String uuid = participant.getParticipantUuid(); - String uuidShort = uuid != null ? uuid.substring(0, 8) : "N/A"; String userName = participant.getUserName(); + if (uuid == null) { + log.warn("[ZOOM] [LEFT] [SKIP] participantUuid 누락 - userName={}", userName); + return; + } + + String uuidShort = uuid.substring(0, 8); + // 이미 펜딩 상태면 패스 if (redisService.hasZoomPendingLeave(uuid)) {Likely an incorrect or invalid review comment.
techeerzip/src/main/java/backend/techeerzip/domain/zoom/service/ZoomAttendanceService.java (1)
30-35: LGTM!
uuidShort를 활용한 로깅 개선이 잘 적용되었습니다. null 체크 후 substring을 수행하여 안전하고, 개인정보 보호를 위해 UUID의 일부만 로깅합니다.techeerzip/src/main/java/backend/techeerzip/infra/zoom/webhook/ZoomWebhookService.java (2)
19-21: LGTM!
ApplicationEventPublisher를 사용한 이벤트 기반 아키텍처로의 전환이 좋습니다. 도메인 서비스와의 결합도를 낮추고, Redis 기반 디바운싱을 통한 비동기 처리가 가능해졌습니다.
43-44: LGTM!
uuidShort계산 시 null 체크가 적절히 적용되어 있습니다. 이 파일에서는 uuid가 로깅 목적으로만 사용되므로 안전합니다.
| public void handleJoined(ZoomAttendanceEvents.ParticipantJoined joined) { | ||
| WebhookParticipant participant = joined.participant(); | ||
| String uuid = participant.getParticipantUuid(); | ||
| String uuidShort = uuid != null ? uuid.substring(0, 8) : "N/A"; | ||
| String userName = participant.getUserName(); | ||
|
|
||
| // 보류 상태면 보류 삭제 (연속 접속으로 간주) | ||
| if (redisService.hasZoomPendingLeave(uuid)) { | ||
| log.info( | ||
| "[ZOOM] [JOIN] [SESSION_KEPT] 연속 접속으로 간주, DB 기록 안함 - userName={} uuid={}", | ||
| userName, | ||
| uuidShort); | ||
| redisService.deleteZoomPendingLeave(uuid); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
동일한 null 체크 누락 문제
handleJoined 메서드에서도 uuid가 null일 경우 동일한 문제가 발생합니다. 라인 70, 75에서 null uuid로 Redis 작업이 수행될 수 있습니다.
🔎 null 체크 추가 제안
public void handleJoined(ZoomAttendanceEvents.ParticipantJoined joined) {
WebhookParticipant participant = joined.participant();
String uuid = participant.getParticipantUuid();
- String uuidShort = uuid != null ? uuid.substring(0, 8) : "N/A";
String userName = participant.getUserName();
+ if (uuid == null) {
+ log.warn("[ZOOM] [JOIN] [SKIP] participantUuid 누락 - userName={}", userName);
+ return;
+ }
+
+ String uuidShort = uuid.substring(0, 8);
+
// 보류 상태면 보류 삭제 (연속 접속으로 간주)
if (redisService.hasZoomPendingLeave(uuid)) {📝 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 void handleJoined(ZoomAttendanceEvents.ParticipantJoined joined) { | |
| WebhookParticipant participant = joined.participant(); | |
| String uuid = participant.getParticipantUuid(); | |
| String uuidShort = uuid != null ? uuid.substring(0, 8) : "N/A"; | |
| String userName = participant.getUserName(); | |
| // 보류 상태면 보류 삭제 (연속 접속으로 간주) | |
| if (redisService.hasZoomPendingLeave(uuid)) { | |
| log.info( | |
| "[ZOOM] [JOIN] [SESSION_KEPT] 연속 접속으로 간주, DB 기록 안함 - userName={} uuid={}", | |
| userName, | |
| uuidShort); | |
| redisService.deleteZoomPendingLeave(uuid); | |
| return; | |
| } | |
| public void handleJoined(ZoomAttendanceEvents.ParticipantJoined joined) { | |
| WebhookParticipant participant = joined.participant(); | |
| String uuid = participant.getParticipantUuid(); | |
| String userName = participant.getUserName(); | |
| if (uuid == null) { | |
| log.warn("[ZOOM] [JOIN] [SKIP] participantUuid 누락 - userName={}", userName); | |
| return; | |
| } | |
| String uuidShort = uuid.substring(0, 8); | |
| // 보류 상태면 보류 삭제 (연속 접속으로 간주) | |
| if (redisService.hasZoomPendingLeave(uuid)) { | |
| log.info( | |
| "[ZOOM] [JOIN] [SESSION_KEPT] 연속 접속으로 간주, DB 기록 안함 - userName={} uuid={}", | |
| userName, | |
| uuidShort); | |
| redisService.deleteZoomPendingLeave(uuid); | |
| return; | |
| } |
| log.info( | ||
| "참가자 퇴장: {} (uuid: {}, 시간: {}, 사유: {})", | ||
| "[ZOOM] [LEFT] [EVENT_PUBLISHED] userName={} uuid={} leaveTime={} reason={}", | ||
| participant.getUserName(), | ||
| participantUuidLogMsg, | ||
| participant.getLeaveTime() != null ? participant.getLeaveTime() : "없음", | ||
| leaveReason != null ? leaveReason : "없음"); | ||
| uuidShort, | ||
| participant.getLeaveTime() != null ? participant.getLeaveTime() : "N/A", | ||
| leaveReason != null ? leaveReason : "N/A"); | ||
|
|
||
| // 도메인 서비스 호출하여 출석 데이터 저장 | ||
| zoomAttendanceService.handleParticipantLeft(participant); | ||
| eventPublisher.publishEvent( | ||
| new ZoomAttendanceEvents.ParticipantLeftAttempt( | ||
| participant, System.currentTimeMillis())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로그 순서 불일치: 이벤트 발행 전에 EVENT_PUBLISHED 로그 기록
라인 70-75에서 EVENT_PUBLISHED 로그가 기록되지만, 실제 이벤트 발행은 라인 77-79에서 수행됩니다. 발행 중 예외가 발생하면 로그가 오해의 소지가 있습니다. Join 이벤트(라인 55-62)처럼 발행 후 로그를 기록하는 것이 일관성 있습니다.
🔎 로그 순서 수정 제안
/* 참가자 퇴장 이벤트 처리 */
if (ZoomWebhookEventType.isParticipantLeft(event.getEventName())
|| ZoomWebhookEventType.isParticipantLeftBreakoutRoom(event.getEventName())) {
String leaveReason = participant.getLeaveReason();
- log.info(
- "[ZOOM] [LEFT] [EVENT_PUBLISHED] userName={} uuid={} leaveTime={} reason={}",
- participant.getUserName(),
- uuidShort,
- participant.getLeaveTime() != null ? participant.getLeaveTime() : "N/A",
- leaveReason != null ? leaveReason : "N/A");
-
eventPublisher.publishEvent(
new ZoomAttendanceEvents.ParticipantLeftAttempt(
participant, System.currentTimeMillis()));
+ log.info(
+ "[ZOOM] [LEFT] [EVENT_PUBLISHED] userName={} uuid={} leaveTime={} reason={}",
+ participant.getUserName(),
+ uuidShort,
+ participant.getLeaveTime() != null ? participant.getLeaveTime() : "N/A",
+ leaveReason != null ? leaveReason : "N/A");
}📝 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.
| log.info( | |
| "참가자 퇴장: {} (uuid: {}, 시간: {}, 사유: {})", | |
| "[ZOOM] [LEFT] [EVENT_PUBLISHED] userName={} uuid={} leaveTime={} reason={}", | |
| participant.getUserName(), | |
| participantUuidLogMsg, | |
| participant.getLeaveTime() != null ? participant.getLeaveTime() : "없음", | |
| leaveReason != null ? leaveReason : "없음"); | |
| uuidShort, | |
| participant.getLeaveTime() != null ? participant.getLeaveTime() : "N/A", | |
| leaveReason != null ? leaveReason : "N/A"); | |
| // 도메인 서비스 호출하여 출석 데이터 저장 | |
| zoomAttendanceService.handleParticipantLeft(participant); | |
| eventPublisher.publishEvent( | |
| new ZoomAttendanceEvents.ParticipantLeftAttempt( | |
| participant, System.currentTimeMillis())); | |
| eventPublisher.publishEvent( | |
| new ZoomAttendanceEvents.ParticipantLeftAttempt( | |
| participant, System.currentTimeMillis())); | |
| log.info( | |
| "[ZOOM] [LEFT] [EVENT_PUBLISHED] userName={} uuid={} leaveTime={} reason={}", | |
| participant.getUserName(), | |
| uuidShort, | |
| participant.getLeaveTime() != null ? participant.getLeaveTime() : "N/A", | |
| leaveReason != null ? leaveReason : "N/A"); |
🤖 Prompt for AI Agents
In
techeerzip/src/main/java/backend/techeerzip/infra/zoom/webhook/ZoomWebhookService.java
around lines 70 to 79, the code logs "[ZOOM] [LEFT] [EVENT_PUBLISHED]" before
actually calling eventPublisher.publishEvent which can be misleading if publish
fails; move the INFO log so it occurs after eventPublisher.publishEvent
(matching the join event pattern), and optionally wrap publishEvent in a
try-catch to log an error if publishing throws, ensuring the success log is only
emitted after a successful publish.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (2)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/events/ZoomAttendanceEventListener.java (2)
77-113: uuid null 체크 누락 (이전 리뷰와 동일한 문제)이전 리뷰에서 이미 지적된 내용과 동일합니다. Line 81의
uuid가 null일 경우 lines 86, 91, 96, 111에서 Redis 작업 시"zoom:joining-null"또는"zoom:pending-null"키가 생성되어 로직이 손상됩니다.
45-75: verifyFinalLeave에서도 uuid null 체크 필요Line 48의
uuid가 null일 경우 lines 51, 74에서 Redis 작업 시 문제가 발생합니다.handleLeftAttempt에서 null uuid를 조기 차단하면 이 메서드까지 도달하지 않겠지만, 방어적 프로그래밍 차원에서 여기서도 null 체크를 추가하는 것이 안전합니다.🔎 제안하는 수정 사항
private void verifyFinalLeave( WebhookParticipant participant, String myToken, String uuidShort, String userName) { String uuid = participant.getParticipantUuid(); + if (uuid == null) { + log.warn("[ZOOM] [LEFT] [VERIFY_SKIP] participantUuid 누락 - userName={}", userName); + return; + } + // Redis에서 현재 저장된 토큰 조회 String currentToken = redisService.getZoomPendingLeaveToken(uuid);
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/events/ZoomAttendanceEventListener.javatecheerzip/src/main/java/backend/techeerzip/infra/redis/service/RedisService.java
🧰 Additional context used
🧬 Code graph analysis (2)
techeerzip/src/main/java/backend/techeerzip/domain/zoom/events/ZoomAttendanceEventListener.java (1)
techeerzip/src/main/java/backend/techeerzip/infra/zoom/dto/ZoomAttendanceEvents.java (1)
ZoomAttendanceEvents(3-10)
techeerzip/src/main/java/backend/techeerzip/infra/redis/service/RedisService.java (3)
techeerzip/src/main/java/backend/techeerzip/global/config/RedisConfig.java (1)
Slf4j(26-143)techeerzip/src/main/java/backend/techeerzip/infra/redis/AbstractRedisCompletionListener.java (1)
Slf4j(15-115)techeerzip/src/main/java/backend/techeerzip/infra/redis/RedisTaskReader.java (1)
Slf4j(20-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (1)
techeerzip/src/main/java/backend/techeerzip/infra/redis/service/RedisService.java (1)
92-106: deleteTask 반환 타입 개선 승인
void에서boolean으로 반환 타입을 변경한 것은 삭제 성공 여부를 호출자가 확인할 수 있게 하여 관찰성을 향상시킵니다. 로깅 개선도 적절합니다.
| // 퇴장 이벤트 (줌 나가기 / 회의실 이동) | ||
| @EventListener | ||
| public void handleLeftAttempt(ZoomAttendanceEvents.ParticipantLeftAttempt attempt) { | ||
| WebhookParticipant participant = attempt.participant(); | ||
| String uuid = participant.getParticipantUuid(); | ||
| String uuidShort = uuid != null ? uuid.substring(0, 8) : "N/A"; | ||
| String userName = participant.getUserName(); | ||
|
|
||
| // 이번 퇴장 시도를 식별할 고유 토큰 생성 (타임스탬프 사용) | ||
| String myToken = String.valueOf(System.currentTimeMillis()); | ||
|
|
||
| // Redis에 토큰 저장 (덮어쓰기 허용 - 가장 최신 퇴장이 중요하므로) | ||
| redisService.setZoomPendingLeave(uuid, myToken, 10L); | ||
| log.info("[ZOOM] [LEFT] [PENDING] userName={} uuid={} token={} delay=10s", userName, uuidShort, myToken); | ||
|
|
||
| // 스케줄러 등록 (myToken을 람다 안으로 캡처) | ||
| scheduler.schedule( | ||
| () -> verifyFinalLeave(participant, myToken, uuidShort, userName), | ||
| Instant.now().plusSeconds(10)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uuid null 체크 누락으로 인한 데이터 무결성 문제
Line 28에서 uuid가 null일 수 있으나, line 36에서 null 체크 없이 Redis 작업에 사용됩니다. uuid가 null이면 "zoom:pending-null" 키가 생성되어 uuid가 없는 모든 참가자가 동일한 Redis 엔트리를 공유하게 되므로 디바운싱 로직이 완전히 망가집니다.
🔎 제안하는 수정 사항
public void handleLeftAttempt(ZoomAttendanceEvents.ParticipantLeftAttempt attempt) {
WebhookParticipant participant = attempt.participant();
String uuid = participant.getParticipantUuid();
- String uuidShort = uuid != null ? uuid.substring(0, 8) : "N/A";
String userName = participant.getUserName();
+ if (uuid == null) {
+ log.warn("[ZOOM] [LEFT] [SKIP] participantUuid 누락 - userName={}", userName);
+ return;
+ }
+
+ String uuidShort = uuid.substring(0, 8);
+
// 이번 퇴장 시도를 식별할 고유 토큰 생성 (타임스탬프 사용)
String myToken = String.valueOf(System.currentTimeMillis());🤖 Prompt for AI Agents
In
techeerzip/src/main/java/backend/techeerzip/domain/zoom/events/ZoomAttendanceEventListener.java
around lines 24-43, the method assumes participant.getParticipantUuid() is
non-null but uses it as a Redis key and in scheduling, which creates a shared
"zoom:pending-null" entry if uuid is null; fix by checking uuid for null
immediately after obtaining it and, if null, log a warning/error (including
userName) and return early (do not call redisService.setZoomPendingLeave or
schedule verifyFinalLeave); keep uuidShort derived only after the null check so
you never use "null" as a key.
| private static final String ZOOM_PENDING_PREFIX = "zoom:pending-"; | ||
| private static final String ZOOM_JOINING_PREFIX = "zoom:joining-"; | ||
|
|
||
| /** 퇴장 대기 상태 설정 (토큰 저장, 덮어쓰기 허용) */ | ||
| public void setZoomPendingLeave(String uuid, String token, Long ttlSeconds) { | ||
| String key = setZoomPendingKey(uuid); | ||
| redisTemplate.opsForValue().set(key, token, Duration.ofSeconds(ttlSeconds)); | ||
| } | ||
|
|
||
| /** 퇴장 대기 상태의 토큰 조회 */ | ||
| public String getZoomPendingLeaveToken(String uuid) { | ||
| String key = setZoomPendingKey(uuid); | ||
| return redisTemplate.opsForValue().get(key); | ||
| } | ||
|
|
||
| /** 퇴장 대기 상태 확인 (키 존재 여부) */ | ||
| public boolean hasZoomPendingLeave(String uuid) { | ||
| String key = setZoomPendingKey(uuid); | ||
| return Boolean.TRUE.equals(redisTemplate.hasKey(key)); | ||
| } | ||
|
|
||
| /** 퇴장 대기 상태 삭제 */ | ||
| public boolean deleteZoomPendingLeave(String uuid) { | ||
| String key = setZoomPendingKey(uuid); | ||
| return deleteTask(key); | ||
| } | ||
|
|
||
| private static String setZoomPendingKey(String uuid) { | ||
| return ZOOM_PENDING_PREFIX + uuid; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Zoom pending leave 메서드에 방어적 null 체크 추가 권장
Lines 112-137의 모든 메서드가 uuid 파라미터에 대한 null 체크를 수행하지 않습니다. uuid가 null이면 "zoom:pending-null" 같은 잘못된 키가 생성되어 여러 사용자가 동일한 Redis 엔트리를 공유하게 됩니다. 호출자 레벨에서 검증해야 하지만, 방어적 프로그래밍 차원에서 여기서도 null 체크를 추가하면 더 안전합니다.
🔎 제안하는 수정 사항
/** 퇴장 대기 상태 설정 (토큰 저장, 덮어쓰기 허용) */
public void setZoomPendingLeave(String uuid, String token, Long ttlSeconds) {
+ if (uuid == null) {
+ log.error("[{}] setZoomPendingLeave - uuid는 null일 수 없습니다", CONTEXT);
+ throw new IllegalArgumentException("uuid는 null일 수 없습니다");
+ }
String key = setZoomPendingKey(uuid);
redisTemplate.opsForValue().set(key, token, Duration.ofSeconds(ttlSeconds));
}동일한 패턴을 getZoomPendingLeaveToken, hasZoomPendingLeave, deleteZoomPendingLeave 메서드에도 적용하세요.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
techeerzip/src/main/java/backend/techeerzip/infra/redis/service/RedisService.java
around lines 108-137, add defensive null/empty validation for the uuid parameter
in setZoomPendingLeave, getZoomPendingLeaveToken, hasZoomPendingLeave,
deleteZoomPendingLeave (and/or in the private setZoomPendingKey helper) so you
never build a key like "zoom:pending-null"; if uuid is null or blank throw an
IllegalArgumentException with a clear message (e.g. "uuid must not be null or
empty") before interacting with redis.
| /** 입장 처리 중인지 확인 (Race Condition 방지) */ | ||
| public boolean setZoomJoining(String uuid, Long ttlSeconds) { | ||
| String key = setZoomJoiningKey(uuid); | ||
| return Boolean.TRUE.equals( | ||
| redisTemplate | ||
| .opsForValue() | ||
| .setIfAbsent(key, "JOINING", Duration.ofSeconds(ttlSeconds))); | ||
| } | ||
|
|
||
| public boolean hasZoomJoining(String uuid) { | ||
| String key = setZoomJoiningKey(uuid); | ||
| return Boolean.TRUE.equals(redisTemplate.hasKey(key)); | ||
| } | ||
|
|
||
| public boolean deleteZoomJoining(String uuid) { | ||
| String key = setZoomJoiningKey(uuid); | ||
| return deleteTask(key); | ||
| } | ||
|
|
||
| private static String setZoomJoiningKey(String uuid) { | ||
| return ZOOM_JOINING_PREFIX + uuid; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Zoom joining 메서드에도 방어적 null 체크 추가 권장
Lines 140-160의 모든 메서드도 uuid 파라미터에 대한 null 검증이 없습니다. uuid가 null이면 "zoom:joining-null" 키가 생성되어 Race Condition 방지 로직이 손상됩니다. 동일한 방어적 null 체크를 추가하는 것이 좋습니다.
🔎 제안하는 수정 사항
/** 입장 처리 중인지 확인 (Race Condition 방지) */
public boolean setZoomJoining(String uuid, Long ttlSeconds) {
+ if (uuid == null) {
+ log.error("[{}] setZoomJoining - uuid는 null일 수 없습니다", CONTEXT);
+ throw new IllegalArgumentException("uuid는 null일 수 없습니다");
+ }
String key = setZoomJoiningKey(uuid);
return Boolean.TRUE.equals(
redisTemplate
.opsForValue()
.setIfAbsent(key, "JOINING", Duration.ofSeconds(ttlSeconds)));
}동일한 패턴을 hasZoomJoining, deleteZoomJoining 메서드에도 적용하세요.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
techeerzip/src/main/java/backend/techeerzip/infra/redis/service/RedisService.java
around lines 139 to 160, add defensive null validation for the uuid parameter in
setZoomJoining, hasZoomJoining and deleteZoomJoining to prevent creating the
"zoom:joining-null" key; for example, call Objects.requireNonNull(uuid, "uuid
must not be null") (or throw new IllegalArgumentException("uuid must not be
null")) at the start of each method so the methods fail fast instead of
operating with a null uuid, and keep the existing setZoomJoiningKey private
helper unchanged.



요약
리포지토리 조회시 IsNull 조건 삭제
작업 내용
커밋 메시지 참고
참고 사항
관련 이슈
BACKEND-198
Summary by CodeRabbit
새로운 기능
버그 수정 / 안정성
리팩토링
설정
✏️ Tip: You can customize this high-level summary in your review settings.