Skip to content

feat: 콜벤 기능 구현#2151

Open
DHkimgit wants to merge 15 commits intodevelopfrom
feat/2150-callvan-sprint
Open

feat: 콜벤 기능 구현#2151
DHkimgit wants to merge 15 commits intodevelopfrom
feat/2150-callvan-sprint

Conversation

@DHkimgit
Copy link
Collaborator

@DHkimgit DHkimgit commented Feb 11, 2026

🔍 개요

  • 신고 기능을 제외한 콜벤 도메인 구현

🚀 주요 변경 내용

비동기 이벤트 기반 알림 저장 구현

  • Spring의 ApplicationEventPublisher@TransactionalEventListener를 활용하여 도메인 이벤트 기반 알림 시스템을 구현
  • 구현된 알림 타입
    • RECRUITMENT_COMPLETE : 모집 인원 마감 시
    • NEW_MESSAGE : 새로운 채팅 메시지 수신 시
    • PARTICIPANT_JOINED : 새로운 참여자 합류 시
    • DEPARTURE_UPCOMING : 출발 30분 전 알림
sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant EventPublisher
    participant EventListener
    participant NotificationService
    participant DB

    Client->>Controller: POST /callvan/posts/{id}/participants
    Controller->>Service: join(postId, userId)
    Service->>DB: 참여자 저장
    Service->>EventPublisher: publishEvent(ParticipantJoinedEvent)
    Service-->>Controller: 완료
    Controller-->>Client: 201 Created
    
    Note over EventPublisher,EventListener: 트랜잭션 커밋 후 비동기 실행
    
    EventPublisher->>EventListener: @TransactionalEventListener
    EventListener->>NotificationService: notifyParticipantJoined()
    NotificationService->>DB: 알림 저장 (다른 참여자들에게)
Loading

Redis Sorted Set(ZSET) 기반 지연 작업 큐

  • 출발 30분 전 알림을 효율적으로 처리하기 위해 Redis Sorted Set을 활용하여 지연 작업 큐를 구현
  • 매 분마다 스케줄러가 실행되지만 출발 30분 전 1번만 RDB 조회/삽입 작업 수행
sequenceDiagram
    participant PostCreate as 게시글 생성
    participant Scheduler as CallvanNotificationScheduler
    participant Redis as Redis Sorted Set
    participant CronJob as @Scheduled (매분 실행)
    participant DB as Database

    PostCreate->>Scheduler: scheduleNotification(post)
    Scheduler->>Scheduler: 출발시간 - 30분 계산
    Scheduler->>Redis: ZADD (score: timestamp)
    Note over Redis: Key: callvan:notification:queue<br/>Score: 알림 발송 시각 (epoch)

    loop 매분마다
        CronJob->>Redis: ZRANGEBYSCORE (0, now)
        Redis-->>CronJob: 발송 시각 도래한 작업들
        CronJob->>DB: 참여자들에게 알림 생성
        CronJob->>Redis: ZREM (처리 완료된 작업 제거)
    end
Loading
  • DB 폴링 대비 Redis 활용으로 성능 향상 기대

콜벤 가입 API 동시성 제어 적용

