Skip to content

Conversation

@dahyun24
Copy link
Contributor

@dahyun24 dahyun24 commented Nov 13, 2025

🔗 연관된 이슈

🚀 변경 유형

  • ✨ 기능 추가 (feature)
  • 🐛 버그 수정 (fix)
  • 📝 문서 변경 (docs)
  • ♻️ 리팩토링 (refactor)
  • 🧪 테스트 추가 / 수정 (test)
  • ⚙️ 설정 변경 (chore)

📝 작업 내용

  • 멘토링때 받은 피드백을 고려하여 redis를 그대로 사용하기로 했습니다. 다만 성능 측면에서 고민을 해본 결과 redis TTL 기반으로 tracking 하는 것이 아닌 sorted set을 사용하기로 하였습니다.
  • 최종 치즈네컷 프레임 한장을 백엔드에서 저장하지 않기로 함에 따라 cheese4cutPhoto 엔티티를 추가하여 원본 이미지 url과 혹시 모를 thumbnailImageUrl을 저장했습니다.

[작업한 내용]

  • 치즈네컷 조회
  • 치즈네컷 확정
  • 치즈네컷 생성 (만료 7일이 되었을 때)

📸 스크린샷

Redis에 저장

스크린샷 2025-11-13 오전 12 40 32

치즈네컷이 생성되기 전 치즈네컷 조회 response (cdn 추가하여 response 제공)

스크린샷 2025-11-13 오전 10 29 12

치즈네컷이 확정된 후(MAKER 혹은 앨범 만료) 치즈네컷 조회 response

스크린샷 2025-11-13 오후 9 47 42

치즈네컷 확정 api

스크린샷 2025-11-13 오후 9 44 05 스크린샷 2025-11-13 오후 9 46 56

💬 리뷰 요구사항

📜 리뷰 규칙

Reviewer는 아래 P5 Rule을 참고하여 리뷰를 진행합니다.
P5 Rule을 통해 Reviewer는 Reviewee에게 리뷰의 의도를 보다 정확히 전달할 수 있습니다.

  • P1: 꼭 반영해주세요 (Comment)
  • P2: 적극적으로 고려해주세요 (Comment)
  • P3: 웬만하면 반영해 주세요 (Comment)
  • P4: 반영해도 좋고 넘어가도 좋습니다 (Approve)
  • P5: 그냥 사소한 의견입니다 (Approve)

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • Cheese4cut 미리보기에 사용자 역할 및 사진 순위 정보 추가
    • Cheese4cut 최종 응답에 정렬된 사진 세부정보 포함
  • 개선 사항

    • 사진 정렬 및 검증 메커니즘 강화
    • 앨범 만료 처리 절차 개선
    • 사용자 인증 기반 콘텐츠 처리 최적화

@coderabbitai
Copy link

coderabbitai bot commented Nov 13, 2025

Walkthrough

앨범 만료 메커니즘을 Redis TTL에서 ZSET 기반 시스템으로 재구조화하고, Cheese4cut 엔티티를 List photoIds에서 매핑된 Cheese4cutPhoto 엔티티 관계로 전환하며, 유저 인증 컨텍스트를 Cheese4cut 서비스에 추가합니다.

Changes

