Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1e93ca3
TRI-232 변경사항 merge
Wuxxeong Mar 28, 2025
ac335aa
TRI-232 검색 쿼리 변경
Wuxxeong Mar 27, 2025
8603871
TRI-232 검색 쿼리 변경 후 코드 정리
Wuxxeong Mar 27, 2025
ea027e9
TRI-232 batch size 적용
Wuxxeong Mar 28, 2025
bf6fe92
TRI-232 logging 활성화
Wuxxeong Mar 28, 2025
61aa05f
TRI-232 N+1문제 해결
Wuxxeong Mar 28, 2025
e1c4b0f
TRI-232 주석 제거
Wuxxeong Mar 28, 2025
c22d054
Batch Size대신 FetchMode적용
Wuxxeong Mar 28, 2025
7a5e5f7
restaurantCategory <-> category N+1문제 해결
Wuxxeong Mar 28, 2025
9e101db
n+1문제 해결 후 불필요한 category 반환 문제 해결
Wuxxeong Mar 30, 2025
23fe710
TRI-246 발송 시 서버 내 에러에 대해 TODO 처리 추가
gener-lst Mar 30, 2025
e0ea6a5
TRI-246 예약/빈자리 알림 slice 단위로 조회 + 배치 단위 발송으로 방식 변경
gener-lst Mar 30, 2025
04b40a3
TRI-246 알림 history 배치 저장 구현/저장 메서드 분기 생성
gener-lst Mar 30, 2025
c4a9609
TRI-246 알림 history 저장 시 error code가 null 경우 처리
gener-lst Mar 30, 2025
d7efd6a
TRI-248 기존 재발송 로직 strategyV1으로 분리
gener-lst Mar 30, 2025
0cce73e
TRI-248 재전송 전략 v2 구현(가상 스레드 block)
gener-lst Mar 30, 2025
fda0deb
TRI-248 재전송 로직으로 전달하는 Message 객체 구조 변경
gener-lst Mar 30, 2025
d21ec31
TRI-248 재전송 전략 v3 구현(Delay Queue Custom)
gener-lst Mar 30, 2025
239b3ba
TRI-248 재전송 전략 v4 구현 (Spring Integration Channel)
gener-lst Mar 30, 2025
943be3d
TRI-240 응답 처리 로직을 callback 메서드로 변경
gener-lst Mar 30, 2025
05e43c1
TRI-240 로직 별 가상 스레드 할당 name-prefix 설정
gener-lst Mar 30, 2025
98cb5ea
Merge pull request #146 from Trinity-goorm/TRI-232-test-search-api
Wuxxeong Mar 31, 2025
dd088fe
Fix - fcm 토큰 식별자로 조회 후 중복 처리로 변경
gener-lst Mar 31, 2025
389c4b2
Fix - FCM Message 포멧 변경(notification -> data)
gener-lst Mar 31, 2025
478dba9
Merging
gener-lst Mar 31, 2025
54eee4a
Merge pull request #150 from Trinity-goorm/TRI-240-apply-virtual-thread
gener-lst Mar 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# 1. Base image
FROM openjdk:17-jdk-slim
FROM openjdk:21-jdk-slim

# 2. Add JAR file
ARG JAR_FILE=build/libs/ctc.jar
Expand Down
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ version = '0.0.1-SNAPSHOT'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
languageVersion = JavaLanguageVersion.of(21)
}
}

Expand Down Expand Up @@ -60,6 +60,10 @@ dependencies {
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'

// Spring Integration
implementation 'org.springframework.boot:spring-boot-starter-integration'
implementation 'org.springframework.integration:spring-integration-core'
}
def querydslDir = "$buildDir/generated/querydsl"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

import com.trinity.ctc.domain.restaurant.entity.RestaurantCategory;
import com.trinity.ctc.domain.user.entity.UserPreferenceCategory;
import jakarta.persistence.*;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.BatchSize;

@Entity
@NoArgsConstructor
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/trinity/ctc/domain/fcm/entity/Fcm.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,10 @@ public Fcm(String token, LocalDateTime registeredAt, LocalDateTime expiresAt, Us
this.expiresAt = expiresAt;
this.user = user;
}

