Skip to content

[WTH-389] sse 연결 끊기는 문제 해결#74

Merged
soo0711 merged 11 commits into
devfrom
fix/WTH-389-SSE-연결-끊기는-문제-해결
May 18, 2026

Hidden character warning

The head ref may contain hidden characters: "fix/WTH-389-SSE-\uc5f0\uacb0-\ub04a\uae30\ub294-\ubb38\uc81c-\ud574\uacb0"
Merged

[WTH-389] sse 연결 끊기는 문제 해결#74
soo0711 merged 11 commits into
devfrom
fix/WTH-389-SSE-연결-끊기는-문제-해결

Conversation

@soo0711
Copy link
Copy Markdown
Collaborator

@soo0711 soo0711 commented May 17, 2026

📌 Summary

어떤 작업인지 한 줄 요약해 주세요.

SSE 응답이 제대로 가지 않는 문제 해결

📝 Changes

변경사항을 what, why, how로 구분해 작성해 주세요.

What

클럽별 현재 활성 QR 세션을 Redis에 저장하도록 추가
SSE 구독 시 QR 상태 판단 기준을 Redis active QR 기준으로 변경

Why

기존에는 SSE 재접속 시 OPEN 세션 중 하나를 조회해 QR 상태를 추측해서 qr-none으로 응답이 보내졌습니다.
이전 QR 만료 이벤트가 최신 QR 상태를 닫는 상황이 있었습니다.

How

QR 생성 시 Redis에 QR 코드와 클럽별 active QR sessionId를 함께 저장했습니다.
Redis Lua script로 active QR 비교와 삭제를 원자적으로 처리했습니다.

📸 Screenshots / Logs

필요시 스크린샷 or 로그를 첨부해주세요.

  • SSE 응답
스크린샷 2026-05-17 오후 3 39 52

💡 Reviewer 참고사항

리뷰에 참고할 내용을 작성해주세요.

✅ Checklist

  • PR 제목 설정 완료 (WTH-123 인증 필터 설정)
  • 테스트 구현 완료
  • 리뷰어 등록 완료
  • 자체 코드 리뷰 완료

Summary by CodeRabbit

릴리스 노트

  • 버그 수정

    • QR 출석 세션 조회 시 유효하지 않은 세션에 대한 에러 처리 개선
    • 클럽별 활성 QR 세션 추적으로 세션 관리 신뢰성 향상
    • QR 코드 만료 시 세션 정리 로직 개선
  • 테스트

    • Redis 기반 QR 출석 저장소 동작 검증 테스트 추가
    • 기존 출석 관련 테스트 갱신

Review Change Stack

@soo0711 soo0711 requested a review from hyxklee May 17, 2026 06:41
@soo0711 soo0711 self-assigned this May 17, 2026
@soo0711 soo0711 added ✨ Feature 새로운 기능 추가 🐞 BugFix 버그 수정 labels May 17, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

📝 Walkthrough

Walkthrough

QR 출석 시스템의 활성 세션 추적을 QrAttendancePort를 중심으로 재설계하여, Redis 기반 원자적 저장/조회/삭제를 구현하고, 생성·구독·만료 흐름에서 일관되게 클럽별 세션 상태를 관리합니다.

Changes

QR 활성 세션 포트 기반 관리