Cohort / File(s) 요약
앨범 만료 Redis 저장소 재구조화
src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumExpirationRedisRepository.java
Redis TTL 기반 추적에서 ZSET 기반 만료 메커니즘으로 변경; registerAlbum에 LocalDateTime 파라미터 추가; getTrackedAlbumIds() → getExpiredAlbumIds() 메서드 변경; TRACKING_KEY/ALBUM_TTL 상수 제거, EXPIRATION_ZSET_KEY/KST_ZONE 추가
앨범 만료 스케줄러 및 서비스 업데이트
src/main/java/com/cheeeese/album/application/AlbumExpirationScheduler.java, src/main/java/com/cheeeese/album/application/AlbumExpirationService.java
스케줄러 FixedDelay 10000L로 증가; getExpiredAlbumIds() 사용; AlbumExpirationService에서 Photo 엔티티 검색 후 정렬된 리스트로 Cheese4cut 생성; album.expire() 대신 albumRepository.updateStatus(..., EXPIRED) 사용
앨범 도메인 및 저장소 정리
src/main/java/com/cheeeese/album/domain/Album.java, src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumRepository.java
Album.expire() 공개 메서드 제거; AlbumRepository에 updateStatus(Long id, AlbumStatus status) 쿼리 메서드 추가
앨범 생성 시 만료 타임스탬프 관리
src/main/java/com/cheeeese/album/application/AlbumService.java
expiredAt 타임스탬프(현재 + 7일) 계산 및 Album 엔티티와 registerAlbum(albumId, expiredAt) 호출에 전달
Cheese4cut 도메인 엔티티 재구조화
src/main/java/com/cheeeese/cheese4cut/domain/Cheese4cut.java, src/main/java/com/cheeeese/cheese4cut/domain/Cheese4cutPhoto.java
photoIds: List → photos: List OneToMany 관계로 변경; Cheese4cutPhoto 새로운 JPA 엔티티 추가(id, photoId, imageUrl, thumbnailImageUrl, photoRank 포함)
Cheese4cut 응답 DTO 구조 업데이트
src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutFinalResponse.java, src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutPreviewResponse.java
Cheese4cutFinalResponse에 photos: List 필드 추가; PreviewPhotoInfo에 photoRank 필드 추가; Cheese4cutPreviewResponse에 myRole: Role 필드 추가
Cheese4cut 매핑 및 서비스 로직
src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java, src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java
toEntity(Album, List) 서명 변경; toFinalResponse/toPreviewResponse에 PhotoInfo 리스트 파라미터 추가; getCheese4cutByAlbumCode에 Authentication 파라미터 추가; extractUser() 및 getOrderedPhotos() 헬퍼 메서드 추가; CDN URL 해상도 적용
Cheese4cut 컨트롤러 및 Swagger
src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java, src/main/java/com/cheeeese/cheese4cut/presentation/swagger/Cheese4cutSwagger.java
getCheese4cut 엔드포인트에 Authentication 파라미터 추가; 서비스 호출에 전달
Photo 저장소 쿼리 업데이트
src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java
findTop4CompletedPhotoIdsByLikes의 createdAt 정렬 순서 ASC → DESC로 변경; findAllByIdInOrderByLikesDescCreatedDesc 메서드 추가

Sequence Diagram(s)

sequenceDiagram
    participant Scheduler as AlbumExpirationScheduler
    participant Service as AlbumExpirationService
    participant Redis as AlbumExpirationRedisRepository
    participant Repo as AlbumRepository<br/>PhotoRepository
    participant C4Service as Cheese4cutService

    Scheduler->>Redis: getExpiredAlbumIds()
    Redis-->>Scheduler: expiredAlbumIds: Set<Long>
    
    loop for each expiredAlbumId
        Scheduler->>Service: expire(expiredAlbumId)
        
        Service->>Repo: Album findById(expiredAlbumId)
        Service->>Repo: Photo findAllByIdInOrderByLikesDescCreatedDesc(topPhotoIds)
        Service->>Service: 검증: 모든 Photo 존재 확인
        
        alt photos exist
            Service->>C4Service: toEntity(album, orderedPhotos)
            C4Service-->>Service: Cheese4cut with photos
            Service->>Repo: Cheese4cut save(cheese4cut)
            Service->>Repo: Album updateStatus(expiredAlbumId, EXPIRED)
        else missing photos
            Service->>Service: 경고 로깅 및 중단
        end
        
        Scheduler->>Redis: unregister(expiredAlbumId)
    end
Loading
sequenceDiagram
    participant Client
    participant Controller as Cheese4cutController
    participant Service as Cheese4cutService
    participant Repo as Cheese4cutRepository<br/>PhotoRepository
    participant Mapper as Cheese4cutMapper
    participant CDN as CdnUrlResolver

    Client->>Controller: GET /api/cheese4cut/{code}<br/>(with Authentication)
    Controller->>Service: getCheese4cutByAlbumCode(authentication, code)
    
    Service->>Service: extractUser(authentication)
    Service->>Repo: Cheese4cut findByAlbumCode(code)
    
    alt final Cheese4cut exists
        Service->>Repo: cheese4cut.getPhotos()
        loop for each Cheese4cutPhoto
            Service->>CDN: 이미지 URL 해석
            Service->>Mapper: toFinalPhotoInfo(photoId, cdnUrl, photoRank)
        end
        Service->>Mapper: toFinalResponse(photos: List<FinalPhotoInfo>)
        Service-->>Controller: Cheese4cutFinalResponse
    else preview only
        Service->>Repo: UserAlbum findByAlbumId(albumId) → myRole
        Service->>Repo: getOrderedPhotos(topPhotoIds)
        loop for each Photo
            Service->>CDN: 이미지 URL 해석
            Service->>Mapper: toPreviewPhotoInfo(photoId, cdnUrl, photoRank)
        end
        Service->>Mapper: toPreviewResponse(photos, likes, participants, myRole)
        Service-->>Controller: Cheese4cutPreviewResponse
    end
    
    Controller-->>Client: CommonResponse<Cheese4cutResponse>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • 주의 깊게 검토 필요한 영역:
    • AlbumExpirationRedisRepository: ZSET 기반 만료 메커니즘의 정확성, 타임스탬프 변환(KST 존 처리) 검증
    • Cheese4cut 도메인 및 Cheese4cutPhoto: OneToMany 관계 매핑의 양방향 연결, cascade 설정, 포토 순서 보장
    • Cheese4cutService.getCheese4cutByAlbumCode(): Authentication 처리, null 안전성, 미존재 Photo에 대한 오류 처리
    • Cheese4cutMapper.toEntity(): Photo 엔티티 리스트에서 Cheese4cutPhoto 생성 로직의 정확성
    • AlbumExpirationService.expire(): 트랜잭션 경계, 예외 처리, Cheese4cut 생성 실패 시나리오
    • PhotoRepository 쿼리: createdAt DESC 변경의 의도, 새 쿼리의 정렬 순서 우선순위

Possibly related issues

Possibly related PRs

Suggested labels

✨feature, 🐛bug

Suggested reviewers

  • zyovn

Poem

🐰 Redis ZSET으로 시간을 세고,
Photo 친구들 순서대로 줄 세우고,
인증 사용자 손을 잡고,
Cheese4cut의 새로운 춤을 춘다네!
만료된 앨범들은 조용히 작별하고,
선정된 사진 4장의 이야기를 시작한다. ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 변경 사항의 핵심을 정확하게 반영합니다. Cheese4cutPhoto 엔티티 추가와 스케줄러 기능 구현 변경이 주요 변경 사항이며, 제목이 이를 명확하게 요약하고 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#57-scheduler

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.

@dahyun24 dahyun24 self-assigned this Nov 13, 2025
@dahyun24 dahyun24 added 🐛bug Something isn't working ✨feature New feature or request labels Nov 13, 2025
@dahyun24 dahyun24 linked an issue Nov 13, 2025 that may be closed by this pull request
3 tasks
Copy link

@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

🧹 Nitpick comments (1)
src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumExpirationRedisRepository.java (1)

18-19: 타임존 설정을 하드코딩하지 않도록 개선을 권장합니다.

KST_ZONEZoneOffset.of("+09:00")로 하드코딩되어 있습니다. 타임존은 애플리케이션 설정에서 관리하거나 시스템 기본값을 사용하는 것이 유지보수성과 유연성 측면에서 더 좋습니다.

설정 파일에서 타임존을 관리하도록 개선할 수 있습니다:

-    private static final ZoneOffset KST_ZONE = ZoneOffset.of("+09:00");
+    @Value("${application.timezone.offset:+09:00}")
+    private String timezoneOffset;
+    
+    private ZoneOffset getZoneOffset() {
+        return ZoneOffset.of(timezoneOffset);
+    }

그리고 사용처에서 KST_ZONE 대신 getZoneOffset()을 호출하도록 변경:

-        long expirationMillis = expiredAt.toInstant(KST_ZONE).toEpochMilli();
+        long expirationMillis = expiredAt.toInstant(getZoneOffset()).toEpochMilli();
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d8468e and 88ae482.