public void renewFcmToken(String newToken, LocalDateTime registeredAt, LocalDateTime expiresAt) {
this.token = newToken;
this.registeredAt = registeredAt;
this.expiresAt = expiresAt;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ void updateToken(@Param("token") String token,

List<Fcm> findByUserIn(List<User> userList);

boolean existsByToken(String fcmToken);

@Query("SELECT f.token FROM Fcm f WHERE f.user.id = :userId ORDER BY f.id")
Optional<List<String>> findByUser(@Param("userId") Long userId);

@Query("SELECT f FROM Fcm f WHERE f.user IN :users ORDER BY f.id")
Slice<Fcm> findByUserIn(@Param("users") List<User> users, Pageable pageable);

Optional<Fcm> findByTokenStartingWith(String prefix);
}
21 changes: 10 additions & 11 deletions src/main/java/com/trinity/ctc/domain/fcm/service/FcmService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import java.time.LocalDateTime;

import static com.trinity.ctc.domain.fcm.util.FcmTokenUtil.extractTokenPrefix;

@Service
@RequiredArgsConstructor
@Slf4j
Expand All @@ -31,10 +33,7 @@ public class FcmService {
*/
@Transactional
public void registerFcmToken(String fcmToken) {
if (fcmRepository.existsByToken(fcmToken)) {
renewFcmToken(fcmToken);
return;
}
fcmRepository.findByTokenStartingWith(extractTokenPrefix(fcmToken)).ifPresent(fcmRepository::delete);

String kakaoId = authService.getAuthenticatedKakaoId();

Expand All @@ -54,7 +53,6 @@ public void registerFcmToken(String fcmToken) {
.user(user)
.build();


// FCM 토큰 저장
fcmRepository.save(fcm);
}
Expand All @@ -76,14 +74,15 @@ public void deleteFcmToken(String fcmToken) {
*/
@Transactional
public void renewFcmToken(String fcmToken) {
// 토큰 업데이트 시간과 만료 시간 설정
LocalDateTime updatedAt = DateTimeUtil.truncateToMinute(LocalDateTime.now());
LocalDateTime expiresAt = updatedAt.plusDays(30);
fcmRepository.findByTokenStartingWith(extractTokenPrefix(fcmToken))
.ifPresent(existingFcm -> {
LocalDateTime updatedAt = DateTimeUtil.truncateToMinute(LocalDateTime.now());
LocalDateTime expiresAt = updatedAt.plusDays(30);

// 토큰값이 같은 record 업데이트
fcmRepository.updateToken(fcmToken, updatedAt, expiresAt);
existingFcm.renewFcmToken(fcmToken, updatedAt, expiresAt);
fcmRepository.save(existingFcm);
});
}

/**
* 매일 자정에 만료된 fcm 토큰 삭제 (스케줄링)
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.trinity.ctc.domain.fcm.util;

public class FcmTokenUtil {
public static String extractTokenPrefix(String fcmToken) {
return fcmToken.split(":")[0]; // 혹시 모를 예외에 대비해 유효성 검사도 같이 해줘도 좋음
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
Expand All @@ -16,4 +18,8 @@ public interface LikeRepository extends JpaRepository<Likes, Long> {
void deleteByUserAndRestaurant(User user, Restaurant restaurant);

List<Likes> findByUser(User user);

@Query("SELECT l.restaurant.id FROM Likes l WHERE l.user = :user AND l.restaurant.id IN :restaurantIds")
List<Long> findLikedRestaurantIds(@Param("user") User user, @Param("restaurantIds") List<Long> restaurantIds);
}

10 changes: 10 additions & 0 deletions src/main/java/com/trinity/ctc/domain/like/service/LikeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
import com.trinity.ctc.global.exception.error_code.UserErrorCode;
import jakarta.transaction.Transactional;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -73,5 +76,12 @@ public List<RestaurantDetailResponse> getLikeList(String kakaoId) {
.map(like -> RestaurantDetailResponse.fromLike(like.getRestaurant()))
.collect(Collectors.toList());
}

public Map<Long, Boolean> existsByUserAndRestaurantIds(User user, List<Long> restaurantIds) {
List<Long> likedIds = likeRepository.findLikedRestaurantIds(user, restaurantIds);
Set<Long> likedSet = new HashSet<>(likedIds);
return restaurantIds.stream()
.collect(Collectors.toMap(id -> id, likedSet::contains));
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

Expand All @@ -16,6 +17,7 @@
import java.util.Map;

@Entity
@Getter
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class NotificationHistory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class NotificationHistoryFormatter {

// 단 건 알림에 대한 notificationHistory formatter
public static NotificationHistory formattingSingleNotificationHistory(FcmMessage message,
FcmSendingResultDto result, NotificationType type) {
FcmSendingResultDto result) {

// 발송한 알림 data 를 Json 으로 저장하기 위해 Map 에 저장
Map<String, String> messageHistory = new HashMap<>();
Expand All @@ -28,7 +28,7 @@ public static NotificationHistory formattingSingleNotificationHistory(FcmMessage

// notificationHistory entity 를 생성하는 factory 메서드 호출 -> notificationHistory 반환
return createNotificationHistory(
type,
message.getType(),
messageHistory,
result.getSentAt(),
result.getSentResult(),
Expand All @@ -40,7 +40,7 @@ public static NotificationHistory formattingSingleNotificationHistory(FcmMessage

// 여러 건의 알림에 대한 notificationHistory formatter
public static List<NotificationHistory> formattingMultipleNotificationHistory(List<FcmMessage> messageList,
List<FcmSendingResultDto> resultList, NotificationType type) {
List<FcmSendingResultDto> resultList) {
// notificationHistoryList 초기화
List<NotificationHistory> notificationHistoryList = new ArrayList<>();

Expand All @@ -54,7 +54,7 @@ public static List<NotificationHistory> formattingMultipleNotificationHistory(Li

// notificationHistory entity 를 생성하는 factory 메서드 호출 -> 반환된 notificationHistory 을 list 에 저장
notificationHistoryList.add(createNotificationHistory(
type,
messageList.get(i).getType(),
messageHistory,
resultList.get(i).getSentAt(),
resultList.get(i).getSentResult(),
Expand All @@ -70,7 +70,7 @@ public static List<NotificationHistory> formattingMultipleNotificationHistory(Li

// Multicast 알림에 대한 notificationHistory formatter
public static List<NotificationHistory> formattingMulticastNotificationHistory(FcmMulticastMessage multicastMessage,
List<FcmSendingResultDto> resultList, NotificationType type) {
List<FcmSendingResultDto> resultList) {
// notificationHistoryList 초기화
List<NotificationHistory> notificationHistoryList = new ArrayList<>();

Expand All @@ -84,7 +84,7 @@ public static List<NotificationHistory> formattingMulticastNotificationHistory(F

// notificationHistory entity 를 생성하는 factory 메서드 호출 -> 반환된 notificationHistory 을 list 에 저장
notificationHistoryList.add(createNotificationHistory(
type,
multicastMessage.getType(),
messageHistory,
resultList.get(i).getSentAt(),
resultList.get(i).getSentResult(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.MulticastMessage;
import com.google.firebase.messaging.Notification;
import com.trinity.ctc.domain.fcm.entity.Fcm;
import com.trinity.ctc.domain.notification.message.FcmMessage;
import com.trinity.ctc.domain.notification.message.FcmMulticastMessage;
import com.trinity.ctc.domain.notification.type.NotificationType;
import com.trinity.ctc.domain.reservation.entity.Reservation;

import java.time.LocalDate;
Expand All @@ -19,7 +21,7 @@
// FCM 에서 제공하는 Message, MulticastMessage 의 Wrapper 객체(FcmMessage, FcmMulticastMessage)를 생성하는 Util
public class NotificationMessageFormatter {

public static FcmMessage createMessageWithUrl(String title, String body, String url, Fcm fcm) {
public static FcmMessage createMessageWithUrl(String title, String body, String url, Fcm fcm, NotificationType type) {
Message message = Message.builder()
.putData("title", title)
.putData("body", body)
Expand All @@ -32,10 +34,10 @@ public static FcmMessage createMessageWithUrl(String title, String body, String
data.put("body", body);
data.put("url", url);

return new FcmMessage(message, fcm, data);
return new FcmMessage(message, fcm, data, type);
}

public static FcmMulticastMessage createMulticastMessageWithUrl(String title, String body, String url, List<Fcm> fcmList) {
public static FcmMulticastMessage createMulticastMessageWithUrl(String title, String body, String url, List<Fcm> fcmList, NotificationType type) {
MulticastMessage multicastMessage = MulticastMessage.builder()
.putData("title", title)
.putData("body", body)
Expand All @@ -48,12 +50,12 @@ public static FcmMulticastMessage createMulticastMessageWithUrl(String title, St
data.put("body", body);
data.put("url", url);

return new FcmMulticastMessage(multicastMessage, fcmList, data);
return new FcmMulticastMessage(multicastMessage, fcmList, data, type);
}

// 예약 완료 알림 메세지를 포멧팅하는 메서드
// 예약 완료 알림은 바로 발송하여 저장하지 않기 때문에 FcmMessage 객체로 반환
public static FcmMessage formattingReservationCompletedNotification(Fcm fcm, Reservation reservation) {
public static FcmMessage formattingReservationCompletedNotification(Fcm fcm, Reservation reservation, NotificationType type) {
// 예약 완료 알림 메세지에 필요한 정보 변수 선언
String restaurantName = reservation.getRestaurant().getName();
LocalDate reservedDate = reservation.getReservationDate();
Expand All @@ -67,13 +69,12 @@ public static FcmMessage formattingReservationCompletedNotification(Fcm fcm, Res
String url = formatReservationNotificationUrl();

// reservationNotification entity 를 생성하는 팩토리 메서드 호출 -> reservationNotification 반환
return createMessageWithUrl(title, body, url, fcm);
return createMessageWithUrl(title, body, url, fcm, type);
}


// 예약 취소 메세지를 포멧팅하는 메서드
// 예약 취소 알림은 바로 발송하여 저장하지 않기 때문에 FcmMessage 객체로 반환
public static FcmMessage formattingReservationCanceledNotification(Fcm fcm, Reservation reservation, boolean isCODPassed) {
public static FcmMessage formattingReservationCanceledNotification(Fcm fcm, Reservation reservation, boolean isCODPassed, NotificationType type) {
// 예약 완료 알림 메세지에 필요한 정보 변수 선언
String restaurantName = reservation.getRestaurant().getName();
LocalDate reservedDate = reservation.getReservationDate();
Expand All @@ -94,6 +95,6 @@ public static FcmMessage formattingReservationCanceledNotification(Fcm fcm, Rese
String url = formatReservationNotificationUrl();

// 알림 메세지 data 로 FcmMessage 객체를 생성하는 메서드 호출
return createMessageWithUrl(title, body, url, fcm);
return createMessageWithUrl(title, body, url, fcm, type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.firebase.messaging.Message;
import com.trinity.ctc.domain.fcm.entity.Fcm;
import com.trinity.ctc.domain.notification.type.NotificationType;
import lombok.Builder;
import lombok.Getter;

Expand All @@ -10,15 +11,16 @@
// Firebase의 Message 객체의 wrapper 클래스
@Getter
public class FcmMessage {
//
private final Message message;
private final Fcm fcm;
private final Map<String, String> data;
private NotificationType type;

@Builder
public FcmMessage(Message message, Fcm fcm, Map<String, String> data) {
public FcmMessage(Message message, Fcm fcm, Map<String, String> data, NotificationType type) {
this.message = message;
this.fcm = fcm;
this.data = data;
this.type = type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.firebase.messaging.MulticastMessage;
import com.trinity.ctc.domain.fcm.entity.Fcm;
import com.trinity.ctc.domain.notification.type.NotificationType;
import lombok.Builder;
import lombok.Getter;

Expand All @@ -14,11 +15,13 @@ public class FcmMulticastMessage {
private final MulticastMessage multicastMessage;
private final List<Fcm> fcmList;
private final Map<String, String> data;
private NotificationType type;

@Builder
public FcmMulticastMessage(MulticastMessage multicastMessage, List<Fcm> fcmList, Map<String, String> data) {
public FcmMulticastMessage(MulticastMessage multicastMessage, List<Fcm> fcmList, Map<String, String> data, NotificationType type) {
this.multicastMessage = multicastMessage;
this.fcmList = fcmList;
this.data = data;
this.type = type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ public int getBatchSize() {
});
}


log.info("✅ ReservationNotification Insert 완료");
}

Expand Down Expand Up @@ -83,7 +82,6 @@ public int getBatchSize() {
});
}


log.info("✅ SeatNotification Insert 완료 (총 {}건)", seatNotifications.size());
}

Expand Down Expand Up @@ -112,8 +110,6 @@ public int getBatchSize() {
});
}



log.info("✅ SeatNotificationSubscription Insert 완료 (총 {}건)", subscriptions.size());
}

Expand Down
Loading