Layer / File(s) Summary
QrAttendancePort 포트 계약 확장
src/main/kotlin/com/weeth/domain/attendance/domain/port/QrAttendancePort.kt
활성 세션 관리용 TTL 및 키 prefix 상수를 companion object에 추가하고, 클럽별 활성 세션 ID 조회(getActiveSessionId)와 저장된 활성 세션이 주어진 sessionId와 일치할 때만 삭제하는 메서드(clearActiveSessionIfMatches)를 포트 계약에 정의합니다.
RedisQrAttendanceAdapter 활성 세션 구현
src/main/kotlin/com/weeth/domain/attendance/infrastructure/RedisQrAttendanceAdapter.kt, src/test/kotlin/com/weeth/domain/attendance/infrastructure/RedisQrAttendanceAdapterTest.kt
store() 호출 시 세션 코드 키와 클럽 활성 세션 키를 별도 TTL로 동시 저장하고, Lua 스크립트를 통해 활성 세션 조회 및 조건부 삭제를 원자적으로 구현합니다. Redis 기반 동작을 검증하는 통합 테스트를 추가합니다.
GenerateQrTokenUseCase clubId 소유권 검증 및 저장
src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/GenerateQrTokenUseCase.kt, src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/GenerateQrTokenUseCaseTest.kt
세션 조회 시 clubId 소유권을 검증하여 다른 클럽 세션 접근을 차단하고, 저장 호출에 clubId를 포함하여 포트의 활성 세션 추적 구조에 통합합니다. 테스트를 clubId 중심으로 갱신합니다.
SubscribeAttendanceSseUseCase SessionReader → QrAttendancePort 마이그레이션
src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/SubscribeAttendanceSseUseCase.kt, src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/SubscribeAttendanceSseUseCaseTest.kt
SessionReader 의존성을 제거하고 QrAttendancePort를 통해 활성 세션 ID 및 만료 시각을 조회하여 SSE 브로드캐스트 판단 로직을 포트 기반으로 전환합니다. 테스트 모킹 및 검증을 포트 메서드에 맞춰 갱신합니다.
QrExpiredEventListener 조건부 clear 및 브로드캐스트
src/main/kotlin/com/weeth/domain/attendance/infrastructure/QrExpiredEventListener.kt, src/test/kotlin/com/weeth/domain/attendance/infrastructure/QrExpiredEventListenerTest.kt
만료 이벤트 수신 시 clearActiveSessionIfMatches를 호출하여 저장된 활성 세션과 만료되는 세션의 일치 여부를 확인하고, 일치하는 경우만 QR_CLOSE를 브로드캐스트합니다. 테스트에서 매칭/미매칭 케이스를 검증합니다.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Team-Weeth/weeth-server#59: 동일한 QR SSE 흐름(SubscribeAttendanceSseUseCase, QrExpiredEventListener, QrAttendancePort)을 수정하여 Redis/세션 상태 기반 qr-open/qr-none/qr-close 동작을 구현합니다.
  • Team-Weeth/weeth-server#51: GenerateQrTokenUseCaseSubscribeAttendanceSseUseCase SSE 흐름을 다루며, 이 PR이 QR 토큰/세션 저장 및 open/none 판정 로직을 변경합니다.

Suggested reviewers

  • nabbang6
  • hyxklee