@Transactional
@ConcurrencyGuard(lockName = "callvanJoin")
public void join(Integer postId, Integer userId) {

💬 참고 사항

  • 구현 단위로 커밋 나눠놨습니다.

✅ Checklist (완료 조건)

  • 코드 스타일 가이드 준수
  • [] 테스트 코드 포함됨
  • Reviewers / Assignees / Labels 지정 완료
  • 보안 및 민감 정보 검증 (API 키, 환경 변수, 개인정보 등)

@DHkimgit DHkimgit self-assigned this Feb 11, 2026
@DHkimgit DHkimgit added 기능 새로운 기능을 개발합니다. Team Campus 캠퍼스 팀에서 작업할 이슈입니다 labels Feb 11, 2026
@github-actions
Copy link

github-actions bot commented Feb 11, 2026

Unit Test Results

360 tests   312 ✔️  4s ⏱️
164 suites      1 💤
164 files      47

For more details on these failures, see this check.

Results for commit b9ce83f.

♻️ This comment has been updated with latest results.

Copy link
Collaborator

@Soundbar91 Soundbar91 left a comment

Choose a reason for hiding this comment

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

Comment on lines 23 to 25
if (!callvanPost.getAuthor().getId().equals(userId)) {
throw CustomException.of(ApiResponseCode.FORBIDDEN_AUTHOR);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

C

콜벤 게시글 작성자 검증 로직을 CallvanPost 내부에 넣어도 괜찮을 거 같아요 !

Copy link
Collaborator

@BaeJinho4028 BaeJinho4028 left a comment

Choose a reason for hiding this comment

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

) {
public static CallvanPostDetailResponse from(CallvanPost post, Integer userId) {
String departureName = post.getDepartureType().getName();
if (post.getDepartureCustomName() != null && !post.getDepartureCustomName().isBlank()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

StringUtils.hasText() 써도 좋을거 같아요 1

}

String arrivalName = post.getArrivalType().getName();
if (post.getArrivalCustomName() != null && !post.getArrivalCustomName().isBlank()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

StringUtils.hasText() 써도 좋을거 같아요 2

Comment on lines 52 to 53
) {
public static CallvanPostDetailResponse from(CallvanPost post, Integer userId) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

줄넘김 이슈

Comment on lines 90 to 91
) {
public static CallvanParticipantResponse from(CallvanParticipant participant, Integer userId) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

줄넘김 이슈

Comment on lines 23 to 24
) {
public static CallvanChatMessageResponse of(CallvanPost post, List<CallvanChatMessage> messages, Integer userId) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

줄넘김 이슈

@Schema(description = "내 메시지 여부", example = "true")
Boolean isMine
) {
public static CallvanMessageDto from(CallvanChatMessage message, Integer currentUserId) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

줄넘김 이슈

Comment on lines 56 to 64
if(this.isDeleted) {
this.isDeleted = false;
}
}

public void leaveCallvan() {
if(!this.isDeleted) {
this.isDeleted = true;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

둘다 if문이 필요 없을지도요

Comment on lines 68 to 73
if (user.getNickname() != null) {
return user.getNickname();
}
if (user.getAnonymousNickname() != null) {
return user.getAnonymousNickname();
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

중복되어서 이게 모델에서 공통으로 처리를 해주거나, 근본적으로 해결할 수 있는 방법이 있을 거 같아요.

Comment on lines 98 to 103
if (user.getNickname() != null) {
return user.getNickname();
}
if (user.getAnonymousNickname() != null) {
return user.getAnonymousNickname();
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

또 중복되네요

Comment on lines 62 to 69
if (notification.getDepartureCustomName() != null && !notification.getDepartureCustomName().isBlank()) {
departureName = notification.getDepartureCustomName();
}

String arrivalName = notification.getArrivalType().getName();
if (notification.getArrivalCustomName() != null && !notification.getArrivalCustomName().isBlank()) {
arrivalName = notification.getArrivalCustomName();
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

이것도 중복으로 나타나네요

#### 비즈니스 로직
1. 출발지/도착지 필터링 시, 선택된 장소 타입들에 해당하는 게시글을 조회합니다.
2. `CUSTOM` 타입이 선택되고 키워드가 입력된 경우, 사용자가 직접 입력한 장소명에서 해당 키워드를 포함하는 게시글도 결과에 포함됩니다.
3. 정렬 기준이 `DEPARTURE`인 경우, 출발 날짜와 시간 순으로 내림차순(최신 순) 정렬됩니다.
Copy link
Contributor

Choose a reason for hiding this comment

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

명세에서는 내림차순 정렬로 되어있는데, CallvanPostQueryRepository에서 135번째 줄 메소드는 오름차순으로 정렬 되어있는 것 같으로 보여요!

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

Labels

Team Campus 캠퍼스 팀에서 작업할 이슈입니다 기능 새로운 기능을 개발합니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[공통] 콜벤 스프린트 기본 기능 구현

4 participants