From cbcad447fca65f7aa5df0a406edbc258f55adca4 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 03:06:08 +0900 Subject: [PATCH 01/45] =?UTF-8?q?feat:=20=EC=9E=85=EC=9E=A5=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EC=A0=84=EC=86=A1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festago/common/exception/ErrorCode.java | 1 + .../com/festago/config/SchedulingConfig.java | 10 ++ .../application/EntryAlertEventListener.java | 47 ++++++++ .../application/EntryAlertService.java | 102 ++++++++++++++++++ .../entry_alert/domain/EntryAlert.java | 62 +++++++++++ .../repository/EntryAlertRepository.java | 8 ++ .../fcm/repository/MemberFCMRepository.java | 2 + .../ticket/application/TicketService.java | 8 +- .../ticket/dto/event/TicketCreateEvent.java | 10 ++ .../repository/MemberTicketRepository.java | 10 ++ .../domain/MemberTicketRepositoryTest.java | 35 ++++-- .../repository/MemberFCMRepositoryTest.java | 20 ++++ .../com/festago/support/MemberFixture.java | 3 +- 13 files changed, 310 insertions(+), 8 deletions(-) create mode 100644 backend/src/main/java/com/festago/config/SchedulingConfig.java create mode 100644 backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java create mode 100644 backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java create mode 100644 backend/src/main/java/com/festago/entry_alert/domain/EntryAlert.java create mode 100644 backend/src/main/java/com/festago/entry_alert/repository/EntryAlertRepository.java create mode 100644 backend/src/main/java/com/festago/ticket/dto/event/TicketCreateEvent.java diff --git a/backend/src/main/java/com/festago/common/exception/ErrorCode.java b/backend/src/main/java/com/festago/common/exception/ErrorCode.java index bd3708054..e31a37e01 100644 --- a/backend/src/main/java/com/festago/common/exception/ErrorCode.java +++ b/backend/src/main/java/com/festago/common/exception/ErrorCode.java @@ -25,6 +25,7 @@ public enum ErrorCode { DUPLICATE_STUDENT_EMAIL("이미 인증된 이메일입니다."), TICKET_CANNOT_RESERVE_STAGE_START("공연의 시작 시간 이후로 예매할 수 없습니다."), INVALID_STUDENT_VERIFICATION_CODE("올바르지 않은 학생 인증 코드입니다."), + INVALID_ENTRY_ALERT_TIME("올바르지 않은 입장 알림 시간입니다"), // 401 EXPIRED_AUTH_TOKEN("만료된 로그인 토큰입니다."), diff --git a/backend/src/main/java/com/festago/config/SchedulingConfig.java b/backend/src/main/java/com/festago/config/SchedulingConfig.java new file mode 100644 index 000000000..987ad11bf --- /dev/null +++ b/backend/src/main/java/com/festago/config/SchedulingConfig.java @@ -0,0 +1,10 @@ +package com.festago.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling +public class SchedulingConfig { + +} diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java new file mode 100644 index 000000000..e54f76ce9 --- /dev/null +++ b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java @@ -0,0 +1,47 @@ +package com.festago.entry_alert.application; + +import com.festago.entry_alert.domain.EntryAlert; +import com.festago.entry_alert.repository.EntryAlertRepository; +import com.festago.ticket.dto.event.TicketCreateEvent; +import java.time.Clock; +import java.time.LocalDateTime; +import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@Profile({"dev", "prod"}) +public class EntryAlertEventListener { + + private final EntryAlertRepository entryAlertRepository; + private final EntryAlertService entryAlertService; + private final TaskScheduler taskScheduler; + private final Clock clock; + + public EntryAlertEventListener(EntryAlertRepository entryAlertRepository, EntryAlertService entryAlertService, + TaskScheduler taskScheduler, Clock clock) { + this.entryAlertRepository = entryAlertRepository; + this.entryAlertService = entryAlertService; + this.taskScheduler = taskScheduler; + this.clock = clock; + } + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + @Transactional(propagation = Propagation.REQUIRES_NEW) + @Async + public void addEntryAlertSchedule(TicketCreateEvent event) { + EntryAlert entryAlert = entryAlertRepository.save(new EntryAlert(event.stageId(), event.entryTime())); + Runnable task = createEntryAlertTask(entryAlert.getId()); + LocalDateTime alertTime = entryAlert.findAlertTime(); + taskScheduler.schedule(task, alertTime.atZone(clock.getZone()).toInstant()); + } + + private Runnable createEntryAlertTask(Long id) { + return () -> entryAlertService.sendEntryAlert(id); + } +} diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java new file mode 100644 index 000000000..aa1b6f07c --- /dev/null +++ b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java @@ -0,0 +1,102 @@ +package com.festago.entry_alert.application; + +import com.festago.common.exception.ErrorCode; +import com.festago.common.exception.InternalServerException; +import com.festago.entry_alert.domain.EntryAlert; +import com.festago.entry_alert.repository.EntryAlertRepository; +import com.festago.fcm.domain.FCMChannel; +import com.festago.fcm.domain.MemberFCM; +import com.festago.fcm.repository.MemberFCMRepository; +import com.festago.ticketing.repository.MemberTicketRepository; +import com.google.firebase.messaging.AndroidConfig; +import com.google.firebase.messaging.AndroidNotification; +import com.google.firebase.messaging.BatchResponse; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingException; +import com.google.firebase.messaging.Message; +import com.google.firebase.messaging.SendResponse; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +public class EntryAlertService { + + private static final Logger log = LoggerFactory.getLogger(EntryAlertService.class); + + private final EntryAlertRepository entryAlertRepository; + private final MemberTicketRepository memberTicketRepository; + private final MemberFCMRepository memberFCMRepository; + private final FirebaseMessaging firebaseMessaging; + + public EntryAlertService(EntryAlertRepository entryAlertRepository, MemberTicketRepository memberTicketRepository, + MemberFCMRepository memberFCMRepository, FirebaseMessaging firebaseMessaging) { + this.entryAlertRepository = entryAlertRepository; + this.memberTicketRepository = memberTicketRepository; + this.memberFCMRepository = memberFCMRepository; + this.firebaseMessaging = firebaseMessaging; + } + + @Async + public void sendEntryAlert(Long id) { + // TODO: 비관적락 + EntryAlert entryAlert = entryAlertRepository.findById(id) + .orElseThrow(IllegalArgumentException::new); + List memberIds = memberTicketRepository.findAllOwnerIdByStageIdAndEntryTime(entryAlert.getStageId(), + entryAlert.getEntryTime()); + // TODO: 500건씩 잘라서 가져오기. + List tokens = memberFCMRepository.findAllByMemberIdIn(memberIds).stream() + .map(MemberFCM::getFcmToken) + .toList(); + List messages = createMessages(tokens, FCMChannel.NOT_DEFINED.toString()); + try { + BatchResponse batchResponse = firebaseMessaging.sendAll(messages); + checkAllSuccess(batchResponse); + } catch (FirebaseMessagingException e) { + throw new RuntimeException(e); + } + entryAlertRepository.delete(entryAlert); + } + + // TODO: fcm 패키지에게 요청 -> 중복 줄이기 + + private List createMessages(List tokens, String channelId) { + return tokens.stream() + .map(token -> createMessage(token, channelId)) + .toList(); + } + + private Message createMessage(String token, String channelId) { + return Message.builder() + .setAndroidConfig(createAndroidConfig(channelId)) + .setToken(token) + .build(); + } + + private AndroidConfig createAndroidConfig(String channelId) { + return AndroidConfig.builder() + .setNotification(createAndroidNotification(channelId)) + .build(); + } + + private AndroidNotification createAndroidNotification(String channelId) { + return AndroidNotification.builder() + .setTitle("입장 알림") + .setBody("입장 어쩌구저쩌구") + .setChannelId(channelId) + .build(); + } + + private void checkAllSuccess(BatchResponse batchResponse) { + List failSend = batchResponse.getResponses().stream() + .filter(sendResponse -> !sendResponse.isSuccessful()) + .toList(); + + log.warn("다음 요청들이 실패했습니다. {}", failSend); + throw new InternalServerException(ErrorCode.FAIL_SEND_FCM_MESSAGE); + } +} diff --git a/backend/src/main/java/com/festago/entry_alert/domain/EntryAlert.java b/backend/src/main/java/com/festago/entry_alert/domain/EntryAlert.java new file mode 100644 index 000000000..b3acdbb5b --- /dev/null +++ b/backend/src/main/java/com/festago/entry_alert/domain/EntryAlert.java @@ -0,0 +1,62 @@ +package com.festago.entry_alert.domain; + +import com.festago.common.exception.BadRequestException; +import com.festago.common.exception.ErrorCode; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; + +@Entity +public class EntryAlert { + + private static final int ENTRY_ALERT_MINUTES_BEFORE = 10; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + private Long stageId; + + @NotNull + private LocalDateTime entryTime; + + protected EntryAlert() { + } + + public EntryAlert(Long stageId, LocalDateTime entryTime) { + this(null, stageId, entryTime); + } + + public EntryAlert(Long id, Long stageId, LocalDateTime entryTime) { + this.id = id; + this.stageId = stageId; + this.entryTime = entryTime; + } + + public static EntryAlert create(Long stageId, LocalDateTime entryTime, LocalDateTime currentTime) { + if (currentTime.isAfter(entryTime.minusMinutes(ENTRY_ALERT_MINUTES_BEFORE))) { + throw new BadRequestException(ErrorCode.INVALID_ENTRY_ALERT_TIME); + } + return new EntryAlert(stageId, entryTime); + } + + public LocalDateTime findAlertTime() { + return entryTime.minusMinutes(ENTRY_ALERT_MINUTES_BEFORE); + } + + public Long getId() { + return id; + } + + public Long getStageId() { + return stageId; + } + + public LocalDateTime getEntryTime() { + return entryTime; + } +} diff --git a/backend/src/main/java/com/festago/entry_alert/repository/EntryAlertRepository.java b/backend/src/main/java/com/festago/entry_alert/repository/EntryAlertRepository.java new file mode 100644 index 000000000..446e19184 --- /dev/null +++ b/backend/src/main/java/com/festago/entry_alert/repository/EntryAlertRepository.java @@ -0,0 +1,8 @@ +package com.festago.entry_alert.repository; + +import com.festago.entry_alert.domain.EntryAlert; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EntryAlertRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/com/festago/fcm/repository/MemberFCMRepository.java b/backend/src/main/java/com/festago/fcm/repository/MemberFCMRepository.java index e5bee3719..052d5c1a5 100644 --- a/backend/src/main/java/com/festago/fcm/repository/MemberFCMRepository.java +++ b/backend/src/main/java/com/festago/fcm/repository/MemberFCMRepository.java @@ -7,4 +7,6 @@ public interface MemberFCMRepository extends JpaRepository { List findByMemberId(Long memberId); + + List findAllByMemberIdIn(List memberIds); } diff --git a/backend/src/main/java/com/festago/ticket/application/TicketService.java b/backend/src/main/java/com/festago/ticket/application/TicketService.java index 88b022e11..075decd86 100644 --- a/backend/src/main/java/com/festago/ticket/application/TicketService.java +++ b/backend/src/main/java/com/festago/ticket/application/TicketService.java @@ -10,9 +10,11 @@ import com.festago.ticket.dto.StageTicketsResponse; import com.festago.ticket.dto.TicketCreateRequest; import com.festago.ticket.dto.TicketCreateResponse; +import com.festago.ticket.dto.event.TicketCreateEvent; import com.festago.ticket.repository.TicketRepository; import java.time.Clock; import java.time.LocalDateTime; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,11 +25,14 @@ public class TicketService { private final TicketRepository ticketRepository; private final StageRepository stageRepository; private final Clock clock; + private final ApplicationEventPublisher publisher; - public TicketService(TicketRepository ticketRepository, StageRepository stageRepository, Clock clock) { + public TicketService(TicketRepository ticketRepository, StageRepository stageRepository, Clock clock, + ApplicationEventPublisher publisher) { this.ticketRepository = ticketRepository; this.stageRepository = stageRepository; this.clock = clock; + this.publisher = publisher; } public TicketCreateResponse create(TicketCreateRequest request) { @@ -40,6 +45,7 @@ public TicketCreateResponse create(TicketCreateRequest request) { ticket.addTicketEntryTime(LocalDateTime.now(clock), request.entryTime(), request.amount()); + publisher.publishEvent(new TicketCreateEvent(request.stageId(), request.entryTime())); return TicketCreateResponse.from(ticket); } diff --git a/backend/src/main/java/com/festago/ticket/dto/event/TicketCreateEvent.java b/backend/src/main/java/com/festago/ticket/dto/event/TicketCreateEvent.java new file mode 100644 index 000000000..5fad588ff --- /dev/null +++ b/backend/src/main/java/com/festago/ticket/dto/event/TicketCreateEvent.java @@ -0,0 +1,10 @@ +package com.festago.ticket.dto.event; + +import java.time.LocalDateTime; + +public record TicketCreateEvent( + Long stageId, + LocalDateTime entryTime +) { + +} diff --git a/backend/src/main/java/com/festago/ticketing/repository/MemberTicketRepository.java b/backend/src/main/java/com/festago/ticketing/repository/MemberTicketRepository.java index 4608b6208..01db0ae22 100644 --- a/backend/src/main/java/com/festago/ticketing/repository/MemberTicketRepository.java +++ b/backend/src/main/java/com/festago/ticketing/repository/MemberTicketRepository.java @@ -3,13 +3,23 @@ import com.festago.member.domain.Member; import com.festago.stage.domain.Stage; import com.festago.ticketing.domain.MemberTicket; +import java.time.LocalDateTime; import java.util.List; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface MemberTicketRepository extends JpaRepository { List findAllByOwnerId(Long memberId, Pageable pageable); boolean existsByOwnerAndStage(Member owner, Stage stage); + + @Query(""" + SELECT m.owner.id + FROM MemberTicket m + WHERE m.stage.id = :stageId + AND m.entryTime = :entryTime + """) + List findAllOwnerIdByStageIdAndEntryTime(Long stageId, LocalDateTime entryTime); } diff --git a/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java b/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java index bafb2b838..24f09aa9a 100644 --- a/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java +++ b/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java @@ -20,6 +20,7 @@ import com.festago.ticket.repository.TicketRepository; import com.festago.ticketing.domain.MemberTicket; import com.festago.ticketing.repository.MemberTicketRepository; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -90,9 +91,7 @@ class 회원의_ID로_에매한_티켓을_모두_조회 { int expected = 10; Member member = memberRepository.save(MemberFixture.member().build()); - School school = schoolRepository.save(SchoolFixture.school().build()); - Festival festival = festivalRepository.save(FestivalFixture.festival().school(school).build()); - Stage stage = stageRepository.save(StageFixture.stage().festival(festival).build()); + Stage stage = saveStage(); for (int i = 0; i < 20; i++) { memberTicketRepository.save(MemberTicketFixture.memberTicket().stage(stage).owner(member).build()); @@ -111,9 +110,7 @@ class 회원의_ID로_에매한_티켓을_모두_조회 { // given Member member = memberRepository.save(MemberFixture.member().build()); - School school = schoolRepository.save(SchoolFixture.school().build()); - Festival festival = festivalRepository.save(FestivalFixture.festival().school(school).build()); - Stage stage = stageRepository.save(StageFixture.stage().festival(festival).build()); + Stage stage = saveStage(); List memberTickets = new ArrayList<>(); for (int i = 0; i < 20; i++) { @@ -134,4 +131,30 @@ class 회원의_ID로_에매한_티켓을_모두_조회 { assertThat(actual).isEqualTo(expected); } } + + @Test + void 무대와_입장시간으로_멤버아이디리스트_조회() { + // given + LocalDateTime entryTime = LocalDateTime.now(); + Stage stage = saveStage(); + Member member1 = memberRepository.save(MemberFixture.member().build()); + Member member2 = memberRepository.save(MemberFixture.member().build()); + MemberTicket memberTicket1 = memberTicketRepository.save( + MemberTicketFixture.memberTicket().stage(stage).owner(member1).entryTime(entryTime).build()); + MemberTicket memberTicket2 = memberTicketRepository.save( + MemberTicketFixture.memberTicket().stage(stage).owner(member2).entryTime(entryTime).build()); + + // when + List actual = memberTicketRepository.findAllOwnerIdByStageIdAndEntryTime( + stage.getId(), entryTime); + + // then + assertThat(actual).containsExactlyInAnyOrder(member1.getId(), member2.getId()); + } + + private Stage saveStage() { + School school = schoolRepository.save(SchoolFixture.school().build()); + Festival festival = festivalRepository.save(FestivalFixture.festival().school(school).build()); + return stageRepository.save(StageFixture.stage().festival(festival).build()); + } } diff --git a/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java b/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java index 3ea135724..b441a27be 100644 --- a/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java +++ b/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java @@ -6,6 +6,7 @@ import com.festago.fcm.domain.MemberFCM; import com.festago.member.domain.Member; import com.festago.member.repository.MemberRepository; +import com.festago.support.MemberFixture; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -33,4 +34,23 @@ class MemberFCMRepositoryTest { // then assertThat(actual).contains(expect); } + + @Test + void memberId리스트로_MemberFcm을_찾을_수_있다() { + // given + Member member1 = memberRepository.save(MemberFixture.member().build()); + Member member2 = memberRepository.save(MemberFixture.member().build()); + Member member3 = memberRepository.save(MemberFixture.member().build()); + MemberFCM token1 = memberFCMRepository.save(new MemberFCM(member1.getId(), "token1")); + MemberFCM token2 = memberFCMRepository.save(new MemberFCM(member1.getId(), "token2")); + MemberFCM token3 = memberFCMRepository.save(new MemberFCM(member2.getId(), "token3")); + MemberFCM token4 = memberFCMRepository.save(new MemberFCM(member3.getId(), "token4")); + + // when + List actual = memberFCMRepository.findAllByMemberIdIn( + List.of(member1.getId(), member2.getId())); + + // then + assertThat(actual).containsExactlyInAnyOrder(token1, token2, token3); + } } diff --git a/backend/src/test/java/com/festago/support/MemberFixture.java b/backend/src/test/java/com/festago/support/MemberFixture.java index ebc4f8f1d..d4d23d685 100644 --- a/backend/src/test/java/com/festago/support/MemberFixture.java +++ b/backend/src/test/java/com/festago/support/MemberFixture.java @@ -2,11 +2,12 @@ import com.festago.auth.domain.SocialType; import com.festago.member.domain.Member; +import java.util.UUID; public class MemberFixture { private Long id; - private String socialId = "123"; + private String socialId = UUID.randomUUID().toString(); private SocialType socialType = SocialType.KAKAO; private String nickname = "nickname"; private String profileImage = "https://profileImage.com/image.png"; From 21f86ffb4f28316dda59bd57f5f221dd3d518ce9 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 11:52:41 +0900 Subject: [PATCH 02/45] =?UTF-8?q?refactor:=20FCMClient=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/EntryAlertEventListener.java | 2 +- .../application/EntryAlertService.java | 66 ++------------ .../FCMNotificationEventListener.java | 67 ++------------- .../festago/fcm/application/FcmClient.java | 12 +++ .../com/festago/fcm/domain/FCMChannel.java | 5 +- .../java/com/festago/fcm/dto/FcmPayload.java | 21 +++++ .../fcm/infrastructure/FcmClientImpl.java | 86 +++++++++++++++++++ 7 files changed, 140 insertions(+), 119 deletions(-) create mode 100644 backend/src/main/java/com/festago/fcm/application/FcmClient.java create mode 100644 backend/src/main/java/com/festago/fcm/dto/FcmPayload.java create mode 100644 backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java index e54f76ce9..92a185765 100644 --- a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java +++ b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java @@ -32,7 +32,7 @@ public EntryAlertEventListener(EntryAlertRepository entryAlertRepository, EntryA } @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional @Async public void addEntryAlertSchedule(TicketCreateEvent event) { EntryAlert entryAlert = entryAlertRepository.save(new EntryAlert(event.stageId(), event.entryTime())); diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java index aa1b6f07c..9911ade03 100644 --- a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java @@ -1,20 +1,13 @@ package com.festago.entry_alert.application; -import com.festago.common.exception.ErrorCode; -import com.festago.common.exception.InternalServerException; import com.festago.entry_alert.domain.EntryAlert; import com.festago.entry_alert.repository.EntryAlertRepository; +import com.festago.fcm.application.FcmClient; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.domain.MemberFCM; +import com.festago.fcm.dto.FcmPayload; import com.festago.fcm.repository.MemberFCMRepository; import com.festago.ticketing.repository.MemberTicketRepository; -import com.google.firebase.messaging.AndroidConfig; -import com.google.firebase.messaging.AndroidNotification; -import com.google.firebase.messaging.BatchResponse; -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.FirebaseMessagingException; -import com.google.firebase.messaging.Message; -import com.google.firebase.messaging.SendResponse; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,14 +24,14 @@ public class EntryAlertService { private final EntryAlertRepository entryAlertRepository; private final MemberTicketRepository memberTicketRepository; private final MemberFCMRepository memberFCMRepository; - private final FirebaseMessaging firebaseMessaging; + private final FcmClient fcmClient; public EntryAlertService(EntryAlertRepository entryAlertRepository, MemberTicketRepository memberTicketRepository, - MemberFCMRepository memberFCMRepository, FirebaseMessaging firebaseMessaging) { + MemberFCMRepository memberFCMRepository, FcmClient fcmClient) { this.entryAlertRepository = entryAlertRepository; this.memberTicketRepository = memberTicketRepository; this.memberFCMRepository = memberFCMRepository; - this.firebaseMessaging = firebaseMessaging; + this.fcmClient = fcmClient; } @Async @@ -49,54 +42,11 @@ public void sendEntryAlert(Long id) { List memberIds = memberTicketRepository.findAllOwnerIdByStageIdAndEntryTime(entryAlert.getStageId(), entryAlert.getEntryTime()); // TODO: 500건씩 잘라서 가져오기. - List tokens = memberFCMRepository.findAllByMemberIdIn(memberIds).stream() + List tokens = memberFCMRepository.findAllByMemberIdIn(memberIds) + .stream() .map(MemberFCM::getFcmToken) .toList(); - List messages = createMessages(tokens, FCMChannel.NOT_DEFINED.toString()); - try { - BatchResponse batchResponse = firebaseMessaging.sendAll(messages); - checkAllSuccess(batchResponse); - } catch (FirebaseMessagingException e) { - throw new RuntimeException(e); - } + fcmClient.sendAll(tokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert()); entryAlertRepository.delete(entryAlert); } - - // TODO: fcm 패키지에게 요청 -> 중복 줄이기 - - private List createMessages(List tokens, String channelId) { - return tokens.stream() - .map(token -> createMessage(token, channelId)) - .toList(); - } - - private Message createMessage(String token, String channelId) { - return Message.builder() - .setAndroidConfig(createAndroidConfig(channelId)) - .setToken(token) - .build(); - } - - private AndroidConfig createAndroidConfig(String channelId) { - return AndroidConfig.builder() - .setNotification(createAndroidNotification(channelId)) - .build(); - } - - private AndroidNotification createAndroidNotification(String channelId) { - return AndroidNotification.builder() - .setTitle("입장 알림") - .setBody("입장 어쩌구저쩌구") - .setChannelId(channelId) - .build(); - } - - private void checkAllSuccess(BatchResponse batchResponse) { - List failSend = batchResponse.getResponses().stream() - .filter(sendResponse -> !sendResponse.isSuccessful()) - .toList(); - - log.warn("다음 요청들이 실패했습니다. {}", failSend); - throw new InternalServerException(ErrorCode.FAIL_SEND_FCM_MESSAGE); - } } diff --git a/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java b/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java index 1de6c5418..e64f6b0cf 100644 --- a/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java +++ b/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java @@ -1,20 +1,9 @@ package com.festago.fcm.application; -import com.festago.common.exception.ErrorCode; -import com.festago.common.exception.InternalServerException; import com.festago.entry.dto.event.EntryProcessEvent; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.MemberFCMResponse; -import com.google.firebase.messaging.AndroidConfig; -import com.google.firebase.messaging.AndroidNotification; -import com.google.firebase.messaging.BatchResponse; -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.FirebaseMessagingException; -import com.google.firebase.messaging.Message; -import com.google.firebase.messaging.SendResponse; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Profile; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @@ -25,66 +14,26 @@ @Profile({"dev", "prod"}) public class FCMNotificationEventListener { - private static final Logger log = LoggerFactory.getLogger(FCMNotificationEventListener.class); - - private final FirebaseMessaging firebaseMessaging; + private final FcmClient fcmClient; private final MemberFCMService memberFCMService; - public FCMNotificationEventListener(FirebaseMessaging firebaseMessaging, MemberFCMService memberFCMService) { - this.firebaseMessaging = firebaseMessaging; + public FCMNotificationEventListener(FcmClient fcmClient, MemberFCMService memberFCMService) { + this.fcmClient = fcmClient; this.memberFCMService = memberFCMService; } @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) @Async public void sendFcmNotification(EntryProcessEvent event) { - List messages = createMessages(getMemberFCMToken(event.memberId()), FCMChannel.NOT_DEFINED.name()); - try { - BatchResponse batchResponse = firebaseMessaging.sendAll(messages); - checkAllSuccess(batchResponse, event.memberId()); - } catch (FirebaseMessagingException e) { - log.error("fail send FCM message", e); - throw new InternalServerException(ErrorCode.FAIL_SEND_FCM_MESSAGE); - } + List tokens = getMemberFCMToken(event.memberId()); + fcmClient.sendAll(tokens, FCMChannel.ENTRY_PROCESS); } private List getMemberFCMToken(Long memberId) { - return memberFCMService.findMemberFCM(memberId).memberFCMs().stream() + return memberFCMService.findMemberFCM(memberId) + .memberFCMs() + .stream() .map(MemberFCMResponse::fcmToken) .toList(); } - - private List createMessages(List tokens, String channelId) { - return tokens.stream() - .map(token -> createMessage(token, channelId)) - .toList(); - } - - private Message createMessage(String token, String channelId) { - return Message.builder() - .setAndroidConfig(createAndroidConfig(channelId)) - .setToken(token) - .build(); - } - - private AndroidConfig createAndroidConfig(String channelId) { - return AndroidConfig.builder() - .setNotification(createAndroidNotification(channelId)) - .build(); - } - - private AndroidNotification createAndroidNotification(String channelId) { - return AndroidNotification.builder() - .setChannelId(channelId) - .build(); - } - - private void checkAllSuccess(BatchResponse batchResponse, Long memberId) { - List failSend = batchResponse.getResponses().stream() - .filter(sendResponse -> !sendResponse.isSuccessful()) - .toList(); - - log.warn("member {} 에 대한 다음 요청들이 실패했습니다. {}", memberId, failSend); - throw new InternalServerException(ErrorCode.FAIL_SEND_FCM_MESSAGE); - } } diff --git a/backend/src/main/java/com/festago/fcm/application/FcmClient.java b/backend/src/main/java/com/festago/fcm/application/FcmClient.java new file mode 100644 index 000000000..a33715b86 --- /dev/null +++ b/backend/src/main/java/com/festago/fcm/application/FcmClient.java @@ -0,0 +1,12 @@ +package com.festago.fcm.application; + +import com.festago.fcm.domain.FCMChannel; +import com.festago.fcm.dto.FcmPayload; +import java.util.List; + +public interface FcmClient { + + void sendAll(List tokens, FCMChannel channel); + + void sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload); +} diff --git a/backend/src/main/java/com/festago/fcm/domain/FCMChannel.java b/backend/src/main/java/com/festago/fcm/domain/FCMChannel.java index 836622edf..2fc63b7c0 100644 --- a/backend/src/main/java/com/festago/fcm/domain/FCMChannel.java +++ b/backend/src/main/java/com/festago/fcm/domain/FCMChannel.java @@ -1,5 +1,8 @@ package com.festago.fcm.domain; public enum FCMChannel { - NOT_DEFINED; + NOT_DEFINED, + ENTRY_ALERT, + ENTRY_PROCESS, + ; } diff --git a/backend/src/main/java/com/festago/fcm/dto/FcmPayload.java b/backend/src/main/java/com/festago/fcm/dto/FcmPayload.java new file mode 100644 index 000000000..86ccfb530 --- /dev/null +++ b/backend/src/main/java/com/festago/fcm/dto/FcmPayload.java @@ -0,0 +1,21 @@ +package com.festago.fcm.dto; + +public record FcmPayload( + String title, + String body +) { + + public static FcmPayload empty() { + return new FcmPayload( + null, + null + ); + } + + public static FcmPayload entryAlert() { + return new FcmPayload( + "🎫 입장 안내", + "잠시 후 입장인 무대가 있습니다!" + ); + } +} diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java new file mode 100644 index 000000000..3c23d1598 --- /dev/null +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -0,0 +1,86 @@ +package com.festago.fcm.infrastructure; + +import com.festago.common.exception.ErrorCode; +import com.festago.common.exception.InternalServerException; +import com.festago.fcm.application.FcmClient; +import com.festago.fcm.domain.FCMChannel; +import com.festago.fcm.dto.FcmPayload; +import com.google.firebase.messaging.AndroidConfig; +import com.google.firebase.messaging.AndroidNotification; +import com.google.firebase.messaging.BatchResponse; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingException; +import com.google.firebase.messaging.Message; +import com.google.firebase.messaging.SendResponse; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class FcmClientImpl implements FcmClient { + + private static final Logger log = LoggerFactory.getLogger(FcmClientImpl.class); + + private final FirebaseMessaging firebaseMessaging; + + public FcmClientImpl(FirebaseMessaging firebaseMessaging) { + this.firebaseMessaging = firebaseMessaging; + } + + @Override + public void sendAll(List tokens, FCMChannel channel) { + sendAll(tokens, channel, FcmPayload.empty()); + } + + @Override + public void sendAll(List tokens, FCMChannel channel, FcmPayload payload) { + List messages = createMessages(tokens, channel, payload); + try { + BatchResponse response = firebaseMessaging.sendAll(messages); + checkAllSuccess(response, channel); + } catch (FirebaseMessagingException e) { + throw new RuntimeException(e); + } + } + + private List createMessages(List tokens, FCMChannel channel, FcmPayload payload) { + return tokens.stream() + .map(token -> createMessage(token, channel, payload)) + .toList(); + } + + private Message createMessage(String token, FCMChannel channel, FcmPayload payload) { + return Message.builder() + .setAndroidConfig(createAndroidConfig(channel, payload)) + .setToken(token) + .build(); + } + + private AndroidConfig createAndroidConfig(FCMChannel channel, FcmPayload payload) { + return AndroidConfig.builder() + .setNotification(createAndroidNotification(channel, payload)) + .build(); + } + + private AndroidNotification createAndroidNotification(FCMChannel channel, FcmPayload payload) { + return AndroidNotification.builder() + .setTitle(payload.title()) + .setBody(payload.body()) + .setChannelId(channel.toString()) + .build(); + } + + private void checkAllSuccess(BatchResponse batchResponse, FCMChannel channel) { + List failSend = batchResponse.getResponses().stream() + .filter(sendResponse -> !sendResponse.isSuccessful()) + .toList(); + + if (failSend.isEmpty()) { + return; + } + + log.warn("[FCM: {}] 다음 요청들이 실패했습니다. {}", channel, failSend); + throw new InternalServerException(ErrorCode.FAIL_SEND_FCM_MESSAGE); + } +} From 4c17ab6020ed1069b174f08c5b9f3a370931f5ef Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 13:00:55 +0900 Subject: [PATCH 03/45] =?UTF-8?q?refactor:=20=EC=8A=A4=EC=BC=80=EC=A5=B4?= =?UTF-8?q?=EB=A7=81=20=EC=A4=91=EB=B3=B5=20=EC=8B=A4=ED=96=89=EC=8B=9C=20?= =?UTF-8?q?409=20Conflict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festago/common/exception/ConflictException.java | 8 ++++++++ .../java/com/festago/common/exception/ErrorCode.java | 3 +++ .../festago/presentation/GlobalExceptionHandler.java | 10 +++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/com/festago/common/exception/ConflictException.java diff --git a/backend/src/main/java/com/festago/common/exception/ConflictException.java b/backend/src/main/java/com/festago/common/exception/ConflictException.java new file mode 100644 index 000000000..c2936669a --- /dev/null +++ b/backend/src/main/java/com/festago/common/exception/ConflictException.java @@ -0,0 +1,8 @@ +package com.festago.common.exception; + +public class ConflictException extends FestaGoException { + + public ConflictException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/backend/src/main/java/com/festago/common/exception/ErrorCode.java b/backend/src/main/java/com/festago/common/exception/ErrorCode.java index e31a37e01..5dc149c70 100644 --- a/backend/src/main/java/com/festago/common/exception/ErrorCode.java +++ b/backend/src/main/java/com/festago/common/exception/ErrorCode.java @@ -47,6 +47,9 @@ public enum ErrorCode { TICKET_NOT_FOUND("존재하지 않는 티켓입니다."), SCHOOL_NOT_FOUND("존재하지 않는 학교입니다."), + // 409 + ALREADY_ALERT("이미 입장 알림이 전송되었습니다."), + // 500 INTERNAL_SERVER_ERROR("서버 내부에 문제가 발생했습니다."), INVALID_ENTRY_CODE_PERIOD("올바르지 않은 입장코드 유효기간입니다."), diff --git a/backend/src/main/java/com/festago/presentation/GlobalExceptionHandler.java b/backend/src/main/java/com/festago/presentation/GlobalExceptionHandler.java index 1bc3b023b..46af45b37 100644 --- a/backend/src/main/java/com/festago/presentation/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/festago/presentation/GlobalExceptionHandler.java @@ -1,6 +1,7 @@ package com.festago.presentation; import com.festago.common.exception.BadRequestException; +import com.festago.common.exception.ConflictException; import com.festago.common.exception.ErrorCode; import com.festago.common.exception.FestaGoException; import com.festago.common.exception.ForbiddenException; @@ -89,6 +90,12 @@ public ResponseEntity handle(NotFoundException e, HttpServletRequ return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ErrorResponse.from(e)); } + @ExceptionHandler(ConflictException.class) + public ResponseEntity handle(ConflictException e, HttpServletRequest request) { + logInfo(e, request); + return ResponseEntity.status(HttpStatus.CONFLICT).body(ErrorResponse.from(e)); + } + @ExceptionHandler(InternalServerException.class) public ResponseEntity handle(InternalServerException e, HttpServletRequest request) { logWarn(e, request); @@ -104,7 +111,8 @@ public ResponseEntity handle(Exception e, HttpServletRequest requ } @Override - protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers, + protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException e, + HttpHeaders headers, HttpStatusCode status, WebRequest request) { return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(ErrorResponse.from(ErrorCode.INVALID_REQUEST_ARGUMENT)); From 2e4530d229621d021067e57d7a763043f4c21378 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 13:01:21 +0900 Subject: [PATCH 04/45] =?UTF-8?q?refactor:=20entryAlert=20=EB=B9=84?= =?UTF-8?q?=EA=B4=80=EC=A0=81=EB=9D=BD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entry_alert/application/EntryAlertService.java | 5 ++--- .../entry_alert/repository/EntryAlertRepository.java | 8 ++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java index 9911ade03..1935fad38 100644 --- a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java @@ -36,9 +36,8 @@ public EntryAlertService(EntryAlertRepository entryAlertRepository, MemberTicket @Async public void sendEntryAlert(Long id) { - // TODO: 비관적락 - EntryAlert entryAlert = entryAlertRepository.findById(id) - .orElseThrow(IllegalArgumentException::new); + EntryAlert entryAlert = entryAlertRepository.findByIdForUpdate(id) + .orElseThrow(() -> new ConflictException(ErrorCode.ALREADY_ALERT)); List memberIds = memberTicketRepository.findAllOwnerIdByStageIdAndEntryTime(entryAlert.getStageId(), entryAlert.getEntryTime()); // TODO: 500건씩 잘라서 가져오기. diff --git a/backend/src/main/java/com/festago/entry_alert/repository/EntryAlertRepository.java b/backend/src/main/java/com/festago/entry_alert/repository/EntryAlertRepository.java index 446e19184..c86d5cbe0 100644 --- a/backend/src/main/java/com/festago/entry_alert/repository/EntryAlertRepository.java +++ b/backend/src/main/java/com/festago/entry_alert/repository/EntryAlertRepository.java @@ -1,8 +1,16 @@ package com.festago.entry_alert.repository; import com.festago.entry_alert.domain.EntryAlert; +import jakarta.persistence.LockModeType; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface EntryAlertRepository extends JpaRepository { + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("SELECT ea FROM EntryAlert ea WHERE ea.id = :id") + Optional findByIdForUpdate(@Param("id") Long id); } From 4a77dd7446d223d0571bc2cce479e3efe1c0cf45 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 13:03:05 +0900 Subject: [PATCH 05/45] =?UTF-8?q?refactor:=20EntryAlertEventListener?= =?UTF-8?q?=EC=97=90=EC=84=9C=20EntryAlertRepository=20=EC=B0=B8=EC=A1=B0?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/EntryAlertEventListener.java | 15 +++++---------- .../application/EntryAlertService.java | 5 +++++ .../entry_alert/dto/EntryAlertResponse.java | 10 ++++++++++ 3 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 backend/src/main/java/com/festago/entry_alert/dto/EntryAlertResponse.java diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java index 92a185765..c7142155c 100644 --- a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java +++ b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java @@ -1,7 +1,6 @@ package com.festago.entry_alert.application; -import com.festago.entry_alert.domain.EntryAlert; -import com.festago.entry_alert.repository.EntryAlertRepository; +import com.festago.entry_alert.dto.EntryAlertResponse; import com.festago.ticket.dto.event.TicketCreateEvent; import java.time.Clock; import java.time.LocalDateTime; @@ -9,7 +8,6 @@ import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionPhase; import org.springframework.transaction.event.TransactionalEventListener; @@ -18,14 +16,11 @@ @Profile({"dev", "prod"}) public class EntryAlertEventListener { - private final EntryAlertRepository entryAlertRepository; private final EntryAlertService entryAlertService; private final TaskScheduler taskScheduler; private final Clock clock; - public EntryAlertEventListener(EntryAlertRepository entryAlertRepository, EntryAlertService entryAlertService, - TaskScheduler taskScheduler, Clock clock) { - this.entryAlertRepository = entryAlertRepository; + public EntryAlertEventListener(EntryAlertService entryAlertService, TaskScheduler taskScheduler, Clock clock) { this.entryAlertService = entryAlertService; this.taskScheduler = taskScheduler; this.clock = clock; @@ -35,9 +30,9 @@ public EntryAlertEventListener(EntryAlertRepository entryAlertRepository, EntryA @Transactional @Async public void addEntryAlertSchedule(TicketCreateEvent event) { - EntryAlert entryAlert = entryAlertRepository.save(new EntryAlert(event.stageId(), event.entryTime())); - Runnable task = createEntryAlertTask(entryAlert.getId()); - LocalDateTime alertTime = entryAlert.findAlertTime(); + EntryAlertResponse entryAlert = entryAlertService.create(event.stageId(), event.entryTime()); + Runnable task = createEntryAlertTask(entryAlert.id()); + LocalDateTime alertTime = entryAlert.alertTime(); taskScheduler.schedule(task, alertTime.atZone(clock.getZone()).toInstant()); } diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java index 1935fad38..99a9b806d 100644 --- a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java @@ -34,6 +34,11 @@ public EntryAlertService(EntryAlertRepository entryAlertRepository, MemberTicket this.fcmClient = fcmClient; } + public EntryAlertResponse create(Long stageId, LocalDateTime entryTime) { + EntryAlert entryAlert = entryAlertRepository.save(new EntryAlert(stageId, entryTime)); + return new EntryAlertResponse(entryAlert.getId(), entryAlert.findAlertTime()); + } + @Async public void sendEntryAlert(Long id) { EntryAlert entryAlert = entryAlertRepository.findByIdForUpdate(id) diff --git a/backend/src/main/java/com/festago/entry_alert/dto/EntryAlertResponse.java b/backend/src/main/java/com/festago/entry_alert/dto/EntryAlertResponse.java new file mode 100644 index 000000000..daa627aa0 --- /dev/null +++ b/backend/src/main/java/com/festago/entry_alert/dto/EntryAlertResponse.java @@ -0,0 +1,10 @@ +package com.festago.entry_alert.dto; + +import java.time.LocalDateTime; + +public record EntryAlertResponse( + Long id, + LocalDateTime alertTime +) { + +} From 690f90f45a14e948e00fb61576665fa91ec44a38 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 13:05:22 +0900 Subject: [PATCH 06/45] =?UTF-8?q?refactor:=20FCM=20Aycnc=20Config=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/festago/config/AsyncConfig.java | 22 +++++++++++++++++++ .../application/EntryAlertService.java | 18 ++++++++++----- .../FCMNotificationEventListener.java | 3 ++- .../festago/fcm/application/FcmClient.java | 4 +--- .../java/com/festago/fcm/dto/FcmPayload.java | 12 +++++----- .../fcm/infrastructure/FcmClientImpl.java | 8 +++---- 6 files changed, 47 insertions(+), 20 deletions(-) diff --git a/backend/src/main/java/com/festago/config/AsyncConfig.java b/backend/src/main/java/com/festago/config/AsyncConfig.java index fbf428a9b..522c86596 100644 --- a/backend/src/main/java/com/festago/config/AsyncConfig.java +++ b/backend/src/main/java/com/festago/config/AsyncConfig.java @@ -1,10 +1,32 @@ package com.festago.config; +import java.util.concurrent.Executor; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @EnableAsync @Configuration public class AsyncConfig { + private static final String DEFAULT_EXECUTOR_NAME = "taskExecutor"; + public static final String FCM_EXECUTOR_NAME = "fcmExecutor"; + + @Bean(name = DEFAULT_EXECUTOR_NAME) + public Executor defaultExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(8); + executor.initialize(); + return executor; + } + + @Bean(name = FCM_EXECUTOR_NAME) + public Executor fcmExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(8); + executor.setQueueCapacity(10); + executor.initialize(); + return executor; + } } diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java index 99a9b806d..c87adfb7b 100644 --- a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java @@ -1,6 +1,9 @@ package com.festago.entry_alert.application; +import com.festago.common.exception.ConflictException; +import com.festago.common.exception.ErrorCode; import com.festago.entry_alert.domain.EntryAlert; +import com.festago.entry_alert.dto.EntryAlertResponse; import com.festago.entry_alert.repository.EntryAlertRepository; import com.festago.fcm.application.FcmClient; import com.festago.fcm.domain.FCMChannel; @@ -8,9 +11,8 @@ import com.festago.fcm.dto.FcmPayload; import com.festago.fcm.repository.MemberFCMRepository; import com.festago.ticketing.repository.MemberTicketRepository; +import java.time.LocalDateTime; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,7 +21,7 @@ @Transactional public class EntryAlertService { - private static final Logger log = LoggerFactory.getLogger(EntryAlertService.class); + private static final int BATCH_ALERT_SIZE = 500; private final EntryAlertRepository entryAlertRepository; private final MemberTicketRepository memberTicketRepository; @@ -45,12 +47,18 @@ public void sendEntryAlert(Long id) { .orElseThrow(() -> new ConflictException(ErrorCode.ALREADY_ALERT)); List memberIds = memberTicketRepository.findAllOwnerIdByStageIdAndEntryTime(entryAlert.getStageId(), entryAlert.getEntryTime()); - // TODO: 500건씩 잘라서 가져오기. List tokens = memberFCMRepository.findAllByMemberIdIn(memberIds) .stream() .map(MemberFCM::getFcmToken) .toList(); - fcmClient.sendAll(tokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert()); + sendMessages(tokens); entryAlertRepository.delete(entryAlert); } + + private void sendMessages(List tokens) { + for (int i = 0; i < tokens.size(); i += BATCH_ALERT_SIZE) { + List subTokens = tokens.subList(i, Math.min(i + BATCH_ALERT_SIZE, tokens.size())); + fcmClient.sendAll(subTokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert()); + } + } } diff --git a/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java b/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java index e64f6b0cf..95c8e12b2 100644 --- a/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java +++ b/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java @@ -2,6 +2,7 @@ import com.festago.entry.dto.event.EntryProcessEvent; import com.festago.fcm.domain.FCMChannel; +import com.festago.fcm.dto.FcmPayload; import com.festago.fcm.dto.MemberFCMResponse; import java.util.List; import org.springframework.context.annotation.Profile; @@ -26,7 +27,7 @@ public FCMNotificationEventListener(FcmClient fcmClient, MemberFCMService member @Async public void sendFcmNotification(EntryProcessEvent event) { List tokens = getMemberFCMToken(event.memberId()); - fcmClient.sendAll(tokens, FCMChannel.ENTRY_PROCESS); + fcmClient.sendAll(tokens, FCMChannel.ENTRY_PROCESS, FcmPayload.entryProcess()); } private List getMemberFCMToken(Long memberId) { diff --git a/backend/src/main/java/com/festago/fcm/application/FcmClient.java b/backend/src/main/java/com/festago/fcm/application/FcmClient.java index a33715b86..7ef794ab8 100644 --- a/backend/src/main/java/com/festago/fcm/application/FcmClient.java +++ b/backend/src/main/java/com/festago/fcm/application/FcmClient.java @@ -5,8 +5,6 @@ import java.util.List; public interface FcmClient { - - void sendAll(List tokens, FCMChannel channel); - + void sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload); } diff --git a/backend/src/main/java/com/festago/fcm/dto/FcmPayload.java b/backend/src/main/java/com/festago/fcm/dto/FcmPayload.java index 86ccfb530..6934c21f1 100644 --- a/backend/src/main/java/com/festago/fcm/dto/FcmPayload.java +++ b/backend/src/main/java/com/festago/fcm/dto/FcmPayload.java @@ -5,17 +5,17 @@ public record FcmPayload( String body ) { - public static FcmPayload empty() { + public static FcmPayload entryAlert() { return new FcmPayload( - null, - null + "🎫 입장 안내", + "잠시 후 입장인 무대가 있습니다!" ); } - public static FcmPayload entryAlert() { + public static FcmPayload entryProcess() { return new FcmPayload( - "🎫 입장 안내", - "잠시 후 입장인 무대가 있습니다!" + "🎉 입장 완료", + "즐거운 축제 관람 되세요!" ); } } diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java index 3c23d1598..dfe955f42 100644 --- a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -2,6 +2,7 @@ import com.festago.common.exception.ErrorCode; import com.festago.common.exception.InternalServerException; +import com.festago.config.AsyncConfig; import com.festago.fcm.application.FcmClient; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; @@ -15,6 +16,7 @@ import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component @@ -29,11 +31,7 @@ public FcmClientImpl(FirebaseMessaging firebaseMessaging) { } @Override - public void sendAll(List tokens, FCMChannel channel) { - sendAll(tokens, channel, FcmPayload.empty()); - } - - @Override + @Async(value = AsyncConfig.FCM_EXECUTOR_NAME) public void sendAll(List tokens, FCMChannel channel, FcmPayload payload) { List messages = createMessages(tokens, channel, payload); try { From 323c6d97e4043dc74b344b66e87b276f9c871602 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 13:14:52 +0900 Subject: [PATCH 07/45] =?UTF-8?q?feat:=20flyway=20script=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/db/migration/V6__entry_alert.sql | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 backend/src/main/resources/db/migration/V6__entry_alert.sql diff --git a/backend/src/main/resources/db/migration/V6__entry_alert.sql b/backend/src/main/resources/db/migration/V6__entry_alert.sql new file mode 100644 index 000000000..37f2e15f2 --- /dev/null +++ b/backend/src/main/resources/db/migration/V6__entry_alert.sql @@ -0,0 +1,9 @@ +create table if not exists entry_alert +( + id bigint not null auto_increment, + entry_time datetime(6) not null, + stage_id bigint not null, + primary key (id) +) engine innodb + default charset = utf8mb4 + collate = utf8mb4_0900_ai_ci; From 1651604ae701c3860dd4c11746411d0f4b80ab30 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 13:19:42 +0900 Subject: [PATCH 08/45] =?UTF-8?q?feat:=20MockFcmClient=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/EntryAlertEventListener.java | 2 -- .../FCMNotificationEventListener.java | 2 -- .../fcm/application/MockFcmClient.java | 21 +++++++++++++++++++ .../fcm/infrastructure/FcmClientImpl.java | 2 ++ 4 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 backend/src/main/java/com/festago/fcm/application/MockFcmClient.java diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java index c7142155c..6405563e3 100644 --- a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java +++ b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java @@ -4,7 +4,6 @@ import com.festago.ticket.dto.event.TicketCreateEvent; import java.time.Clock; import java.time.LocalDateTime; -import org.springframework.context.annotation.Profile; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @@ -13,7 +12,6 @@ import org.springframework.transaction.event.TransactionalEventListener; @Component -@Profile({"dev", "prod"}) public class EntryAlertEventListener { private final EntryAlertService entryAlertService; diff --git a/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java b/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java index 95c8e12b2..7240fb149 100644 --- a/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java +++ b/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java @@ -5,14 +5,12 @@ import com.festago.fcm.dto.FcmPayload; import com.festago.fcm.dto.MemberFCMResponse; import java.util.List; -import org.springframework.context.annotation.Profile; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionPhase; import org.springframework.transaction.event.TransactionalEventListener; @Component -@Profile({"dev", "prod"}) public class FCMNotificationEventListener { private final FcmClient fcmClient; diff --git a/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java new file mode 100644 index 000000000..de46e0328 --- /dev/null +++ b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java @@ -0,0 +1,21 @@ +package com.festago.fcm.application; + +import com.festago.fcm.domain.FCMChannel; +import com.festago.fcm.dto.FcmPayload; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Component +@Profile({"!prod", "!dev"}) +public class MockFcmClient implements FcmClient { + + private static final Logger log = LoggerFactory.getLogger(MockFcmClient.class); + + @Override + public void sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload) { + log.info("[FCM] title: {} / body: {} / to {} device", fcmPayload.title(), fcmPayload.body(), tokens.size()); + } +} diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java index dfe955f42..df1d3c79f 100644 --- a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -16,10 +16,12 @@ import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component +@Profile({"dev", "prod"}) public class FcmClientImpl implements FcmClient { private static final Logger log = LoggerFactory.getLogger(FcmClientImpl.class); From 3b308b57472a485791270d8ecad06162b4244b2e Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 13:41:21 +0900 Subject: [PATCH 09/45] =?UTF-8?q?refactor:=20FCMNotificationEventListener?= =?UTF-8?q?=20Async=20=EB=B9=88=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festago/fcm/application/FCMNotificationEventListener.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java b/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java index 7240fb149..64eda4a80 100644 --- a/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java +++ b/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java @@ -1,5 +1,6 @@ package com.festago.fcm.application; +import com.festago.config.AsyncConfig; import com.festago.entry.dto.event.EntryProcessEvent; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; @@ -22,7 +23,7 @@ public FCMNotificationEventListener(FcmClient fcmClient, MemberFCMService member } @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) - @Async + @Async(value = AsyncConfig.FCM_EXECUTOR_NAME) public void sendFcmNotification(EntryProcessEvent event) { List tokens = getMemberFCMToken(event.memberId()); fcmClient.sendAll(tokens, FCMChannel.ENTRY_PROCESS, FcmPayload.entryProcess()); From 97daba8c23ba9c2868074ca4d20c95139b5522d6 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 14:20:09 +0900 Subject: [PATCH 10/45] =?UTF-8?q?feat:=20SpringBoot=20=EC=8B=A4=ED=96=89?= =?UTF-8?q?=EC=8B=9C=20initSchedule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/EntryAlertEventListener.java | 20 +++++++++++++++++-- .../application/EntryAlertService.java | 7 +++++++ .../fcm/application/MockFcmClient.java | 3 +++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java index 6405563e3..63f9d1021 100644 --- a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java +++ b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java @@ -4,16 +4,22 @@ import com.festago.ticket.dto.event.TicketCreateEvent; import java.time.Clock; import java.time.LocalDateTime; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionPhase; import org.springframework.transaction.event.TransactionalEventListener; @Component public class EntryAlertEventListener { + private static final Logger log = LoggerFactory.getLogger(EntryAlertEventListener.class); + private final EntryAlertService entryAlertService; private final TaskScheduler taskScheduler; private final Clock clock; @@ -25,10 +31,14 @@ public EntryAlertEventListener(EntryAlertService entryAlertService, TaskSchedule } @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) - @Transactional @Async public void addEntryAlertSchedule(TicketCreateEvent event) { EntryAlertResponse entryAlert = entryAlertService.create(event.stageId(), event.entryTime()); + addSchedule(entryAlert); + } + + private void addSchedule(EntryAlertResponse entryAlert) { + log.info("add entryAlert schedule: {}", entryAlert.id()); Runnable task = createEntryAlertTask(entryAlert.id()); LocalDateTime alertTime = entryAlert.alertTime(); taskScheduler.schedule(task, alertTime.atZone(clock.getZone()).toInstant()); @@ -37,4 +47,10 @@ public void addEntryAlertSchedule(TicketCreateEvent event) { private Runnable createEntryAlertTask(Long id) { return () -> entryAlertService.sendEntryAlert(id); } + + @EventListener(ApplicationReadyEvent.class) + public void initEntryAlertSchedule() { + List entryAlerts = entryAlertService.findAll(); + entryAlerts.forEach(this::addSchedule); + } } diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java index c87adfb7b..9814a9c4f 100644 --- a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java @@ -36,6 +36,13 @@ public EntryAlertService(EntryAlertRepository entryAlertRepository, MemberTicket this.fcmClient = fcmClient; } + @Transactional(readOnly = true) + public List findAll() { + return entryAlertRepository.findAll().stream() + .map(alert -> new EntryAlertResponse(alert.getId(), alert.findAlertTime())) + .toList(); + } + public EntryAlertResponse create(Long stageId, LocalDateTime entryTime) { EntryAlert entryAlert = entryAlertRepository.save(new EntryAlert(stageId, entryTime)); return new EntryAlertResponse(entryAlert.getId(), entryAlert.findAlertTime()); diff --git a/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java index de46e0328..ae2776f44 100644 --- a/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java +++ b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java @@ -1,11 +1,13 @@ package com.festago.fcm.application; +import com.festago.config.AsyncConfig; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component @@ -15,6 +17,7 @@ public class MockFcmClient implements FcmClient { private static final Logger log = LoggerFactory.getLogger(MockFcmClient.class); @Override + @Async(value = AsyncConfig.FCM_EXECUTOR_NAME) public void sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload) { log.info("[FCM] title: {} / body: {} / to {} device", fcmPayload.title(), fcmPayload.body(), tokens.size()); } From aa4e5306858627a3a7ff755ce1598021dddb4f4a Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 15:13:38 +0900 Subject: [PATCH 11/45] =?UTF-8?q?refactor:=20Async=20ThreadPool=20?= =?UTF-8?q?=ED=95=98=EB=82=98=EB=A7=8C=20=ED=99=9C=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/festago/config/AsyncConfig.java | 22 ------------------- .../application/EntryAlertService.java | 2 +- .../FCMNotificationEventListener.java | 3 +-- .../festago/fcm/application/FcmClient.java | 4 +++- .../fcm/application/MockFcmClient.java | 8 +++++-- .../fcm/infrastructure/FcmClientImpl.java | 8 +++++-- 6 files changed, 17 insertions(+), 30 deletions(-) diff --git a/backend/src/main/java/com/festago/config/AsyncConfig.java b/backend/src/main/java/com/festago/config/AsyncConfig.java index 522c86596..fbf428a9b 100644 --- a/backend/src/main/java/com/festago/config/AsyncConfig.java +++ b/backend/src/main/java/com/festago/config/AsyncConfig.java @@ -1,32 +1,10 @@ package com.festago.config; -import java.util.concurrent.Executor; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @EnableAsync @Configuration public class AsyncConfig { - private static final String DEFAULT_EXECUTOR_NAME = "taskExecutor"; - public static final String FCM_EXECUTOR_NAME = "fcmExecutor"; - - @Bean(name = DEFAULT_EXECUTOR_NAME) - public Executor defaultExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(8); - executor.initialize(); - return executor; - } - - @Bean(name = FCM_EXECUTOR_NAME) - public Executor fcmExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(8); - executor.setQueueCapacity(10); - executor.initialize(); - return executor; - } } diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java index 9814a9c4f..80ec14f58 100644 --- a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java @@ -65,7 +65,7 @@ public void sendEntryAlert(Long id) { private void sendMessages(List tokens) { for (int i = 0; i < tokens.size(); i += BATCH_ALERT_SIZE) { List subTokens = tokens.subList(i, Math.min(i + BATCH_ALERT_SIZE, tokens.size())); - fcmClient.sendAll(subTokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert()); + fcmClient.sendAllAsync(subTokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert()); } } } diff --git a/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java b/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java index 64eda4a80..7240fb149 100644 --- a/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java +++ b/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java @@ -1,6 +1,5 @@ package com.festago.fcm.application; -import com.festago.config.AsyncConfig; import com.festago.entry.dto.event.EntryProcessEvent; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; @@ -23,7 +22,7 @@ public FCMNotificationEventListener(FcmClient fcmClient, MemberFCMService member } @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) - @Async(value = AsyncConfig.FCM_EXECUTOR_NAME) + @Async public void sendFcmNotification(EntryProcessEvent event) { List tokens = getMemberFCMToken(event.memberId()); fcmClient.sendAll(tokens, FCMChannel.ENTRY_PROCESS, FcmPayload.entryProcess()); diff --git a/backend/src/main/java/com/festago/fcm/application/FcmClient.java b/backend/src/main/java/com/festago/fcm/application/FcmClient.java index 7ef794ab8..77b93e4a8 100644 --- a/backend/src/main/java/com/festago/fcm/application/FcmClient.java +++ b/backend/src/main/java/com/festago/fcm/application/FcmClient.java @@ -5,6 +5,8 @@ import java.util.List; public interface FcmClient { - + + void sendAllAsync(List tokens, FCMChannel channel, FcmPayload fcmPayload); + void sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload); } diff --git a/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java index ae2776f44..95196cbb6 100644 --- a/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java +++ b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java @@ -1,6 +1,5 @@ package com.festago.fcm.application; -import com.festago.config.AsyncConfig; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; import java.util.List; @@ -17,7 +16,12 @@ public class MockFcmClient implements FcmClient { private static final Logger log = LoggerFactory.getLogger(MockFcmClient.class); @Override - @Async(value = AsyncConfig.FCM_EXECUTOR_NAME) + @Async + public void sendAllAsync(List tokens, FCMChannel channel, FcmPayload fcmPayload) { + sendAll(tokens, channel, fcmPayload); + } + + @Override public void sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload) { log.info("[FCM] title: {} / body: {} / to {} device", fcmPayload.title(), fcmPayload.body(), tokens.size()); } diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java index df1d3c79f..7d1588550 100644 --- a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -2,7 +2,6 @@ import com.festago.common.exception.ErrorCode; import com.festago.common.exception.InternalServerException; -import com.festago.config.AsyncConfig; import com.festago.fcm.application.FcmClient; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; @@ -33,7 +32,12 @@ public FcmClientImpl(FirebaseMessaging firebaseMessaging) { } @Override - @Async(value = AsyncConfig.FCM_EXECUTOR_NAME) + @Async + public void sendAllAsync(List tokens, FCMChannel channel, FcmPayload fcmPayload) { + sendAll(tokens, channel, fcmPayload); + } + + @Override public void sendAll(List tokens, FCMChannel channel, FcmPayload payload) { List messages = createMessages(tokens, channel, payload); try { From 44ced57dcfbd10b92a2f6d7d74de19e0373a0bb2 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 15:26:48 +0900 Subject: [PATCH 12/45] =?UTF-8?q?test:=20FcmClientImplTet=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FCMNotificationEventListenerTest.java | 75 ------------- .../fcm/infrastructure/FcmClientImplTest.java | 101 ++++++++++++++++++ 2 files changed, 101 insertions(+), 75 deletions(-) delete mode 100644 backend/src/test/java/com/festago/fcm/application/FCMNotificationEventListenerTest.java create mode 100644 backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java diff --git a/backend/src/test/java/com/festago/fcm/application/FCMNotificationEventListenerTest.java b/backend/src/test/java/com/festago/fcm/application/FCMNotificationEventListenerTest.java deleted file mode 100644 index 795aa245c..000000000 --- a/backend/src/test/java/com/festago/fcm/application/FCMNotificationEventListenerTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.festago.fcm.application; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.festago.common.exception.InternalServerException; -import com.festago.entry.dto.event.EntryProcessEvent; -import com.festago.fcm.dto.MemberFCMResponse; -import com.festago.fcm.dto.MemberFCMsResponse; -import com.google.firebase.messaging.BatchResponse; -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.FirebaseMessagingException; -import com.google.firebase.messaging.SendResponse; -import java.util.List; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@DisplayNameGeneration(ReplaceUnderscores.class) -@SuppressWarnings("NonAsciiCharacters") -@ExtendWith(MockitoExtension.class) -class FCMNotificationEventListenerTest { - - @Mock - FirebaseMessaging firebaseMessaging; - - @Mock - MemberFCMService memberFCMService; - - @InjectMocks - FCMNotificationEventListener FCMNotificationEventListener; - - @Test - void 유저의_FCM_요청_중_하나라도_실패하면_예외() throws FirebaseMessagingException { - // given - given(memberFCMService.findMemberFCM(anyLong())).willReturn( - new MemberFCMsResponse(List.of(new MemberFCMResponse(1L, 1L, "token1"), new MemberFCMResponse(2L, 1L, "token2")))); - - given(firebaseMessaging.sendAll(any())).willReturn(new MockBatchResponse()); - - EntryProcessEvent event = new EntryProcessEvent(1L); - - // when & then - Assertions.assertThatThrownBy(() -> FCMNotificationEventListener.sendFcmNotification(event)) - .isInstanceOf(InternalServerException.class); - } - - private static class MockBatchResponse implements BatchResponse { - - @Override - public List getResponses() { - SendResponse mockResponse = mock(SendResponse.class); - when(mockResponse.isSuccessful()).thenReturn(false); - return List.of(mockResponse, mockResponse); - } - - @Override - public int getSuccessCount() { - return 0; - } - - @Override - public int getFailureCount() { - return 0; - } - } -} diff --git a/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java b/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java new file mode 100644 index 000000000..3acb861e5 --- /dev/null +++ b/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java @@ -0,0 +1,101 @@ +package com.festago.fcm.infrastructure; + +import static com.festago.common.exception.ErrorCode.FAIL_SEND_FCM_MESSAGE; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.festago.common.exception.InternalServerException; +import com.festago.fcm.domain.FCMChannel; +import com.festago.fcm.dto.FcmPayload; +import com.google.firebase.messaging.BatchResponse; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingException; +import com.google.firebase.messaging.SendResponse; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +@ExtendWith(MockitoExtension.class) +class FcmClientImplTest { + + @Mock + FirebaseMessaging firebaseMessaging; + + @InjectMocks + FcmClientImpl fcmClient; + + List tokens; + FCMChannel fcmChannel; + FcmPayload fcmPayload; + + @BeforeEach + void setUp() { + tokens = List.of("token1", "token2"); + fcmChannel = FCMChannel.ENTRY_ALERT; + fcmPayload = FcmPayload.entryAlert(); + } + + @Test + void 유저의_FCM_요청_중_하나라도_실패하면_예외() throws FirebaseMessagingException { + // given + given(firebaseMessaging.sendAll(anyList())) + .willReturn(new MockBatchResponse(false)); + + // when & then + assertThatThrownBy(() -> fcmClient.sendAll(tokens, fcmChannel, fcmPayload)) + .isInstanceOf(InternalServerException.class) + .hasMessage(FAIL_SEND_FCM_MESSAGE.getMessage()); + } + + @Test + void 모두_성공하면_성공() throws FirebaseMessagingException { + // given + given(firebaseMessaging.sendAll(anyList())) + .willReturn(new MockBatchResponse(true)); + + // when & then + assertThatNoException() + .isThrownBy(() -> fcmClient.sendAll(tokens, fcmChannel, fcmPayload)); + } + + private static class MockBatchResponse implements BatchResponse { + + private final boolean isSuccessful; + + private MockBatchResponse(boolean isSuccessful) { + this.isSuccessful = isSuccessful; + } + + @Override + public List getResponses() { + SendResponse mockResponse = mock(SendResponse.class); + + when(mockResponse.isSuccessful()) + .thenReturn(isSuccessful); + + return List.of(mockResponse, mockResponse); + } + + @Override + public int getSuccessCount() { + return 0; + } + + @Override + public int getFailureCount() { + return 0; + } + } +} From bd7451b7a1d69ede50c477a6fc6050de9e9da5c9 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 22:59:40 +0900 Subject: [PATCH 13/45] =?UTF-8?q?feat:=20MemberFcmFixture=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/festago/support/MemberFcmFixture.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 backend/src/test/java/com/festago/support/MemberFcmFixture.java diff --git a/backend/src/test/java/com/festago/support/MemberFcmFixture.java b/backend/src/test/java/com/festago/support/MemberFcmFixture.java new file mode 100644 index 000000000..d4c6d615d --- /dev/null +++ b/backend/src/test/java/com/festago/support/MemberFcmFixture.java @@ -0,0 +1,34 @@ +package com.festago.support; + +import com.festago.fcm.domain.MemberFCM; +import java.util.UUID; + +public class MemberFcmFixture { + + private Long id; + private Long memberId = 1L; + private String fcmToken = UUID.randomUUID().toString(); + + public static MemberFcmFixture memberFcm() { + return new MemberFcmFixture(); + } + + public MemberFcmFixture id(Long id) { + this.id = id; + return this; + } + + public MemberFcmFixture memberId(Long memberId) { + this.memberId = memberId; + return this; + } + + public MemberFcmFixture fcmToken(String fcmToken) { + this.fcmToken = fcmToken; + return this; + } + + public MemberFCM build() { + return new MemberFCM(id, memberId, fcmToken); + } +} From 221cac04d0f446df83cea64f604f9f386edcdccc Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 23:00:12 +0900 Subject: [PATCH 14/45] =?UTF-8?q?refactor:=20findAllTokenByMemberIdIn=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=ED=99=9C=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/EntryAlertService.java | 6 +-- .../fcm/repository/MemberFCMRepository.java | 5 +- .../repository/MemberFCMRepositoryTest.java | 49 +++++++++++++------ 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java index 80ec14f58..9aadd23a5 100644 --- a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java @@ -7,7 +7,6 @@ import com.festago.entry_alert.repository.EntryAlertRepository; import com.festago.fcm.application.FcmClient; import com.festago.fcm.domain.FCMChannel; -import com.festago.fcm.domain.MemberFCM; import com.festago.fcm.dto.FcmPayload; import com.festago.fcm.repository.MemberFCMRepository; import com.festago.ticketing.repository.MemberTicketRepository; @@ -54,10 +53,7 @@ public void sendEntryAlert(Long id) { .orElseThrow(() -> new ConflictException(ErrorCode.ALREADY_ALERT)); List memberIds = memberTicketRepository.findAllOwnerIdByStageIdAndEntryTime(entryAlert.getStageId(), entryAlert.getEntryTime()); - List tokens = memberFCMRepository.findAllByMemberIdIn(memberIds) - .stream() - .map(MemberFCM::getFcmToken) - .toList(); + List tokens = memberFCMRepository.findAllTokenByMemberIdIn(memberIds); sendMessages(tokens); entryAlertRepository.delete(entryAlert); } diff --git a/backend/src/main/java/com/festago/fcm/repository/MemberFCMRepository.java b/backend/src/main/java/com/festago/fcm/repository/MemberFCMRepository.java index 052d5c1a5..f00c4b5db 100644 --- a/backend/src/main/java/com/festago/fcm/repository/MemberFCMRepository.java +++ b/backend/src/main/java/com/festago/fcm/repository/MemberFCMRepository.java @@ -3,10 +3,13 @@ import com.festago.fcm.domain.MemberFCM; 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; public interface MemberFCMRepository extends JpaRepository { List findByMemberId(Long memberId); - List findAllByMemberIdIn(List memberIds); + @Query("SELECT mf.fcmToken FROM MemberFCM mf WHERE mf.memberId IN :memberIds") + List findAllTokenByMemberIdIn(@Param("memberIds") List memberIds); } diff --git a/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java b/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java index b441a27be..f01c5fb8f 100644 --- a/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java +++ b/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java @@ -6,8 +6,11 @@ import com.festago.fcm.domain.MemberFCM; import com.festago.member.domain.Member; import com.festago.member.repository.MemberRepository; +import com.festago.support.MemberFcmFixture; import com.festago.support.MemberFixture; +import java.util.Collections; import java.util.List; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; @@ -26,7 +29,7 @@ class MemberFCMRepositoryTest { // given Member member = memberRepository.save(new Member("socialId", SocialType.FESTAGO, "nickname", "image.jpg")); Long memberId = member.getId(); - MemberFCM expect = memberFCMRepository.save(new MemberFCM(memberId, "fcmToken")); + MemberFCM expect = memberFCMRepository.save(MemberFcmFixture.memberFcm().memberId(memberId).build()); // when List actual = memberFCMRepository.findByMemberId(memberId); @@ -35,22 +38,36 @@ class MemberFCMRepositoryTest { assertThat(actual).contains(expect); } - @Test - void memberId리스트로_MemberFcm을_찾을_수_있다() { - // given - Member member1 = memberRepository.save(MemberFixture.member().build()); - Member member2 = memberRepository.save(MemberFixture.member().build()); - Member member3 = memberRepository.save(MemberFixture.member().build()); - MemberFCM token1 = memberFCMRepository.save(new MemberFCM(member1.getId(), "token1")); - MemberFCM token2 = memberFCMRepository.save(new MemberFCM(member1.getId(), "token2")); - MemberFCM token3 = memberFCMRepository.save(new MemberFCM(member2.getId(), "token3")); - MemberFCM token4 = memberFCMRepository.save(new MemberFCM(member3.getId(), "token4")); + @Nested + class memberId리스트로_token리스트_조회 { - // when - List actual = memberFCMRepository.findAllByMemberIdIn( - List.of(member1.getId(), member2.getId())); + @Test + void 빈리스트면_빈리스트반환() { + // given + List actual = memberFCMRepository.findAllTokenByMemberIdIn(Collections.emptyList()); - // then - assertThat(actual).containsExactlyInAnyOrder(token1, token2, token3); + // then + assertThat(actual).isEmpty(); + } + + @Test + void 성공적으로_조회() { + // given + Member member1 = memberRepository.save(MemberFixture.member().build()); + Member member2 = memberRepository.save(MemberFixture.member().build()); + Member member3 = memberRepository.save(MemberFixture.member().build()); + + MemberFCM fcm1 = memberFCMRepository.save(MemberFcmFixture.memberFcm().memberId(member1.getId()).build()); + MemberFCM fcm2 = memberFCMRepository.save(MemberFcmFixture.memberFcm().memberId(member1.getId()).build()); + MemberFCM fcm3 = memberFCMRepository.save(MemberFcmFixture.memberFcm().memberId(member2.getId()).build()); + MemberFCM fcm4 = memberFCMRepository.save(MemberFcmFixture.memberFcm().memberId(member3.getId()).build()); + + // when + List actual = memberFCMRepository.findAllTokenByMemberIdIn( + List.of(member1.getId(), member2.getId())); + + // then + assertThat(actual).containsExactlyInAnyOrder(fcm1.getFcmToken(), fcm2.getFcmToken(), fcm3.getFcmToken()); + } } } From a083e3cc0503dc681d11f0e670b83863e1013dc9 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Mon, 2 Oct 2023 23:00:58 +0900 Subject: [PATCH 15/45] =?UTF-8?q?style:=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95=20(entry=5Fale?= =?UTF-8?q?rt=20->=20entryalert)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/EntryAlertEventListener.java | 4 ++-- .../application/EntryAlertService.java | 8 ++++---- .../{entry_alert => entryalert}/domain/EntryAlert.java | 2 +- .../dto/EntryAlertResponse.java | 2 +- .../repository/EntryAlertRepository.java | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) rename backend/src/main/java/com/festago/{entry_alert => entryalert}/application/EntryAlertEventListener.java (95%) rename backend/src/main/java/com/festago/{entry_alert => entryalert}/application/EntryAlertService.java (92%) rename backend/src/main/java/com/festago/{entry_alert => entryalert}/domain/EntryAlert.java (97%) rename backend/src/main/java/com/festago/{entry_alert => entryalert}/dto/EntryAlertResponse.java (75%) rename backend/src/main/java/com/festago/{entry_alert => entryalert}/repository/EntryAlertRepository.java (85%) diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java similarity index 95% rename from backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java rename to backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java index 63f9d1021..d947550ed 100644 --- a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertEventListener.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java @@ -1,6 +1,6 @@ -package com.festago.entry_alert.application; +package com.festago.entryalert.application; -import com.festago.entry_alert.dto.EntryAlertResponse; +import com.festago.entryalert.dto.EntryAlertResponse; import com.festago.ticket.dto.event.TicketCreateEvent; import java.time.Clock; import java.time.LocalDateTime; diff --git a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java similarity index 92% rename from backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java rename to backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java index 9aadd23a5..6ca073d0c 100644 --- a/backend/src/main/java/com/festago/entry_alert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java @@ -1,10 +1,10 @@ -package com.festago.entry_alert.application; +package com.festago.entryalert.application; import com.festago.common.exception.ConflictException; import com.festago.common.exception.ErrorCode; -import com.festago.entry_alert.domain.EntryAlert; -import com.festago.entry_alert.dto.EntryAlertResponse; -import com.festago.entry_alert.repository.EntryAlertRepository; +import com.festago.entryalert.domain.EntryAlert; +import com.festago.entryalert.dto.EntryAlertResponse; +import com.festago.entryalert.repository.EntryAlertRepository; import com.festago.fcm.application.FcmClient; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; diff --git a/backend/src/main/java/com/festago/entry_alert/domain/EntryAlert.java b/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java similarity index 97% rename from backend/src/main/java/com/festago/entry_alert/domain/EntryAlert.java rename to backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java index b3acdbb5b..e685f1b11 100644 --- a/backend/src/main/java/com/festago/entry_alert/domain/EntryAlert.java +++ b/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java @@ -1,4 +1,4 @@ -package com.festago.entry_alert.domain; +package com.festago.entryalert.domain; import com.festago.common.exception.BadRequestException; import com.festago.common.exception.ErrorCode; diff --git a/backend/src/main/java/com/festago/entry_alert/dto/EntryAlertResponse.java b/backend/src/main/java/com/festago/entryalert/dto/EntryAlertResponse.java similarity index 75% rename from backend/src/main/java/com/festago/entry_alert/dto/EntryAlertResponse.java rename to backend/src/main/java/com/festago/entryalert/dto/EntryAlertResponse.java index daa627aa0..1e53fc45a 100644 --- a/backend/src/main/java/com/festago/entry_alert/dto/EntryAlertResponse.java +++ b/backend/src/main/java/com/festago/entryalert/dto/EntryAlertResponse.java @@ -1,4 +1,4 @@ -package com.festago.entry_alert.dto; +package com.festago.entryalert.dto; import java.time.LocalDateTime; diff --git a/backend/src/main/java/com/festago/entry_alert/repository/EntryAlertRepository.java b/backend/src/main/java/com/festago/entryalert/repository/EntryAlertRepository.java similarity index 85% rename from backend/src/main/java/com/festago/entry_alert/repository/EntryAlertRepository.java rename to backend/src/main/java/com/festago/entryalert/repository/EntryAlertRepository.java index c86d5cbe0..9a34885c4 100644 --- a/backend/src/main/java/com/festago/entry_alert/repository/EntryAlertRepository.java +++ b/backend/src/main/java/com/festago/entryalert/repository/EntryAlertRepository.java @@ -1,6 +1,6 @@ -package com.festago.entry_alert.repository; +package com.festago.entryalert.repository; -import com.festago.entry_alert.domain.EntryAlert; +import com.festago.entryalert.domain.EntryAlert; import jakarta.persistence.LockModeType; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; From 6aebcd56ec7ca7952e9272d67260681d5d3fc161 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Wed, 4 Oct 2023 16:24:49 +0900 Subject: [PATCH 16/45] =?UTF-8?q?refactor:=20500=EA=B1=B4=EB=A7=8C=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=ED=95=98=EB=8F=84=EB=A1=9D=20=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20FcmClientImpl=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/EntryAlertService.java | 13 ++----- .../festago/fcm/application/FcmClient.java | 4 +-- .../fcm/application/MockFcmClient.java | 7 ---- .../fcm/infrastructure/FcmClientImpl.java | 34 ++++++++++++++----- .../fcm/infrastructure/FcmClientImplTest.java | 2 +- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java index 6ca073d0c..0bcefe167 100644 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java @@ -19,9 +19,7 @@ @Service @Transactional public class EntryAlertService { - - private static final int BATCH_ALERT_SIZE = 500; - + private final EntryAlertRepository entryAlertRepository; private final MemberTicketRepository memberTicketRepository; private final MemberFCMRepository memberFCMRepository; @@ -54,14 +52,7 @@ public void sendEntryAlert(Long id) { List memberIds = memberTicketRepository.findAllOwnerIdByStageIdAndEntryTime(entryAlert.getStageId(), entryAlert.getEntryTime()); List tokens = memberFCMRepository.findAllTokenByMemberIdIn(memberIds); - sendMessages(tokens); + fcmClient.sendAll(tokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert()); entryAlertRepository.delete(entryAlert); } - - private void sendMessages(List tokens) { - for (int i = 0; i < tokens.size(); i += BATCH_ALERT_SIZE) { - List subTokens = tokens.subList(i, Math.min(i + BATCH_ALERT_SIZE, tokens.size())); - fcmClient.sendAllAsync(subTokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert()); - } - } } diff --git a/backend/src/main/java/com/festago/fcm/application/FcmClient.java b/backend/src/main/java/com/festago/fcm/application/FcmClient.java index 77b93e4a8..7ef794ab8 100644 --- a/backend/src/main/java/com/festago/fcm/application/FcmClient.java +++ b/backend/src/main/java/com/festago/fcm/application/FcmClient.java @@ -5,8 +5,6 @@ import java.util.List; public interface FcmClient { - - void sendAllAsync(List tokens, FCMChannel channel, FcmPayload fcmPayload); - + void sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload); } diff --git a/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java index 95196cbb6..de46e0328 100644 --- a/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java +++ b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java @@ -6,7 +6,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Profile; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component @@ -15,12 +14,6 @@ public class MockFcmClient implements FcmClient { private static final Logger log = LoggerFactory.getLogger(MockFcmClient.class); - @Override - @Async - public void sendAllAsync(List tokens, FCMChannel channel, FcmPayload fcmPayload) { - sendAll(tokens, channel, fcmPayload); - } - @Override public void sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload) { log.info("[FCM] title: {} / body: {} / to {} device", fcmPayload.title(), fcmPayload.body(), tokens.size()); diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java index 7d1588550..3566782bb 100644 --- a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -13,10 +13,11 @@ import com.google.firebase.messaging.Message; import com.google.firebase.messaging.SendResponse; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Profile; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component @@ -24,22 +25,39 @@ public class FcmClientImpl implements FcmClient { private static final Logger log = LoggerFactory.getLogger(FcmClientImpl.class); + private static final int BATCH_ALERT_SIZE = 500; private final FirebaseMessaging firebaseMessaging; + private final Executor taskExecutor; - public FcmClientImpl(FirebaseMessaging firebaseMessaging) { + public FcmClientImpl(FirebaseMessaging firebaseMessaging, Executor taskExecutor) { this.firebaseMessaging = firebaseMessaging; - } - - @Override - @Async - public void sendAllAsync(List tokens, FCMChannel channel, FcmPayload fcmPayload) { - sendAll(tokens, channel, fcmPayload); + this.taskExecutor = taskExecutor; } @Override public void sendAll(List tokens, FCMChannel channel, FcmPayload payload) { + validateTokenSize(tokens); List messages = createMessages(tokens, channel, payload); + + int messageSize = messages.size(); + if (messageSize <= BATCH_ALERT_SIZE) { + sendMessages(messages, channel); + return; + } + for (int i = 0; i < messages.size(); i += BATCH_ALERT_SIZE) { + List batchMessages = messages.subList(i, i + BATCH_ALERT_SIZE); + CompletableFuture.runAsync(() -> sendMessages(batchMessages, channel), taskExecutor); + } + } + + private void validateTokenSize(List tokens) { + if (tokens.isEmpty()) { + throw new InternalServerException(ErrorCode.FCM_NOT_FOUND); + } + } + + public void sendMessages(List messages, FCMChannel channel) { try { BatchResponse response = firebaseMessaging.sendAll(messages); checkAllSuccess(response, channel); diff --git a/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java b/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java index 3acb861e5..5afed6429 100644 --- a/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java +++ b/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java @@ -32,7 +32,7 @@ class FcmClientImplTest { @Mock FirebaseMessaging firebaseMessaging; - + @InjectMocks FcmClientImpl fcmClient; From da70c4d52bea4e2a3012f72ac8d0a4dcdc7db4c1 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Wed, 4 Oct 2023 16:36:12 +0900 Subject: [PATCH 17/45] =?UTF-8?q?refactor:=20fcmToken=20String=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FCMNotificationEventListener.java | 11 +---------- .../fcm/application/MemberFCMService.java | 19 +++---------------- .../fcm/repository/MemberFCMRepository.java | 5 +++-- .../fcm/application/MemberFCMServiceTest.java | 19 +++++-------------- .../repository/MemberFCMRepositoryTest.java | 16 +++++++++------- 5 files changed, 21 insertions(+), 49 deletions(-) diff --git a/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java b/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java index 7240fb149..46a777052 100644 --- a/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java +++ b/backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java @@ -3,7 +3,6 @@ import com.festago.entry.dto.event.EntryProcessEvent; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; -import com.festago.fcm.dto.MemberFCMResponse; import java.util.List; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @@ -24,15 +23,7 @@ public FCMNotificationEventListener(FcmClient fcmClient, MemberFCMService member @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) @Async public void sendFcmNotification(EntryProcessEvent event) { - List tokens = getMemberFCMToken(event.memberId()); + List tokens = memberFCMService.findMemberFCMTokens(event.memberId()); fcmClient.sendAll(tokens, FCMChannel.ENTRY_PROCESS, FcmPayload.entryProcess()); } - - private List getMemberFCMToken(Long memberId) { - return memberFCMService.findMemberFCM(memberId) - .memberFCMs() - .stream() - .map(MemberFCMResponse::fcmToken) - .toList(); - } } diff --git a/backend/src/main/java/com/festago/fcm/application/MemberFCMService.java b/backend/src/main/java/com/festago/fcm/application/MemberFCMService.java index e044feb7a..2abb930a5 100644 --- a/backend/src/main/java/com/festago/fcm/application/MemberFCMService.java +++ b/backend/src/main/java/com/festago/fcm/application/MemberFCMService.java @@ -2,14 +2,9 @@ import com.festago.auth.application.AuthExtractor; import com.festago.auth.domain.AuthPayload; -import com.festago.common.exception.ErrorCode; -import com.festago.common.exception.InternalServerException; import com.festago.fcm.domain.MemberFCM; -import com.festago.fcm.dto.MemberFCMsResponse; import com.festago.fcm.repository.MemberFCMRepository; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,10 +12,7 @@ @Service @Transactional public class MemberFCMService { - - private static final Logger log = LoggerFactory.getLogger(MemberFCMService.class); - private static final int LEAST_MEMBER_FCM = 1; - + private final MemberFCMRepository memberFCMRepository; private final AuthExtractor authExtractor; @@ -30,13 +22,8 @@ public MemberFCMService(MemberFCMRepository memberFCMRepository, AuthExtractor a } @Transactional(readOnly = true) - public MemberFCMsResponse findMemberFCM(Long memberId) { - List memberFCM = memberFCMRepository.findByMemberId(memberId); - if (memberFCM.size() < LEAST_MEMBER_FCM) { - log.error("member {} 의 FCM 토큰이 발급되지 않았습니다.", memberId); - throw new InternalServerException(ErrorCode.FCM_NOT_FOUND); - } - return MemberFCMsResponse.from(memberFCM); + public List findMemberFCMTokens(Long memberId) { + return memberFCMRepository.findAllTokenByMemberId(memberId); } @Async diff --git a/backend/src/main/java/com/festago/fcm/repository/MemberFCMRepository.java b/backend/src/main/java/com/festago/fcm/repository/MemberFCMRepository.java index f00c4b5db..f29f3dcb1 100644 --- a/backend/src/main/java/com/festago/fcm/repository/MemberFCMRepository.java +++ b/backend/src/main/java/com/festago/fcm/repository/MemberFCMRepository.java @@ -7,8 +7,9 @@ import org.springframework.data.repository.query.Param; public interface MemberFCMRepository extends JpaRepository { - - List findByMemberId(Long memberId); + + @Query("SELECT mf.fcmToken FROM MemberFCM mf WHERE mf.memberId = :memberId") + List findAllTokenByMemberId(@Param("memberId") Long memberId); @Query("SELECT mf.fcmToken FROM MemberFCM mf WHERE mf.memberId IN :memberIds") List findAllTokenByMemberIdIn(@Param("memberIds") List memberIds); diff --git a/backend/src/test/java/com/festago/fcm/application/MemberFCMServiceTest.java b/backend/src/test/java/com/festago/fcm/application/MemberFCMServiceTest.java index dd8eb7d46..37113bb2d 100644 --- a/backend/src/test/java/com/festago/fcm/application/MemberFCMServiceTest.java +++ b/backend/src/test/java/com/festago/fcm/application/MemberFCMServiceTest.java @@ -11,10 +11,8 @@ import com.festago.auth.domain.AuthPayload; import com.festago.auth.domain.Role; import com.festago.fcm.domain.MemberFCM; -import com.festago.fcm.dto.MemberFCMResponse; import com.festago.fcm.repository.MemberFCMRepository; import java.util.List; -import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -36,22 +34,15 @@ class MemberFCMServiceTest { @Test void 유저의_FCM_정보를_가져온다() { // given - List memberFCMS = List.of( - new MemberFCM(1L, 1L, "token"), - new MemberFCM(2L, 1L, "token2") - ); - given(memberFCMRepository.findByMemberId(anyLong())) - .willReturn(memberFCMS); - - List expect = memberFCMS.stream() - .map(MemberFCMResponse::from) - .collect(Collectors.toList()); + List tokens = List.of("token1", "token2"); + given(memberFCMRepository.findAllTokenByMemberId(anyLong())) + .willReturn(tokens); // when - List actual = memberFCMService.findMemberFCM(1L).memberFCMs(); + List actual = memberFCMService.findMemberFCMTokens(1L); // then - assertThat(actual).isEqualTo(expect); + assertThat(actual).isEqualTo(tokens); } @Test diff --git a/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java b/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java index f01c5fb8f..3567140f8 100644 --- a/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java +++ b/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.festago.auth.domain.SocialType; import com.festago.fcm.domain.MemberFCM; import com.festago.member.domain.Member; import com.festago.member.repository.MemberRepository; @@ -25,17 +24,20 @@ class MemberFCMRepositoryTest { MemberRepository memberRepository; @Test - void member_의_MemberFCM_을_찾을_수_있다() { + void memberId로_token리스트_조회() { // given - Member member = memberRepository.save(new Member("socialId", SocialType.FESTAGO, "nickname", "image.jpg")); - Long memberId = member.getId(); - MemberFCM expect = memberFCMRepository.save(MemberFcmFixture.memberFcm().memberId(memberId).build()); + Member member1 = memberRepository.save(MemberFixture.member().build()); + Member member2 = memberRepository.save(MemberFixture.member().build()); + + MemberFCM fcm1 = memberFCMRepository.save(MemberFcmFixture.memberFcm().memberId(member1.getId()).build()); + MemberFCM fcm2 = memberFCMRepository.save(MemberFcmFixture.memberFcm().memberId(member1.getId()).build()); + MemberFCM fcm3 = memberFCMRepository.save(MemberFcmFixture.memberFcm().memberId(member2.getId()).build()); // when - List actual = memberFCMRepository.findByMemberId(memberId); + List actual = memberFCMRepository.findAllTokenByMemberId(member1.getId()); // then - assertThat(actual).contains(expect); + assertThat(actual).containsExactlyInAnyOrder(fcm1.getFcmToken(), fcm2.getFcmToken()); } @Nested From 4c20e30cb33904067a40a841162178ba0026b345 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Wed, 4 Oct 2023 16:37:23 +0900 Subject: [PATCH 18/45] =?UTF-8?q?refactor:=20ApplicationReadyEvent=20Liste?= =?UTF-8?q?ner=20=EC=B5=9C=EC=83=81=EB=8B=A8=EC=9C=BC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/EntryAlertEventListener.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java index d947550ed..c1ab83fb5 100644 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java @@ -30,11 +30,10 @@ public EntryAlertEventListener(EntryAlertService entryAlertService, TaskSchedule this.clock = clock; } - @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) - @Async - public void addEntryAlertSchedule(TicketCreateEvent event) { - EntryAlertResponse entryAlert = entryAlertService.create(event.stageId(), event.entryTime()); - addSchedule(entryAlert); + @EventListener(ApplicationReadyEvent.class) + public void initEntryAlertSchedule() { + List entryAlerts = entryAlertService.findAll(); + entryAlerts.forEach(this::addSchedule); } private void addSchedule(EntryAlertResponse entryAlert) { @@ -48,9 +47,10 @@ private Runnable createEntryAlertTask(Long id) { return () -> entryAlertService.sendEntryAlert(id); } - @EventListener(ApplicationReadyEvent.class) - public void initEntryAlertSchedule() { - List entryAlerts = entryAlertService.findAll(); - entryAlerts.forEach(this::addSchedule); + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + @Async + public void addEntryAlertSchedule(TicketCreateEvent event) { + EntryAlertResponse entryAlert = entryAlertService.create(event.stageId(), event.entryTime()); + addSchedule(entryAlert); } } From 03588d13c37686abe254f9020c1e20a80b229ea3 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Wed, 4 Oct 2023 16:39:44 +0900 Subject: [PATCH 19/45] =?UTF-8?q?refactor:=20taskScheduler=EC=97=90=20?= =?UTF-8?q?=EB=9E=8C=EB=8B=A4=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=84=98?= =?UTF-8?q?=EA=B2=A8=EC=A3=BC=EB=8F=84=EB=A1=9D=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/EntryAlertEventListener.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java index c1ab83fb5..8b854ce7c 100644 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java @@ -3,6 +3,7 @@ import com.festago.entryalert.dto.EntryAlertResponse; import com.festago.ticket.dto.event.TicketCreateEvent; import java.time.Clock; +import java.time.Instant; import java.time.LocalDateTime; import java.util.List; import org.slf4j.Logger; @@ -37,14 +38,14 @@ public void initEntryAlertSchedule() { } private void addSchedule(EntryAlertResponse entryAlert) { - log.info("add entryAlert schedule: {}", entryAlert.id()); - Runnable task = createEntryAlertTask(entryAlert.id()); - LocalDateTime alertTime = entryAlert.alertTime(); - taskScheduler.schedule(task, alertTime.atZone(clock.getZone()).toInstant()); + Long entryAlertId = entryAlert.id(); + log.info("add entryAlert schedule: {}", entryAlertId); + Instant alertTime = toInstant(entryAlert.alertTime()); + taskScheduler.schedule(() -> entryAlertService.sendEntryAlert(entryAlertId), alertTime); } - private Runnable createEntryAlertTask(Long id) { - return () -> entryAlertService.sendEntryAlert(id); + private Instant toInstant(LocalDateTime localDateTime) { + return localDateTime.atZone(clock.getZone()).toInstant(); } @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) From 64759af144362a0945db1c65dc1e3b6251d43ba4 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 02:06:12 +0900 Subject: [PATCH 20/45] =?UTF-8?q?refactor:=20EntryAlert=EC=97=90=20AlertSt?= =?UTF-8?q?atus=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festago/common/exception/ErrorCode.java | 4 + .../application/EntryAlertEventListener.java | 2 +- .../application/EntryAlertFcmClient.java | 26 +++++ .../application/EntryAlertService.java | 23 ++-- .../application/EntryAlertStatusService.java | 29 +++++ .../entryalert/domain/AlertStatus.java | 10 ++ .../festago/entryalert/domain/EntryAlert.java | 41 ++++++- .../repository/EntryAlertRepository.java | 9 +- .../festago/fcm/application/FcmClient.java | 4 +- .../fcm/application/MockFcmClient.java | 3 +- .../fcm/infrastructure/FcmClientImpl.java | 39 ++++++- .../application/EntryAlertServiceTest.java | 94 ++++++++++++++++ .../entryalert/domain/EntryAlertTest.java | 103 ++++++++++++++++++ .../repository/EntryAlertRepositoryTest.java | 21 ++++ .../fcm/infrastructure/FcmClientImplTest.java | 24 ++-- .../repository/MemberFCMRepositoryTest.java | 4 + .../festago/support/EntryAlertFixture.java | 51 +++++++++ .../com/festago/support/SetUpMockito.java | 30 +++++ 18 files changed, 482 insertions(+), 35 deletions(-) create mode 100644 backend/src/main/java/com/festago/entryalert/application/EntryAlertFcmClient.java create mode 100644 backend/src/main/java/com/festago/entryalert/application/EntryAlertStatusService.java create mode 100644 backend/src/main/java/com/festago/entryalert/domain/AlertStatus.java create mode 100644 backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java create mode 100644 backend/src/test/java/com/festago/entryalert/domain/EntryAlertTest.java create mode 100644 backend/src/test/java/com/festago/entryalert/repository/EntryAlertRepositoryTest.java create mode 100644 backend/src/test/java/com/festago/support/EntryAlertFixture.java create mode 100644 backend/src/test/java/com/festago/support/SetUpMockito.java diff --git a/backend/src/main/java/com/festago/common/exception/ErrorCode.java b/backend/src/main/java/com/festago/common/exception/ErrorCode.java index 5dc149c70..6c0626e6d 100644 --- a/backend/src/main/java/com/festago/common/exception/ErrorCode.java +++ b/backend/src/main/java/com/festago/common/exception/ErrorCode.java @@ -26,6 +26,9 @@ public enum ErrorCode { TICKET_CANNOT_RESERVE_STAGE_START("공연의 시작 시간 이후로 예매할 수 없습니다."), INVALID_STUDENT_VERIFICATION_CODE("올바르지 않은 학생 인증 코드입니다."), INVALID_ENTRY_ALERT_TIME("올바르지 않은 입장 알림 시간입니다"), + NOT_PENDING_ALERT("전송 대기중인 알림이 아닙니다."), + NOT_REQUESTED_ALERT("전송 요청중인 알림이 아닙니다."), + // 401 EXPIRED_AUTH_TOKEN("만료된 로그인 토큰입니다."), @@ -46,6 +49,7 @@ public enum ErrorCode { FESTIVAL_NOT_FOUND("존재하지 않는 축제입니다."), TICKET_NOT_FOUND("존재하지 않는 티켓입니다."), SCHOOL_NOT_FOUND("존재하지 않는 학교입니다."), + ENTRY_ALERT_NOT_FOUND("존재하지 않는 입장 알림입니다."), // 409 ALREADY_ALERT("이미 입장 알림이 전송되었습니다."), diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java index 8b854ce7c..fbf6e6aee 100644 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java @@ -33,7 +33,7 @@ public EntryAlertEventListener(EntryAlertService entryAlertService, TaskSchedule @EventListener(ApplicationReadyEvent.class) public void initEntryAlertSchedule() { - List entryAlerts = entryAlertService.findAll(); + List entryAlerts = entryAlertService.findAllPending(); entryAlerts.forEach(this::addSchedule); } diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertFcmClient.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertFcmClient.java new file mode 100644 index 000000000..21e81ab14 --- /dev/null +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertFcmClient.java @@ -0,0 +1,26 @@ +package com.festago.entryalert.application; + +import com.festago.fcm.application.FcmClient; +import com.festago.fcm.domain.FCMChannel; +import com.festago.fcm.dto.FcmPayload; +import java.util.List; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +@Component +public class EntryAlertFcmClient { + + private final FcmClient fcmClient; + private final EntryAlertStatusService entryAlertStatusService; + + public EntryAlertFcmClient(FcmClient fcmClient, EntryAlertStatusService entryAlertStatusService) { + this.fcmClient = fcmClient; + this.entryAlertStatusService = entryAlertStatusService; + } + + @Async + public void sendAll(Long entryAlertId, List tokens, FCMChannel channel, FcmPayload fcmPayload) { + boolean isSuccess = fcmClient.sendAll(tokens, channel, fcmPayload); + entryAlertStatusService.updateRequestedEntryAlertStatus(entryAlertId, isSuccess); + } +} diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java index 0bcefe167..defd4acad 100644 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java @@ -2,10 +2,10 @@ import com.festago.common.exception.ConflictException; import com.festago.common.exception.ErrorCode; +import com.festago.entryalert.domain.AlertStatus; import com.festago.entryalert.domain.EntryAlert; import com.festago.entryalert.dto.EntryAlertResponse; import com.festago.entryalert.repository.EntryAlertRepository; -import com.festago.fcm.application.FcmClient; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; import com.festago.fcm.repository.MemberFCMRepository; @@ -19,14 +19,14 @@ @Service @Transactional public class EntryAlertService { - + private final EntryAlertRepository entryAlertRepository; private final MemberTicketRepository memberTicketRepository; private final MemberFCMRepository memberFCMRepository; - private final FcmClient fcmClient; + private final EntryAlertFcmClient fcmClient; public EntryAlertService(EntryAlertRepository entryAlertRepository, MemberTicketRepository memberTicketRepository, - MemberFCMRepository memberFCMRepository, FcmClient fcmClient) { + MemberFCMRepository memberFCMRepository, EntryAlertFcmClient fcmClient) { this.entryAlertRepository = entryAlertRepository; this.memberTicketRepository = memberTicketRepository; this.memberFCMRepository = memberFCMRepository; @@ -34,8 +34,9 @@ public EntryAlertService(EntryAlertRepository entryAlertRepository, MemberTicket } @Transactional(readOnly = true) - public List findAll() { - return entryAlertRepository.findAll().stream() + public List findAllPending() { + return entryAlertRepository.findAllByStatus(AlertStatus.PENDING) + .stream() .map(alert -> new EntryAlertResponse(alert.getId(), alert.findAlertTime())) .toList(); } @@ -47,12 +48,12 @@ public EntryAlertResponse create(Long stageId, LocalDateTime entryTime) { @Async public void sendEntryAlert(Long id) { - EntryAlert entryAlert = entryAlertRepository.findByIdForUpdate(id) + EntryAlert entryAlert = entryAlertRepository.findByIdAndStatusForUpdate(id, AlertStatus.PENDING) .orElseThrow(() -> new ConflictException(ErrorCode.ALREADY_ALERT)); - List memberIds = memberTicketRepository.findAllOwnerIdByStageIdAndEntryTime(entryAlert.getStageId(), - entryAlert.getEntryTime()); + List memberIds = memberTicketRepository.findAllOwnerIdByStageIdAndEntryTime( + entryAlert.getStageId(), entryAlert.getEntryTime()); List tokens = memberFCMRepository.findAllTokenByMemberIdIn(memberIds); - fcmClient.sendAll(tokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert()); - entryAlertRepository.delete(entryAlert); + fcmClient.sendAll(entryAlert.getId(), tokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert()); + entryAlert.request(); } } diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertStatusService.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertStatusService.java new file mode 100644 index 000000000..8a4a71267 --- /dev/null +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertStatusService.java @@ -0,0 +1,29 @@ +package com.festago.entryalert.application; + +import com.festago.common.exception.ErrorCode; +import com.festago.common.exception.NotFoundException; +import com.festago.entryalert.domain.EntryAlert; +import com.festago.entryalert.repository.EntryAlertRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class EntryAlertStatusService { + + private final EntryAlertRepository entryAlertRepository; + + public EntryAlertStatusService(EntryAlertRepository entryAlertRepository) { + this.entryAlertRepository = entryAlertRepository; + } + + @Transactional + public void updateRequestedEntryAlertStatus(Long entryAlertId, boolean isSuccess) { + EntryAlert entryAlert = entryAlertRepository.findById(entryAlertId) + .orElseThrow(() -> new NotFoundException(ErrorCode.ENTRY_ALERT_NOT_FOUND)); + if (isSuccess) { + entryAlert.send(); + return; + } + entryAlert.fail(); + } +} diff --git a/backend/src/main/java/com/festago/entryalert/domain/AlertStatus.java b/backend/src/main/java/com/festago/entryalert/domain/AlertStatus.java new file mode 100644 index 000000000..24f19d86b --- /dev/null +++ b/backend/src/main/java/com/festago/entryalert/domain/AlertStatus.java @@ -0,0 +1,10 @@ +package com.festago.entryalert.domain; + +public enum AlertStatus { + + PENDING, + REQUESTED, + SENT, + FAILED, + ; +} diff --git a/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java b/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java index e685f1b11..022d1d28d 100644 --- a/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java +++ b/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java @@ -1,5 +1,6 @@ package com.festago.entryalert.domain; +import com.festago.common.domain.BaseTimeEntity; import com.festago.common.exception.BadRequestException; import com.festago.common.exception.ErrorCode; import jakarta.persistence.Entity; @@ -10,7 +11,7 @@ import java.time.LocalDateTime; @Entity -public class EntryAlert { +public class EntryAlert extends BaseTimeEntity { private static final int ENTRY_ALERT_MINUTES_BEFORE = 10; @@ -24,6 +25,9 @@ public class EntryAlert { @NotNull private LocalDateTime entryTime; + @NotNull + private AlertStatus status = AlertStatus.PENDING; + protected EntryAlert() { } @@ -44,6 +48,37 @@ public static EntryAlert create(Long stageId, LocalDateTime entryTime, LocalDate return new EntryAlert(stageId, entryTime); } + public boolean canRequest() { + return status == AlertStatus.PENDING; + } + + public void request() { + validateNotPending(); + this.status = AlertStatus.REQUESTED; + } + + private void validateNotPending() { + if (status != AlertStatus.PENDING) { + throw new BadRequestException(ErrorCode.NOT_PENDING_ALERT); + } + } + + public void send() { + validateNotRequested(); + this.status = AlertStatus.SENT; + } + + private void validateNotRequested() { + if (status != AlertStatus.REQUESTED) { + throw new BadRequestException(ErrorCode.NOT_PENDING_ALERT); + } + } + + public void fail() { + validateNotRequested(); + this.status = AlertStatus.FAILED; + } + public LocalDateTime findAlertTime() { return entryTime.minusMinutes(ENTRY_ALERT_MINUTES_BEFORE); } @@ -59,4 +94,8 @@ public Long getStageId() { public LocalDateTime getEntryTime() { return entryTime; } + + public AlertStatus getStatus() { + return status; + } } diff --git a/backend/src/main/java/com/festago/entryalert/repository/EntryAlertRepository.java b/backend/src/main/java/com/festago/entryalert/repository/EntryAlertRepository.java index 9a34885c4..6e43269d3 100644 --- a/backend/src/main/java/com/festago/entryalert/repository/EntryAlertRepository.java +++ b/backend/src/main/java/com/festago/entryalert/repository/EntryAlertRepository.java @@ -1,7 +1,9 @@ package com.festago.entryalert.repository; +import com.festago.entryalert.domain.AlertStatus; import com.festago.entryalert.domain.EntryAlert; import jakarta.persistence.LockModeType; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; @@ -10,7 +12,10 @@ public interface EntryAlertRepository extends JpaRepository { + @Query("SELECT ea FROM EntryAlert ea WHERE ea.status = :status") + List findAllByStatus(@Param("status") AlertStatus status); + @Lock(LockModeType.PESSIMISTIC_WRITE) - @Query("SELECT ea FROM EntryAlert ea WHERE ea.id = :id") - Optional findByIdForUpdate(@Param("id") Long id); + @Query("SELECT ea FROM EntryAlert ea WHERE ea.id = :id and ea.status = :status") + Optional findByIdAndStatusForUpdate(@Param("id") Long id, @Param("status") AlertStatus status); } diff --git a/backend/src/main/java/com/festago/fcm/application/FcmClient.java b/backend/src/main/java/com/festago/fcm/application/FcmClient.java index 7ef794ab8..98cf53c4a 100644 --- a/backend/src/main/java/com/festago/fcm/application/FcmClient.java +++ b/backend/src/main/java/com/festago/fcm/application/FcmClient.java @@ -5,6 +5,6 @@ import java.util.List; public interface FcmClient { - - void sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload); + + boolean sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload); } diff --git a/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java index de46e0328..0f32a1f7e 100644 --- a/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java +++ b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java @@ -15,7 +15,8 @@ public class MockFcmClient implements FcmClient { private static final Logger log = LoggerFactory.getLogger(MockFcmClient.class); @Override - public void sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload) { + public boolean sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload) { log.info("[FCM] title: {} / body: {} / to {} device", fcmPayload.title(), fcmPayload.body(), tokens.size()); + return true; } } diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java index 3566782bb..f94221e0a 100644 --- a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -12,6 +12,7 @@ import com.google.firebase.messaging.FirebaseMessagingException; import com.google.firebase.messaging.Message; import com.google.firebase.messaging.SendResponse; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @@ -36,18 +37,46 @@ public FcmClientImpl(FirebaseMessaging firebaseMessaging, Executor taskExecutor) } @Override - public void sendAll(List tokens, FCMChannel channel, FcmPayload payload) { + public boolean sendAll(List tokens, FCMChannel channel, FcmPayload payload) { validateTokenSize(tokens); List messages = createMessages(tokens, channel, payload); int messageSize = messages.size(); if (messageSize <= BATCH_ALERT_SIZE) { - sendMessages(messages, channel); - return; + try { + sendMessages(messages, channel); + return true; + } catch (Exception e) { + return false; + } } + + List> futures = new ArrayList<>(); + for (int i = 0; i < messages.size(); i += BATCH_ALERT_SIZE) { - List batchMessages = messages.subList(i, i + BATCH_ALERT_SIZE); - CompletableFuture.runAsync(() -> sendMessages(batchMessages, channel), taskExecutor); + List batchMessages = messages.subList(i, Math.min(i + BATCH_ALERT_SIZE, messages.size())); + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + sendMessages(batchMessages, channel); + return true; + } catch (Exception e) { + return false; + } + }, taskExecutor); + futures.add(future); + } + CompletableFuture allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + + try { + allOf.join(); + for (CompletableFuture future : futures) { + if (!future.get()) { + return false; + } + } + return true; + } catch (Exception e) { + return false; } } diff --git a/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java b/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java new file mode 100644 index 000000000..598c93da9 --- /dev/null +++ b/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java @@ -0,0 +1,94 @@ +package com.festago.entryalert.application; + +import static com.festago.common.exception.ErrorCode.ALREADY_ALERT; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.BDDMockito.given; + +import com.festago.common.exception.ConflictException; +import com.festago.entryalert.domain.AlertStatus; +import com.festago.entryalert.domain.EntryAlert; +import com.festago.entryalert.repository.EntryAlertRepository; +import com.festago.fcm.application.FcmClient; +import com.festago.fcm.repository.MemberFCMRepository; +import com.festago.support.EntryAlertFixture; +import com.festago.support.SetUpMockito; +import com.festago.ticketing.repository.MemberTicketRepository; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +@ExtendWith(MockitoExtension.class) +class EntryAlertServiceTest { + + @Mock + EntryAlertRepository entryAlertRepository; + + @Mock + MemberTicketRepository memberTicketRepository; + + @Mock + MemberFCMRepository memberFCMRepository; + + @Mock + FcmClient fcmClient; + + @InjectMocks + EntryAlertService entryAlertService; + + @Nested + class 알림_전송 { + + Long id; + EntryAlert entryAlert; + + @BeforeEach + void setUp() { + id = 1L; + entryAlert = EntryAlertFixture.entryAlert().id(id).status(AlertStatus.PENDING).build(); + + SetUpMockito + .given(entryAlertRepository.findByIdAndStatusForUpdate(id, AlertStatus.PENDING)) + .willReturn(Optional.of(entryAlert)); + + SetUpMockito + .given(memberTicketRepository.findAllOwnerIdByStageIdAndEntryTime(entryAlert.getStageId(), + entryAlert.getEntryTime())) + .willReturn(List.of(1L, 2L, 3L)); + + SetUpMockito + .given(memberFCMRepository.findAllTokenByMemberIdIn(anyList())) + .willReturn(List.of("token1", "token2", "token3")); + } + + @Test + void 이미_전송된_알림이면_예외() { + // given + given(entryAlertRepository.findByIdAndStatusForUpdate(id, AlertStatus.PENDING)) + .willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> entryAlertService.sendEntryAlert(id)) + .isInstanceOf(ConflictException.class) + .hasMessage(ALREADY_ALERT.getMessage()); + } + + @Test + void 성공() { + // when & then + assertThatNoException() + .isThrownBy(() -> entryAlertService.sendEntryAlert(id)); + } + } +} diff --git a/backend/src/test/java/com/festago/entryalert/domain/EntryAlertTest.java b/backend/src/test/java/com/festago/entryalert/domain/EntryAlertTest.java new file mode 100644 index 000000000..b414c2e04 --- /dev/null +++ b/backend/src/test/java/com/festago/entryalert/domain/EntryAlertTest.java @@ -0,0 +1,103 @@ +package com.festago.entryalert.domain; + +import static com.festago.common.exception.ErrorCode.NOT_PENDING_ALERT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.festago.common.exception.BadRequestException; +import com.festago.support.EntryAlertFixture; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class EntryAlertTest { + + @Nested + class 요청처리 { + + @ValueSource(strings = {"REQUESTED", "SENT", "FAILED"}) + @ParameterizedTest + void 대기상태가_아니면_예외(AlertStatus status) { + // given + EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(status).build(); + + // when & then + assertThatThrownBy(entryAlert::request) + .isInstanceOf(BadRequestException.class) + .hasMessage(NOT_PENDING_ALERT.getMessage()); + } + + @Test + void 요청_상태로_변경() { + // given + EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(AlertStatus.PENDING).build(); + + // when + entryAlert.request(); + + // then + assertThat(entryAlert.getStatus()).isEqualTo(AlertStatus.REQUESTED); + } + } + + @Nested + class 전송처리 { + + @ValueSource(strings = {"PENDING", "SENT", "FAILED"}) + @ParameterizedTest + void 요청상태가_아니면_예외(AlertStatus status) { + // given + EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(status).build(); + + // when & then + assertThatThrownBy(entryAlert::send) + .isInstanceOf(BadRequestException.class) + .hasMessage(NOT_PENDING_ALERT.getMessage()); + } + + @Test + void 전송_상태로_변경() { + // given + EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(AlertStatus.REQUESTED).build(); + + // when + entryAlert.send(); + + // then + assertThat(entryAlert.getStatus()).isEqualTo(AlertStatus.SENT); + } + } + + @Nested + class 실패처리 { + + @ValueSource(strings = {"PENDING", "SENT", "FAILED"}) + @ParameterizedTest + void 요청상태가_아니면_예외(AlertStatus status) { + // given + EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(status).build(); + + // when & then + assertThatThrownBy(entryAlert::fail) + .isInstanceOf(BadRequestException.class) + .hasMessage(NOT_PENDING_ALERT.getMessage()); + } + + @Test + void 전송_상태로_변경() { + // given + EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(AlertStatus.REQUESTED).build(); + + // when + entryAlert.fail(); + + // then + assertThat(entryAlert.getStatus()).isEqualTo(AlertStatus.FAILED); + } + } +} diff --git a/backend/src/test/java/com/festago/entryalert/repository/EntryAlertRepositoryTest.java b/backend/src/test/java/com/festago/entryalert/repository/EntryAlertRepositoryTest.java new file mode 100644 index 000000000..1da48c073 --- /dev/null +++ b/backend/src/test/java/com/festago/entryalert/repository/EntryAlertRepositoryTest.java @@ -0,0 +1,21 @@ +package com.festago.entryalert.repository; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +@DataJpaTest +class EntryAlertRepositoryTest { + + @Autowired + EntryAlertRepository entryAlertRepository; + + @Test + void 상태로_전부_조회() { + // given + } +} diff --git a/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java b/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java index 5afed6429..1cad03db5 100644 --- a/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java +++ b/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java @@ -1,14 +1,11 @@ package com.festago.fcm.infrastructure; -import static com.festago.common.exception.ErrorCode.FAIL_SEND_FCM_MESSAGE; -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.festago.common.exception.InternalServerException; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; import com.google.firebase.messaging.BatchResponse; @@ -32,7 +29,7 @@ class FcmClientImplTest { @Mock FirebaseMessaging firebaseMessaging; - + @InjectMocks FcmClientImpl fcmClient; @@ -53,10 +50,11 @@ void setUp() { given(firebaseMessaging.sendAll(anyList())) .willReturn(new MockBatchResponse(false)); - // when & then - assertThatThrownBy(() -> fcmClient.sendAll(tokens, fcmChannel, fcmPayload)) - .isInstanceOf(InternalServerException.class) - .hasMessage(FAIL_SEND_FCM_MESSAGE.getMessage()); + // when + boolean result = fcmClient.sendAll(tokens, fcmChannel, fcmPayload); + + // then + assertThat(result).isFalse(); } @Test @@ -65,9 +63,11 @@ void setUp() { given(firebaseMessaging.sendAll(anyList())) .willReturn(new MockBatchResponse(true)); - // when & then - assertThatNoException() - .isThrownBy(() -> fcmClient.sendAll(tokens, fcmChannel, fcmPayload)); + // when + boolean result = fcmClient.sendAll(tokens, fcmChannel, fcmPayload); + + // then + assertThat(result).isTrue(); } private static class MockBatchResponse implements BatchResponse { diff --git a/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java b/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java index 3567140f8..ce57bb963 100644 --- a/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java +++ b/backend/src/test/java/com/festago/fcm/repository/MemberFCMRepositoryTest.java @@ -9,11 +9,15 @@ import com.festago.support.MemberFixture; import java.util.Collections; import java.util.List; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") @DataJpaTest class MemberFCMRepositoryTest { diff --git a/backend/src/test/java/com/festago/support/EntryAlertFixture.java b/backend/src/test/java/com/festago/support/EntryAlertFixture.java new file mode 100644 index 000000000..9c87ccc83 --- /dev/null +++ b/backend/src/test/java/com/festago/support/EntryAlertFixture.java @@ -0,0 +1,51 @@ +package com.festago.support; + +import com.festago.entryalert.domain.AlertStatus; +import com.festago.entryalert.domain.EntryAlert; +import java.time.LocalDateTime; + +public class EntryAlertFixture { + + private Long id; + private Long stageId = 1L; + private LocalDateTime entryTime = LocalDateTime.now().plusMinutes(15); + private AlertStatus status = AlertStatus.PENDING; + + public static EntryAlertFixture entryAlert() { + return new EntryAlertFixture(); + } + + public EntryAlertFixture id(Long id) { + this.id = id; + return this; + } + + public EntryAlertFixture stageId(Long stageId) { + this.stageId = stageId; + return this; + } + + public EntryAlertFixture entryTime(LocalDateTime entryTime) { + this.entryTime = entryTime; + return this; + } + + public EntryAlertFixture status(AlertStatus status) { + this.status = status; + return this; + } + + public EntryAlert build() { + EntryAlert entryAlert = new EntryAlert(id, stageId, entryTime); + if (status != AlertStatus.PENDING) { + entryAlert.request(); + } + if (status == AlertStatus.SENT) { + entryAlert.send(); + } + if (status == AlertStatus.FAILED) { + entryAlert.fail(); + } + return entryAlert; + } +} diff --git a/backend/src/test/java/com/festago/support/SetUpMockito.java b/backend/src/test/java/com/festago/support/SetUpMockito.java new file mode 100644 index 000000000..4c642e3f6 --- /dev/null +++ b/backend/src/test/java/com/festago/support/SetUpMockito.java @@ -0,0 +1,30 @@ +package com.festago.support; + +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; +import org.mockito.stubbing.OngoingStubbing; + +public class SetUpMockito { + + public static Given given(T methodCall) { + OngoingStubbing ongoingStubbing = Mockito.lenient().when(methodCall); + return new Given<>(ongoingStubbing); + } + + public static class Given { + + private final OngoingStubbing ongoingStubbing; + + public Given(OngoingStubbing ongoingStubbing) { + this.ongoingStubbing = ongoingStubbing; + } + + public void willReturn(T value) { + ongoingStubbing.thenReturn(value); + } + + public void willAnswer(Answer answer) { + ongoingStubbing.thenAnswer(answer); + } + } +} From a29ae79c35cac157c154667418320f7f6cc611e3 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 02:09:27 +0900 Subject: [PATCH 21/45] =?UTF-8?q?refactor:=20FCM=20=EC=A0=84=EC=86=A1?= =?UTF-8?q?=EC=8B=9C=20=EB=8D=B0=EB=93=9C=EB=9D=BD=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/festago/config/SchedulingConfig.java | 22 +++++++++++++++++++ .../fcm/infrastructure/FcmClientImpl.java | 5 ++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/com/festago/config/SchedulingConfig.java b/backend/src/main/java/com/festago/config/SchedulingConfig.java index 987ad11bf..86e936012 100644 --- a/backend/src/main/java/com/festago/config/SchedulingConfig.java +++ b/backend/src/main/java/com/festago/config/SchedulingConfig.java @@ -1,10 +1,32 @@ package com.festago.config; +import java.util.concurrent.Executor; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @Configuration @EnableScheduling public class SchedulingConfig { + private static final String DEFAULT_EXECUTOR_NAME = "taskExecutor"; + public static final String FCM_EXECUTOR_NAME = "fcmExecutor"; + + @Bean(name = DEFAULT_EXECUTOR_NAME) + public Executor defaultExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(8); + executor.initialize(); + return executor; + } + + @Bean(name = FCM_EXECUTOR_NAME) + public Executor fcmExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(4); + executor.setQueueCapacity(10); + executor.initialize(); + return executor; + } } diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java index f94221e0a..eb9b733cc 100644 --- a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -2,6 +2,7 @@ import com.festago.common.exception.ErrorCode; import com.festago.common.exception.InternalServerException; +import com.festago.config.SchedulingConfig; import com.festago.fcm.application.FcmClient; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; @@ -18,6 +19,7 @@ import java.util.concurrent.Executor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; @@ -31,7 +33,8 @@ public class FcmClientImpl implements FcmClient { private final FirebaseMessaging firebaseMessaging; private final Executor taskExecutor; - public FcmClientImpl(FirebaseMessaging firebaseMessaging, Executor taskExecutor) { + public FcmClientImpl(FirebaseMessaging firebaseMessaging, + @Qualifier(SchedulingConfig.FCM_EXECUTOR_NAME) Executor taskExecutor) { this.firebaseMessaging = firebaseMessaging; this.taskExecutor = taskExecutor; } From 49c15e62aee7326a25641e4d012acaec4c67996d Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 02:29:07 +0900 Subject: [PATCH 22/45] =?UTF-8?q?refactor:=20AsyncBatch=20=ED=85=9C?= =?UTF-8?q?=ED=94=8C=EB=A6=BF=20=EC=BD=9C=EB=B0=B1=20=ED=8C=A8=ED=84=B4=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/utils/AsyncBatchExecutor.java | 56 +++++++++++++++++++ .../fcm/infrastructure/FcmClientImpl.java | 39 ++----------- 2 files changed, 61 insertions(+), 34 deletions(-) create mode 100644 backend/src/main/java/com/festago/common/utils/AsyncBatchExecutor.java diff --git a/backend/src/main/java/com/festago/common/utils/AsyncBatchExecutor.java b/backend/src/main/java/com/festago/common/utils/AsyncBatchExecutor.java new file mode 100644 index 000000000..297a82f6e --- /dev/null +++ b/backend/src/main/java/com/festago/common/utils/AsyncBatchExecutor.java @@ -0,0 +1,56 @@ +package com.festago.common.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Function; + +public class AsyncBatchExecutor { + + private final int batchSize; + private final Executor executor; + + public AsyncBatchExecutor(int batchSize, Executor executor) { + this.batchSize = batchSize; + this.executor = executor; + } + + public boolean execute(List items, Function, Boolean> action) { + if (items.size() <= batchSize) { + return action.apply(items); + } + + List> futures = createFuturesForBatches(items, action); + return awaitAllFuturesAndCheckResults(futures); + } + + private List> createFuturesForBatches(List items, Function, Boolean> action) { + List> futures = new ArrayList<>(); + for (int i = 0; i < items.size(); i += batchSize) { + List batchItems = items.subList(i, Math.min(i + batchSize, items.size())); + futures.add(createFutureForBatch(batchItems, action)); + } + return futures; + } + + private CompletableFuture createFutureForBatch(List batchItems, Function, Boolean> action) { + return CompletableFuture.supplyAsync(() -> action.apply(batchItems), executor); + } + + private boolean awaitAllFuturesAndCheckResults(List> futures) { + CompletableFuture allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + + try { + allOf.join(); + for (CompletableFuture future : futures) { + if (!future.get()) { + return false; + } + } + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java index eb9b733cc..11a6c9698 100644 --- a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -2,6 +2,7 @@ import com.festago.common.exception.ErrorCode; import com.festago.common.exception.InternalServerException; +import com.festago.common.utils.AsyncBatchExecutor; import com.festago.config.SchedulingConfig; import com.festago.fcm.application.FcmClient; import com.festago.fcm.domain.FCMChannel; @@ -13,9 +14,7 @@ import com.google.firebase.messaging.FirebaseMessagingException; import com.google.firebase.messaging.Message; import com.google.firebase.messaging.SendResponse; -import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,43 +43,15 @@ public boolean sendAll(List tokens, FCMChannel channel, FcmPayload paylo validateTokenSize(tokens); List messages = createMessages(tokens, channel, payload); - int messageSize = messages.size(); - if (messageSize <= BATCH_ALERT_SIZE) { + AsyncBatchExecutor executor = new AsyncBatchExecutor<>(BATCH_ALERT_SIZE, taskExecutor); + return executor.execute(messages, batchMessages -> { try { - sendMessages(messages, channel); + sendMessages(batchMessages, channel); return true; } catch (Exception e) { return false; } - } - - List> futures = new ArrayList<>(); - - for (int i = 0; i < messages.size(); i += BATCH_ALERT_SIZE) { - List batchMessages = messages.subList(i, Math.min(i + BATCH_ALERT_SIZE, messages.size())); - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - try { - sendMessages(batchMessages, channel); - return true; - } catch (Exception e) { - return false; - } - }, taskExecutor); - futures.add(future); - } - CompletableFuture allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); - - try { - allOf.join(); - for (CompletableFuture future : futures) { - if (!future.get()) { - return false; - } - } - return true; - } catch (Exception e) { - return false; - } + }); } private void validateTokenSize(List tokens) { From e253317fe2700a918fc67cf59960a133f8d11419 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 02:31:40 +0900 Subject: [PATCH 23/45] =?UTF-8?q?refactor:=20Async=20ThreadPool=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/festago/config/AsyncConfig.java | 22 +++++++++++++++++++ .../com/festago/config/SchedulingConfig.java | 22 ------------------- .../fcm/infrastructure/FcmClientImpl.java | 4 ++-- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/backend/src/main/java/com/festago/config/AsyncConfig.java b/backend/src/main/java/com/festago/config/AsyncConfig.java index fbf428a9b..768682955 100644 --- a/backend/src/main/java/com/festago/config/AsyncConfig.java +++ b/backend/src/main/java/com/festago/config/AsyncConfig.java @@ -1,10 +1,32 @@ package com.festago.config; +import java.util.concurrent.Executor; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @EnableAsync @Configuration public class AsyncConfig { + private static final String DEFAULT_EXECUTOR_NAME = "taskExecutor"; + public static final String FCM_EXECUTOR_NAME = "fcmExecutor"; + + @Bean(name = DEFAULT_EXECUTOR_NAME) + public Executor defaultExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(8); + executor.initialize(); + return executor; + } + + @Bean(name = FCM_EXECUTOR_NAME) + public Executor fcmExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(4); + executor.setQueueCapacity(10); + executor.initialize(); + return executor; + } } diff --git a/backend/src/main/java/com/festago/config/SchedulingConfig.java b/backend/src/main/java/com/festago/config/SchedulingConfig.java index 86e936012..987ad11bf 100644 --- a/backend/src/main/java/com/festago/config/SchedulingConfig.java +++ b/backend/src/main/java/com/festago/config/SchedulingConfig.java @@ -1,32 +1,10 @@ package com.festago.config; -import java.util.concurrent.Executor; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @Configuration @EnableScheduling public class SchedulingConfig { - private static final String DEFAULT_EXECUTOR_NAME = "taskExecutor"; - public static final String FCM_EXECUTOR_NAME = "fcmExecutor"; - - @Bean(name = DEFAULT_EXECUTOR_NAME) - public Executor defaultExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(8); - executor.initialize(); - return executor; - } - - @Bean(name = FCM_EXECUTOR_NAME) - public Executor fcmExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(4); - executor.setQueueCapacity(10); - executor.initialize(); - return executor; - } } diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java index 11a6c9698..e7d6bd202 100644 --- a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -3,7 +3,7 @@ import com.festago.common.exception.ErrorCode; import com.festago.common.exception.InternalServerException; import com.festago.common.utils.AsyncBatchExecutor; -import com.festago.config.SchedulingConfig; +import com.festago.config.AsyncConfig; import com.festago.fcm.application.FcmClient; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; @@ -33,7 +33,7 @@ public class FcmClientImpl implements FcmClient { private final Executor taskExecutor; public FcmClientImpl(FirebaseMessaging firebaseMessaging, - @Qualifier(SchedulingConfig.FCM_EXECUTOR_NAME) Executor taskExecutor) { + @Qualifier(AsyncConfig.FCM_EXECUTOR_NAME) Executor taskExecutor) { this.firebaseMessaging = firebaseMessaging; this.taskExecutor = taskExecutor; } From 78e5820c15bb4415197eb3f1c27e694c75ec8ac9 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 03:19:21 +0900 Subject: [PATCH 24/45] =?UTF-8?q?refactor:=20EntryAlertResultHandler=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entryalert/application/EntryAlertFcmClient.java | 8 ++++---- ...ertStatusService.java => EntryAlertResultHandler.java} | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) rename backend/src/main/java/com/festago/entryalert/application/{EntryAlertStatusService.java => EntryAlertResultHandler.java} (81%) diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertFcmClient.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertFcmClient.java index 21e81ab14..920086a9e 100644 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertFcmClient.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertFcmClient.java @@ -11,16 +11,16 @@ public class EntryAlertFcmClient { private final FcmClient fcmClient; - private final EntryAlertStatusService entryAlertStatusService; + private final EntryAlertResultHandler entryAlertResultHandler; - public EntryAlertFcmClient(FcmClient fcmClient, EntryAlertStatusService entryAlertStatusService) { + public EntryAlertFcmClient(FcmClient fcmClient, EntryAlertResultHandler entryAlertResultHandler) { this.fcmClient = fcmClient; - this.entryAlertStatusService = entryAlertStatusService; + this.entryAlertResultHandler = entryAlertResultHandler; } @Async public void sendAll(Long entryAlertId, List tokens, FCMChannel channel, FcmPayload fcmPayload) { boolean isSuccess = fcmClient.sendAll(tokens, channel, fcmPayload); - entryAlertStatusService.updateRequestedEntryAlertStatus(entryAlertId, isSuccess); + entryAlertResultHandler.handle(entryAlertId, isSuccess); } } diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertStatusService.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertResultHandler.java similarity index 81% rename from backend/src/main/java/com/festago/entryalert/application/EntryAlertStatusService.java rename to backend/src/main/java/com/festago/entryalert/application/EntryAlertResultHandler.java index 8a4a71267..9500f6bed 100644 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertStatusService.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertResultHandler.java @@ -8,16 +8,16 @@ import org.springframework.transaction.annotation.Transactional; @Service -public class EntryAlertStatusService { +public class EntryAlertResultHandler { private final EntryAlertRepository entryAlertRepository; - public EntryAlertStatusService(EntryAlertRepository entryAlertRepository) { + public EntryAlertResultHandler(EntryAlertRepository entryAlertRepository) { this.entryAlertRepository = entryAlertRepository; } @Transactional - public void updateRequestedEntryAlertStatus(Long entryAlertId, boolean isSuccess) { + public void handle(Long entryAlertId, boolean isSuccess) { EntryAlert entryAlert = entryAlertRepository.findById(entryAlertId) .orElseThrow(() -> new NotFoundException(ErrorCode.ENTRY_ALERT_NOT_FOUND)); if (isSuccess) { From 7b4a210278cfb7fb7a82851db59d9a7cdd1d5d27 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 03:39:57 +0900 Subject: [PATCH 25/45] =?UTF-8?q?refactor:=20fcmExecutor=20QueueCapacity?= =?UTF-8?q?=20=EA=B8=B0=EB=B3=B8=EA=B0=92=EC=9C=BC=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/com/festago/config/AsyncConfig.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/main/java/com/festago/config/AsyncConfig.java b/backend/src/main/java/com/festago/config/AsyncConfig.java index 768682955..b56f39f88 100644 --- a/backend/src/main/java/com/festago/config/AsyncConfig.java +++ b/backend/src/main/java/com/festago/config/AsyncConfig.java @@ -25,7 +25,6 @@ public Executor defaultExecutor() { public Executor fcmExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(4); - executor.setQueueCapacity(10); executor.initialize(); return executor; } From fff132549dba4b4557a665cb54efbdf3a0143bfc Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 03:47:55 +0900 Subject: [PATCH 26/45] =?UTF-8?q?feat:=20flyway=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/festago/entryalert/domain/EntryAlert.java | 3 +++ backend/src/main/resources/db/migration/V6__entry_alert.sql | 3 +++ 2 files changed, 6 insertions(+) diff --git a/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java b/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java index 022d1d28d..177cd9c8a 100644 --- a/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java +++ b/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java @@ -4,6 +4,8 @@ import com.festago.common.exception.BadRequestException; import com.festago.common.exception.ErrorCode; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -26,6 +28,7 @@ public class EntryAlert extends BaseTimeEntity { private LocalDateTime entryTime; @NotNull + @Enumerated(value = EnumType.STRING) private AlertStatus status = AlertStatus.PENDING; protected EntryAlert() { diff --git a/backend/src/main/resources/db/migration/V6__entry_alert.sql b/backend/src/main/resources/db/migration/V6__entry_alert.sql index 37f2e15f2..33f04f892 100644 --- a/backend/src/main/resources/db/migration/V6__entry_alert.sql +++ b/backend/src/main/resources/db/migration/V6__entry_alert.sql @@ -1,8 +1,11 @@ create table if not exists entry_alert ( id bigint not null auto_increment, + created_at datetime(6), + updated_at datetime(6), entry_time datetime(6) not null, stage_id bigint not null, + status varchar(255) not null, primary key (id) ) engine innodb default charset = utf8mb4 From 6995f686ff9204da96f4ef9dc85f844e44b937a0 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 03:52:04 +0900 Subject: [PATCH 27/45] =?UTF-8?q?test:=20LocalDateTime=20=ED=83=80?= =?UTF-8?q?=EC=9E=84=EC=A1=B4=20UTC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/festago/domain/MemberTicketRepositoryTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java b/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java index 24f09aa9a..4a63f28da 100644 --- a/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java +++ b/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java @@ -20,7 +20,9 @@ import com.festago.ticket.repository.TicketRepository; import com.festago.ticketing.domain.MemberTicket; import com.festago.ticketing.repository.MemberTicketRepository; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -135,7 +137,7 @@ class 회원의_ID로_에매한_티켓을_모두_조회 { @Test void 무대와_입장시간으로_멤버아이디리스트_조회() { // given - LocalDateTime entryTime = LocalDateTime.now(); + LocalDateTime entryTime = LocalDateTime.ofInstant(Instant.now(), ZoneOffset.UTC); Stage stage = saveStage(); Member member1 = memberRepository.save(MemberFixture.member().build()); Member member2 = memberRepository.save(MemberFixture.member().build()); @@ -145,13 +147,13 @@ class 회원의_ID로_에매한_티켓을_모두_조회 { MemberTicketFixture.memberTicket().stage(stage).owner(member2).entryTime(entryTime).build()); // when - List actual = memberTicketRepository.findAllOwnerIdByStageIdAndEntryTime( - stage.getId(), entryTime); + List actual = memberTicketRepository.findAllOwnerIdByStageIdAndEntryTime(stage.getId(), entryTime); // then assertThat(actual).containsExactlyInAnyOrder(member1.getId(), member2.getId()); } + private Stage saveStage() { School school = schoolRepository.save(SchoolFixture.school().build()); Festival festival = festivalRepository.save(FestivalFixture.festival().school(school).build()); From ac669fce29a7eb88c1abcc42d474626ed0ad25b7 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 10:38:27 +0900 Subject: [PATCH 28/45] =?UTF-8?q?fix:=20EntryAlertServiceTest=20Mock=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festago/entryalert/application/EntryAlertServiceTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java b/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java index 598c93da9..abb8f8b0e 100644 --- a/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java +++ b/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java @@ -10,7 +10,6 @@ import com.festago.entryalert.domain.AlertStatus; import com.festago.entryalert.domain.EntryAlert; import com.festago.entryalert.repository.EntryAlertRepository; -import com.festago.fcm.application.FcmClient; import com.festago.fcm.repository.MemberFCMRepository; import com.festago.support.EntryAlertFixture; import com.festago.support.SetUpMockito; @@ -42,7 +41,7 @@ class EntryAlertServiceTest { MemberFCMRepository memberFCMRepository; @Mock - FcmClient fcmClient; + EntryAlertFcmClient fcmClient; @InjectMocks EntryAlertService entryAlertService; From dd9d56fbf7ebbb4a65f9ff5b6c12a344a1c7f9b8 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 10:40:23 +0900 Subject: [PATCH 29/45] =?UTF-8?q?refactor:=20test=20LocalDateTime=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/festago/domain/MemberTicketRepositoryTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java b/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java index 4a63f28da..ae01d8707 100644 --- a/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java +++ b/backend/src/test/java/com/festago/domain/MemberTicketRepositoryTest.java @@ -20,9 +20,7 @@ import com.festago.ticket.repository.TicketRepository; import com.festago.ticketing.domain.MemberTicket; import com.festago.ticketing.repository.MemberTicketRepository; -import java.time.Instant; import java.time.LocalDateTime; -import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -137,7 +135,7 @@ class 회원의_ID로_에매한_티켓을_모두_조회 { @Test void 무대와_입장시간으로_멤버아이디리스트_조회() { // given - LocalDateTime entryTime = LocalDateTime.ofInstant(Instant.now(), ZoneOffset.UTC); + LocalDateTime entryTime = LocalDateTime.of(2023, 10, 5, 10, 30); Stage stage = saveStage(); Member member1 = memberRepository.save(MemberFixture.member().build()); Member member2 = memberRepository.save(MemberFixture.member().build()); From 26fed9fad563d7cb81a34aeef670393451f8aae6 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 11:15:26 +0900 Subject: [PATCH 30/45] =?UTF-8?q?test:=20EntryAlertRepositoryTest=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/EntryAlertRepositoryTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/backend/src/test/java/com/festago/entryalert/repository/EntryAlertRepositoryTest.java b/backend/src/test/java/com/festago/entryalert/repository/EntryAlertRepositoryTest.java index 1da48c073..36d5c37d7 100644 --- a/backend/src/test/java/com/festago/entryalert/repository/EntryAlertRepositoryTest.java +++ b/backend/src/test/java/com/festago/entryalert/repository/EntryAlertRepositoryTest.java @@ -1,7 +1,15 @@ package com.festago.entryalert.repository; +import static org.assertj.core.api.Assertions.assertThat; + +import com.festago.entryalert.domain.AlertStatus; +import com.festago.entryalert.domain.EntryAlert; +import com.festago.support.EntryAlertFixture; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; @@ -17,5 +25,49 @@ class EntryAlertRepositoryTest { @Test void 상태로_전부_조회() { // given + EntryAlert entryAlert1 = entryAlertRepository.save( + EntryAlertFixture.entryAlert().status(AlertStatus.PENDING).build()); + EntryAlert entryAlert2 = entryAlertRepository.save( + EntryAlertFixture.entryAlert().status(AlertStatus.PENDING).build()); + EntryAlert entryAlert3 = entryAlertRepository.save( + EntryAlertFixture.entryAlert().status(AlertStatus.REQUESTED).build()); + + // when + List actual = entryAlertRepository.findAllByStatus(AlertStatus.PENDING); + + // then + assertThat(actual).containsExactlyInAnyOrder(entryAlert1, entryAlert2); + } + + @Nested + class id와_상태로_조회 { + + @Test + void id와_상태로_조회() { + // given + EntryAlert entryAlert = entryAlertRepository.save( + EntryAlertFixture.entryAlert().status(AlertStatus.PENDING).build()); + + // when + Optional actual = entryAlertRepository.findByIdAndStatusForUpdate( + entryAlert.getId(), AlertStatus.PENDING); + + // then + assertThat(actual.get()).isEqualTo(entryAlert); + } + + @Test + void 상태가_다르면_조회되지_않음() { + // given + EntryAlert entryAlert = entryAlertRepository.save( + EntryAlertFixture.entryAlert().status(AlertStatus.REQUESTED).build()); + + // when + Optional actual = entryAlertRepository.findByIdAndStatusForUpdate( + entryAlert.getId(), AlertStatus.PENDING); + + // then + assertThat(actual).isEmpty(); + } } } From 457778460910ec258dd20c6cf490b7240953381b Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 13:33:05 +0900 Subject: [PATCH 31/45] =?UTF-8?q?refactor:=20JPQL=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20@Param=20=EC=96=B4=EB=85=B8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festago/ticketing/repository/MemberTicketRepository.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/com/festago/ticketing/repository/MemberTicketRepository.java b/backend/src/main/java/com/festago/ticketing/repository/MemberTicketRepository.java index 01db0ae22..e5aa1a114 100644 --- a/backend/src/main/java/com/festago/ticketing/repository/MemberTicketRepository.java +++ b/backend/src/main/java/com/festago/ticketing/repository/MemberTicketRepository.java @@ -8,6 +8,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface MemberTicketRepository extends JpaRepository { @@ -21,5 +22,6 @@ public interface MemberTicketRepository extends JpaRepository findAllOwnerIdByStageIdAndEntryTime(Long stageId, LocalDateTime entryTime); + List findAllOwnerIdByStageIdAndEntryTime(@Param("stageId") Long stageId, + @Param("entryTime") LocalDateTime entryTime); } From 44c583b9b11559b1daa4d282757966f8e478377b Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 13:38:01 +0900 Subject: [PATCH 32/45] =?UTF-8?q?refactor:=20=EB=B9=88=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EA=B2=80=EC=82=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/festago/fcm/infrastructure/FcmClientImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java index e7d6bd202..73739622f 100644 --- a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -40,7 +40,7 @@ public FcmClientImpl(FirebaseMessaging firebaseMessaging, @Override public boolean sendAll(List tokens, FCMChannel channel, FcmPayload payload) { - validateTokenSize(tokens); + validateEmptyTokens(tokens); List messages = createMessages(tokens, channel, payload); AsyncBatchExecutor executor = new AsyncBatchExecutor<>(BATCH_ALERT_SIZE, taskExecutor); @@ -54,7 +54,7 @@ public boolean sendAll(List tokens, FCMChannel channel, FcmPayload paylo }); } - private void validateTokenSize(List tokens) { + private void validateEmptyTokens(List tokens) { if (tokens.isEmpty()) { throw new InternalServerException(ErrorCode.FCM_NOT_FOUND); } From a42051ad33f5f300c8123c1b35ca8db4a4733c6f Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 14:09:31 +0900 Subject: [PATCH 33/45] =?UTF-8?q?refactor:=20SENT/FAILED=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/festago/config/AsyncConfig.java | 21 ---- .../application/EntryAlertFcmClient.java | 26 ----- .../application/EntryAlertResultHandler.java | 29 ----- .../application/EntryAlertService.java | 25 ++--- .../entryalert/domain/AlertStatus.java | 2 - .../festago/entryalert/domain/EntryAlert.java | 20 ---- .../festago/fcm/application/FcmClient.java | 4 +- .../fcm/application/MockFcmClient.java | 3 +- .../fcm/infrastructure/FcmClientImpl.java | 50 ++++----- .../application/EntryAlertServiceTest.java | 3 +- .../entryalert/domain/EntryAlertTest.java | 65 +---------- .../fcm/infrastructure/FcmClientImplTest.java | 101 ------------------ .../festago/support/EntryAlertFixture.java | 6 -- 13 files changed, 39 insertions(+), 316 deletions(-) delete mode 100644 backend/src/main/java/com/festago/entryalert/application/EntryAlertFcmClient.java delete mode 100644 backend/src/main/java/com/festago/entryalert/application/EntryAlertResultHandler.java delete mode 100644 backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java diff --git a/backend/src/main/java/com/festago/config/AsyncConfig.java b/backend/src/main/java/com/festago/config/AsyncConfig.java index b56f39f88..fbf428a9b 100644 --- a/backend/src/main/java/com/festago/config/AsyncConfig.java +++ b/backend/src/main/java/com/festago/config/AsyncConfig.java @@ -1,31 +1,10 @@ package com.festago.config; -import java.util.concurrent.Executor; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @EnableAsync @Configuration public class AsyncConfig { - private static final String DEFAULT_EXECUTOR_NAME = "taskExecutor"; - public static final String FCM_EXECUTOR_NAME = "fcmExecutor"; - - @Bean(name = DEFAULT_EXECUTOR_NAME) - public Executor defaultExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(8); - executor.initialize(); - return executor; - } - - @Bean(name = FCM_EXECUTOR_NAME) - public Executor fcmExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(4); - executor.initialize(); - return executor; - } } diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertFcmClient.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertFcmClient.java deleted file mode 100644 index 920086a9e..000000000 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertFcmClient.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.festago.entryalert.application; - -import com.festago.fcm.application.FcmClient; -import com.festago.fcm.domain.FCMChannel; -import com.festago.fcm.dto.FcmPayload; -import java.util.List; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; - -@Component -public class EntryAlertFcmClient { - - private final FcmClient fcmClient; - private final EntryAlertResultHandler entryAlertResultHandler; - - public EntryAlertFcmClient(FcmClient fcmClient, EntryAlertResultHandler entryAlertResultHandler) { - this.fcmClient = fcmClient; - this.entryAlertResultHandler = entryAlertResultHandler; - } - - @Async - public void sendAll(Long entryAlertId, List tokens, FCMChannel channel, FcmPayload fcmPayload) { - boolean isSuccess = fcmClient.sendAll(tokens, channel, fcmPayload); - entryAlertResultHandler.handle(entryAlertId, isSuccess); - } -} diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertResultHandler.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertResultHandler.java deleted file mode 100644 index 9500f6bed..000000000 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertResultHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.festago.entryalert.application; - -import com.festago.common.exception.ErrorCode; -import com.festago.common.exception.NotFoundException; -import com.festago.entryalert.domain.EntryAlert; -import com.festago.entryalert.repository.EntryAlertRepository; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -public class EntryAlertResultHandler { - - private final EntryAlertRepository entryAlertRepository; - - public EntryAlertResultHandler(EntryAlertRepository entryAlertRepository) { - this.entryAlertRepository = entryAlertRepository; - } - - @Transactional - public void handle(Long entryAlertId, boolean isSuccess) { - EntryAlert entryAlert = entryAlertRepository.findById(entryAlertId) - .orElseThrow(() -> new NotFoundException(ErrorCode.ENTRY_ALERT_NOT_FOUND)); - if (isSuccess) { - entryAlert.send(); - return; - } - entryAlert.fail(); - } -} diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java index defd4acad..1520a7eab 100644 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java @@ -6,32 +6,29 @@ import com.festago.entryalert.domain.EntryAlert; import com.festago.entryalert.dto.EntryAlertResponse; import com.festago.entryalert.repository.EntryAlertRepository; +import com.festago.fcm.application.FcmClient; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; import com.festago.fcm.repository.MemberFCMRepository; import com.festago.ticketing.repository.MemberTicketRepository; import java.time.LocalDateTime; import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @Transactional +@RequiredArgsConstructor public class EntryAlertService { private final EntryAlertRepository entryAlertRepository; private final MemberTicketRepository memberTicketRepository; private final MemberFCMRepository memberFCMRepository; - private final EntryAlertFcmClient fcmClient; - - public EntryAlertService(EntryAlertRepository entryAlertRepository, MemberTicketRepository memberTicketRepository, - MemberFCMRepository memberFCMRepository, EntryAlertFcmClient fcmClient) { - this.entryAlertRepository = entryAlertRepository; - this.memberTicketRepository = memberTicketRepository; - this.memberFCMRepository = memberFCMRepository; - this.fcmClient = fcmClient; - } + private final FcmClient fcmClient; + private final TaskExecutor taskExecutor; @Transactional(readOnly = true) public List findAllPending() { @@ -50,10 +47,14 @@ public EntryAlertResponse create(Long stageId, LocalDateTime entryTime) { public void sendEntryAlert(Long id) { EntryAlert entryAlert = entryAlertRepository.findByIdAndStatusForUpdate(id, AlertStatus.PENDING) .orElseThrow(() -> new ConflictException(ErrorCode.ALREADY_ALERT)); + List tokens = findFcmTokens(entryAlert); + taskExecutor.execute(() -> fcmClient.sendAll(tokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert())); + entryAlert.request(); + } + + private List findFcmTokens(EntryAlert entryAlert) { List memberIds = memberTicketRepository.findAllOwnerIdByStageIdAndEntryTime( entryAlert.getStageId(), entryAlert.getEntryTime()); - List tokens = memberFCMRepository.findAllTokenByMemberIdIn(memberIds); - fcmClient.sendAll(entryAlert.getId(), tokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert()); - entryAlert.request(); + return memberFCMRepository.findAllTokenByMemberIdIn(memberIds); } } diff --git a/backend/src/main/java/com/festago/entryalert/domain/AlertStatus.java b/backend/src/main/java/com/festago/entryalert/domain/AlertStatus.java index 24f19d86b..1f94fabea 100644 --- a/backend/src/main/java/com/festago/entryalert/domain/AlertStatus.java +++ b/backend/src/main/java/com/festago/entryalert/domain/AlertStatus.java @@ -4,7 +4,5 @@ public enum AlertStatus { PENDING, REQUESTED, - SENT, - FAILED, ; } diff --git a/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java b/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java index 177cd9c8a..417327694 100644 --- a/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java +++ b/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java @@ -51,10 +51,6 @@ public static EntryAlert create(Long stageId, LocalDateTime entryTime, LocalDate return new EntryAlert(stageId, entryTime); } - public boolean canRequest() { - return status == AlertStatus.PENDING; - } - public void request() { validateNotPending(); this.status = AlertStatus.REQUESTED; @@ -66,22 +62,6 @@ private void validateNotPending() { } } - public void send() { - validateNotRequested(); - this.status = AlertStatus.SENT; - } - - private void validateNotRequested() { - if (status != AlertStatus.REQUESTED) { - throw new BadRequestException(ErrorCode.NOT_PENDING_ALERT); - } - } - - public void fail() { - validateNotRequested(); - this.status = AlertStatus.FAILED; - } - public LocalDateTime findAlertTime() { return entryTime.minusMinutes(ENTRY_ALERT_MINUTES_BEFORE); } diff --git a/backend/src/main/java/com/festago/fcm/application/FcmClient.java b/backend/src/main/java/com/festago/fcm/application/FcmClient.java index 98cf53c4a..7ef794ab8 100644 --- a/backend/src/main/java/com/festago/fcm/application/FcmClient.java +++ b/backend/src/main/java/com/festago/fcm/application/FcmClient.java @@ -5,6 +5,6 @@ import java.util.List; public interface FcmClient { - - boolean sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload); + + void sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload); } diff --git a/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java index 0f32a1f7e..de46e0328 100644 --- a/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java +++ b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java @@ -15,8 +15,7 @@ public class MockFcmClient implements FcmClient { private static final Logger log = LoggerFactory.getLogger(MockFcmClient.class); @Override - public boolean sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload) { + public void sendAll(List tokens, FCMChannel channel, FcmPayload fcmPayload) { log.info("[FCM] title: {} / body: {} / to {} device", fcmPayload.title(), fcmPayload.body(), tokens.size()); - return true; } } diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java index 73739622f..c128bdc32 100644 --- a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -2,8 +2,6 @@ import com.festago.common.exception.ErrorCode; import com.festago.common.exception.InternalServerException; -import com.festago.common.utils.AsyncBatchExecutor; -import com.festago.config.AsyncConfig; import com.festago.fcm.application.FcmClient; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; @@ -13,45 +11,38 @@ import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.FirebaseMessagingException; import com.google.firebase.messaging.Message; -import com.google.firebase.messaging.SendResponse; import java.util.List; import java.util.concurrent.Executor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Qualifier; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; @Component @Profile({"dev", "prod"}) +@RequiredArgsConstructor +@Slf4j public class FcmClientImpl implements FcmClient { - private static final Logger log = LoggerFactory.getLogger(FcmClientImpl.class); private static final int BATCH_ALERT_SIZE = 500; private final FirebaseMessaging firebaseMessaging; private final Executor taskExecutor; - public FcmClientImpl(FirebaseMessaging firebaseMessaging, - @Qualifier(AsyncConfig.FCM_EXECUTOR_NAME) Executor taskExecutor) { - this.firebaseMessaging = firebaseMessaging; - this.taskExecutor = taskExecutor; - } - @Override - public boolean sendAll(List tokens, FCMChannel channel, FcmPayload payload) { + public void sendAll(List tokens, FCMChannel channel, FcmPayload payload) { validateEmptyTokens(tokens); List messages = createMessages(tokens, channel, payload); - AsyncBatchExecutor executor = new AsyncBatchExecutor<>(BATCH_ALERT_SIZE, taskExecutor); - return executor.execute(messages, batchMessages -> { - try { - sendMessages(batchMessages, channel); - return true; - } catch (Exception e) { - return false; - } - }); + if (messages.size() <= BATCH_ALERT_SIZE) { + sendMessages(messages, channel); + return; + } + + for (int i = 0; i < messages.size(); i += BATCH_ALERT_SIZE) { + List batchMessages = messages.subList(i, Math.min(i + BATCH_ALERT_SIZE, messages.size())); + taskExecutor.execute(() -> sendMessages(batchMessages, channel)); + } } private void validateEmptyTokens(List tokens) { @@ -65,7 +56,7 @@ public void sendMessages(List messages, FCMChannel channel) { BatchResponse response = firebaseMessaging.sendAll(messages); checkAllSuccess(response, channel); } catch (FirebaseMessagingException e) { - throw new RuntimeException(e); + log.warn("[FCM: {}] 전송 실패: {}", channel, e.getMessagingErrorCode()); } } @@ -97,15 +88,10 @@ private AndroidNotification createAndroidNotification(FCMChannel channel, FcmPay } private void checkAllSuccess(BatchResponse batchResponse, FCMChannel channel) { - List failSend = batchResponse.getResponses().stream() - .filter(sendResponse -> !sendResponse.isSuccessful()) - .toList(); - - if (failSend.isEmpty()) { + int failCount = batchResponse.getFailureCount(); + if (failCount == 0) { return; } - - log.warn("[FCM: {}] 다음 요청들이 실패했습니다. {}", channel, failSend); - throw new InternalServerException(ErrorCode.FAIL_SEND_FCM_MESSAGE); + log.warn("[FCM: {}] {}건의 요청들이 실패했습니다.", channel, failCount); } } diff --git a/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java b/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java index abb8f8b0e..598c93da9 100644 --- a/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java +++ b/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java @@ -10,6 +10,7 @@ import com.festago.entryalert.domain.AlertStatus; import com.festago.entryalert.domain.EntryAlert; import com.festago.entryalert.repository.EntryAlertRepository; +import com.festago.fcm.application.FcmClient; import com.festago.fcm.repository.MemberFCMRepository; import com.festago.support.EntryAlertFixture; import com.festago.support.SetUpMockito; @@ -41,7 +42,7 @@ class EntryAlertServiceTest { MemberFCMRepository memberFCMRepository; @Mock - EntryAlertFcmClient fcmClient; + FcmClient fcmClient; @InjectMocks EntryAlertService entryAlertService; diff --git a/backend/src/test/java/com/festago/entryalert/domain/EntryAlertTest.java b/backend/src/test/java/com/festago/entryalert/domain/EntryAlertTest.java index b414c2e04..8e80141db 100644 --- a/backend/src/test/java/com/festago/entryalert/domain/EntryAlertTest.java +++ b/backend/src/test/java/com/festago/entryalert/domain/EntryAlertTest.java @@ -10,8 +10,6 @@ import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; @DisplayNameGeneration(ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") @@ -20,11 +18,10 @@ class EntryAlertTest { @Nested class 요청처리 { - @ValueSource(strings = {"REQUESTED", "SENT", "FAILED"}) - @ParameterizedTest - void 대기상태가_아니면_예외(AlertStatus status) { + @Test + void 대기상태가_아니면_예외() { // given - EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(status).build(); + EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(AlertStatus.REQUESTED).build(); // when & then assertThatThrownBy(entryAlert::request) @@ -44,60 +41,4 @@ class 요청처리 { assertThat(entryAlert.getStatus()).isEqualTo(AlertStatus.REQUESTED); } } - - @Nested - class 전송처리 { - - @ValueSource(strings = {"PENDING", "SENT", "FAILED"}) - @ParameterizedTest - void 요청상태가_아니면_예외(AlertStatus status) { - // given - EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(status).build(); - - // when & then - assertThatThrownBy(entryAlert::send) - .isInstanceOf(BadRequestException.class) - .hasMessage(NOT_PENDING_ALERT.getMessage()); - } - - @Test - void 전송_상태로_변경() { - // given - EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(AlertStatus.REQUESTED).build(); - - // when - entryAlert.send(); - - // then - assertThat(entryAlert.getStatus()).isEqualTo(AlertStatus.SENT); - } - } - - @Nested - class 실패처리 { - - @ValueSource(strings = {"PENDING", "SENT", "FAILED"}) - @ParameterizedTest - void 요청상태가_아니면_예외(AlertStatus status) { - // given - EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(status).build(); - - // when & then - assertThatThrownBy(entryAlert::fail) - .isInstanceOf(BadRequestException.class) - .hasMessage(NOT_PENDING_ALERT.getMessage()); - } - - @Test - void 전송_상태로_변경() { - // given - EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(AlertStatus.REQUESTED).build(); - - // when - entryAlert.fail(); - - // then - assertThat(entryAlert.getStatus()).isEqualTo(AlertStatus.FAILED); - } - } } diff --git a/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java b/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java deleted file mode 100644 index 1cad03db5..000000000 --- a/backend/src/test/java/com/festago/fcm/infrastructure/FcmClientImplTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.festago.fcm.infrastructure; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.festago.fcm.domain.FCMChannel; -import com.festago.fcm.dto.FcmPayload; -import com.google.firebase.messaging.BatchResponse; -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.FirebaseMessagingException; -import com.google.firebase.messaging.SendResponse; -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@DisplayNameGeneration(ReplaceUnderscores.class) -@SuppressWarnings("NonAsciiCharacters") -@ExtendWith(MockitoExtension.class) -class FcmClientImplTest { - - @Mock - FirebaseMessaging firebaseMessaging; - - @InjectMocks - FcmClientImpl fcmClient; - - List tokens; - FCMChannel fcmChannel; - FcmPayload fcmPayload; - - @BeforeEach - void setUp() { - tokens = List.of("token1", "token2"); - fcmChannel = FCMChannel.ENTRY_ALERT; - fcmPayload = FcmPayload.entryAlert(); - } - - @Test - void 유저의_FCM_요청_중_하나라도_실패하면_예외() throws FirebaseMessagingException { - // given - given(firebaseMessaging.sendAll(anyList())) - .willReturn(new MockBatchResponse(false)); - - // when - boolean result = fcmClient.sendAll(tokens, fcmChannel, fcmPayload); - - // then - assertThat(result).isFalse(); - } - - @Test - void 모두_성공하면_성공() throws FirebaseMessagingException { - // given - given(firebaseMessaging.sendAll(anyList())) - .willReturn(new MockBatchResponse(true)); - - // when - boolean result = fcmClient.sendAll(tokens, fcmChannel, fcmPayload); - - // then - assertThat(result).isTrue(); - } - - private static class MockBatchResponse implements BatchResponse { - - private final boolean isSuccessful; - - private MockBatchResponse(boolean isSuccessful) { - this.isSuccessful = isSuccessful; - } - - @Override - public List getResponses() { - SendResponse mockResponse = mock(SendResponse.class); - - when(mockResponse.isSuccessful()) - .thenReturn(isSuccessful); - - return List.of(mockResponse, mockResponse); - } - - @Override - public int getSuccessCount() { - return 0; - } - - @Override - public int getFailureCount() { - return 0; - } - } -} diff --git a/backend/src/test/java/com/festago/support/EntryAlertFixture.java b/backend/src/test/java/com/festago/support/EntryAlertFixture.java index 9c87ccc83..2aad9dded 100644 --- a/backend/src/test/java/com/festago/support/EntryAlertFixture.java +++ b/backend/src/test/java/com/festago/support/EntryAlertFixture.java @@ -40,12 +40,6 @@ public EntryAlert build() { if (status != AlertStatus.PENDING) { entryAlert.request(); } - if (status == AlertStatus.SENT) { - entryAlert.send(); - } - if (status == AlertStatus.FAILED) { - entryAlert.fail(); - } return entryAlert; } } From 7d872a9a1ba2325047f75eb1d53e0493f8ef793f Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 14:10:51 +0900 Subject: [PATCH 34/45] =?UTF-8?q?refcator:=20EntryAlert=20=EC=8A=A4?= =?UTF-8?q?=EC=BC=80=EC=A5=B4=EB=A7=81=20=EC=B6=94=EA=B0=80=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=20=EC=9E=90=EC=84=B8=ED=95=98=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festago/entryalert/application/EntryAlertEventListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java index fbf6e6aee..96549b809 100644 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java @@ -39,8 +39,8 @@ public void initEntryAlertSchedule() { private void addSchedule(EntryAlertResponse entryAlert) { Long entryAlertId = entryAlert.id(); - log.info("add entryAlert schedule: {}", entryAlertId); Instant alertTime = toInstant(entryAlert.alertTime()); + log.info("EntryAlert 스케쥴링 추가. entryAlertId: {}, alertTime: {}", entryAlertId, entryAlert.alertTime()); taskScheduler.schedule(() -> entryAlertService.sendEntryAlert(entryAlertId), alertTime); } From 310cd14b27328c75db002e9601d54951842635c6 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 14:12:16 +0900 Subject: [PATCH 35/45] =?UTF-8?q?remove:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=9C=A0=ED=8B=B8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/utils/AsyncBatchExecutor.java | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 backend/src/main/java/com/festago/common/utils/AsyncBatchExecutor.java diff --git a/backend/src/main/java/com/festago/common/utils/AsyncBatchExecutor.java b/backend/src/main/java/com/festago/common/utils/AsyncBatchExecutor.java deleted file mode 100644 index 297a82f6e..000000000 --- a/backend/src/main/java/com/festago/common/utils/AsyncBatchExecutor.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.festago.common.utils; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.function.Function; - -public class AsyncBatchExecutor { - - private final int batchSize; - private final Executor executor; - - public AsyncBatchExecutor(int batchSize, Executor executor) { - this.batchSize = batchSize; - this.executor = executor; - } - - public boolean execute(List items, Function, Boolean> action) { - if (items.size() <= batchSize) { - return action.apply(items); - } - - List> futures = createFuturesForBatches(items, action); - return awaitAllFuturesAndCheckResults(futures); - } - - private List> createFuturesForBatches(List items, Function, Boolean> action) { - List> futures = new ArrayList<>(); - for (int i = 0; i < items.size(); i += batchSize) { - List batchItems = items.subList(i, Math.min(i + batchSize, items.size())); - futures.add(createFutureForBatch(batchItems, action)); - } - return futures; - } - - private CompletableFuture createFutureForBatch(List batchItems, Function, Boolean> action) { - return CompletableFuture.supplyAsync(() -> action.apply(batchItems), executor); - } - - private boolean awaitAllFuturesAndCheckResults(List> futures) { - CompletableFuture allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); - - try { - allOf.join(); - for (CompletableFuture future : futures) { - if (!future.get()) { - return false; - } - } - return true; - } catch (Exception e) { - return false; - } - } -} From ce2adf1bb04d81a1ec0b1edec4808cf7b58fac7c Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 14:14:42 +0900 Subject: [PATCH 36/45] =?UTF-8?q?refactor:=20Test=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festago/entryalert/application/EntryAlertServiceTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java b/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java index 598c93da9..94a745e23 100644 --- a/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java +++ b/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java @@ -26,6 +26,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.task.TaskExecutor; @DisplayNameGeneration(ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") @@ -44,6 +45,9 @@ class EntryAlertServiceTest { @Mock FcmClient fcmClient; + @Mock + TaskExecutor taskExecutor; + @InjectMocks EntryAlertService entryAlertService; From b081b353d1eb348c5fdf63c7a9873238f7fdc1dd Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 14:33:45 +0900 Subject: [PATCH 37/45] =?UTF-8?q?refactor:=20FCmClient=EB=A1=9C=20?= =?UTF-8?q?=EB=B9=88=20=ED=86=A0=ED=81=B0=EC=9D=B4=20=EB=84=98=EC=96=B4?= =?UTF-8?q?=EC=98=A4=EB=A9=B4=20early=20return?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festago/fcm/infrastructure/FcmClientImpl.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java index c128bdc32..d3d2fc791 100644 --- a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -1,7 +1,5 @@ package com.festago.fcm.infrastructure; -import com.festago.common.exception.ErrorCode; -import com.festago.common.exception.InternalServerException; import com.festago.fcm.application.FcmClient; import com.festago.fcm.domain.FCMChannel; import com.festago.fcm.dto.FcmPayload; @@ -31,7 +29,9 @@ public class FcmClientImpl implements FcmClient { @Override public void sendAll(List tokens, FCMChannel channel, FcmPayload payload) { - validateEmptyTokens(tokens); + if (tokens.isEmpty()) { + return; + } List messages = createMessages(tokens, channel, payload); if (messages.size() <= BATCH_ALERT_SIZE) { @@ -45,12 +45,6 @@ public void sendAll(List tokens, FCMChannel channel, FcmPayload payload) } } - private void validateEmptyTokens(List tokens) { - if (tokens.isEmpty()) { - throw new InternalServerException(ErrorCode.FCM_NOT_FOUND); - } - } - public void sendMessages(List messages, FCMChannel channel) { try { BatchResponse response = firebaseMessaging.sendAll(messages); From 40f9359cda573c47adc1a8d3df99487f8f2556ad Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 14:35:28 +0900 Subject: [PATCH 38/45] =?UTF-8?q?feat:=20EntryAlert=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=A0=84=EC=86=A1=20=EC=8B=9C=EC=9E=91=20=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/festago/entryalert/application/EntryAlertService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java index 1520a7eab..bbddfa408 100644 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java @@ -14,6 +14,7 @@ import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -22,6 +23,7 @@ @Service @Transactional @RequiredArgsConstructor +@Slf4j public class EntryAlertService { private final EntryAlertRepository entryAlertRepository; @@ -48,6 +50,7 @@ public void sendEntryAlert(Long id) { EntryAlert entryAlert = entryAlertRepository.findByIdAndStatusForUpdate(id, AlertStatus.PENDING) .orElseThrow(() -> new ConflictException(ErrorCode.ALREADY_ALERT)); List tokens = findFcmTokens(entryAlert); + log.info("EntryAlert 전송 시작 / entryAlertId: {} / to {} devices", id, tokens.size()); taskExecutor.execute(() -> fcmClient.sendAll(tokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert())); entryAlert.request(); } From 896502210dfed23586d00ccaad2cdddbc299c8cc Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 21:56:47 +0900 Subject: [PATCH 39/45] =?UTF-8?q?fix:=20MockFcmClient=20Profile=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/festago/fcm/application/MockFcmClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java index de46e0328..1a4fa6379 100644 --- a/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java +++ b/backend/src/main/java/com/festago/fcm/application/MockFcmClient.java @@ -9,7 +9,7 @@ import org.springframework.stereotype.Component; @Component -@Profile({"!prod", "!dev"}) +@Profile({"!prod & !dev"}) public class MockFcmClient implements FcmClient { private static final Logger log = LoggerFactory.getLogger(MockFcmClient.class); From e374d669a194d15bbd9b153bbbbafeb3b9461a88 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 21:57:46 +0900 Subject: [PATCH 40/45] =?UTF-8?q?refactor:=20FcmClientImpl=20Executor=20->?= =?UTF-8?q?=20TaskExecutor=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/festago/fcm/infrastructure/FcmClientImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java index d3d2fc791..021aea6a7 100644 --- a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -10,10 +10,10 @@ import com.google.firebase.messaging.FirebaseMessagingException; import com.google.firebase.messaging.Message; import java.util.List; -import java.util.concurrent.Executor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Profile; +import org.springframework.core.task.TaskExecutor; import org.springframework.stereotype.Component; @Component @@ -25,7 +25,7 @@ public class FcmClientImpl implements FcmClient { private static final int BATCH_ALERT_SIZE = 500; private final FirebaseMessaging firebaseMessaging; - private final Executor taskExecutor; + private final TaskExecutor taskExecutor; @Override public void sendAll(List tokens, FCMChannel channel, FcmPayload payload) { From d1f1792034340b6873bd66a73b2ac01c4193802c Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 21:58:27 +0900 Subject: [PATCH 41/45] =?UTF-8?q?refactor:=20=EC=A0=91=EA=B7=BC=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=EC=9E=90=20=EB=B3=80=EA=B2=BD=20public=20->=20private?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/festago/fcm/infrastructure/FcmClientImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java index 021aea6a7..ba5c93485 100644 --- a/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java +++ b/backend/src/main/java/com/festago/fcm/infrastructure/FcmClientImpl.java @@ -45,7 +45,7 @@ public void sendAll(List tokens, FCMChannel channel, FcmPayload payload) } } - public void sendMessages(List messages, FCMChannel channel) { + private void sendMessages(List messages, FCMChannel channel) { try { BatchResponse response = firebaseMessaging.sendAll(messages); checkAllSuccess(response, channel); From 6e06a2136200184d07df46ed6301ae2a2a9380a7 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 21:59:51 +0900 Subject: [PATCH 42/45] =?UTF-8?q?refactor:=20=EB=A1=AC=EB=B3=B5=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/EntryAlertEventListener.java | 14 ++++---------- .../com/festago/entryalert/domain/EntryAlert.java | 6 +++--- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java index 96549b809..d7821bcf1 100644 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertEventListener.java @@ -6,8 +6,8 @@ import java.time.Instant; import java.time.LocalDateTime; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.scheduling.TaskScheduler; @@ -17,20 +17,14 @@ import org.springframework.transaction.event.TransactionalEventListener; @Component +@RequiredArgsConstructor +@Slf4j public class EntryAlertEventListener { - private static final Logger log = LoggerFactory.getLogger(EntryAlertEventListener.class); - private final EntryAlertService entryAlertService; private final TaskScheduler taskScheduler; private final Clock clock; - public EntryAlertEventListener(EntryAlertService entryAlertService, TaskScheduler taskScheduler, Clock clock) { - this.entryAlertService = entryAlertService; - this.taskScheduler = taskScheduler; - this.clock = clock; - } - @EventListener(ApplicationReadyEvent.class) public void initEntryAlertSchedule() { List entryAlerts = entryAlertService.findAllPending(); diff --git a/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java b/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java index 417327694..67151fbf5 100644 --- a/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java +++ b/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java @@ -11,8 +11,11 @@ import jakarta.persistence.Id; import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; @Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class EntryAlert extends BaseTimeEntity { private static final int ENTRY_ALERT_MINUTES_BEFORE = 10; @@ -31,9 +34,6 @@ public class EntryAlert extends BaseTimeEntity { @Enumerated(value = EnumType.STRING) private AlertStatus status = AlertStatus.PENDING; - protected EntryAlert() { - } - public EntryAlert(Long stageId, LocalDateTime entryTime) { this(null, stageId, entryTime); } From 94a3d55f8ec9b4b694480d0fcdb0b39ecfca0926 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 22:00:56 +0900 Subject: [PATCH 43/45] =?UTF-8?q?refactor:=20EntryAlert=20request=EC=83=81?= =?UTF-8?q?=ED=83=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/festago/entryalert/application/EntryAlertService.java | 2 +- .../main/java/com/festago/entryalert/domain/EntryAlert.java | 2 +- .../java/com/festago/entryalert/domain/EntryAlertTest.java | 4 ++-- .../src/test/java/com/festago/support/EntryAlertFixture.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java index bbddfa408..f5c7d6a14 100644 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java @@ -52,7 +52,7 @@ public void sendEntryAlert(Long id) { List tokens = findFcmTokens(entryAlert); log.info("EntryAlert 전송 시작 / entryAlertId: {} / to {} devices", id, tokens.size()); taskExecutor.execute(() -> fcmClient.sendAll(tokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert())); - entryAlert.request(); + entryAlert.changeRequested(); } private List findFcmTokens(EntryAlert entryAlert) { diff --git a/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java b/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java index 67151fbf5..e25bd3cba 100644 --- a/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java +++ b/backend/src/main/java/com/festago/entryalert/domain/EntryAlert.java @@ -51,7 +51,7 @@ public static EntryAlert create(Long stageId, LocalDateTime entryTime, LocalDate return new EntryAlert(stageId, entryTime); } - public void request() { + public void changeRequested() { validateNotPending(); this.status = AlertStatus.REQUESTED; } diff --git a/backend/src/test/java/com/festago/entryalert/domain/EntryAlertTest.java b/backend/src/test/java/com/festago/entryalert/domain/EntryAlertTest.java index 8e80141db..7d39f2114 100644 --- a/backend/src/test/java/com/festago/entryalert/domain/EntryAlertTest.java +++ b/backend/src/test/java/com/festago/entryalert/domain/EntryAlertTest.java @@ -24,7 +24,7 @@ class 요청처리 { EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(AlertStatus.REQUESTED).build(); // when & then - assertThatThrownBy(entryAlert::request) + assertThatThrownBy(entryAlert::changeRequested) .isInstanceOf(BadRequestException.class) .hasMessage(NOT_PENDING_ALERT.getMessage()); } @@ -35,7 +35,7 @@ class 요청처리 { EntryAlert entryAlert = EntryAlertFixture.entryAlert().status(AlertStatus.PENDING).build(); // when - entryAlert.request(); + entryAlert.changeRequested(); // then assertThat(entryAlert.getStatus()).isEqualTo(AlertStatus.REQUESTED); diff --git a/backend/src/test/java/com/festago/support/EntryAlertFixture.java b/backend/src/test/java/com/festago/support/EntryAlertFixture.java index 2aad9dded..a2020bce8 100644 --- a/backend/src/test/java/com/festago/support/EntryAlertFixture.java +++ b/backend/src/test/java/com/festago/support/EntryAlertFixture.java @@ -38,7 +38,7 @@ public EntryAlertFixture status(AlertStatus status) { public EntryAlert build() { EntryAlert entryAlert = new EntryAlert(id, stageId, entryTime); if (status != AlertStatus.PENDING) { - entryAlert.request(); + entryAlert.changeRequested(); } return entryAlert; } From e45fde3710c5894522760490a60c0fe000db6577 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 5 Oct 2023 22:03:25 +0900 Subject: [PATCH 44/45] =?UTF-8?q?refactor:=20sendEntryAlert=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=98=88=EC=99=B8=EB=A5=BC=20=EB=8D=98=EC=A7=80?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=B0=A9=ED=96=A5=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/EntryAlertService.java | 15 +++++++-------- .../application/EntryAlertServiceTest.java | 16 ---------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java index f5c7d6a14..710bf17ed 100644 --- a/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java +++ b/backend/src/main/java/com/festago/entryalert/application/EntryAlertService.java @@ -1,7 +1,5 @@ package com.festago.entryalert.application; -import com.festago.common.exception.ConflictException; -import com.festago.common.exception.ErrorCode; import com.festago.entryalert.domain.AlertStatus; import com.festago.entryalert.domain.EntryAlert; import com.festago.entryalert.dto.EntryAlertResponse; @@ -47,12 +45,13 @@ public EntryAlertResponse create(Long stageId, LocalDateTime entryTime) { @Async public void sendEntryAlert(Long id) { - EntryAlert entryAlert = entryAlertRepository.findByIdAndStatusForUpdate(id, AlertStatus.PENDING) - .orElseThrow(() -> new ConflictException(ErrorCode.ALREADY_ALERT)); - List tokens = findFcmTokens(entryAlert); - log.info("EntryAlert 전송 시작 / entryAlertId: {} / to {} devices", id, tokens.size()); - taskExecutor.execute(() -> fcmClient.sendAll(tokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert())); - entryAlert.changeRequested(); + entryAlertRepository.findByIdAndStatusForUpdate(id, AlertStatus.PENDING) + .ifPresent(entryAlert -> { + List tokens = findFcmTokens(entryAlert); + log.info("EntryAlert 전송 시작 / entryAlertId: {} / to {} devices", id, tokens.size()); + taskExecutor.execute(() -> fcmClient.sendAll(tokens, FCMChannel.ENTRY_ALERT, FcmPayload.entryAlert())); + entryAlert.changeRequested(); + }); } private List findFcmTokens(EntryAlert entryAlert) { diff --git a/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java b/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java index 94a745e23..31afad867 100644 --- a/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java +++ b/backend/src/test/java/com/festago/entryalert/application/EntryAlertServiceTest.java @@ -1,12 +1,8 @@ package com.festago.entryalert.application; -import static com.festago.common.exception.ErrorCode.ALREADY_ALERT; import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.BDDMockito.given; -import com.festago.common.exception.ConflictException; import com.festago.entryalert.domain.AlertStatus; import com.festago.entryalert.domain.EntryAlert; import com.festago.entryalert.repository.EntryAlertRepository; @@ -76,18 +72,6 @@ void setUp() { .willReturn(List.of("token1", "token2", "token3")); } - @Test - void 이미_전송된_알림이면_예외() { - // given - given(entryAlertRepository.findByIdAndStatusForUpdate(id, AlertStatus.PENDING)) - .willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> entryAlertService.sendEntryAlert(id)) - .isInstanceOf(ConflictException.class) - .hasMessage(ALREADY_ALERT.getMessage()); - } - @Test void 성공() { // when & then From e2b1eaaef18e68a158ce7fdb3ee4a7d9f7e64dd6 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Sun, 8 Oct 2023 15:47:42 +0900 Subject: [PATCH 45/45] =?UTF-8?q?refactor:=20graceful=20shutdown=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/festago/config/AsyncConfig.java | 12 ++++++++++++ backend/src/main/resources/application-local.yml | 3 +++ 2 files changed, 15 insertions(+) diff --git a/backend/src/main/java/com/festago/config/AsyncConfig.java b/backend/src/main/java/com/festago/config/AsyncConfig.java index fbf428a9b..2906a3f75 100644 --- a/backend/src/main/java/com/festago/config/AsyncConfig.java +++ b/backend/src/main/java/com/festago/config/AsyncConfig.java @@ -1,10 +1,22 @@ package com.festago.config; +import java.util.concurrent.Executor; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @EnableAsync @Configuration public class AsyncConfig { + @Bean + public Executor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(8); + executor.setWaitForTasksToCompleteOnShutdown(true); + executor.setAwaitTerminationSeconds(60); + executor.initialize(); + return executor; + } } diff --git a/backend/src/main/resources/application-local.yml b/backend/src/main/resources/application-local.yml index ddf517560..4180c7287 100644 --- a/backend/src/main/resources/application-local.yml +++ b/backend/src/main/resources/application-local.yml @@ -27,6 +27,9 @@ logging: jdbc: bind: trace +server: + shutdown: graceful + festago: qr-secret-key: festagofestagofestagofestagofestagofestagofestagofestagofestagofestagofestagofestagofestagofestago auth-secret-key: festagofestagofestagofestagofestagofestagofestagofestagofestagofestagofestagofestagofestagofestago