🐰 활성 세션을 Redis에 저장하고,
포트로 말끔히 정리하니
만료는 자동으로 clear 🎉
클럽별 QR은 이제 안전해,
원자적 Lua 스크립트의 마법!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경 사항과 완전히 관련 있습니다. SSE 연결 끊김 문제 해결이라는 핵심 이슈를 명확하게 설명하고 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR 설명이 제목, Summary, Changes(What/Why/How), Screenshots, 체크리스트 등 필수 섹션을 모두 포함하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/WTH-389-SSE-연결-끊기는-문제-해결

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@soo0711 soo0711 requested a review from nabbang6 May 17, 2026 06:41
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a 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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/kotlin/com/weeth/domain/attendance/infrastructure/RedisQrAttendanceAdapter.kt`:
- Around line 19-23: The two sequential redisTemplate.opsForValue().set calls in
RedisQrAttendanceAdapter.store can leave state inconsistent if the second set
fails; make the two key writes atomic by replacing them with a single Redis Lua
script (or MULTI/EXEC) that sets key(sessionId) with TTL
QrAttendancePort.TTL_SECONDS and activeKey(clubId) with TTL
QrAttendancePort.ACTIVE_TTL_SECONDS in one atomic operation. Implement the Lua
script (or transaction) and call it via redisTemplate.execute (or
DefaultRedisScript) from the store method, passing key(sessionId) and
activeKey(clubId) and their TTLs so both keys and expirations are written
together; ensure the code paths still handle and log failures from the script
execution.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 51d9bbf4-e0cd-4be8-9d77-51035eb97b2d

📥 Commits

Reviewing files that changed from the base of the PR and between e3e41bd and 3beaf9a.

📒 Files selected for processing (15)
  • src/main/kotlin/com/weeth/domain/attendance/application/dto/response/QrStatusResponse.kt
  • src/main/kotlin/com/weeth/domain/attendance/application/mapper/AttendanceMapper.kt
  • src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/GenerateQrTokenUseCase.kt
  • src/main/kotlin/com/weeth/domain/attendance/application/usecase/command/SubscribeAttendanceSseUseCase.kt
  • src/main/kotlin/com/weeth/domain/attendance/application/usecase/query/GetQrStatusQueryService.kt
  • src/main/kotlin/com/weeth/domain/attendance/domain/port/QrAttendancePort.kt
  • src/main/kotlin/com/weeth/domain/attendance/infrastructure/QrExpiredEventListener.kt
  • src/main/kotlin/com/weeth/domain/attendance/infrastructure/RedisQrAttendanceAdapter.kt
  • src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceController.kt
  • src/main/kotlin/com/weeth/domain/attendance/presentation/AttendanceResponseCode.kt
  • src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/GenerateQrTokenUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/attendance/application/usecase/command/SubscribeAttendanceSseUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/attendance/application/usecase/query/GetQrStatusQueryServiceTest.kt
  • src/test/kotlin/com/weeth/domain/attendance/infrastructure/QrExpiredEventListenerTest.kt
  • src/test/kotlin/com/weeth/domain/attendance/infrastructure/RedisQrAttendanceAdapterTest.kt

Copy link
Copy Markdown
Contributor

@hyxklee hyxklee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

궁금한 부분이 있어 한 가지 질문 달았습니다!
고생하셨습니당
그리고 SSE의 경우는 1. QR이 없다 2. QR이 생겼다 3. QR의 만료 시간 4. QR 만료 시간 업데이트 이렇게 4가지 케이스에 대해서 실시간으로 값을 넣어주고 있는거졍??

getAttendanceQueryService.findAllDetailsByCurrentCardinal(clubId, userId),
)

@GetMapping("/qr-status")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 해당 API의 사용 시나리오는 어떻게 되는걸까용??
SSE와 연동해서 설명해주시면 감사하겠습니닷

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. QR이 없다 -> qr-none 응답
  2. QR이 생겼다, 3. QR의 만료 시간 -> qr-open, expiredAt 응답
  3. QR 만료 시간 업데이트 -> QR을 다시 생성하면 Redis TTL이 갱신되고 다시 qr-open이 나가면서 새 expiredAt 응답
    으로 실시간으로 값을 넣어주고 있습니다!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 API는 SSE 이벤트가 아니라 프론트에서 요청한 현재 시점의 QR 활성 상태를 조회하는 API입니다!

사용 흐름은 아래와 같습니당

  1. 프론트에서 /qr-status 호출
  2. QR이 열려 있으면 isActive=true, currentSessionId, expiresAt으로 화면에 QR/만료시간 표시
  3. QR이 없으면 isActive=false로 QR 없음 상태 표시

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

회의록 요청사항을 보고했는데.. 폴링방식으로 사용할 때의 경우엿던 것 같습니당..............
해당 API는 지우고 머지하겟습니닷

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗 슬랙에서 말씀드린 부분만 했으면 됐는데 전달이 부족했었구뇽...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SSE는 제가 이해한 방식으로 처리되고 있는게 맞을까용??

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네네 맞습니당
API만 추가된거고 SSE 흐름은 위에 말씀드린 것과 동일합니다!

@soo0711 soo0711 removed the ✨ Feature 새로운 기능 추가 label May 18, 2026
@soo0711 soo0711 merged commit e21f198 into dev May 18, 2026
2 checks passed
@soo0711 soo0711 deleted the fix/WTH-389-SSE-연결-끊기는-문제-해결 branch May 18, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐞 BugFix 버그 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants