From 7abfa77d665093f0b84042dee725673d9153382a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Sun, 31 Mar 2024 01:11:37 +0900 Subject: [PATCH] [FEAT] add notification table and use in deadletter --- .../domain/friend/service/FriendService.java | 4 +- .../domain/group/service/GroupService.java | 4 +- .../notification/dto/NotificationRequest.java | 13 +++- .../notification/entity/Notification.java | 65 +++++++++++++++++++ .../notification/entity/NotificationType.java | 5 ++ .../messagequeue/FcmConsumer.java | 32 ++++----- .../messagequeue/FcmProducer.java | 20 +++++- .../repository/JpaNotificationRepository.java | 8 +++ .../repository/NotificationRepository.java | 14 ++++ .../domain/plan/service/PlanService.java | 7 +- .../backend/support/exclude/ExcludeTest.java | 9 ++- .../backend/support/service/LoginTest.java | 11 ++-- 12 files changed, 159 insertions(+), 33 deletions(-) create mode 100644 backend/src/main/java/com/twtw/backend/domain/notification/entity/Notification.java create mode 100644 backend/src/main/java/com/twtw/backend/domain/notification/entity/NotificationType.java create mode 100644 backend/src/main/java/com/twtw/backend/domain/notification/repository/JpaNotificationRepository.java create mode 100644 backend/src/main/java/com/twtw/backend/domain/notification/repository/NotificationRepository.java diff --git a/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java b/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java index 2cb0991d..ec605bcf 100644 --- a/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java +++ b/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java @@ -12,6 +12,7 @@ import com.twtw.backend.domain.member.service.AuthService; import com.twtw.backend.domain.member.service.MemberService; import com.twtw.backend.domain.notification.dto.NotificationRequest; +import com.twtw.backend.domain.notification.entity.NotificationType; import com.twtw.backend.domain.notification.messagequeue.FcmProducer; import com.twtw.backend.global.constant.NotificationBody; import com.twtw.backend.global.constant.NotificationTitle; @@ -67,7 +68,8 @@ private void sendNotification(final String deviceToken, final String nickname, f deviceToken, NotificationTitle.FRIEND_REQUEST_TITLE.getName(), NotificationBody.FRIEND_REQUEST_BODY.toNotificationBody(nickname), - id.toString())); + id.toString()), + NotificationType.FRIEND_REQUEST); } @Transactional diff --git a/backend/src/main/java/com/twtw/backend/domain/group/service/GroupService.java b/backend/src/main/java/com/twtw/backend/domain/group/service/GroupService.java index 1df08853..cff9ec5c 100644 --- a/backend/src/main/java/com/twtw/backend/domain/group/service/GroupService.java +++ b/backend/src/main/java/com/twtw/backend/domain/group/service/GroupService.java @@ -12,6 +12,7 @@ import com.twtw.backend.domain.member.service.AuthService; import com.twtw.backend.domain.member.service.MemberService; import com.twtw.backend.domain.notification.dto.NotificationRequest; +import com.twtw.backend.domain.notification.entity.NotificationType; import com.twtw.backend.domain.notification.messagequeue.FcmProducer; import com.twtw.backend.global.constant.NotificationBody; import com.twtw.backend.global.constant.NotificationTitle; @@ -137,7 +138,8 @@ private void sendNotification(final String deviceToken, final String groupName, deviceToken, NotificationTitle.GROUP_REQUEST_TITLE.getName(), NotificationBody.GROUP_REQUEST_BODY.toNotificationBody(groupName), - id.toString())); + id.toString()), + NotificationType.GROUP_REQUEST); } public GroupInfoResponse getGroupInfoResponse(Group group) { diff --git a/backend/src/main/java/com/twtw/backend/domain/notification/dto/NotificationRequest.java b/backend/src/main/java/com/twtw/backend/domain/notification/dto/NotificationRequest.java index 27e27806..dd68f258 100644 --- a/backend/src/main/java/com/twtw/backend/domain/notification/dto/NotificationRequest.java +++ b/backend/src/main/java/com/twtw/backend/domain/notification/dto/NotificationRequest.java @@ -5,15 +5,14 @@ import com.google.firebase.messaging.Message; import com.google.firebase.messaging.Notification; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import lombok.ToString; @Getter @ToString @NoArgsConstructor -@AllArgsConstructor public class NotificationRequest { private static final ApnsConfig APNS_CONFIG = @@ -28,6 +27,16 @@ public class NotificationRequest { private String body; private String id; + @Setter private String notificationId; + + public NotificationRequest( + final String deviceToken, final String title, final String body, final String id) { + this.deviceToken = deviceToken; + this.title = title; + this.body = body; + this.id = id; + } + public Message toMessage() { return Message.builder() .setApnsConfig(APNS_CONFIG) diff --git a/backend/src/main/java/com/twtw/backend/domain/notification/entity/Notification.java b/backend/src/main/java/com/twtw/backend/domain/notification/entity/Notification.java new file mode 100644 index 00000000..ff18219e --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/domain/notification/entity/Notification.java @@ -0,0 +1,65 @@ +package com.twtw.backend.domain.notification.entity; + +import com.github.f4b6a3.ulid.UlidCreator; +import com.twtw.backend.global.audit.AuditListener; +import com.twtw.backend.global.audit.Auditable; +import com.twtw.backend.global.audit.BaseTime; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.UUID; + +@Getter +@Entity +@EntityListeners(AuditListener.class) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Notification implements Auditable { + + @Id + @Column(columnDefinition = "BINARY(16)") + private final UUID id = UlidCreator.getMonotonicUlid().toUuid(); + + @Column(nullable = false) + private String title; + + @Column(nullable = false) + private String body; + + @Column(nullable = false) + private String idInfo; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private NotificationType type; + + private Boolean isCompleted = false; + + @Setter private BaseTime baseTime; + + @Builder + public Notification( + final String title, + final String body, + final String idInfo, + final NotificationType type) { + this.title = title; + this.body = body; + this.idInfo = idInfo; + this.type = type; + } + + public void complete() { + this.isCompleted = true; + } +} diff --git a/backend/src/main/java/com/twtw/backend/domain/notification/entity/NotificationType.java b/backend/src/main/java/com/twtw/backend/domain/notification/entity/NotificationType.java new file mode 100644 index 00000000..9abf87b0 --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/domain/notification/entity/NotificationType.java @@ -0,0 +1,5 @@ +package com.twtw.backend.domain.notification.entity; + +public enum NotificationType { + FRIEND_REQUEST, GROUP_REQUEST, DESTINATION_CHANGE, PLAN_REQUEST +} diff --git a/backend/src/main/java/com/twtw/backend/domain/notification/messagequeue/FcmConsumer.java b/backend/src/main/java/com/twtw/backend/domain/notification/messagequeue/FcmConsumer.java index 2978be59..fdbf7113 100644 --- a/backend/src/main/java/com/twtw/backend/domain/notification/messagequeue/FcmConsumer.java +++ b/backend/src/main/java/com/twtw/backend/domain/notification/messagequeue/FcmConsumer.java @@ -1,34 +1,36 @@ package com.twtw.backend.domain.notification.messagequeue; import com.google.firebase.messaging.FirebaseMessaging; -import com.rabbitmq.client.Channel; +import com.google.firebase.messaging.FirebaseMessagingException; import com.twtw.backend.domain.notification.dto.NotificationRequest; +import com.twtw.backend.domain.notification.entity.Notification; +import com.twtw.backend.domain.notification.repository.NotificationRepository; import org.springframework.amqp.rabbit.annotation.RabbitListener; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.messaging.handler.annotation.Header; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; -import java.io.IOException; +import java.util.UUID; @Component public class FcmConsumer { private final FirebaseMessaging firebaseMessaging; + private final NotificationRepository notificationRepository; - public FcmConsumer(FirebaseMessaging firebaseMessaging) { + public FcmConsumer( + FirebaseMessaging firebaseMessaging, NotificationRepository notificationRepository) { this.firebaseMessaging = firebaseMessaging; + this.notificationRepository = notificationRepository; } + @Transactional @RabbitListener(queues = "notification.queue") - public void sendNotification( - final NotificationRequest request, - final Channel channel, - @Header(AmqpHeaders.DELIVERY_TAG) final long tag) - throws IOException { - try { - firebaseMessaging.send(request.toMessage()); - } catch (final Exception e) { - channel.basicNack(tag, false, false); - } + public void sendNotification(final NotificationRequest request) + throws FirebaseMessagingException { + firebaseMessaging.send(request.toMessage()); + + notificationRepository + .findById(UUID.fromString(request.getNotificationId())) + .ifPresent(Notification::complete); } } diff --git a/backend/src/main/java/com/twtw/backend/domain/notification/messagequeue/FcmProducer.java b/backend/src/main/java/com/twtw/backend/domain/notification/messagequeue/FcmProducer.java index aa5b1d4b..e92bd943 100644 --- a/backend/src/main/java/com/twtw/backend/domain/notification/messagequeue/FcmProducer.java +++ b/backend/src/main/java/com/twtw/backend/domain/notification/messagequeue/FcmProducer.java @@ -1,6 +1,9 @@ package com.twtw.backend.domain.notification.messagequeue; import com.twtw.backend.domain.notification.dto.NotificationRequest; +import com.twtw.backend.domain.notification.entity.Notification; +import com.twtw.backend.domain.notification.entity.NotificationType; +import com.twtw.backend.domain.notification.repository.NotificationRepository; import com.twtw.backend.global.constant.RabbitMQConstant; import lombok.RequiredArgsConstructor; @@ -8,13 +11,28 @@ import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Component; +import java.util.UUID; + @Component @RequiredArgsConstructor public class FcmProducer { private final RabbitTemplate rabbitTemplate; + private final NotificationRepository notificationRepository; + + public void sendNotification(final NotificationRequest request, final NotificationType type) { + final UUID id = + notificationRepository + .save( + new Notification( + request.getTitle(), + request.getBody(), + request.getId(), + type)) + .getId(); + + request.setNotificationId(id.toString()); - public void sendNotification(final NotificationRequest request) { rabbitTemplate.convertAndSend( RabbitMQConstant.NOTIFICATION_EXCHANGE.getName(), RabbitMQConstant.NOTIFICATION_ROUTING_KEY.getName(), diff --git a/backend/src/main/java/com/twtw/backend/domain/notification/repository/JpaNotificationRepository.java b/backend/src/main/java/com/twtw/backend/domain/notification/repository/JpaNotificationRepository.java new file mode 100644 index 00000000..d8b530a8 --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/domain/notification/repository/JpaNotificationRepository.java @@ -0,0 +1,8 @@ +package com.twtw.backend.domain.notification.repository; + +import com.twtw.backend.domain.notification.entity.Notification; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface JpaNotificationRepository extends JpaRepository, NotificationRepository {} diff --git a/backend/src/main/java/com/twtw/backend/domain/notification/repository/NotificationRepository.java b/backend/src/main/java/com/twtw/backend/domain/notification/repository/NotificationRepository.java new file mode 100644 index 00000000..3923892b --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/domain/notification/repository/NotificationRepository.java @@ -0,0 +1,14 @@ +package com.twtw.backend.domain.notification.repository; + +import com.twtw.backend.domain.notification.entity.Notification; + +import org.springframework.stereotype.Repository; + +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface NotificationRepository { + Notification save(final Notification notification); + Optional findById(final UUID id); +} diff --git a/backend/src/main/java/com/twtw/backend/domain/plan/service/PlanService.java b/backend/src/main/java/com/twtw/backend/domain/plan/service/PlanService.java index ac46a6c5..3b2a3d15 100644 --- a/backend/src/main/java/com/twtw/backend/domain/plan/service/PlanService.java +++ b/backend/src/main/java/com/twtw/backend/domain/plan/service/PlanService.java @@ -8,6 +8,7 @@ import com.twtw.backend.domain.member.service.AuthService; import com.twtw.backend.domain.member.service.MemberService; import com.twtw.backend.domain.notification.dto.NotificationRequest; +import com.twtw.backend.domain.notification.entity.NotificationType; import com.twtw.backend.domain.notification.messagequeue.FcmProducer; import com.twtw.backend.domain.place.entity.Place; import com.twtw.backend.domain.place.service.PlaceService; @@ -113,7 +114,8 @@ private void sendRequestNotification( deviceToken, NotificationTitle.PLAN_REQUEST_TITLE.getName(), NotificationBody.PLAN_REQUEST_BODY.toNotificationBody(planName), - id.toString())); + id.toString()), + NotificationType.PLAN_REQUEST); } public void outPlan(PlanMemberRequest request) { @@ -223,7 +225,8 @@ private void sendDestinationNotification( NotificationTitle.DESTINATION_CHANGE_TITLE.getName(), NotificationBody.DESTINATION_CHANGE_BODY.toNotificationBody( destinationName), - id.toString())); + id.toString()), + NotificationType.DESTINATION_CHANGE); } @Transactional diff --git a/backend/src/test/java/com/twtw/backend/support/exclude/ExcludeTest.java b/backend/src/test/java/com/twtw/backend/support/exclude/ExcludeTest.java index 8ba33abc..58df18bc 100644 --- a/backend/src/test/java/com/twtw/backend/support/exclude/ExcludeTest.java +++ b/backend/src/test/java/com/twtw/backend/support/exclude/ExcludeTest.java @@ -1,15 +1,14 @@ package com.twtw.backend.support.exclude; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; - import com.twtw.backend.domain.notification.messagequeue.FcmProducer; import com.twtw.backend.support.testcontainer.ContainerTestConfig; - import org.junit.jupiter.api.BeforeEach; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ContextConfiguration; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; + @ContextConfiguration(initializers = ContainerTestConfig.class) public abstract class ExcludeTest { @@ -17,6 +16,6 @@ public abstract class ExcludeTest { @BeforeEach void setUp() { - doNothing().when(fcmProducer).sendNotification(any()); + doNothing().when(fcmProducer).sendNotification(any(), any()); } } diff --git a/backend/src/test/java/com/twtw/backend/support/service/LoginTest.java b/backend/src/test/java/com/twtw/backend/support/service/LoginTest.java index 76904299..d9bfa0c3 100644 --- a/backend/src/test/java/com/twtw/backend/support/service/LoginTest.java +++ b/backend/src/test/java/com/twtw/backend/support/service/LoginTest.java @@ -1,9 +1,5 @@ package com.twtw.backend.support.service; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.when; - import com.twtw.backend.domain.member.entity.Member; import com.twtw.backend.domain.member.repository.MemberRepository; import com.twtw.backend.domain.member.service.AuthService; @@ -11,12 +7,15 @@ import com.twtw.backend.fixture.member.MemberEntityFixture; import com.twtw.backend.support.stub.StubConfig; import com.twtw.backend.support.testcontainer.ContainerTestConfig; - import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ContextConfiguration; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; + @ServiceTest @ContextConfiguration( initializers = {ContainerTestConfig.class}, @@ -33,6 +32,6 @@ public void setup() { loginUser = memberRepository.save(MemberEntityFixture.LOGIN_MEMBER.toEntity()); when(authService.getMemberByJwt()).thenReturn(loginUser); when(authService.getMemberIdValue()).thenReturn(loginUser.getId().toString()); - doNothing().when(fcmProducer).sendNotification(any()); + doNothing().when(fcmProducer).sendNotification(any(), any()); } }