📒 Files selected for processing (15)
  • src/main/java/com/cheeeese/album/application/AlbumExpirationScheduler.java (1 hunks)
  • src/main/java/com/cheeeese/album/application/AlbumExpirationService.java (4 hunks)
  • src/main/java/com/cheeeese/album/application/AlbumService.java (4 hunks)
  • src/main/java/com/cheeeese/album/domain/Album.java (0 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumExpirationRedisRepository.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumRepository.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java (6 hunks)
  • src/main/java/com/cheeeese/cheese4cut/domain/Cheese4cut.java (2 hunks)
  • src/main/java/com/cheeeese/cheese4cut/domain/Cheese4cutPhoto.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutFinalResponse.java (1 hunks)
  • src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutPreviewResponse.java (3 hunks)
  • src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java (2 hunks)
  • src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java (2 hunks)
  • src/main/java/com/cheeeese/cheese4cut/presentation/swagger/Cheese4cutSwagger.java (2 hunks)
  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (2 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/com/cheeeese/album/domain/Album.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-31T13:17:52.523Z
Learnt from: dahyun24
Repo: Say-Cheeeese/BE PR: 35
File: src/main/java/com/cheeeese/photo/application/PhotoService.java:46-52
Timestamp: 2025-10-31T13:17:52.523Z
Learning: In src/main/java/com/cheeeese/photo/application/PhotoService.java, the getRecentThumbnailUrls method intentionally returns only the first thumbnail URL when photos.size() < 5, rather than returning all available thumbnails. This is according to product requirements: 0 photos → empty list, 1-4 photos → single thumbnail (most recent), 5 photos → all 5 thumbnails.

Applied to files:

  • src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java
  • src/main/java/com/cheeeese/album/application/AlbumExpirationService.java
  • src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java
🧬 Code graph analysis (2)
src/main/java/com/cheeeese/album/application/AlbumExpirationService.java (1)
src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java (1)
  • Cheese4cutMapper (16-92)
src/main/java/com/cheeeese/cheese4cut/application/Cheese4cutService.java (1)
src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java (1)
  • Cheese4cutMapper (16-92)
🔇 Additional comments (15)
src/main/java/com/cheeeese/photo/infrastructure/persistence/PhotoRepository.java (1)

108-116: 새로운 정렬 메서드가 잘 구현되었습니다.

findAllByIdInOrderByLikesDescCreatedDesc 메서드가 좋아요 수, 생성일, ID를 기준으로 3단계 정렬을 제공하여 결정적(deterministic)인 결과를 보장합니다. isDeleted = FALSE 필터도 적절히 포함되어 있습니다.

src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumExpirationRedisRepository.java (1)

24-36: ZSET 기반 만료 추적 구현이 효율적입니다.

TTL 기반에서 ZSET 기반으로 마이그레이션하여 성능이 개선되었습니다. Unix timestamp를 score로 사용하는 방식이 적절하며, ZADD 명령을 올바르게 사용하고 있습니다.

src/main/java/com/cheeeese/cheese4cut/presentation/swagger/Cheese4cutSwagger.java (1)

77-80: 인증 파라미터 추가가 기존 클라이언트에 미치는 영향을 확인하세요.

getCheese4cut 메서드에 Authentication 파라미터가 추가되었습니다. 이 변경사항이 기존에 인증 없이 호출하던 클라이언트에게 영향을 주지 않는지 확인이 필요합니다. 엔드포인트가 선택적 인증(optional authentication)을 지원하는지, 아니면 필수 인증(required authentication)으로 변경되었는지 명확히 해야 합니다.

다음 사항을 확인해주세요:

  1. 이 엔드포인트가 익명 사용자도 접근 가능한지 (Spring Security 설정 확인)
  2. 기존 클라이언트가 인증 토큰 없이 호출하는 경우가 있는지
  3. 역할(myRole)이 null일 수 있는 경우를 서비스 레이어에서 처리하는지
src/main/java/com/cheeeese/album/application/AlbumExpirationScheduler.java (2)

19-19: 스케줄러 폴링 간격이 10배 증가했습니다. 비즈니스 요구사항을 확인하세요.

fixedDelay가 1000L(1초)에서 10000L(10초)로 변경되어 앨범 만료 처리가 최대 10초까지 지연될 수 있습니다. 이 변경이 비즈니스 요구사항에 부합하는지 확인이 필요합니다. 만료 후 즉시 처리가 중요한 경우 더 짧은 간격을 고려해야 합니다.

다음 사항을 확인해주세요:

  1. 앨범 만료 처리에 최대 10초 지연이 허용되는지
  2. 이 변경이 성능 최적화를 위한 것인지
  3. Redis ZSET 조회 비용이 높아서 폴링 간격을 늘린 것인지

28-33: 에러 처리 개선이 잘 되었습니다.

개별 앨범 만료 처리 실패 시 try-catch로 감싸서 다른 앨범 처리가 중단되지 않도록 했습니다. 로그도 적절하게 남기고 있어 장애 추적이 용이합니다.

src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumRepository.java (1)

42-44: 불필요한 업데이트를 방지하는 조건이 잘 구현되었습니다.

updateStatus 메서드에 a.status <> :status 조건을 추가하여 상태가 이미 동일한 경우 불필요한 데이터베이스 쓰기를 방지합니다. 이는 좋은 성능 최적화 패턴입니다.

src/main/java/com/cheeeese/cheese4cut/presentation/Cheese4cutController.java (1)

32-38: 인증 파라미터 전달이 일관되게 구현되었습니다.

컨트롤러가 Swagger 인터페이스와 일치하게 Authentication 파라미터를 받아서 서비스 레이어로 전달하고 있습니다. 구현이 정확합니다.

src/main/java/com/cheeeese/album/application/AlbumService.java (1)

62-82: 만료 시간 일관성이 잘 유지되고 있습니다.

expiredAt을 한 번만 생성하여 앨범 엔티티 생성과 Redis 등록에 동일한 타임스탬프를 사용하고 있습니다. 이는 데이터 일관성을 보장하고 LocalDateTime.now()를 여러 번 호출할 때 발생할 수 있는 미세한 시간차를 방지합니다. 7일 만료 정책도 PR 요구사항과 일치합니다.

src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutPreviewResponse.java (2)

22-25: 역할 기반 응답을 위한 필드 추가가 적절합니다.

myRole 필드를 추가하여 사용자의 역할(MAKER/GUEST)에 따라 프론트엔드에서 다른 UI를 표시할 수 있게 되었습니다. Schema 어노테이션도 명확하게 작성되었습니다.


35-38: 사진 순위 정보 제공이 유용합니다.

photoRank 필드를 추가하여 좋아요 TOP 4 사진의 순위(1~4)를 명시적으로 제공합니다. 클라이언트가 정렬 로직을 구현할 필요 없이 순위 정보를 바로 사용할 수 있어 좋습니다.

src/main/java/com/cheeeese/album/application/AlbumExpirationService.java (1)

64-75: 포토 누락 방지 처리 좋습니다.

ID 목록을 기반으로 순서를 복원하고 null 검사를 통해 잘못된 치즈네컷 생성 가능성을 사전에 차단한 점이 인상적입니다.

src/main/java/com/cheeeese/cheese4cut/domain/Cheese4cutPhoto.java (1)

45-47: 양방향 연관관계 설정 👍

assignToCheese4cut으로 owning side를 확실히 묶어 일관된 연관관계가 유지되도록 한 설계가 좋습니다.

src/main/java/com/cheeeese/cheese4cut/dto/response/Cheese4cutFinalResponse.java (1)

14-29: 최종 응답 스키마 확장 👍

FinalPhotoInfo 레코드로 ID와 순위를 명시적으로 내려주니 소비 측에서 해석이 훨씬 쉬워졌습니다.

src/main/java/com/cheeeese/cheese4cut/domain/Cheese4cut.java (1)

29-45: Cheese4cutPhoto 컬렉션 매핑이 깔끔합니다.

@OneToManyorphanRemoval 조합으로 사진 엔티티 생명주기가 자연스럽게 정리된 점이 좋습니다.

src/main/java/com/cheeeese/cheese4cut/infrastructure/mapper/Cheese4cutMapper.java (1)

51-65: 정렬 보존된 Cheese4cut 생성 좋습니다.

IntStream을 활용해 원본 순서를 그대로 유지하면서 Cheese4cutPhoto를 구성해 photoRank 관리가 분명해졌습니다.

Copy link
Member

@zyovn zyovn left a comment

Choose a reason for hiding this comment

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

🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀🧀

수고하셧습니다

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐛bug Something isn't working ✨feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 스케줄러 구현 변경 및 치즈네컷 생성 로직 구현

3 participants