diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/data/request/CapsuleSkinNotificationSendRequest.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/data/request/CapsuleSkinNotificationSendRequest.java deleted file mode 100644 index cbad10cd3..000000000 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/data/request/CapsuleSkinNotificationSendRequest.java +++ /dev/null @@ -1,28 +0,0 @@ -package site.timecapsulearchive.notification.data.request; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import site.timecapsulearchive.notification.entity.NotificationStatus; - -public record CapsuleSkinNotificationSendRequest( - - @NotNull(message = "멤버 아이디는 필수 입니다.") - Long memberId, - - @NotNull(message = "알림 상태는 필수 입니다.") - NotificationStatus status, - - @NotBlank(message = "스킨 이름은 필수 입니다.") - String skinName, - - @NotBlank(message = "알림 내용은 필수 입니다.") - String title, - - @NotBlank(message = "알림 내용은 필수 입니다.") - String text, - - @NotBlank(message = "스킨 URL은 필수 입니다.") - String skinUrl -) { - -} diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/data/request/FriendNotificationRequest.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/data/request/FriendNotificationRequest.java deleted file mode 100644 index 44956bcd7..000000000 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/data/request/FriendNotificationRequest.java +++ /dev/null @@ -1,22 +0,0 @@ -package site.timecapsulearchive.notification.data.request; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import site.timecapsulearchive.notification.entity.NotificationStatus; - -public record FriendNotificationRequest( - - @NotNull(message = "멤버 아이디는 필수 입니다.") - Long targetId, - - @NotNull(message = "알림 상태는 필수 입니다.") - NotificationStatus status, - - @NotBlank(message = "알림 제목은 필수 입니다.") - String text, - - @NotBlank(message = "알림 내용은 필수 입니다.") - String title -) { - -} diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/RabbitmqComponentConstants.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/rabbitmq/RabbitmqComponentConstants.java similarity index 57% rename from backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/RabbitmqComponentConstants.java rename to backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/rabbitmq/RabbitmqComponentConstants.java index 87f632b4f..e55e89c9d 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/RabbitmqComponentConstants.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/rabbitmq/RabbitmqComponentConstants.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.notification.global.config; +package site.timecapsulearchive.notification.global.config.rabbitmq; import java.util.Arrays; import lombok.Getter; @@ -6,24 +6,25 @@ @Getter public enum RabbitmqComponentConstants { - CAPSULE_SKIN_QUEUE("notification.createCapsuleSkin.queue", + CAPSULE_SKIN_NOTIFICATION_QUEUE("notification.createCapsuleSkin.queue", "fail.notification.createCapsuleSkin.queue"), - CAPSULE_SKIN_EXCHANGE("notification.createCapsuleSkin.exchange", + CAPSULE_SKIN_NOTIFICATION_EXCHANGE("notification.createCapsuleSkin.exchange", "fail.notification.createCapsuleSkin.exchange"), FRIEND_REQUEST_NOTIFICATION_QUEUE("notification.friendRequest.queue", "fail.notification.friendRequest.queue"), FRIEND_REQUEST_NOTIFICATION_EXCHANGE("notification.friendRequest.exchange", "fail.notification.friendRequest.exchange"), - FRIEND_REQUESTS_NOTIFICATION_QUEUE("notification.friendRequests.queue", - "fail.notification.friendRequests.queue"), - FRIEND_REQUESTS_NOTIFICATION_EXCHANGE("notification.friendRequests.exchange", - "fail.notification.friendRequests.exchange"), + BATCH_FRIEND_REQUESTS_NOTIFICATION_QUEUE("batch.notification.friendRequests.queue", + "fail.batch.notification.friendRequests.queue"), + BATCH_FRIEND_REQUESTS_NOTIFICATION_EXCHANGE("batch.notification.friendRequests.exchange", + "fail.batch.notification.friendRequests.exchange"), FRIEND_ACCEPT_NOTIFICATION_QUEUE("notification.friendAccept.queue", "fail.notification.friendAccept.queue"), FRIEND_ACCEPT_NOTIFICATION_EXCHANGE("notification.friendAccept.exchange", "fail.notification.friendAccept.exchange"), - GROUP_INVITE_QUEUE("notification.groupInvite.queue", "fail.notification.groupInvite.queue"), - GROUP_INVITE_EXCHANGE("notification.groupInvite.exchange", + GROUP_INVITE_NOTIFICATION_QUEUE("notification.groupInvite.queue", + "fail.notification.groupInvite.queue"), + GROUP_INVITE_NOTIFICATION_EXCHANGE("notification.groupInvite.exchange", "fail.notification.groupInvite.exchange"), GROUP_ACCEPT_NOTIFICATION_QUEUE("notification.groupAccept.queue", "fail.notification.groupAccept.queue"), @@ -37,12 +38,4 @@ public enum RabbitmqComponentConstants { this.successComponent = successComponent; this.failComponent = failComponent; } - - public static String getFailComponent(String successComponent) { - return Arrays.stream(RabbitmqComponentConstants.values()) - .filter(constants -> constants.getSuccessComponent().equals(successComponent)) - .map(RabbitmqComponentConstants::getFailComponent) - .toList() - .get(0); - } } \ No newline at end of file diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/RabbitmqConfig.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/rabbitmq/RabbitmqConfig.java similarity index 57% rename from backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/RabbitmqConfig.java rename to backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/rabbitmq/RabbitmqConfig.java index 41a5d2ca0..36bace3b1 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/RabbitmqConfig.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/rabbitmq/RabbitmqConfig.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.notification.global.config; +package site.timecapsulearchive.notification.global.config.rabbitmq; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; @@ -7,7 +7,9 @@ import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.QueueBuilder; import org.springframework.amqp.rabbit.annotation.EnableRabbit; +import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; @@ -24,13 +26,17 @@ public class RabbitmqConfig { @Bean public Queue capsuleSkinQueue() { - return new Queue(RabbitmqComponentConstants.CAPSULE_SKIN_QUEUE.getSuccessComponent(), true); + return QueueBuilder.durable( + RabbitmqComponentConstants.CAPSULE_SKIN_NOTIFICATION_QUEUE.getSuccessComponent()) + .withArgument("x-dead-letter-exchange", + RabbitmqComponentConstants.CAPSULE_SKIN_NOTIFICATION_EXCHANGE.getFailComponent()) + .build(); } @Bean public DirectExchange capsuleSkinExchange() { return new DirectExchange( - RabbitmqComponentConstants.CAPSULE_SKIN_EXCHANGE.getSuccessComponent()); + RabbitmqComponentConstants.CAPSULE_SKIN_NOTIFICATION_EXCHANGE.getSuccessComponent()); } @Bean @@ -43,13 +49,17 @@ public Binding capsuleSkinBinding() { @Bean public Queue groupInviteQueue() { - return new Queue(RabbitmqComponentConstants.GROUP_INVITE_QUEUE.getSuccessComponent(), true); + return QueueBuilder.durable( + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_QUEUE.getSuccessComponent()) + .withArgument("x-dead-letter-exchange", + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_EXCHANGE.getFailComponent()) + .build(); } @Bean public DirectExchange groupInviteExchange() { return new DirectExchange( - RabbitmqComponentConstants.GROUP_INVITE_EXCHANGE.getSuccessComponent()); + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_EXCHANGE.getSuccessComponent()); } @Bean @@ -62,8 +72,11 @@ public Binding groupInviteBinding() { @Bean public Queue groupAcceptQueue() { - return new Queue( - RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_QUEUE.getSuccessComponent(), true); + return QueueBuilder.durable( + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_QUEUE.getSuccessComponent()) + .withArgument("x-dead-letter-exchange", + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_EXCHANGE.getFailComponent()) + .build(); } @Bean @@ -82,9 +95,11 @@ public Binding groupAcceptBinding() { @Bean public Queue friendRequestQueue() { - return new Queue( - RabbitmqComponentConstants.FRIEND_REQUEST_NOTIFICATION_QUEUE.getSuccessComponent(), - true); + return QueueBuilder.durable( + RabbitmqComponentConstants.FRIEND_REQUEST_NOTIFICATION_QUEUE.getSuccessComponent()) + .withArgument("x-dead-letter-exchange", + RabbitmqComponentConstants.FRIEND_REQUEST_NOTIFICATION_EXCHANGE.getFailComponent()) + .build(); } @Bean @@ -103,9 +118,11 @@ public Binding friendRequestBinding() { @Bean public Queue friendAcceptQueue() { - return new Queue( - RabbitmqComponentConstants.FRIEND_ACCEPT_NOTIFICATION_QUEUE.getSuccessComponent(), - true); + return QueueBuilder.durable( + RabbitmqComponentConstants.FRIEND_ACCEPT_NOTIFICATION_QUEUE.getSuccessComponent()) + .withArgument("x-dead-letter-exchange", + RabbitmqComponentConstants.FRIEND_ACCEPT_NOTIFICATION_EXCHANGE.getFailComponent()) + .build(); } @Bean @@ -123,11 +140,26 @@ public Binding friendAcceptBinding() { } @Bean - public RabbitTemplate rabbitTemplate() { - RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory()); - rabbitTemplate.setMessageConverter(jsonMessageConverter()); + public Queue batchFriendRequestsQueue() { + return QueueBuilder.durable( + RabbitmqComponentConstants.BATCH_FRIEND_REQUESTS_NOTIFICATION_QUEUE.getSuccessComponent()) + .withArgument("x-dead-letter-exchange", + RabbitmqComponentConstants.BATCH_FRIEND_REQUESTS_NOTIFICATION_EXCHANGE.getFailComponent()) + .build(); + } - return rabbitTemplate; + @Bean + public DirectExchange batchFriendRequestsExchange() { + return new DirectExchange( + RabbitmqComponentConstants.BATCH_FRIEND_REQUESTS_NOTIFICATION_EXCHANGE.getSuccessComponent()); + } + + @Bean + public Binding batchFriendRequestsBinding() { + return BindingBuilder + .bind(batchFriendRequestsQueue()) + .to(batchFriendRequestsExchange()) + .withQueueName(); } @Bean @@ -135,6 +167,14 @@ public Jackson2JsonMessageConverter jsonMessageConverter() { return new Jackson2JsonMessageConverter(); } + @Bean + public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + factory.setConnectionFactory(connectionFactory()); + factory.setMessageConverter(jsonMessageConverter()); + return factory; + } + @Bean public CachingConnectionFactory connectionFactory() { CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); @@ -155,4 +195,11 @@ public CachingConnectionFactory connectionFactory() { return connectionFactory; } + + @Bean + public RabbitTemplate rabbitTemplate() { + RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory()); + rabbitTemplate.setMessageConverter(jsonMessageConverter()); + return rabbitTemplate; + } } \ No newline at end of file diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/RabbitmqFailComponentConfig.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/rabbitmq/RabbitmqFailComponentConfig.java similarity index 54% rename from backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/RabbitmqFailComponentConfig.java rename to backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/rabbitmq/RabbitmqFailComponentConfig.java index 22c70f011..cf0b8dee2 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/RabbitmqFailComponentConfig.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/rabbitmq/RabbitmqFailComponentConfig.java @@ -1,8 +1,8 @@ -package site.timecapsulearchive.notification.global.config; +package site.timecapsulearchive.notification.global.config.rabbitmq; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; -import org.springframework.amqp.core.DirectExchange; +import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -12,40 +12,40 @@ public class RabbitmqFailComponentConfig { @Bean public Queue capsuleSkinFailQueue() { - return new Queue(RabbitmqComponentConstants.CAPSULE_SKIN_QUEUE.getFailComponent(), true); + return new Queue( + RabbitmqComponentConstants.CAPSULE_SKIN_NOTIFICATION_QUEUE.getFailComponent(), true); } @Bean - public DirectExchange capsuleSkinFailExchange() { - return new DirectExchange( - RabbitmqComponentConstants.CAPSULE_SKIN_EXCHANGE.getFailComponent()); + public FanoutExchange capsuleSkinFailExchange() { + return new FanoutExchange( + RabbitmqComponentConstants.CAPSULE_SKIN_NOTIFICATION_EXCHANGE.getFailComponent()); } @Bean public Binding capsuleSkinFailBinding() { return BindingBuilder .bind(capsuleSkinFailQueue()) - .to(capsuleSkinFailExchange()) - .withQueueName(); + .to(capsuleSkinFailExchange()); } @Bean public Queue groupInviteFailQueue() { - return new Queue(RabbitmqComponentConstants.GROUP_INVITE_QUEUE.getFailComponent(), true); + return new Queue( + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_QUEUE.getFailComponent(), true); } @Bean - public DirectExchange groupInviteFailExchange() { - return new DirectExchange( - RabbitmqComponentConstants.GROUP_INVITE_EXCHANGE.getFailComponent()); + public FanoutExchange groupInviteFailExchange() { + return new FanoutExchange( + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_EXCHANGE.getFailComponent()); } @Bean public Binding groupInviteFailBinding() { return BindingBuilder .bind(groupInviteFailQueue()) - .to(groupInviteFailExchange()) - .withQueueName(); + .to(groupInviteFailExchange()); } @@ -56,8 +56,8 @@ public Queue groupAcceptFailQueue() { } @Bean - public DirectExchange groupAcceptFailExchange() { - return new DirectExchange( + public FanoutExchange groupAcceptFailExchange() { + return new FanoutExchange( RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_EXCHANGE.getFailComponent()); } @@ -65,8 +65,7 @@ public DirectExchange groupAcceptFailExchange() { public Binding groupAcceptFailBinding() { return BindingBuilder .bind(groupAcceptFailQueue()) - .to(groupAcceptFailExchange()) - .withQueueName(); + .to(groupAcceptFailExchange()); } @@ -78,8 +77,8 @@ public Queue friendRequestFailQueue() { } @Bean - public DirectExchange friendRequestFailExchange() { - return new DirectExchange( + public FanoutExchange friendRequestFailExchange() { + return new FanoutExchange( RabbitmqComponentConstants.FRIEND_REQUEST_NOTIFICATION_EXCHANGE.getFailComponent()); } @@ -87,8 +86,7 @@ public DirectExchange friendRequestFailExchange() { public Binding friendRequestFailBinding() { return BindingBuilder .bind(friendRequestFailQueue()) - .to(friendRequestFailExchange()) - .withQueueName(); + .to(friendRequestFailExchange()); } @Bean @@ -99,8 +97,8 @@ public Queue friendAcceptFailQueue() { } @Bean - public DirectExchange friendAcceptFailExchange() { - return new DirectExchange( + public FanoutExchange friendAcceptFailExchange() { + return new FanoutExchange( RabbitmqComponentConstants.FRIEND_ACCEPT_NOTIFICATION_EXCHANGE.getFailComponent()); } @@ -108,7 +106,26 @@ public DirectExchange friendAcceptFailExchange() { public Binding friendAcceptFailBinding() { return BindingBuilder .bind(friendAcceptFailQueue()) - .to(friendAcceptFailExchange()) - .withQueueName(); + .to(friendAcceptFailExchange()); + } + + @Bean + public Queue batchFriendRequestsFailQueue() { + return new Queue( + RabbitmqComponentConstants.BATCH_FRIEND_REQUESTS_NOTIFICATION_QUEUE.getFailComponent(), + true); + } + + @Bean + public FanoutExchange batchFriendRequestsFailExchange() { + return new FanoutExchange( + RabbitmqComponentConstants.BATCH_FRIEND_REQUESTS_NOTIFICATION_EXCHANGE.getFailComponent()); + } + + @Bean + public Binding batchFriendRequestsFailBinding() { + return BindingBuilder + .bind(batchFriendRequestsFailQueue()) + .to(batchFriendRequestsFailExchange()); } } \ No newline at end of file diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/RabbitmqProperties.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/rabbitmq/RabbitmqProperties.java similarity index 88% rename from backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/RabbitmqProperties.java rename to backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/rabbitmq/RabbitmqProperties.java index fc52d5282..d5040fe1a 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/RabbitmqProperties.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/config/rabbitmq/RabbitmqProperties.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.notification.global.config; +package site.timecapsulearchive.notification.global.config.rabbitmq; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/error/ErrorResponse.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/error/ErrorResponse.java deleted file mode 100644 index a38fa9886..000000000 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/error/ErrorResponse.java +++ /dev/null @@ -1,20 +0,0 @@ -package site.timecapsulearchive.notification.global.error; - -public record ErrorResponse(String message) { - - public static ErrorResponse httpMessageNotReadable() { - return new ErrorResponse("요청 메시지를 읽을 수 없습니다."); - } - - public static ErrorResponse messageNotSendable() { - return new ErrorResponse("메시지를 보낼 수 없습니다."); - } - - public static ErrorResponse methodArgumentNotValid() { - return new ErrorResponse("타당하지 않은 요청 인자 입니다."); - } - - public static ErrorResponse exception() { - return new ErrorResponse("서버에 에러가 발생하였습니다."); - } -} diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/error/GlobalExceptionHandler.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/error/GlobalExceptionHandler.java deleted file mode 100644 index 619b1cd0a..000000000 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/error/GlobalExceptionHandler.java +++ /dev/null @@ -1,66 +0,0 @@ -package site.timecapsulearchive.notification.global.error; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import site.timecapsulearchive.notification.infra.exception.MessageNotSendAbleException; - -@RestControllerAdvice -@Slf4j -public class GlobalExceptionHandler { - - @ExceptionHandler(Exception.class) - protected ResponseEntity handleGlobalException(final Exception e) { - log.error(e.getMessage(), e); - final ErrorResponse errorResponse = ErrorResponse.exception(); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(errorResponse); - } - - @ExceptionHandler(MessageNotSendAbleException.class) - protected ResponseEntity handleMessageNotSendableException( - final MessageNotSendAbleException e) { - log.warn(e.getMessage(), e); - - final ErrorResponse errorResponse = ErrorResponse.messageNotSendable(); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(errorResponse); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - protected ResponseEntity handleRequestArgumentNotValidException( - MethodArgumentNotValidException e - ) { - log.warn(e.getMessage(), e); - - final ErrorResponse errorResponse = ErrorResponse.methodArgumentNotValid(); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(errorResponse); - } - - @ExceptionHandler(HttpMessageNotReadableException.class) - protected ResponseEntity handleRequestTypeNotValidException( - HttpMessageNotReadableException e - ) { - log.warn(e.getMessage(), e); - - final ErrorResponse response = ErrorResponse.httpMessageNotReadable(); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(response); - } - - @ExceptionHandler(InternalServerException.class) - protected ResponseEntity handleInternalServerException( - InternalServerException e - ) { - log.error(e.getMessage(), e); - - final ErrorResponse errorResponse = ErrorResponse.exception(); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(errorResponse); - } -} diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/error/RabbitMQErrorHandler.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/error/RabbitMQErrorHandler.java new file mode 100644 index 000000000..b902789a7 --- /dev/null +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/error/RabbitMQErrorHandler.java @@ -0,0 +1,37 @@ +package site.timecapsulearchive.notification.global.error; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.listener.api.RabbitListenerErrorHandler; +import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class RabbitMQErrorHandler implements RabbitListenerErrorHandler { + + private final RabbitTemplate rabbitTemplate; + + @Override + public Object handleError(Message amqpMessage, org.springframework.messaging.Message message, + ListenerExecutionFailedException exception) { + log.error("message handle error", exception); + + MessageProperties properties = amqpMessage.getMessageProperties(); + + String routingKey = "fail." + properties.getReceivedRoutingKey(); + String exchange = "fail." + properties.getReceivedExchange(); + + properties.setHeader("x-exception-message", exception.getCause().getMessage()); + properties.setHeader("x-original-exchange", properties.getReceivedExchange()); + properties.setHeader("x-original-routing-key", properties.getReceivedRoutingKey()); + + rabbitTemplate.send(exchange, routingKey, amqpMessage); + log.info("[{} to {}] {}", properties.getConsumerQueue(), exchange, amqpMessage); + return null; + } +} diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/log/LoggingComponent.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/log/LoggingComponent.java new file mode 100644 index 000000000..ff8fa1405 --- /dev/null +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/log/LoggingComponent.java @@ -0,0 +1,26 @@ +package site.timecapsulearchive.notification.global.log; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; + +@Slf4j +@Aspect +@Component +public class LoggingComponent { + + @Before("@annotation(site.timecapsulearchive.notification.global.log.Trace)") + public void doTraceBefore(JoinPoint joinPoint) { + Object[] args = joinPoint.getArgs(); + + log.info("[before] {} args={}", joinPoint.getSignature(), args); + } + + @After("@annotation(site.timecapsulearchive.notification.global.log.Trace)") + public void doTraceAfter(JoinPoint joinPoint) { + log.info("[after] {}", joinPoint.getSignature()); + } +} diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/global/log/Trace.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/log/Trace.java new file mode 100644 index 000000000..a519520a2 --- /dev/null +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/global/log/Trace.java @@ -0,0 +1,11 @@ +package site.timecapsulearchive.notification.global.log; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Trace { +} \ No newline at end of file diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/FCMManager.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/FCMManager.java index e409db1ae..ec617448d 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/FCMManager.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/FCMManager.java @@ -7,9 +7,11 @@ import java.io.IOException; import java.io.InputStream; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; +@Slf4j @Component @RequiredArgsConstructor public class FCMManager { @@ -18,12 +20,14 @@ public class FCMManager { @PostConstruct public void initialize() throws IOException { + log.info("fcm manager 초기화 시작"); FirebaseOptions options = FirebaseOptions.builder() .setCredentials(GoogleCredentials.fromStream(getCredential())) .setProjectId(fcmProperties.projectId()) .build(); FirebaseApp.initializeApp(options); + log.info("fcm manager 초기화 완료"); } private InputStream getCredential() throws IOException { diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/FcmMessageData.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/FCMMessageData.java similarity index 82% rename from backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/FcmMessageData.java rename to backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/FCMMessageData.java index c979c8aa0..adcb64c60 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/FcmMessageData.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/FCMMessageData.java @@ -1,6 +1,6 @@ package site.timecapsulearchive.notification.infra.fcm; -public enum FcmMessageData { +public enum FCMMessageData { TOPIC("topic"), STATUS("status"), @@ -11,7 +11,7 @@ public enum FcmMessageData { private final String data; - FcmMessageData(String data) { + FCMMessageData(String data) { this.data = data; } diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/capsuleskin/CapsuleSkinFcmManagerImpl.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/capsuleskin/CapsuleSkinFcmManagerImpl.java index fa4cdbe61..5710d9756 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/capsuleskin/CapsuleSkinFcmManagerImpl.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/capsuleskin/CapsuleSkinFcmManagerImpl.java @@ -7,18 +7,18 @@ import org.springframework.stereotype.Component; import site.timecapsulearchive.notification.data.dto.CapsuleSkinNotificationSendDto; import site.timecapsulearchive.notification.entity.CategoryName; +import site.timecapsulearchive.notification.global.log.Trace; import site.timecapsulearchive.notification.infra.exception.MessageNotSendAbleException; -import site.timecapsulearchive.notification.infra.fcm.FCMManager; -import site.timecapsulearchive.notification.infra.fcm.FcmMessageData; +import site.timecapsulearchive.notification.infra.fcm.FCMMessageData; import site.timecapsulearchive.notification.infra.s3.S3PreSignedUrlManager; @Component @RequiredArgsConstructor public class CapsuleSkinFcmManagerImpl implements CapsuleSkinFcmManager { - private final FCMManager fcmManager; private final S3PreSignedUrlManager s3PreSignedUrlManager; + @Trace public void sendCapsuleSkinNotification( final CapsuleSkinNotificationSendDto dto, final CategoryName categoryName, @@ -28,12 +28,12 @@ public void sendCapsuleSkinNotification( FirebaseMessaging.getInstance() .send( Message.builder() - .putData(FcmMessageData.TOPIC.getData(), String.valueOf(categoryName)) - .putData(FcmMessageData.STATUS.getData(), String.valueOf(dto.status())) - .putData(FcmMessageData.TITLE.getData(), dto.title()) - .putData(FcmMessageData.TEXT.getData(), dto.text()) + .putData(FCMMessageData.TOPIC.getData(), String.valueOf(categoryName)) + .putData(FCMMessageData.STATUS.getData(), String.valueOf(dto.status())) + .putData(FCMMessageData.TITLE.getData(), dto.title()) + .putData(FCMMessageData.TEXT.getData(), dto.text()) .setToken(fcmToken) - .putData(FcmMessageData.IMAGE.getData(), + .putData(FCMMessageData.IMAGE.getData(), s3PreSignedUrlManager.createS3PreSignedUrlForGet(dto.skinUrl())) .build() ); diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/friend/FriendFcmManagerImpl.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/friend/FriendFcmManagerImpl.java index ff88e1599..cbcf512e1 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/friend/FriendFcmManagerImpl.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/friend/FriendFcmManagerImpl.java @@ -10,16 +10,15 @@ import site.timecapsulearchive.notification.data.dto.FriendNotificationDto; import site.timecapsulearchive.notification.data.dto.FriendNotificationsDto; import site.timecapsulearchive.notification.entity.CategoryName; +import site.timecapsulearchive.notification.global.log.Trace; import site.timecapsulearchive.notification.infra.exception.MessageNotSendAbleException; -import site.timecapsulearchive.notification.infra.fcm.FCMManager; -import site.timecapsulearchive.notification.infra.fcm.FcmMessageData; +import site.timecapsulearchive.notification.infra.fcm.FCMMessageData; @Component @RequiredArgsConstructor public class FriendFcmManagerImpl implements FriendFcmManager { - private final FCMManager fcmManager; - + @Trace public void sendFriendNotification( final FriendNotificationDto dto, final CategoryName categoryName, @@ -29,11 +28,11 @@ public void sendFriendNotification( FirebaseMessaging.getInstance() .send( Message.builder() - .putData(FcmMessageData.TOPIC.getData(), String.valueOf(categoryName)) - .putData(FcmMessageData.STATUS.getData(), + .putData(FCMMessageData.TOPIC.getData(), String.valueOf(categoryName)) + .putData(FCMMessageData.STATUS.getData(), String.valueOf(dto.notificationStatus())) - .putData(FcmMessageData.TITLE.getData(), dto.title()) - .putData(FcmMessageData.TEXT.getData(), dto.text()) + .putData(FCMMessageData.TITLE.getData(), dto.title()) + .putData(FCMMessageData.TEXT.getData(), dto.text()) .setToken(fcmToken) .build() ); @@ -42,6 +41,7 @@ public void sendFriendNotification( } } + @Trace public void sendFriendNotifications( final FriendNotificationsDto dto, final CategoryName categoryName, @@ -52,11 +52,11 @@ public void sendFriendNotifications( .sendEachForMulticast( MulticastMessage.builder() .addAllTokens(fcmTokens) - .putData(FcmMessageData.TOPIC.getData(), String.valueOf(categoryName)) - .putData(FcmMessageData.STATUS.getData(), + .putData(FCMMessageData.TOPIC.getData(), String.valueOf(categoryName)) + .putData(FCMMessageData.STATUS.getData(), String.valueOf(dto.notificationStatus())) - .putData(FcmMessageData.TITLE.getData(), dto.title()) - .putData(FcmMessageData.TEXT.getData(), dto.text()) + .putData(FCMMessageData.TITLE.getData(), dto.title()) + .putData(FCMMessageData.TEXT.getData(), dto.text()) .build() ); } catch (FirebaseMessagingException exception) { diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/group/GroupFcmManagerImpl.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/group/GroupFcmManagerImpl.java index 86123f416..015359ef5 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/group/GroupFcmManagerImpl.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/infra/fcm/group/GroupFcmManagerImpl.java @@ -10,18 +10,18 @@ import site.timecapsulearchive.notification.data.dto.GroupAcceptNotificationDto; import site.timecapsulearchive.notification.data.dto.GroupInviteNotificationDto; import site.timecapsulearchive.notification.entity.CategoryName; +import site.timecapsulearchive.notification.global.log.Trace; import site.timecapsulearchive.notification.infra.exception.MessageNotSendAbleException; -import site.timecapsulearchive.notification.infra.fcm.FCMManager; -import site.timecapsulearchive.notification.infra.fcm.FcmMessageData; +import site.timecapsulearchive.notification.infra.fcm.FCMMessageData; import site.timecapsulearchive.notification.infra.s3.S3PreSignedUrlManager; @Component @RequiredArgsConstructor public class GroupFcmManagerImpl implements GroupFcmManager { - private final FCMManager fcmManager; private final S3PreSignedUrlManager s3PreSignedUrlManager; + @Trace public void sendGroupInviteNotifications( final GroupInviteNotificationDto dto, final CategoryName categoryName, @@ -32,12 +32,12 @@ public void sendGroupInviteNotifications( .sendEachForMulticast( MulticastMessage.builder() .addAllTokens(fcmTokens) - .putData(FcmMessageData.TOPIC.getData(), String.valueOf(categoryName)) - .putData(FcmMessageData.STATUS.getData(), + .putData(FCMMessageData.TOPIC.getData(), String.valueOf(categoryName)) + .putData(FCMMessageData.STATUS.getData(), String.valueOf(dto.notificationStatus())) - .putData(FcmMessageData.TITLE.getData(), dto.title()) - .putData(FcmMessageData.TEXT.getData(), dto.text()) - .putData(FcmMessageData.IMAGE.getData(), + .putData(FCMMessageData.TITLE.getData(), dto.title()) + .putData(FCMMessageData.TEXT.getData(), dto.text()) + .putData(FCMMessageData.IMAGE.getData(), s3PreSignedUrlManager.createS3PreSignedUrlForGet(dto.groupProfileUrl())) .build() ); @@ -46,6 +46,7 @@ public void sendGroupInviteNotifications( } } + @Trace public void sendGroupAcceptNotification( final GroupAcceptNotificationDto dto, final CategoryName categoryName, @@ -55,11 +56,11 @@ public void sendGroupAcceptNotification( FirebaseMessaging.getInstance() .send( Message.builder() - .putData(FcmMessageData.TOPIC.getData(), String.valueOf(categoryName)) - .putData(FcmMessageData.STATUS.getData(), + .putData(FCMMessageData.TOPIC.getData(), String.valueOf(categoryName)) + .putData(FCMMessageData.STATUS.getData(), String.valueOf(dto.notificationStatus())) - .putData(FcmMessageData.TITLE.getData(), dto.title()) - .putData(FcmMessageData.TEXT.getData(), dto.text()) + .putData(FCMMessageData.TITLE.getData(), dto.title()) + .putData(FCMMessageData.TEXT.getData(), dto.text()) .setToken(fcmToken) .build() ); diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/repository/member/MemberRepository.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/repository/member/MemberRepository.java index 5049da2dc..a634f6f61 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/repository/member/MemberRepository.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/repository/member/MemberRepository.java @@ -2,38 +2,52 @@ import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.stereotype.Repository; +import site.timecapsulearchive.notification.global.log.Trace; +import site.timecapsulearchive.notification.service.dto.MemberNotificationInfoDto; @Repository @RequiredArgsConstructor public class MemberRepository { - private final JdbcTemplate jdbcTemplate; private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; - public String findFCMToken(Long memberId) { - String sql = "SELECT m.fcm_token FROM member m WHERE m.member_id = ? and m.deleted_at is null"; + @Trace + public MemberNotificationInfoDto findFCMToken(Long memberId) { + final String sql = """ + SELECT m.fcm_token, m.notification_enabled + FROM member m + WHERE m.member_id = :memberId and m.deleted_at is null + """; - return jdbcTemplate.queryForObject( + final SqlParameterSource parameters = new MapSqlParameterSource("memberId", memberId); + + return namedParameterJdbcTemplate.queryForObject( sql, - (rs, rowNum) -> rs.getString("fcm_token"), - memberId + parameters, + (rs, rowNum) -> new MemberNotificationInfoDto(rs.getString("fcm_token"), + rs.getBoolean("notification_enabled")) ); } - public List findFCMTokens(List memberIds) { - final String sql = "SELECT m.fcm_token FROM member m WHERE m.member_id IN (:memberIds) and m.deleted_at is null"; + @Trace + public List findFCMTokens(List memberIds) { + final String sql = """ + SELECT m.fcm_token, m.notification_enabled + FROM member m + WHERE m.member_id IN (:memberIds) and m.deleted_at is null + """; final SqlParameterSource parameters = new MapSqlParameterSource("memberIds", memberIds); return namedParameterJdbcTemplate.query( sql, parameters, - (rs, rowNum) -> rs.getString("fcm_token") + (rs, rowNum) -> new MemberNotificationInfoDto(rs.getString("fcm_token"), + rs.getBoolean("notification_enabled")) ); } } diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/repository/notification/NotificationQueryRepositoryImpl.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/repository/notification/NotificationQueryRepositoryImpl.java index 30556d072..9af18efec 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/repository/notification/NotificationQueryRepositoryImpl.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/repository/notification/NotificationQueryRepositoryImpl.java @@ -12,6 +12,7 @@ import org.springframework.stereotype.Repository; import site.timecapsulearchive.notification.entity.Notification; import site.timecapsulearchive.notification.entity.NotificationStatus; +import site.timecapsulearchive.notification.global.log.Trace; @Repository @RequiredArgsConstructor @@ -19,6 +20,7 @@ public class NotificationQueryRepositoryImpl implements NotificationQueryReposit private final JdbcTemplate jdbcTemplate; + @Trace public void bulkSave(List notifications) { jdbcTemplate.batchUpdate( """ diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/capsuleskin/CapsuleSkinAlarmListener.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/capsuleskin/CapsuleSkinAlarmListener.java index 03f68f5ec..e63d192c8 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/capsuleskin/CapsuleSkinAlarmListener.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/capsuleskin/CapsuleSkinAlarmListener.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.notification.service.capsuleskin; +import org.springframework.amqp.rabbit.annotation.Argument; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; @@ -10,12 +11,17 @@ public interface CapsuleSkinAlarmListener { @RabbitListener( bindings = @QueueBinding( - value = @Queue(value = "notification.createCapsuleSkin.queue", durable = "true"), + value = @Queue( + value = "notification.createCapsuleSkin.queue", + durable = "true", + arguments = @Argument(name = "x-dead-letter-exchange", value = "fail.notification.createCapsuleSkin.exchange") + ), exchange = @Exchange(value = "notification.createCapsuleSkin.exchange"), key = "notification.createCapsuleSkin.queue" ), returnExceptions = "false", - messageConverter = "jsonMessageConverter" + messageConverter = "jsonMessageConverter", + errorHandler = "rabbitMQErrorHandler" ) void sendCapsuleSkinAlarm(final CapsuleSkinNotificationSendDto dto); diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/capsuleskin/CapsuleSkinAlarmService.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/capsuleskin/CapsuleSkinAlarmService.java index f3b75431c..ed80cedab 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/capsuleskin/CapsuleSkinAlarmService.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/capsuleskin/CapsuleSkinAlarmService.java @@ -9,23 +9,26 @@ import site.timecapsulearchive.notification.entity.CategoryName; import site.timecapsulearchive.notification.entity.Notification; import site.timecapsulearchive.notification.entity.NotificationCategory; +import site.timecapsulearchive.notification.global.log.Trace; import site.timecapsulearchive.notification.infra.fcm.capsuleskin.CapsuleSkinFcmManager; import site.timecapsulearchive.notification.repository.member.MemberRepository; import site.timecapsulearchive.notification.repository.notification.NotificationCategoryRepository; import site.timecapsulearchive.notification.repository.notification.NotificationRepository; +import site.timecapsulearchive.notification.service.dto.MemberNotificationInfoDto; +import site.timecapsulearchive.notification.service.validator.NotificationSendValidator; @Service @RequiredArgsConstructor public class CapsuleSkinAlarmService implements CapsuleSkinAlarmListener { - private final CapsuleSkinFcmManager capsuleSkinFcmManager; - + private final NotificationSendValidator notificationSendValidator; private final NotificationRepository notificationRepository; private final NotificationCategoryRepository notificationCategoryRepository; private final MemberRepository memberRepository; private final TransactionTemplate transactionTemplate; + @Trace public void sendCapsuleSkinAlarm(final CapsuleSkinNotificationSendDto dto) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override @@ -39,8 +42,11 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } }); - final String fcmToken = memberRepository.findFCMToken(dto.memberId()); - capsuleSkinFcmManager.sendCapsuleSkinNotification(dto, CategoryName.CAPSULE_SKIN, fcmToken); + final MemberNotificationInfoDto notificationInfoDto = memberRepository.findFCMToken( + dto.memberId()); + if (notificationSendValidator.canSend(notificationInfoDto)) { + capsuleSkinFcmManager.sendCapsuleSkinNotification(dto, CategoryName.CAPSULE_SKIN, + notificationInfoDto.fcmToken()); + } } - } diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/dto/MemberNotificationInfoDto.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/dto/MemberNotificationInfoDto.java new file mode 100644 index 000000000..685a67a03 --- /dev/null +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/dto/MemberNotificationInfoDto.java @@ -0,0 +1,8 @@ +package site.timecapsulearchive.notification.service.dto; + +public record MemberNotificationInfoDto( + String fcmToken, + Boolean notificationEnabled +) { + +} \ No newline at end of file diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/friend/FriendAlarmListener.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/friend/FriendAlarmListener.java index d50038282..54bebf3b6 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/friend/FriendAlarmListener.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/friend/FriendAlarmListener.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.notification.service.friend; +import org.springframework.amqp.rabbit.annotation.Argument; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; @@ -11,35 +12,50 @@ public interface FriendAlarmListener { @RabbitListener( bindings = @QueueBinding( - value = @Queue(value = "notification.friendRequest.queue", durable = "true"), + value = @Queue( + value = "notification.friendRequest.queue", + durable = "true", + arguments = @Argument(name = "x-dead-letter-exchange", value = "fail.notification.friendRequest.exchange") + ), exchange = @Exchange(value = "notification.friendRequest.exchange"), key = "notification.friendRequest.queue" ), returnExceptions = "false", - messageConverter = "jsonMessageConverter" + messageConverter = "jsonMessageConverter", + errorHandler = "rabbitMQErrorHandler" ) void sendFriendRequestNotification(final FriendNotificationDto dto); @RabbitListener( bindings = @QueueBinding( - value = @Queue(value = "notification.friendAccept.queue", durable = "true"), + value = @Queue( + value = "notification.friendAccept.queue", + durable = "true", + arguments = @Argument(name = "x-dead-letter-exchange", value = "fail.notification.friendAccept.exchange") + ), exchange = @Exchange(value = "notification.friendAccept.exchange"), key = "notification.friendAccept.queue" ), returnExceptions = "false", - messageConverter = "jsonMessageConverter" + messageConverter = "jsonMessageConverter", + errorHandler = "rabbitMQErrorHandler" ) void sendFriendAcceptNotification(final FriendNotificationDto dto); @RabbitListener( bindings = @QueueBinding( - value = @Queue(value = "notification.friendRequests.queue", durable = "true"), - exchange = @Exchange(value = "notification.friendRequests.exchange"), - key = "notification.friendRequests.queue" + value = @Queue( + value = "batch.notification.friendRequests.queue", + durable = "true", + arguments = @Argument(name = "x-dead-letter-exchange", value = "fail.batch.notification.friendRequests.exchange") + ), + exchange = @Exchange(value = "batch.notification.friendRequests.exchange"), + key = "batch.notification.friendRequests.queue" ), returnExceptions = "false", - messageConverter = "jsonMessageConverter" + messageConverter = "jsonMessageConverter", + errorHandler = "rabbitMQErrorHandler" ) void sendFriendRequestNotifications(final FriendNotificationsDto dto); diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/friend/FriendAlarmService.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/friend/FriendAlarmService.java index dba816810..60f6fb357 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/friend/FriendAlarmService.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/friend/FriendAlarmService.java @@ -11,22 +11,27 @@ import site.timecapsulearchive.notification.entity.CategoryName; import site.timecapsulearchive.notification.entity.Notification; import site.timecapsulearchive.notification.entity.NotificationCategory; +import site.timecapsulearchive.notification.global.log.Trace; import site.timecapsulearchive.notification.infra.fcm.friend.FriendFcmManager; import site.timecapsulearchive.notification.repository.member.MemberRepository; import site.timecapsulearchive.notification.repository.notification.NotificationCategoryRepository; import site.timecapsulearchive.notification.repository.notification.NotificationRepository; +import site.timecapsulearchive.notification.service.dto.MemberNotificationInfoDto; +import site.timecapsulearchive.notification.service.validator.NotificationSendValidator; @Service @RequiredArgsConstructor public class FriendAlarmService implements FriendAlarmListener { private final FriendFcmManager friendFcmManager; + private final NotificationSendValidator notificationSendValidator; private final NotificationRepository notificationRepository; private final NotificationCategoryRepository notificationCategoryRepository; private final MemberRepository memberRepository; private final TransactionTemplate transactionTemplate; - + @Trace + @Override public void sendFriendRequestNotification(final FriendNotificationDto dto) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override @@ -40,12 +45,16 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } }); - final String fcmToken = memberRepository.findFCMToken(dto.targetId()); - if (fcmToken != null && !fcmToken.isBlank()) { - friendFcmManager.sendFriendNotification(dto, CategoryName.FRIEND_REQUEST, fcmToken); + final MemberNotificationInfoDto notificationInfoDto = memberRepository.findFCMToken( + dto.targetId()); + if (notificationSendValidator.canSend(notificationInfoDto)) { + friendFcmManager.sendFriendNotification(dto, CategoryName.FRIEND_REQUEST, + notificationInfoDto.fcmToken()); } } + @Trace + @Override public void sendFriendAcceptNotification(final FriendNotificationDto dto) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override @@ -59,13 +68,16 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } }); - final String fcmToken = memberRepository.findFCMToken(dto.targetId()); - if (fcmToken != null && !fcmToken.isBlank()) { - friendFcmManager.sendFriendNotification(dto, CategoryName.FRIEND_ACCEPT, fcmToken); + final MemberNotificationInfoDto notificationInfoDto = memberRepository.findFCMToken( + dto.targetId()); + if (notificationSendValidator.canSend(notificationInfoDto)) { + friendFcmManager.sendFriendNotification(dto, CategoryName.FRIEND_ACCEPT, + notificationInfoDto.fcmToken()); } } - + @Trace + @Override public void sendFriendRequestNotifications(final FriendNotificationsDto dto) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override @@ -78,16 +90,15 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } }); - final List fcmTokens = getTargetFcmTokens(dto.targetIds()); - if (fcmTokens != null && !fcmTokens.isEmpty()) { - friendFcmManager.sendFriendNotifications(dto, CategoryName.FRIEND_ACCEPT, fcmTokens); - } - } - - private List getTargetFcmTokens(List targetIds) { - return memberRepository.findFCMTokens(targetIds) + final List filteredFCMTokens = memberRepository.findFCMTokens(dto.targetIds()) .stream() + .filter(notificationSendValidator::canSend) + .map(MemberNotificationInfoDto::fcmToken) .toList(); - } + if (!filteredFCMTokens.isEmpty()) { + friendFcmManager.sendFriendNotifications(dto, CategoryName.FRIEND_ACCEPT, + filteredFCMTokens); + } + } } diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/group/GroupAlarmListener.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/group/GroupAlarmListener.java index 85296d065..d7ce77b42 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/group/GroupAlarmListener.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/group/GroupAlarmListener.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.notification.service.group; +import org.springframework.amqp.rabbit.annotation.Argument; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; @@ -11,24 +12,34 @@ public interface GroupAlarmListener { @RabbitListener( bindings = @QueueBinding( - value = @Queue(value = "notification.groupInvite.queue", durable = "true"), + value = @Queue( + value = "notification.groupInvite.queue", + durable = "true", + arguments = @Argument(name = "x-dead-letter-exchange", value = "fail.notification.groupInvite.exchange") + ), exchange = @Exchange(value = "notification.groupInvite.exchange"), key = "notification.groupInvite.queue" ), returnExceptions = "false", - messageConverter = "jsonMessageConverter" + messageConverter = "jsonMessageConverter", + errorHandler = "rabbitMQErrorHandler" ) void sendGroupInviteNotification(final GroupInviteNotificationDto dto); @RabbitListener( bindings = @QueueBinding( - value = @Queue(value = "notification.groupAccept.queue", durable = "true"), + value = @Queue( + value = "notification.groupAccept.queue", + durable = "true", + arguments = @Argument(name = "x-dead-letter-exchange", value = "fail.notification.groupAccept.exchange") + ), exchange = @Exchange(value = "notification.groupAccept.exchange"), key = "notification.groupAccept.queue" ), returnExceptions = "false", - messageConverter = "jsonMessageConverter" + messageConverter = "jsonMessageConverter", + errorHandler = "rabbitMQErrorHandler" ) void sendGroupAcceptNotification(final GroupAcceptNotificationDto dto); } diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/group/GroupAlarmService.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/group/GroupAlarmService.java index ef0728e08..f26e56fef 100644 --- a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/group/GroupAlarmService.java +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/group/GroupAlarmService.java @@ -11,22 +11,27 @@ import site.timecapsulearchive.notification.entity.CategoryName; import site.timecapsulearchive.notification.entity.Notification; import site.timecapsulearchive.notification.entity.NotificationCategory; +import site.timecapsulearchive.notification.global.log.Trace; import site.timecapsulearchive.notification.infra.fcm.group.GroupFcmManager; import site.timecapsulearchive.notification.repository.member.MemberRepository; import site.timecapsulearchive.notification.repository.notification.NotificationCategoryRepository; import site.timecapsulearchive.notification.repository.notification.NotificationRepository; +import site.timecapsulearchive.notification.service.dto.MemberNotificationInfoDto; +import site.timecapsulearchive.notification.service.validator.NotificationSendValidator; @Service @RequiredArgsConstructor public class GroupAlarmService implements GroupAlarmListener { private final GroupFcmManager groupFcmManager; + private final NotificationSendValidator notificationSendValidator; private final NotificationRepository notificationRepository; private final NotificationCategoryRepository notificationCategoryRepository; private final MemberRepository memberRepository; private final TransactionTemplate transactionTemplate; - + @Trace + @Override public void sendGroupInviteNotification(final GroupInviteNotificationDto dto) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override @@ -38,12 +43,20 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } }); - List fcmTokens = getTargetFcmTokens(dto.targetIds()); - if (fcmTokens != null && !fcmTokens.isEmpty()) { - groupFcmManager.sendGroupInviteNotifications(dto, CategoryName.GROUP_INVITE, fcmTokens); + List filteredFCMTokens = memberRepository.findFCMTokens( + dto.targetIds()) + .stream() + .filter(notificationSendValidator::canSend) + .map(MemberNotificationInfoDto::fcmToken) + .toList(); + + if (!filteredFCMTokens.isEmpty()) { + groupFcmManager.sendGroupInviteNotifications(dto, CategoryName.GROUP_INVITE, + filteredFCMTokens); } } + @Trace @Override public void sendGroupAcceptNotification(GroupAcceptNotificationDto dto) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @@ -59,16 +72,11 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } }); - final String fcmToken = memberRepository.findFCMToken(dto.targetId()); - if (fcmToken != null && !fcmToken.isBlank()) { - groupFcmManager.sendGroupAcceptNotification(dto, CategoryName.FRIEND_ACCEPT, fcmToken); + final MemberNotificationInfoDto notificationInfoDto = memberRepository.findFCMToken( + dto.targetId()); + if (notificationSendValidator.canSend(notificationInfoDto)) { + groupFcmManager.sendGroupAcceptNotification(dto, CategoryName.FRIEND_ACCEPT, + notificationInfoDto.fcmToken()); } } - - private List getTargetFcmTokens(List targetIds) { - return memberRepository.findFCMTokens(targetIds) - .stream() - .toList(); - } - } diff --git a/backend/notification/src/main/java/site/timecapsulearchive/notification/service/validator/NotificationSendValidator.java b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/validator/NotificationSendValidator.java new file mode 100644 index 000000000..9741ff31e --- /dev/null +++ b/backend/notification/src/main/java/site/timecapsulearchive/notification/service/validator/NotificationSendValidator.java @@ -0,0 +1,15 @@ +package site.timecapsulearchive.notification.service.validator; + +import org.springframework.stereotype.Component; +import site.timecapsulearchive.notification.service.dto.MemberNotificationInfoDto; + +@Component +public class NotificationSendValidator { + + public boolean canSend(MemberNotificationInfoDto memberNotificationInfoDto) { + return memberNotificationInfoDto != null + && memberNotificationInfoDto.notificationEnabled() + && memberNotificationInfoDto.fcmToken() != null + && !memberNotificationInfoDto.fcmToken().isBlank(); + } +} diff --git a/backend/notification/src/main/resources/logback-spring.xml b/backend/notification/src/main/resources/logback-spring.xml index 4b25a32da..372ba3c5a 100644 --- a/backend/notification/src/main/resources/logback-spring.xml +++ b/backend/notification/src/main/resources/logback-spring.xml @@ -2,7 +2,7 @@ - [%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{36} - %msg%n + [%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{36} - [trace] %msg%n @@ -12,7 +12,7 @@ INFO - [%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n + [%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - [trace] %msg%n