-
Notifications
You must be signed in to change notification settings - Fork 0
Feat: [FN-96][FN-97][FN-99][FN-110] 그룹 초대 기능 #29
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
Conversation
Walkthrough스케줄러와 Redis 기반 분산 락(ShedLock/Redisson) 의존성·설정이 추가되고, 그룹 초대에 만료(EXPIRED/expiredAt) 로직 및 일괄 만료 스케줄러가 도입되었습니다. 멤버 추가 책임이 GroupMemberPolicyService로 이동하고 이벤트/레포지토리/컨트롤러 시그니처 일부가 변경되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Cron as Spring Scheduler
participant ShedLock as ShedLock(Redis)
participant Scheduler as GroupInvitationExpireScheduler
participant Repo as GroupInvitationRepository
participant DB as Database
Cron->>Scheduler: runExpireJob()
Scheduler->>ShedLock: acquire lock "GroupInvitationExpireScheduler_ExpireJob"
alt lock acquired
Scheduler->>Repo: bulkExpire(now)
Repo->>DB: UPDATE ... SET status=EXPIRED WHERE status=PENDING AND expiredAt < now
DB-->>Repo: updatedCount
Repo-->>Scheduler: updatedCount
Scheduler->>ShedLock: release lock
else lock not acquired
ShedLock-->>Scheduler: skip execution
end
sequenceDiagram
autonumber
participant Service as GroupInvitationService
participant Repo as GroupInvitationRepository
participant Invitation as GroupInvitation
participant Policy as GroupMemberPolicyService
participant Group as Group
Service->>Repo: findByIdAndGroup_IdAndInviteeUserIdAndStatus(...)
Repo-->>Service: GroupInvitation
Service->>Invitation: validateNotExpired()
Service->>Policy: addGroupMember(inviteeUserId, groupId)
alt success or ALREADY_GROUP_MEMBER
Service->>Invitation: mark ACCEPTED
else other failure
Service->>Invitation: handle accordingly
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~35 minutes Possibly related PRs
Suggested reviewers
Poem
✨ 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
🧹 Nitpick comments (5)
src/main/java/project/flipnote/group/model/GroupInvitationStatus.java (1)
11-17: switch 기본절 제거로 매핑 누락을 컴파일 타임에 탐지하세요기본절이 있으면 새 enum 상수가 추가되어도 컴파일러가 경고하지 않습니다. 기본절을 제거하면 매핑 누락을 컴파일 타임에 강제할 수 있어 안전합니다.
다음과 같이 수정하세요:
- return switch (status) { - case PENDING -> PENDING; - case ACCEPTED -> ACCEPTED; - case REJECTED -> REJECTED; - case EXPIRED -> EXPIRED; - default -> throw new IllegalArgumentException("Unknown GroupInvitationStatus: " + status); - }; + return switch (status) { + case PENDING -> PENDING; + case ACCEPTED -> ACCEPTED; + case REJECTED -> REJECTED; + case EXPIRED -> EXPIRED; + };추가로, Line 7의 null 체크는
Objects.requireNonNull(status, "...")로 단순화할 수 있습니다.src/main/java/project/flipnote/group/entity/Group.java (1)
28-29: 상수 중복 사용 리스크: 상수의 단일 소스화 고려현재 100 상수는 GroupService.validateMaxMember(>100), GroupJoinService 등 여러 위치에서 하드코딩 혹은 상이한 비교로 사용됩니다. 본 상수를 단일 소스로 노출(예: 도메인 공용 상수 or Config)하거나, 서비스/검증 로직에서 본 상수를 참조하도록 일원화하면 유지보수성이 향상됩니다.
src/main/java/project/flipnote/group/repository/GroupInvitationRepository.java (1)
58-64: 대량 만료 성능 최적화: 인덱스 추가 검토(STATUS, EXPIRED_AT).해당 벌크 업데이트는 조건절에 status, expiredAt을 사용하므로 (status, expired_at) 복합 인덱스가 있으면 MySQL에서 스캔 비용을 크게 줄일 수 있습니다. 현 테이블 인덱스 목록에는 expired_at 관련 인덱스가 없어 보입니다.
DDL 예시:
- MySQL
- CREATE INDEX idx_status_expired_at ON group_invitation (status, expired_at);
- Postgres
- CREATE INDEX idx_status_expired_at ON group_invitation (status, expired_at);
src/main/java/project/flipnote/group/scheduler/GroupInvitationExpireScheduler.java (1)
34-37: 대량 업데이트 예외/모니터링 보강 제안.배치로 리팩토링 전까지는 만료 건수에 대한 메트릭 수집(Micrometer 카운터, 로그 구조화)과 예외 발생 시 경고 알림 연동(Slack/Webhook 등)을 권장합니다. 장애 시 초대 처리 누락/적체를 빠르게 감지할 수 있습니다.
원한다면 Micrometer MeterRegistry를 주입해 updatedCount를 Counter로 기록하는 코드 스니펫을 제공하겠습니다.
src/main/java/project/flipnote/group/entity/GroupInvitation.java (1)
75-76: 만료 기간 하드코딩(7일) -> 환경 설정으로 외부화 권장.DEFAULT_EXPIRATION_DAYS(7L)과 LocalDateTime.now().plusDays(...)는 정책 변경에 취약합니다. 환경 변수/설정값으로 주입받아 테스트 가능성과 운영 유연성을 높이는 것을 권장합니다.
예: @value("${group.invitation.expiration-days:7}") long expirationDays
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
build.gradle(1 hunks)src/main/java/project/flipnote/common/config/SchedulerConfig.java(1 hunks)src/main/java/project/flipnote/common/config/ShedLockConfig.java(1 hunks)src/main/java/project/flipnote/group/entity/Group.java(3 hunks)src/main/java/project/flipnote/group/entity/GroupInvitation.java(4 hunks)src/main/java/project/flipnote/group/entity/GroupInvitationStatus.java(1 hunks)src/main/java/project/flipnote/group/model/GroupInvitationStatus.java(2 hunks)src/main/java/project/flipnote/group/repository/GroupInvitationRepository.java(2 hunks)src/main/java/project/flipnote/group/scheduler/GroupInvitationExpireScheduler.java(1 hunks)src/main/java/project/flipnote/group/service/GroupInvitationService.java(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
src/main/java/project/flipnote/common/config/SchedulerConfig.java (1)
src/main/java/project/flipnote/common/config/ShedLockConfig.java (1)
Configuration(10-17)
src/main/java/project/flipnote/group/entity/GroupInvitationStatus.java (3)
src/main/java/project/flipnote/group/model/GroupInvitationResponseStatus.java (1)
GroupInvitationResponseStatus(3-5)src/main/java/project/flipnote/group/model/GroupInvitationRespondRequest.java (1)
GroupInvitationRespondRequest(6-17)src/main/java/project/flipnote/group/model/OutgoingGroupInvitationResponse.java (1)
OutgoingGroupInvitationResponse(9-32)
src/main/java/project/flipnote/common/config/ShedLockConfig.java (1)
src/main/java/project/flipnote/common/config/RedisConfig.java (2)
RedisConfig(10-21)tokenVersionRedisTemplate(13-20)
src/main/java/project/flipnote/group/repository/GroupInvitationRepository.java (3)
src/main/java/project/flipnote/group/controller/GroupInvitationQueryController.java (1)
RequiredArgsConstructor(20-51)src/main/java/project/flipnote/auth/repository/UserAuthRepository.java (1)
Modifying(32-34)src/main/java/project/flipnote/group/controller/docs/GroupInvitationQueryControllerDocs.java (1)
Tag(13-30)
src/main/java/project/flipnote/group/scheduler/GroupInvitationExpireScheduler.java (2)
src/main/java/project/flipnote/group/service/GroupInvitationService.java (1)
RequiredArgsConstructor(35-295)src/main/java/project/flipnote/group/listener/UserRegisteredEventListener.java (1)
Slf4j(16-37)
src/main/java/project/flipnote/group/entity/GroupInvitation.java (2)
src/main/java/project/flipnote/group/model/OutgoingGroupInvitationResponse.java (1)
OutgoingGroupInvitationResponse(9-32)src/main/java/project/flipnote/group/model/IncomingGroupInvitationResponse.java (1)
IncomingGroupInvitationResponse(9-26)
src/main/java/project/flipnote/group/entity/Group.java (4)
src/main/java/project/flipnote/groupjoin/service/GroupJoinService.java (2)
checkMaxMember(61-71)Transactional(161-205)src/main/java/project/flipnote/group/service/GroupService.java (1)
validateMaxMember(139-143)src/main/java/project/flipnote/groupjoin/entity/GroupJoin.java (1)
Getter(22-65)src/main/java/project/flipnote/group/entity/GroupMember.java (1)
Getter(20-48)
src/main/java/project/flipnote/group/model/GroupInvitationStatus.java (4)
src/main/java/project/flipnote/group/model/GroupInvitationResponseStatus.java (1)
GroupInvitationResponseStatus(3-5)src/main/java/project/flipnote/group/model/OutgoingGroupInvitationResponse.java (1)
OutgoingGroupInvitationResponse(9-32)src/main/java/project/flipnote/group/model/IncomingGroupInvitationResponse.java (1)
IncomingGroupInvitationResponse(9-26)src/main/java/project/flipnote/group/model/GroupInvitationRespondRequest.java (1)
GroupInvitationRespondRequest(6-17)
🔇 Additional comments (4)
src/main/java/project/flipnote/group/model/GroupInvitationStatus.java (1)
4-4: EXPIRED 추가에 따른 API/클라이언트 영향 검증 필요상태값 EXPIRED가 노출되는 모든 API 응답(예: Incoming/OutgoingGroupInvitationResponse)에 대해 클라이언트/문서/테스트가 새 상태를 처리하는지 확인이 필요합니다. 호환성 보장을 위해 e2e/컨트랙트 테스트 추가를 권장합니다.
src/main/java/project/flipnote/group/entity/GroupInvitationStatus.java (1)
4-4: GroupInvitation.status가 STRING으로 저장되고 있어 ORDINAL-shifting 이슈 없음 확인됨
src/main/java/project/flipnote/group/entity/GroupInvitation.java(63행):@Enumerated(EnumType.STRING)적용 확인따라서 EXPIRED 상수 추가로 인한 기존 데이터 손상 우려가 없습니다.
src/main/java/project/flipnote/common/config/SchedulerConfig.java (1)
6-9: 스케줄링 활성화 구성 LGTM스케줄러 활성화에 필요한 최소 설정이 명확합니다. ShedLock 구성과 함께 정상 동작할 것으로 보입니다.
src/main/java/project/flipnote/group/entity/GroupInvitation.java (1)
82-86: 만료 검증 시 에러 코드 의미 재검토.만료된 경우 INVITATION_NOT_FOUND를 던지는 것은 API 소비자 입장에서 혼란을 줄 수 있습니다. 도메인적으로 INVITATION_EXPIRED 등 보다 명확한 에러 코드가 적합해 보입니다.
해당 에러 코드가 존재한다면 아래처럼 변경을 검토해 주세요.
- throw new BizException(GroupInvitationErrorCode.INVITATION_NOT_FOUND); + throw new BizException(GroupInvitationErrorCode.INVITATION_EXPIRED);존재하지 않는다면, 새로운 에러 코드를 정의하는 것을 제안합니다.
src/main/java/project/flipnote/group/repository/GroupInvitationRepository.java
Outdated
Show resolved
Hide resolved
src/main/java/project/flipnote/group/scheduler/GroupInvitationExpireScheduler.java
Show resolved
Hide resolved
src/main/java/project/flipnote/group/service/GroupInvitationService.java
Show resolved
Hide resolved
src/main/java/project/flipnote/group/service/GroupInvitationService.java
Outdated
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: 9
🔭 Outside diff range comments (1)
src/main/java/project/flipnote/group/exception/GroupErrorCode.java (1)
12-20: GroupInvitation 도메인 ALREADY_GROUP_MEMBER 에러코드 전환 누락GroupInvitationService.java(222라인)에서 여전히
GroupErrorCode.ALREADY_GROUP_MEMBER를 사용 중입니다. invitation 전용 에러코드인GroupInvitationErrorCode.ALREADY_GROUP_MEMBER로 변경해야 합니다.
- src/main/java/project/flipnote/group/service/GroupInvitationService.java:222 –
GroupErrorCode.ALREADY_GROUP_MEMBER→GroupInvitationErrorCode.ALREADY_GROUP_MEMBER- src/main/java/project/flipnote/group/service/GroupMemberPolicyService.java:32 – 가입/권한 검증 로직으로,
GroupErrorCode.ALREADY_GROUP_MEMBER참조 유지 여부 검토
♻️ Duplicate comments (3)
src/main/java/project/flipnote/group/entity/Group.java (1)
22-26: 이전 리뷰 반영: isFull 의미 오류 제거 및 정원 검사 의미 명확화isFull/경계값 혼동 문제를 없애고, 정원 검증을 memberCount vs maxMember로 명확히 한 방향성은 적절합니다.
src/main/java/project/flipnote/group/entity/GroupInvitation.java (1)
65-67: DB 마이그레이션/백필 필수: expired_at NOT NULL 추가@column(nullable = false)로 expiredAt을 추가했습니다. 운영에 기존 데이터가 있다면 마이그레이션에서 DEFAULT/백필 없이는 실패하거나 NPE 위험이 있습니다.
다음 스크립트로 마이그레이션 파일에 expired_at 추가/백필이 있는지 확인해 주세요.
#!/bin/bash echo "== Search migration files for expiredAt/expired_at ==" fd -tf -H ".*\.(sql|yml|yaml|xml|java)" | rg -n "expiredAt|expired_at|group_invitation" -S -n || truesrc/main/java/project/flipnote/group/repository/GroupInvitationRepository.java (1)
35-40: 벌크 업데이트 후 1차 캐시 자동 동기화 설정 적절@Modifying(clearAutomatically = true, flushAutomatically = true) 적용 좋아요. 벌크 업데이트로 인한 1차 캐시 불일치 문제를 방지합니다. 트랜잭션은 호출 측(서비스/스케줄러)에서 관리하는 현재 구조가 적절합니다.
🧹 Nitpick comments (5)
src/main/java/project/flipnote/group/listener/UserRegisteredEventListener.java (2)
29-31: 메서드 네이밍 컨벤션 정렬 제안핸들러 메서드가 대문자로 시작합니다. 스프링 관례 및 자바 컨벤션에 맞춰 소문자 시작으로 변경을 권장합니다.
아래처럼 변경을 제안합니다.
- public void HandleUserRegisteredEvent(UserRegisteredEvent event) { + public void handleUserRegisteredEvent(UserRegisteredEvent event) {
23-31: Retry 범위 축소 고려현재 @retryable이 예외 제한 없이 적용되어 비회복성 예외에도 재시도가 발생할 수 있습니다. 네트워크/일시적 예외(예: DataAccessException 계열) 등으로 한정하는 것을 권장합니다.
원한다면 include/exclude 파라미터로 구체화하는 패치를 제공하겠습니다.
src/main/java/project/flipnote/group/entity/GroupInvitation.java (1)
44-45: 만료 기간 상수의 설정화 제안DEFAULT_EXPIRATION_DAYS를 하드코딩하기보다 환경설정(예: application.yml)로 외부화하면 운영 중 정책 변경이 용이합니다. Clock 주입(테스트 가능성 확보)도 고려해 보세요.
예: @value("${group.invitation.expiration-days:7}") Long expirationDays, 그리고 LocalDateTime.now() 대신 Clock 주입.
src/main/java/project/flipnote/group/service/GroupMemberPolicyService.java (1)
35-39: 불필요한 getReferenceById 호출 제거하고 이미 로드한 엔티티 재사용행 잠금으로 조회한 lockedGroup이 있으므로 다시 groupRepository.getReferenceById(groupId)를 호출할 필요가 없습니다. 중복 호출을 제거해 의도를 명확히 하세요.
GroupMember groupMember = GroupMember.builder() - .group(groupRepository.getReferenceById(groupId)) + .group(lockedGroup) .user(em.getReference(UserProfile.class, inviteeUserId)) .role(GroupMemberRole.MEMBER) .build();src/main/java/project/flipnote/group/service/GroupInvitationService.java (1)
130-154: 페이지 쿼리 튜닝 TODO 남김: Projection 도입 시 레이턴시 감소 기대현재 엔티티 매핑 후 DTO 변환 중 별도 사용자 닉네임 조회가 있습니다. Projection/DTO 조회로 전환하면 N+1 및 불필요한 필드 로딩을 줄일 수 있습니다. 인덱스는 엔티티에 정의되어 있어 조건과 정렬에 맞추면 효과적입니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
build.gradle(1 hunks)src/main/java/project/flipnote/auth/service/AuthService.java(1 hunks)src/main/java/project/flipnote/common/config/ShedLockConfig.java(1 hunks)src/main/java/project/flipnote/common/event/UserRegisteredEvent.java(0 hunks)src/main/java/project/flipnote/group/entity/Group.java(3 hunks)src/main/java/project/flipnote/group/entity/GroupInvitation.java(4 hunks)src/main/java/project/flipnote/group/exception/GroupErrorCode.java(1 hunks)src/main/java/project/flipnote/group/exception/GroupInvitationErrorCode.java(1 hunks)src/main/java/project/flipnote/group/listener/UserRegisteredEventListener.java(1 hunks)src/main/java/project/flipnote/group/repository/GroupInvitationRepository.java(2 hunks)src/main/java/project/flipnote/group/repository/GroupRepository.java(1 hunks)src/main/java/project/flipnote/group/scheduler/GroupInvitationExpireScheduler.java(1 hunks)src/main/java/project/flipnote/group/service/GroupInvitationService.java(5 hunks)src/main/java/project/flipnote/group/service/GroupMemberPolicyService.java(1 hunks)
💤 Files with no reviewable changes (1)
- src/main/java/project/flipnote/common/event/UserRegisteredEvent.java
🚧 Files skipped from review as they are similar to previous changes (3)
- build.gradle
- src/main/java/project/flipnote/common/config/ShedLockConfig.java
- src/main/java/project/flipnote/group/scheduler/GroupInvitationExpireScheduler.java
🧰 Additional context used
🧬 Code Graph Analysis (8)
src/main/java/project/flipnote/group/listener/UserRegisteredEventListener.java (1)
src/main/java/project/flipnote/common/event/UserRegisteredEvent.java (1)
UserRegisteredEvent(3-7)
src/main/java/project/flipnote/auth/service/AuthService.java (2)
src/main/java/project/flipnote/common/event/UserRegisteredEvent.java (1)
UserRegisteredEvent(3-7)src/main/java/project/flipnote/common/event/UserWithdrawnEvent.java (1)
UserWithdrawnEvent(3-6)
src/main/java/project/flipnote/group/exception/GroupInvitationErrorCode.java (4)
src/main/java/project/flipnote/group/entity/GroupInvitationStatus.java (1)
GroupInvitationStatus(3-5)src/main/java/project/flipnote/group/model/GroupInvitationResponseStatus.java (1)
GroupInvitationResponseStatus(3-5)src/main/java/project/flipnote/group/model/GroupInvitationStatus.java (1)
GroupInvitationStatus(3-18)src/main/java/project/flipnote/group/entity/GroupPermissionStatus.java (1)
GroupPermissionStatus(3-5)
src/main/java/project/flipnote/group/exception/GroupErrorCode.java (5)
src/main/java/project/flipnote/groupjoin/exception/GroupJoinErrorCode.java (2)
Getter(8-26)Override(22-25)src/main/java/project/flipnote/cardset/exception/CardSetErrorCode.java (1)
Getter(9-23)src/main/java/project/flipnote/groupjoin/service/GroupJoinService.java (1)
GroupJoinErrorCode(76-76)src/main/java/project/flipnote/group/service/GroupService.java (1)
GroupErrorCode(74-74)src/main/java/project/flipnote/common/exception/CommonErrorCode.java (1)
CommonErrorCode(8-17)
src/main/java/project/flipnote/group/repository/GroupRepository.java (3)
src/main/java/project/flipnote/group/repository/GroupMemberRepository.java (3)
Repository(13-23)findByGroup_IdAndUser_Id(21-21)findByGroupAndUser(15-15)src/main/java/project/flipnote/group/repository/GroupPermissionRepository.java (1)
Repository(9-12)src/main/java/project/flipnote/group/repository/GroupRolePermissionRepository.java (1)
Repository(11-14)
src/main/java/project/flipnote/group/entity/Group.java (1)
src/main/java/project/flipnote/groupjoin/service/GroupJoinService.java (1)
checkMaxMember(61-71)
src/main/java/project/flipnote/group/entity/GroupInvitation.java (2)
src/main/java/project/flipnote/group/model/OutgoingGroupInvitationResponse.java (1)
OutgoingGroupInvitationResponse(9-32)src/main/java/project/flipnote/group/controller/GroupInvitationQueryController.java (1)
RequiredArgsConstructor(20-51)
src/main/java/project/flipnote/group/service/GroupInvitationService.java (3)
src/main/java/project/flipnote/group/controller/docs/GroupInvitationControllerDocs.java (1)
Tag(13-28)src/main/java/project/flipnote/group/controller/GroupInvitationController.java (1)
PatchMapping(52-62)src/main/java/project/flipnote/groupjoin/service/GroupJoinService.java (1)
Slf4j(30-244)
🔇 Additional comments (7)
src/main/java/project/flipnote/group/exception/GroupInvitationErrorCode.java (1)
15-15: 검증 완료:ALREADY_GROUP_MEMBER잔존 참조 없음 확인
실행한 검색 결과GroupInvitationErrorCode.ALREADY_GROUP_MEMBER및GROUP_INVITATION_005에 대한 어떠한 참조도 발견되지 않았습니다. 문제 없이 코드 변경을 승인합니다.src/main/java/project/flipnote/group/exception/GroupErrorCode.java (1)
14-16: 그룹 정원/중복 멤버 에러코드 추가 적절합니다그룹 도메인에 속하는 정책 위반을 GroupErrorCode로 통합한 것은 일관성 측면에서 좋습니다. API 코드 스페이스도 깔끔해졌습니다.
src/main/java/project/flipnote/group/listener/UserRegisteredEventListener.java (1)
30-31: 레코드 시그니처 일치 확인 완료
UserRegisteredEvent가String email단일 파라미터로 정의되어 있어event.email()호출과 완벽히 일치합니다. 별도 수정 필요 없습니다.src/main/java/project/flipnote/auth/service/AuthService.java (2)
64-84: 이벤트 발행 시점 적절@transaction 내에서 발행하고, 리스너는 AFTER_COMMIT으로 처리하여 롤백 시 후속 작업이 실행되지 않도록 한 설계가 적절합니다.
81-81: UserRegisteredEvent 생성자 시그니처 일치 확인 완료
UserRegisteredEvent 레코드 정의를 확인한 결과, 단일String email매개변수를 가지므로new UserRegisteredEvent(email)호출과 완벽히 일치합니다. 추가 조치 필요 없습니다.src/main/java/project/flipnote/group/entity/GroupInvitation.java (1)
88-96: 만료 판정/노출 로직 일관성 확보되어 좋습니다PENDING 상태에서만 시간 기반 만료를 적용하고, getStatus가 EXPIRED를 적절히 매핑하는 현재 구현은 이전 지적사항을 잘 반영합니다.
Also applies to: 98-103
src/main/java/project/flipnote/group/service/GroupInvitationService.java (1)
106-113: 만료 검증 추가는 적절함수락 로직 시작 전에 validateNotExpired 호출로 만료 응답을 차단하는 흐름은 올바릅니다.
| ALREADY_INVITED(HttpStatus.CONFLICT, "GROUP_INVITATION_001", "이미 초대된 사용자입니다."), | ||
| NO_INVITATION_PERMISSION(HttpStatus.FORBIDDEN, "GROUP_INVITATION_002", "해당 그룹에 초대할 권한이 없습니다."), | ||
| INVITATION_NOT_FOUND(HttpStatus.NOT_FOUND, "GROUP_INVITATION_003", "유효하지 않은 초대입니다."), | ||
| CANNOT_INVITE_SELF(HttpStatus.BAD_REQUEST, "GROUP_INVITATION_004", "본인을 초대할 수 없습니다."), | ||
| ALREADY_GROUP_MEMBER(HttpStatus.CONFLICT, "GROUP_INVITATION_005", "이미 그룹 회원입니다."); | ||
| CANNOT_INVITE_SELF(HttpStatus.BAD_REQUEST, "GROUP_INVITATION_004", "본인을 초대할 수 없습니다."); | ||
|
|
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
만료된 초대(Expired) 시나리오용 에러 코드를 추가하는 것이 명확합니다
초대 만료 로직이 도입되었다면, 만료 케이스를 NOT_FOUND로 뭉뚱그리기보다 전용 에러코드로 구분하는 편이 API 소비자 관점에서 명확합니다.
아래와 같이 초대 만료 전용 코드를 추가하는 것을 제안합니다.
public enum GroupInvitationErrorCode implements ErrorCode {
ALREADY_INVITED(HttpStatus.CONFLICT, "GROUP_INVITATION_001", "이미 초대된 사용자입니다."),
NO_INVITATION_PERMISSION(HttpStatus.FORBIDDEN, "GROUP_INVITATION_002", "해당 그룹에 초대할 권한이 없습니다."),
INVITATION_NOT_FOUND(HttpStatus.NOT_FOUND, "GROUP_INVITATION_003", "유효하지 않은 초대입니다."),
+ INVITATION_EXPIRED(HttpStatus.GONE, "GROUP_INVITATION_005", "만료된 초대입니다."),
CANNOT_INVITE_SELF(HttpStatus.BAD_REQUEST, "GROUP_INVITATION_004", "본인을 초대할 수 없습니다.");- 상태값은 410 GONE을 권장합니다. 클라이언트가 “만료됨”을 명확히 처리할 수 있습니다.
- 코드값은 기존 005가 비어 있으므로 재사용을 제안합니다.
📝 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.
| ALREADY_INVITED(HttpStatus.CONFLICT, "GROUP_INVITATION_001", "이미 초대된 사용자입니다."), | |
| NO_INVITATION_PERMISSION(HttpStatus.FORBIDDEN, "GROUP_INVITATION_002", "해당 그룹에 초대할 권한이 없습니다."), | |
| INVITATION_NOT_FOUND(HttpStatus.NOT_FOUND, "GROUP_INVITATION_003", "유효하지 않은 초대입니다."), | |
| CANNOT_INVITE_SELF(HttpStatus.BAD_REQUEST, "GROUP_INVITATION_004", "본인을 초대할 수 없습니다."), | |
| ALREADY_GROUP_MEMBER(HttpStatus.CONFLICT, "GROUP_INVITATION_005", "이미 그룹 회원입니다."); | |
| CANNOT_INVITE_SELF(HttpStatus.BAD_REQUEST, "GROUP_INVITATION_004", "본인을 초대할 수 없습니다."); | |
| public enum GroupInvitationErrorCode implements ErrorCode { | |
| ALREADY_INVITED(HttpStatus.CONFLICT, "GROUP_INVITATION_001", "이미 초대된 사용자입니다."), | |
| NO_INVITATION_PERMISSION(HttpStatus.FORBIDDEN, "GROUP_INVITATION_002", "해당 그룹에 초대할 권한이 없습니다."), | |
| INVITATION_NOT_FOUND(HttpStatus.NOT_FOUND, "GROUP_INVITATION_003", "유효하지 않은 초대입니다."), | |
| INVITATION_EXPIRED(HttpStatus.GONE, "GROUP_INVITATION_005", "만료된 초대입니다."), | |
| CANNOT_INVITE_SELF(HttpStatus.BAD_REQUEST, "GROUP_INVITATION_004", "본인을 초대할 수 없습니다."); |
🤖 Prompt for AI Agents
In src/main/java/project/flipnote/group/exception/GroupInvitationErrorCode.java
around lines 12 to 16, add a dedicated enum constant for expired invitations
instead of folding it into NOT_FOUND: declare
EXPIRED_INVITATION(HttpStatus.GONE, "GROUP_INVITATION_005", "초대가 만료되었습니다.") (use
HTTP 410 GONE and the unused code 005), place it with the other constants and
ensure commas/semicolon around the enum list remain correct so the enum
compiles.
src/main/java/project/flipnote/group/service/GroupInvitationService.java
Show resolved
Hide resolved
src/main/java/project/flipnote/group/service/GroupInvitationService.java
Show resolved
Hide resolved
| @Transactional | ||
| public void addGroupMember(Long inviteeUserId, Long groupId) { | ||
| Group lockedGroup = groupRepository.findByIdForUpdate(groupId) |
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
입력값 null 가드 추가
inviteeUserId가 null일 가능성(게스트 초대 자동 수락 플로우)이 있습니다. 조기에 방어(validation)하지 않으면 em.getReference에서 NPE/IAE 가능성이 있습니다.
아래처럼 간단한 가드를 추가하세요.
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.Assert;
@Transactional
public void addGroupMember(Long inviteeUserId, Long groupId) {
+ Assert.notNull(inviteeUserId, "inviteeUserId must not be null");🤖 Prompt for AI Agents
In src/main/java/project/flipnote/group/service/GroupMemberPolicyService.java
around lines 25-27, add an early null-guard for inviteeUserId before calling
em.getReference/findByIdForUpdate: check if inviteeUserId == null and handle it
(either return early/skip the auto-accept flow or throw a clear validation
exception such as IllegalArgumentException with a descriptive message), ensuring
no further code calls em.getReference or performs DB operations when
inviteeUserId is null.
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: 8
🔭 Outside diff range comments (2)
src/main/java/project/flipnote/group/entity/GroupInvitation.java (1)
30-41: Unique 제약이 모든 상태에 적용되어 재초대가 불가능해집니다 (부분 유니크로 전환 필요).
@UniqueConstraint(name = "uq_group_invitee_user", columnNames = {"group_id", "invitee_user_id"})와 이메일용 제약은 PENDING 외의 상태(ACCEPTED/REJECTED/EXPIRED)까지 막아, 한 번이라도 초대가 존재하면 이후 재초대를 영구적으로 방지합니다. 서비스 로직은 "PENDING 중복만" 방지하고 있어 DB 제약과 불일치하며, 실제로는 INSERT 시 무결성 예외가 터질 수 있습니다.권장:
- 엔티티의
uniqueConstraints는 제거- DB 레벨에서 부분 유니크 인덱스(예: PostgreSQL)로 PENDING 상태에만 유니크 적용
- 예) CREATE UNIQUE INDEX ... ON group_invitations(group_id, invitee_user_id) WHERE status = 'PENDING'
아래와 같이 엔티티에서 제약을 제거하고, Flyway 스크립트로 부분 유니크 인덱스를 추가하세요.
@Table( - name = "group_invitations", + name = "group_invitations", indexes = { @Index(name = "idx_group_invitee_user", columnList = "group_id, invitee_user_id, status"), @Index(name = "idx_group_invitee_email", columnList = "group_id, invitee_email, status"), @Index(name = "idx_invitee_user_status", columnList = "invitee_user_id, status"), @Index(name = "idx_invitee_email_status", columnList = "invitee_email, status") - }, - uniqueConstraints = { - @UniqueConstraint(name = "uq_group_invitee_user", columnNames = {"group_id", "invitee_user_id"}), - @UniqueConstraint(name = "uq_group_invitee_email", columnNames = {"group_id", "invitee_email"}) - } + } )추가로, 기존 테이블명이 바뀌었다면(아래 코멘트 참고) 마이그레이션에서 부분 유니크 인덱스를 정확한 테이블명에 맞춰 생성해야 합니다.
src/main/java/project/flipnote/group/service/GroupInvitationService.java (1)
83-91: 보안/권한 이슈: 다른 그룹의 초대를 삭제할 수 있습니다.현재 삭제는
findByIdAndStatus(...PENDING)만 확인하고 groupId 매칭을 확인하지 않습니다. 특정 그룹에 초대 권한이 있는 사용자가 다른 그룹의 초대를 삭제할 수 있게 됩니다.groupId조건을 포함한 조회로 교체하세요.-GroupInvitation invitation = groupInvitationRepository - .findByIdAndStatus(invitationId, GroupInvitationStatus.PENDING) +GroupInvitation invitation = groupInvitationRepository + .findByIdAndGroup_IdAndStatus(invitationId, groupId, GroupInvitationStatus.PENDING) .orElseThrow(() -> new BizException(GroupInvitationErrorCode.INVITATION_NOT_FOUND));Repository에 아래 메서드가 없다면 추가가 필요합니다.
Optional<GroupInvitation> findByIdAndGroup_IdAndStatus(Long id, Long groupId, GroupInvitationStatus status);
♻️ Duplicate comments (4)
src/main/java/project/flipnote/group/entity/GroupInvitation.java (1)
65-66: DB 마이그레이션/백필 필요: NOT NULL 컬럼 추가(expired_at).
@Column(nullable = false)로expiredAt이 추가되었습니다. 기존 데이터가 있으면 DEFAULT/백필 없이 NOT NULL 제약 추가 시 실패합니다. 마이그레이션에서 안전한 기본값 또는 백필을 반드시 포함하세요.확인을 위해 아래 스크립트를 실행해 마이그레이션 파일에서
expired_at관련 변경이 있는지 점검해 주세요.#!/bin/bash echo "== Search migration files for expired_at/backfill ==" fd -tf -H ".*\.(sql|yml|yaml|xml|java)" | rg -n "expiredAt|expired_at|group_invitations" -S -n || truesrc/main/java/project/flipnote/group/service/GroupMemberPolicyService.java (1)
31-33: 입력값 null 가드 누락: inviteeUserId가 null이면 런타임 오류 발생.회원가입 연동 자동 수락 플로우 등에서
inviteeUserId가 null일 수 있습니다. 조기 검증으로 방어하세요.import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; @Transactional public void addGroupMember(Long inviteeUserId, Long groupId) { - // TODO: AOP로 분산락 적용할 수 있도록 수정 예정 + // TODO: AOP로 분산락 적용할 수 있도록 수정 예정 + Assert.notNull(inviteeUserId, "inviteeUserId must not be null");src/main/java/project/flipnote/group/service/GroupInvitationService.java (2)
109-120: 수락 시 멤버 추가 실패의 멱등 처리 필요(ALREADY_GROUP_MEMBER 허용).이미 멤버인 경우 초대 수락은 멱등적으로 성공 처리하는 것이 일반적입니다. 현재는
addGroupMember에서ALREADY_GROUP_MEMBER가 발생하면 트랜잭션이 롤백됩니다.invitation.respond(req.toEntityStatus()); if (invitation.getStatus() == GroupInvitationStatus.ACCEPTED) { - groupMemberPolicyService.addGroupMember(inviteeUserId, groupId); + try { + groupMemberPolicyService.addGroupMember(inviteeUserId, groupId); + } catch (BizException e) { + // 접근자 이름은 실제 구현에 맞게 변경(getErrorCode 등) + if (e.getErrorCode() == GroupErrorCode.ALREADY_GROUP_MEMBER) { + // 멱등 허용: 이미 멤버면 초대만 ACCEPTED로 유지 + } else { + throw e; + } + } }
184-199: 게스트 자동 수락 로직의 심각한 오류: userId 누락 + 실패해도 ACCEPTED로 저장.
- 게스트 초대의
inviteeUserId는 null일 수 있어addGroupMember호출 시 실패합니다.- finally 블록에서 무조건
ACCEPTED로 바꿔 멤버 추가 실패(정원 초과 등)여도 ACCEPTED 저장 → 데이터 불일치/UX 혼란.회원가입 이메일로 유저를 조회해 userId를 확보하고, 멤버 추가 성공 시에만 ACCEPTED로 전환하세요. 이미 멤버 예외는 멱등 허용하되, 그 외 예외는 상태 유지(PENDING)로 두는 것이 안전합니다.
@Transactional public void acceptPendingInvitationsOnRegister(String inviteeEmail) { List<GroupInvitation> invitations = groupInvitationRepository .findAllByInviteeEmailAndStatus(inviteeEmail, GroupInvitationStatus.PENDING); - for (GroupInvitation invitation : invitations) { - if (invitation.isExpired()) { - continue; - } - - try { - groupMemberPolicyService.addGroupMember(invitation.getInviteeUserId(), invitation.getGroup().getId()); - } catch (Exception ignored) { - } finally { - invitation.respond(GroupInvitationStatus.ACCEPTED); - } - } + // 이메일로 가입된 사용자 조회 + Long inviteeUserId = userService.findActiveUserByEmail(inviteeEmail) + .map(UserProfile::getId) + .orElse(null); + if (inviteeUserId == null) { + // 가입 실패/비활성 등 예외 상황: 조용히 종료(또는 로깅) + return; + } + + for (GroupInvitation invitation : invitations) { + if (invitation.isExpired()) { + continue; + } + try { + groupMemberPolicyService.addGroupMember(inviteeUserId, invitation.getGroup().getId()); + invitation.respond(GroupInvitationStatus.ACCEPTED); + } catch (BizException e) { + if (e.getErrorCode() == GroupErrorCode.ALREADY_GROUP_MEMBER) { + // 멱등 허용 + invitation.respond(GroupInvitationStatus.ACCEPTED); + } + // 그 외(정원 초과 등)는 PENDING 유지 + } + } }
🧹 Nitpick comments (6)
src/main/java/project/flipnote/common/config/RedissonConfig.java (1)
14-24: @value 기반 문자열 파싱 대신 @ConfigurationProperties로의 전환 고려Redis 설정은 리스트(클러스터 노드), 옵션 유무 등 타입이 다양합니다.
@ConfigurationProperties(prefix = "spring.data.redis")로 타입 세이프한 바인딩을 사용하면 split/trim 로직을 제거하고, yml의 리스트 문법도 자연스럽게 지원합니다. 유지보수성과 테스트 용이성이 크게 좋아집니다.원하시면
RedissonProperties와RedissonConfig리팩토링 패치를 작성해드리겠습니다.src/main/java/project/flipnote/common/exception/CommonErrorCode.java (1)
12-13: 429 SERVICE_TEMPORARILY_UNAVAILABLE 코드 추가 적절잠시 후 재시도 유도 시나리오(락 획득 실패 등)에 적합한 상태/메시지 구성이며, 도메인 서비스에서의 활용 의도가 명확합니다. 다만, 현재 CommonErrorCode는
int status를 사용하고, 다른 도메인 에러코드는HttpStatus필드를 보관하는 등 스타일이 혼재되어 있습니다. 장기적으로는 통일을 권장합니다.src/main/java/project/flipnote/group/entity/GroupInvitation.java (2)
75-76: 만료 시점 계산을 엔티티에서 직접 하지 않도록 리팩터링 제안.
LocalDateTime.now()는 테스트 어려움/타임존 이슈를 유발합니다. Clock 주입 또는 서비스 계층에서 만료 시점을 계산해 전달하는 방식으로 분리하는 것을 권장합니다. 또한DEFAULT_EXPIRATION_DAYS를 설정값으로 외부화하면 운영 중 정책 변경이 용이합니다.
82-86: 만료된 초대를 ‘NOT_FOUND’로 매핑하는 정책 재확인 요청.만료 시
INVITATION_NOT_FOUND로 예외를 던집니다. 의도적으로 존재 여부를 감추는 정책이라면 OK지만, 클라이언트가 “만료됨”을 구분해야 한다면 별도 에러 코드가 더 명확합니다. 요구사항 의도 재확인 바랍니다.src/main/java/project/flipnote/group/service/GroupMemberPolicyService.java (2)
39-43: Redisson tryLock leaseTime이 너무 짧아 조기 해제 위험.
tryLock(2, 3, SECONDS)는 3초 후 락이 자동 해제됩니다. 처리 시간이 3초를 초과하면 다른 노드가 락을 재획득할 수 있습니다. 현재는 DB 행 잠금으로 2차 방어가 되지만(좋음), 분산락 목적을 약화합니다. Watchdog 사용 또는 충분히 긴 leaseTime으로 조정하세요.- isLocked = lock.tryLock(2, 3, TimeUnit.SECONDS); + // 권장 1: watchdog 사용(leaseTime 생략) + isLocked = lock.tryLock(2, TimeUnit.SECONDS); + // 권장 2: 혹은 처리 시간 상한을 고려해 leaseTime을 넉넉히 설정 + // isLocked = lock.tryLock(2, 30, TimeUnit.SECONDS);
34-36: 락 키 프리픽스 상수화 제안."문자열 상수" 대신 상수 필드로 관리하면 오타를 줄이고 재사용성이 좋아집니다.
-public void addGroupMember(Long inviteeUserId, Long groupId) { - // TODO: AOP로 분산락 적용할 수 있도록 수정 예정 - String lockKey = "group_member_lock:" + groupId; +private static final String GROUP_MEMBER_LOCK_PREFIX = "group_member_lock:"; +public void addGroupMember(Long inviteeUserId, Long groupId) { + // TODO: AOP로 분산락 적용할 수 있도록 수정 예정 + String lockKey = GROUP_MEMBER_LOCK_PREFIX + groupId;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/main/java/project/flipnote/common/config/RedissonConfig.java(1 hunks)src/main/java/project/flipnote/common/exception/CommonErrorCode.java(1 hunks)src/main/java/project/flipnote/group/controller/GroupInvitationQueryController.java(2 hunks)src/main/java/project/flipnote/group/controller/docs/GroupInvitationQueryControllerDocs.java(2 hunks)src/main/java/project/flipnote/group/entity/GroupInvitation.java(4 hunks)src/main/java/project/flipnote/group/repository/GroupRepository.java(1 hunks)src/main/java/project/flipnote/group/service/GroupInvitationService.java(6 hunks)src/main/java/project/flipnote/group/service/GroupMemberPolicyService.java(1 hunks)src/main/resources/application.yml(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/project/flipnote/group/repository/GroupRepository.java
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/main/java/project/flipnote/group/service/GroupMemberPolicyService.java (2)
src/main/java/project/flipnote/groupjoin/service/GroupJoinService.java (2)
Transactional(161-205)Transactional(100-135)src/main/java/project/flipnote/group/service/GroupService.java (1)
Transactional(51-70)
src/main/java/project/flipnote/common/exception/CommonErrorCode.java (7)
src/main/java/project/flipnote/common/exception/ErrorCode.java (1)
ErrorCode(3-10)src/main/java/project/flipnote/group/exception/GroupErrorCode.java (2)
Getter(10-25)Override(21-24)src/main/java/project/flipnote/group/exception/GroupInvitationErrorCode.java (2)
Getter(10-27)Override(23-26)src/main/java/project/flipnote/user/exception/UserErrorCode.java (1)
Getter(9-24)src/main/java/project/flipnote/auth/exception/AuthErrorCode.java (1)
Getter(9-36)src/main/java/project/flipnote/cardset/exception/CardSetErrorCode.java (1)
Getter(9-23)src/main/java/project/flipnote/groupjoin/exception/GroupJoinErrorCode.java (1)
Getter(8-26)
src/main/java/project/flipnote/group/service/GroupInvitationService.java (2)
src/main/java/project/flipnote/group/repository/GroupInvitationRepository.java (1)
GroupInvitationRepository(15-53)src/main/java/project/flipnote/group/controller/docs/GroupInvitationControllerDocs.java (1)
Tag(13-28)
src/main/java/project/flipnote/group/entity/GroupInvitation.java (6)
src/main/java/project/flipnote/group/entity/Group.java (1)
Getter(22-89)src/main/java/project/flipnote/group/repository/GroupInvitationRepository.java (1)
GroupInvitationRepository(15-53)src/main/java/project/flipnote/group/controller/docs/GroupInvitationControllerDocs.java (1)
Tag(13-28)src/main/java/project/flipnote/group/model/GroupInvitationCreateResponse.java (1)
GroupInvitationCreateResponse(3-6)src/main/java/project/flipnote/group/model/OutgoingGroupInvitationResponse.java (1)
OutgoingGroupInvitationResponse(9-32)src/main/java/project/flipnote/group/controller/GroupInvitationController.java (1)
RequiredArgsConstructor(23-63)
🔇 Additional comments (4)
src/main/java/project/flipnote/group/entity/GroupInvitation.java (1)
88-103: 만료 판정/상태 노출 로직 LGTM.
- PENDING 상태에만 시간 기반 만료 적용
- EXPIRED 상태는 즉시 만료로 간주
- getStatus에서 일관되게 EXPIRED 반영
이전 리뷰 피드백이 잘 반영되었습니다.
src/main/java/project/flipnote/group/service/GroupInvitationService.java (3)
53-71: 자기 자신 초대 방지/분기 LGTM.이메일 동일성 체크로 자기 자신 초대를 막고, 유저/게스트 분기 처리도 명확합니다.
223-227: 이미 그룹 멤버인 사용자 초대 시 에러 코드 일관성 LGTM.
GroupErrorCode.ALREADY_GROUP_MEMBER사용으로 의미가 명확합니다.
109-113: 확인 완료:GroupInvitationRepository에findByIdAndGroup_IdAndInviteeUserIdAndStatus메서드가 정의되어 있어 문제없습니다.
src/main/java/project/flipnote/common/config/RedissonConfig.java
Outdated
Show resolved
Hide resolved
src/main/java/project/flipnote/group/controller/docs/GroupInvitationQueryControllerDocs.java
Show resolved
Hide resolved
src/main/java/project/flipnote/group/controller/GroupInvitationQueryController.java
Show resolved
Hide resolved
src/main/java/project/flipnote/group/service/GroupMemberPolicyService.java
Show resolved
Hide resolved
src/main/java/project/flipnote/group/service/GroupMemberPolicyService.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: 3
♻️ Duplicate comments (3)
src/main/java/project/flipnote/group/service/GroupInvitationService.java (2)
109-114: 레포지토리 메서드 존재 여부와 정원 동시성 가드 확인 필요
- findByIdAndGroup_IdAndInviteeUserIdAndStatus(...) 메서드가 실제 레포지토리에 선언돼 있는지 확인이 필요합니다. 기존에는 findWithGroupBy... JPQL을 사용했습니다. 선언 누락 시 런타임/컴파일 오류가 납니다.
- 정원(capacity) 초과 방지는 GroupMemberPolicyService.addGroupMember 내부에서 분산락/행락과 함께 수행된다는 전제일 텐데, 해당 보장이 없다면 이전에 지적된 동시성 이슈가 재발할 수 있습니다. 서비스 내부에서 그룹 행 잠금 + 현재 멤버 수 카운트를 확인하고 있는지 점검해 주세요.
다음 스크립트로 확인해 보세요.
#!/bin/bash # 레포지토리 메서드 존재 여부 fd -a GroupInvitationRepository.java | xargs -I{} rg -n "findByIdAndGroup_IdAndInviteeUserIdAndStatus\\(" {} fd -a GroupMemberPolicyService.java | xargs -I{} rg -n "class GroupMemberPolicyService|addGroupMember\\(|Lock|Redisson|ShedLock|synchronized|@Lock" -n -A 20 {}
191-209: 치명적: 회원가입 시 대기 초대 자동 수락에서 inviteeUserId 누락 가능 + 광범위 예외 삼키기
- guest 초대(이메일 기반)의 경우 invitation.getInviteeUserId()는 null일 수 있습니다. 현재 구현은 null을 그대로 addGroupMember에 전달하여 NPE/IAE를 유발할 수 있습니다.
- catch (Exception ignored)로 모든 예외를 삼키면, 실패 원인을 추적할 수 없고 상태도 PENDING으로 남아 재시도 판단이 어려워집니다. 멱등 허용(ALREADY_GROUP_MEMBER)은 허용하되 그 외는 상태 유지 + 로깅이 바람직합니다.
- 추가로, 이 시점에서 이메일로 가입한 실제 userId를 조회해 사용해야 합니다.
아래와 같이 수정하세요.
@Transactional - public void acceptPendingInvitationsOnRegister(String inviteeEmail) { - List<GroupInvitation> invitations = groupInvitationRepository - .findAllByInviteeEmailAndStatus(inviteeEmail, GroupInvitationStatus.PENDING); - - for (GroupInvitation invitation : invitations) { - if (invitation.isExpired()) { - continue; - } - - try { - groupMemberPolicyService.addGroupMember(invitation.getInviteeUserId(), invitation.getGroup().getId()); - invitation.respond(GroupInvitationStatus.ACCEPTED); - } catch (BizException ex) { - if (ex.getErrorCode() == GroupErrorCode.ALREADY_GROUP_MEMBER) { - invitation.respond(GroupInvitationStatus.ACCEPTED); - } - } catch (Exception ignored) { } - } - } + public void acceptPendingInvitationsOnRegister(String inviteeEmail) { + List<GroupInvitation> invitations = groupInvitationRepository + .findAllByInviteeEmailAndStatus(inviteeEmail, GroupInvitationStatus.PENDING); + + // 이메일로 가입한 사용자 ID를 확정 + Long inviteeUserId = userService.findActiveUserByEmail(inviteeEmail) + .map(UserProfile::getId) + .orElse(null); + if (inviteeUserId == null) { + return; // 방어적 종료(정상적 환경에서는 거의 발생하지 않음) + } + + for (GroupInvitation invitation : invitations) { + if (invitation.isExpired()) { + continue; + } + + try { + groupMemberPolicyService.addGroupMember(inviteeUserId, invitation.getGroup().getId()); + invitation.respond(GroupInvitationStatus.ACCEPTED); + } catch (BizException ex) { + // 이미 멤버인 경우는 멱등 허용 + if (ex.getErrorCode() == GroupErrorCode.ALREADY_GROUP_MEMBER) { + invitation.respond(GroupInvitationStatus.ACCEPTED); + } else { + // 그 외(정원 초과/락 타임아웃 등) 실패는 상태 유지(PENDING)하여 재시도 가능 + // TODO: 적절한 로깅/모니터링 추가 + } + } + } + }부가 제안:
- 가능하다면 초대 엔티티에 inviteeUserId를 바인딩하는 도메인 메서드(ex. bindInviteeUser(Long userId))를 도입해, guest 초대가 ACCEPTED 되는 시점에 userId를 세팅해 두면 데이터 일관성이 좋아집니다.
src/main/java/project/flipnote/group/entity/Group.java (1)
88-89: 그룹 생성 시 memberCount=1 초기화는 실제 멤버 테이블과 불일치 위험현재 생성자에서 memberCount를 1로 초기화하지만, GroupService.create(...)에서 오너 멤버를 즉시 생성/저장하지 않습니다. 이 경우 “멤버는 0명인데 카운터는 1”인 드리프트가 시작 시점부터 발생합니다. memberCount 초기값을 0으로 두고, 오너 저장 직후 정책 흐름(락 + 카운터 증가)으로 1로 맞추는 방식을 권장합니다.
권장 수정(엔티티):
- this.memberCount = 1; + this.memberCount = 0;권장 수정(서비스; 파일 외 참고): 그룹 생성 직후 오너를 추가하고 카운터를 증가시키세요. 최소 변경으로는 기존 saveGroupOwner를 호출하고 바로 증가시키는 방법이 있습니다.
// src/main/java/project/flipnote/group/service/GroupService.java (참고) @Transactional public GroupCreateResponse create(AuthPrinciple authPrinciple, GroupCreateRequest req) { UserProfile userProfile = findUser(authPrinciple); validateMaxMember(req.maxMember()); Group group = createGroup(req); // 오너 멤버 저장 + 카운터 증가(잠금/정책 서비스가 있다면 그 경로로 일원화 권장) saveGroupOwner(group, userProfile); group.increaseMemberCount(); initializeGroupPermissions(group); return GroupCreateResponse.from(group.getId()); }가능하면 GroupMemberPolicyService를 통한 “잠금 + 멤버 추가 + 카운터 증가” 경로로 일원화해 일관성을 보장해 주세요.
🧹 Nitpick comments (1)
src/main/java/project/flipnote/group/entity/Group.java (1)
97-100: 증가 메서드 내 사전 검증 포함 OK. 감소 메서드 추가 제안increaseMemberCount에 사전 검증을 내장한 점은 좋습니다. 짝을 이루는 decreaseMemberCount(하한 0 가드)도 마련하면 멤버 탈퇴/강퇴 플로우에서 도메인 정합성 유지가 쉬워집니다. validateJoinable는 도메인 내부에서만 쓰인다면 가시성 축소(private)도 검토해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
build.gradle(1 hunks)src/main/java/project/flipnote/group/entity/Group.java(3 hunks)src/main/java/project/flipnote/group/repository/GroupRepository.java(1 hunks)src/main/java/project/flipnote/group/service/GroupInvitationService.java(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- build.gradle
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/main/java/project/flipnote/group/service/GroupInvitationService.java (3)
src/main/java/project/flipnote/group/repository/GroupInvitationRepository.java (1)
GroupInvitationRepository(15-53)src/main/java/project/flipnote/group/entity/GroupInvitation.java (1)
Getter(21-70)src/main/java/project/flipnote/group/controller/docs/GroupInvitationControllerDocs.java (1)
Tag(13-28)
src/main/java/project/flipnote/group/entity/Group.java (3)
src/main/java/project/flipnote/group/entity/GroupMember.java (1)
Getter(20-48)src/main/java/project/flipnote/groupjoin/service/GroupJoinService.java (1)
checkMaxMember(61-71)src/main/java/project/flipnote/group/service/GroupService.java (1)
Slf4j(31-145)
src/main/java/project/flipnote/group/repository/GroupRepository.java (1)
src/main/java/project/flipnote/group/repository/GroupInvitationRepository.java (2)
GroupInvitationRepository(15-53)Query(19-33)
🔇 Additional comments (10)
src/main/java/project/flipnote/group/service/GroupInvitationService.java (6)
19-19: 에러 코드 import 전환 적절함멤버십 관련 중복 초대/가입 상태는 Group 도메인의 책임이므로 GroupErrorCode로 전환한 결정이 타당합니다.
42-42: 멤버 추가 책임의 단일화 방향 LGTMGroupMemberPolicyService 주입을 통해 멤버 추가 정책과 동시성 제어를 한 곳으로 모은 방향이 좋습니다. 이 서비스가 트랜잭션 경계/락/정원 검증을 보장하도록 유지해 주세요.
118-126: ALREADY_GROUP_MEMBER 멱등 허용 처리 적절수락 시 이미 멤버인 경우를 예외 삼키고 진행하는 멱등 처리 방향이 UX/운영 측면에서 적절합니다. 트랜잭션 롤백 조건도 명확합니다.
234-234: ALREADY_GROUP_MEMBER 에러 코드 도메인 정합성 개선이미 그룹 멤버 여부는 Group 도메인의 규칙이므로 GroupErrorCode로 통일한 점 좋습니다.
193-193: 레포지토리 파생 쿼리 메서드 존재 확인 완료
- 파일: src/main/java/project/flipnote/group/repository/GroupInvitationRepository.java
위치: 27행
선언:List<GroupInvitation> findAllByInviteeEmailAndStatus(String inviteeEmail, GroupInvitationStatus status);해당 메서드가 정상적으로 선언되어 있어 런타임 오류 우려가 없습니다.
53-57: 영향 범위 확인 완료createGroupInvitation 메소드의 파라미터 순서 변경에 맞추어 호출부가 모두 업데이트되어 있어 컴파일/런타임 오류가 발생하지 않습니다.
- src/main/java/project/flipnote/group/controller/GroupInvitationController.java:36
- src/main/java/project/flipnote/group/controller/docs/GroupInvitationControllerDocs.java:17
src/main/java/project/flipnote/group/entity/Group.java (1)
91-95: 용량 체크 로직 자체는 타당함memberCount >= maxMember 시 예외를 던지는 현재 로직은 의도에 부합합니다. 잠금 하에서 호출된다는 전제라면 정합성 유지에 충분합니다.
src/main/java/project/flipnote/group/repository/GroupRepository.java (3)
3-4: Optional 임포트 추가는 무해Optional 임포트 추가는 문제 없습니다.
17-18: Soft-delete 필터 메서드 시그니처 OKfindByIdAndDeletedAtIsNull 시그니처는 의도에 부합합니다.
19-22: @param 임포트 패키지 수정 및 트랜잭션 경계 확인 결과
- 잘못된
@Param임포트가 확인되었습니다.
파일:src/main/java/project/flipnote/group/repository/GroupRepository.java(라인 10)
기존io.lettuce.core.dynamic.annotation.Param→org.springframework.data.repository.query.Param로 교체 필요findByIdForUpdate호출부(GroupJoinService,GroupMemberPolicyService)에는@Transactional(readOnly = true)설정이 없으므로, 현재는 쓰기 가능한 트랜잭션 경계 내에서 호출되고 있습니다.--- a/src/main/java/project/flipnote/group/repository/GroupRepository.java +++ b/src/main/java/project/flipnote/group/repository/GroupRepository.java @@ -10,7 +10,7 @@ -import io.lettuce.core.dynamic.annotation.Param; +import org.springframework.data.repository.query.Param; @Lock(LockModeType.PESSIMISTIC_WRITE) @Query("select g from Group g where g.id = :id")Likely an incorrect or invalid review comment.
src/main/java/project/flipnote/group/repository/GroupRepository.java
Outdated
Show resolved
Hide resolved
src/main/java/project/flipnote/group/service/GroupInvitationService.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: 4
♻️ Duplicate comments (1)
src/main/java/project/flipnote/common/config/RedissonConfig.java (1)
11-11: 이전 리뷰 반영 완료 — Spring StringUtils 사용으로 의존성 정합성 개선Thymeleaf StringUtils 의존을 제거하고 Spring의 StringUtils로 교체한 점 좋습니다. 설정 레이어에서의 불필요한 뷰 레이어 의존이 제거되었습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
.github/workflows/ci.yml(1 hunks)src/main/java/project/flipnote/common/config/RedissonConfig.java(1 hunks)src/main/java/project/flipnote/group/repository/GroupRepository.java(1 hunks)src/main/java/project/flipnote/group/service/GroupMemberPolicyService.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/java/project/flipnote/group/repository/GroupRepository.java
- src/main/java/project/flipnote/group/service/GroupMemberPolicyService.java
🧰 Additional context used
🪛 YAMLlint (1.37.1)
.github/workflows/ci.yml
[warning] 37-37: wrong indentation: expected 8 but found 10
(indentation)
⏰ 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
| services: | ||
| redis: | ||
| image: redis:7.0-alpine | ||
| ports: | ||
| - 6379:6379 | ||
| options: >- | ||
| --health-cmd "redis-cli ping" | ||
| --health-interval 10s | ||
| --health-timeout 5s | ||
| --health-retries 5 |
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.
💡 Verification agent
🧩 Analysis chain
테스트 프로파일의 Redis 설정과 CI의 Redis 서비스가 일치하는지 확인 필요
CI에서는 비밀번호 없이 localhost:6379로 접근 가능한 Redis를 띄웁니다. 애플리케이션의 테스트 프로파일(application-test.yml 등)이 동일한 호스트/포트/비번 없이 접속하도록 설정돼 있는지 확인하세요. 또한 일부 라이브러리는 spring.redis.* 프리픽스를, 현재 RedissonConfig는 spring.data.redis.* 프리픽스를 사용합니다. 설정 키 불일치 시 접속 실패가 발생할 수 있습니다.
다음 스크립트로 리소스 내 Redis 설정 키를 점검해 주세요.
🏁 Script executed:
#!/bin/bash
# 리소스에서 Redis 관련 설정 키/값을 검색
echo "Searching for Redis properties under src/main/resources and test resources..."
fd -t f -a | grep -E 'src/.*/resources/.*\.(ya?ml|properties)$' | while read -r f; do
echo "---- $f"
rg -n -A 1 -e 'spring\.data\.redis\.(host|port|password|cluster\.nodes|ssl)' -e 'spring\.redis\.(host|port|password|cluster\.nodes|ssl)' "$f" || true
done
echo
echo "Checking for application-test profile existence..."
fd -t f -a | grep -E 'src/.*/resources/application(-test)?\.(ya?ml|properties)$' || trueLength of output: 1250
테스트 프로파일에 Redis 설정 누락됨 – CI 서비스와 동기화 필요
현재 CI는 비밀번호 없이 localhost:6379로 Redis를 실행하지만, 리포지토리의 설정 파일에는 spring.data.redis.* 또는 spring.redis.* 관련 키가 전혀 없습니다. 또한 RedissonConfig는 spring.data.redis.* 프리픽스를 사용 중이므로, 테스트 프로파일에 명시적인 설정이 반드시 필요합니다.
조치 사항:
- src/test/resources/application-test.yml (필요 시 src/main/resources/application.yml) 에 아래와 같이 Redis 접속 정보를 추가
spring: data: redis: host: localhost port: 6379 # 비밀번호가 없으면 빈 문자열로 지정 password: ""
- 또는 애플리케이션 전반에서 사용하는 프리픽스가 spring.redis.* 인 경우, RedissonConfig 쪽을 spring.redis.* 로 통일
파일 위치:
- src/test/resources/application-test.yml
- (필요 시) src/main/resources/application.yml
🧰 Tools
🪛 YAMLlint (1.37.1)
[warning] 37-37: wrong indentation: expected 8 but found 10
(indentation)
🤖 Prompt for AI Agents
.github/workflows/ci.yml around lines 33 to 42: CI starts Redis on
localhost:6379 without auth but the project lacks test Redis properties and
RedissonConfig expects spring.data.redis.*; add explicit Redis settings to the
test profile or unify prefixes. Add spring.data.redis.host: localhost,
spring.data.redis.port: 6379 and spring.data.redis.password: "" to
src/test/resources/application-test.yml (or to
src/main/resources/application.yml if needed), or alternatively change
RedissonConfig to use spring.redis.* everywhere to match existing app
properties; ensure keys and prefix naming in config and code match exactly.
Redis 서비스 추가는 적절합니다만, YAML 들여쓰기 경고와 포트 매핑 들여쓰기 수정이 필요합니다
- YAMLlint가 지적한 대로 ports 항목의 리스트 들여쓰기가 과합니다. 깃허브 액션스 파서에 따라 무시될 수도 있지만, 린트 에러로 CI가 실패할 수 있으니 수정 권장합니다.
- 기능 측면에서는 Redis 서비스 정의, healthcheck, 포트 매핑 모두 적절합니다. 테스트/빌드가 러너(호스트)에서 직접 Redis에 접속한다면 ports 매핑은 유지하는 것이 맞습니다.
아래처럼 ports의 리스트 들여쓰기를 2칸 줄여주세요.
ports:
- - 6379:6379
+ - 6379:6379📝 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.
| services: | |
| redis: | |
| image: redis:7.0-alpine | |
| ports: | |
| - 6379:6379 | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| services: | |
| redis: | |
| image: redis:7.0-alpine | |
| ports: | |
| - 6379:6379 | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 |
🧰 Tools
🪛 YAMLlint (1.37.1)
[warning] 37-37: wrong indentation: expected 8 but found 10
(indentation)
🤖 Prompt for AI Agents
.github/workflows/ci.yml around lines 33 to 42: YAML lint warns about excessive
indentation for the ports list; reduce the indentation of the ports list items
by two spaces so the dash for "- 6379:6379" is aligned under "ports:" as a
proper YAML sequence item, preserving the existing redis service, image,
healthcheck options and port mapping but fixing the list indentation to satisfy
linters and avoid CI failures.
| @Value("${spring.data.redis.host}") | ||
| private String host; | ||
|
|
||
| @Value("${spring.data.redis.port}") | ||
| private int port; | ||
|
|
||
| @Value("${spring.data.redis.password:}") | ||
| private String password; | ||
|
|
||
| @Value("${spring.data.redis.cluster.nodes:}") | ||
| private String clusterNodes; | ||
|
|
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
spring.redis.와 spring.data.redis. 동시 지원 + SSL 플래그 추가 제안
현재 모든 프로퍼티가 spring.data.redis.* 프리픽스를 사용합니다. Spring Boot와 서드파티 오토컨피그(예: Spring Data Redis, ShedLock의 RedisLockProvider)는 일반적으로 spring.redis.*를 기본으로 사용합니다. 양쪽 프리픽스를 모두 지원하면 설정 실수를 줄이고 호환성을 높일 수 있습니다. 또한 TLS 환경을 고려해 ssl 플래그도 주입받을 것을 권장합니다.
아래처럼 키 폴백과 ssl 플래그를 추가해 주세요.
- @Value("${spring.data.redis.host}")
+ @Value("${spring.redis.host:${spring.data.redis.host}}")
private String host;
- @Value("${spring.data.redis.port}")
+ @Value("${spring.redis.port:${spring.data.redis.port}}")
private int port;
- @Value("${spring.data.redis.password:}")
+ @Value("${spring.redis.password:${spring.data.redis.password:}}")
private String password;
- @Value("${spring.data.redis.cluster.nodes:}")
+ @Value("${spring.redis.cluster.nodes:${spring.data.redis.cluster.nodes:}}")
private String clusterNodes;
+
+ @Value("${spring.redis.ssl:${spring.data.redis.ssl:false}}")
+ private boolean ssl;📝 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.
| @Value("${spring.data.redis.host}") | |
| private String host; | |
| @Value("${spring.data.redis.port}") | |
| private int port; | |
| @Value("${spring.data.redis.password:}") | |
| private String password; | |
| @Value("${spring.data.redis.cluster.nodes:}") | |
| private String clusterNodes; | |
| @Value("${spring.redis.host:${spring.data.redis.host}}") | |
| private String host; | |
| @Value("${spring.redis.port:${spring.data.redis.port}}") | |
| private int port; | |
| @Value("${spring.redis.password:${spring.data.redis.password:}}") | |
| private String password; | |
| @Value("${spring.redis.cluster.nodes:${spring.data.redis.cluster.nodes:}}") | |
| private String clusterNodes; | |
| @Value("${spring.redis.ssl:${spring.data.redis.ssl:false}}") | |
| private boolean ssl; |
🤖 Prompt for AI Agents
In src/main/java/project/flipnote/common/config/RedissonConfig.java around lines
16-27, the fields only read spring.data.redis.* properties; update the
injections to fall back to spring.redis.* equivalents and add an ssl flag: read
host from spring.data.redis.host with fallback to spring.redis.host, port from
spring.data.redis.port with fallback to spring.redis.port (use Integer if null),
password from spring.data.redis.password with fallback to spring.redis.password
(default empty), clusterNodes from spring.data.redis.cluster.nodes with fallback
to spring.redis.cluster.nodes (default empty), and add a boolean ssl injected
from spring.data.redis.ssl with fallback to spring.redis.ssl (default false);
ensure null-safe defaults so existing behavior remains unchanged.
| if (!clusterNodes.isBlank()) { | ||
| config.useClusterServers() | ||
| .addNodeAddress( | ||
| Arrays.stream(clusterNodes.split(",")) | ||
| .map(String::trim) | ||
| .filter(s -> !s.isEmpty()) | ||
| .map(addr -> addr.startsWith("redis://") || addr.startsWith("rediss://") ? addr : | ||
| "redis://" + addr) | ||
| .toArray(String[]::new) | ||
| ) | ||
| .setPassword(StringUtils.hasText(password) ? password : null); | ||
| } else { | ||
| config.useSingleServer() | ||
| .setAddress("redis://" + host + ":" + port) | ||
| .setPassword(StringUtils.hasText(password) ? password : null); | ||
| } |
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
TLS(rediss://) 및 주소 스킴 일관성 처리
현재 클러스터/싱글 모두 스킴이 하드코딩되거나(싱글: redis://), 클러스터는 기존 스킴만 보존하고 기본값은 redis://로 붙입니다. ssl 설정에 따라 rediss:// 스킴을 일관되게 적용할 수 있도록 스킴 변수를 도입하는 것을 권장합니다.
public RedissonClient redissonClient() {
Config config = new Config();
- if (!clusterNodes.isBlank()) {
+ final String scheme = ssl ? "rediss" : "redis";
+
+ if (!clusterNodes.isBlank()) {
config.useClusterServers()
.addNodeAddress(
Arrays.stream(clusterNodes.split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
- .map(addr -> addr.startsWith("redis://") || addr.startsWith("rediss://") ? addr :
- "redis://" + addr)
+ .map(addr ->
+ (addr.startsWith("redis://") || addr.startsWith("rediss://"))
+ ? addr
+ : (scheme + "://" + addr)
+ )
.toArray(String[]::new)
)
.setPassword(StringUtils.hasText(password) ? password : null);
} else {
config.useSingleServer()
- .setAddress("redis://" + host + ":" + port)
+ .setAddress(scheme + "://" + host + ":" + port)
.setPassword(StringUtils.hasText(password) ? password : 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.
| if (!clusterNodes.isBlank()) { | |
| config.useClusterServers() | |
| .addNodeAddress( | |
| Arrays.stream(clusterNodes.split(",")) | |
| .map(String::trim) | |
| .filter(s -> !s.isEmpty()) | |
| .map(addr -> addr.startsWith("redis://") || addr.startsWith("rediss://") ? addr : | |
| "redis://" + addr) | |
| .toArray(String[]::new) | |
| ) | |
| .setPassword(StringUtils.hasText(password) ? password : null); | |
| } else { | |
| config.useSingleServer() | |
| .setAddress("redis://" + host + ":" + port) | |
| .setPassword(StringUtils.hasText(password) ? password : null); | |
| } | |
| // introduce a scheme variable to pick redis:// vs rediss:// based on SSL | |
| final String scheme = ssl ? "rediss" : "redis"; | |
| if (!clusterNodes.isBlank()) { | |
| config.useClusterServers() | |
| .addNodeAddress( | |
| Arrays.stream(clusterNodes.split(",")) | |
| .map(String::trim) | |
| .filter(s -> !s.isEmpty()) | |
| .map(addr -> | |
| (addr.startsWith("redis://") || addr.startsWith("rediss://")) | |
| ? addr | |
| : (scheme + "://" + addr) | |
| ) | |
| .toArray(String[]::new) | |
| ) | |
| .setPassword(StringUtils.hasText(password) ? password : null); | |
| } else { | |
| config.useSingleServer() | |
| .setAddress(scheme + "://" + host + ":" + port) | |
| .setPassword(StringUtils.hasText(password) ? password : null); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/project/flipnote/common/config/RedissonConfig.java around lines
32 to 47, introduce a scheme variable (e.g. String scheme = useSsl ? "rediss://"
: "redis://") and use it for both cluster and single-server address
construction: for cluster, preserve existing "redis://" or "rediss://" prefixes
but otherwise prefix with scheme; for single-server, build the address using
scheme + host + ":" + port; keep the existing password handling unchanged.
Ensure the code reads the SSL flag (or config property) to set useSsl and then
consistently applies the derived scheme for all addresses.
📝 변경 내용
✅ 체크리스트
💬 기타 참고 사항
Summary by CodeRabbit
New Features
Refactor
Chores
API