Skip to content

Commit

Permalink
feat: #11 notification 종류 별로 event&request 분리
Browse files Browse the repository at this point in the history
  • Loading branch information
psychology50 committed Feb 27, 2024
1 parent 242d651 commit e408684
Show file tree
Hide file tree
Showing 23 changed files with 554 additions and 167 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package kr.co.fitapet.api.common.event.notification;

import kr.co.fitapet.domain.domains.notification.domain.Notification;
import kr.co.fitapet.domain.domains.notification.type.NotificationType;
import lombok.Builder;

@Builder
public record AnnouncementEvent(
String title,
String content,
String imageUrl
) {
public Notification toEntity(Long toId) {
return Notification.builder()
.title(title)
.content(content)
.imageUrl(imageUrl)
.ctype(NotificationType.ANNOUNCEMENT)
.toId(toId)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
package kr.co.fitapet.api.common.event.notification;

import kr.co.fitapet.domain.domains.notification.domain.Notification;
import kr.co.fitapet.domain.domains.notification.type.NotificationType;
import kr.co.fitapet.infra.client.fcm.NotificationDataKey;
import lombok.Builder;

import java.util.Map;
import java.util.Objects;

@Builder
public record NoticeEvent(
NoticeType noticeType,
Map<NotificationDataKey, ?> data
NotificationType notificationType,
String title,
String content,
String imageUrl,
Map<NotificationDataKey, String> data
) {
public Notification toEntity() {
String fromId = data.getOrDefault(NotificationDataKey.FROM_ID, null);
String toId = data.getOrDefault(NotificationDataKey.TO_ID, null);
String domainId = data.getOrDefault(NotificationDataKey.DOMAIN_ID, null);
String subjectId = data.getOrDefault(NotificationDataKey.SUBJECT_ID, null);

return Notification.builder()
.title(title)
.content(content)
.imageUrl(imageUrl)
.ctype(notificationType)
.fromId(Objects.isNull(fromId) ? null : Long.parseLong(fromId))
.toId(Objects.isNull(toId) ? null : Long.parseLong(toId))
.domainId(Objects.isNull(domainId) ? null : Long.parseLong(domainId))
.subjectId(Objects.isNull(subjectId) ? null : Long.parseLong(subjectId))
.build();
}

public String getValueFromNotificationDataKey(NotificationDataKey key) {
return data.getOrDefault(key, null);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package kr.co.fitapet.api.common.event.notification;

import kr.co.fitapet.domain.domains.device.domain.DeviceToken;
import kr.co.fitapet.domain.domains.device.service.DeviceTokenSearchService;
import kr.co.fitapet.domain.domains.notification.domain.Notification;
import kr.co.fitapet.domain.domains.notification.service.NotificationSaveService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
Expand All @@ -9,18 +13,49 @@
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

import java.util.*;

@Slf4j
@Component
@RequiredArgsConstructor
public class NoticeEventHandler {
private final ApplicationEventPublisher eventPublisher;
private final DeviceTokenSearchService deviceTokenSearchService;
private final NotificationSaveService notificationSaveService;

@Transactional(propagation = Propagation.REQUIRES_NEW)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
public void handleNoticeEventToManager(NoticeEvent event) {
public void handleNoticeEvent(NoticeEvent event) {
log.info("handleNoticeEvent: {}", event);
// notice 저장
// notice 전송할 대상 조회 (deviceToken 리스트)
// deviceToken 리스트 전체 NotificationEvent 등록
}

/**
* 공지사항 이벤트{@link AnnouncementEvent} 핸들러입니다.
* <br/>
* 공지사항 이벤트를 받아서 공지사항을 저장하고, 해당 공지사항을 전송할 대상을 조회하여 {@link Notification}를 등록합니다.
* <br/>
* {@link TransactionalEventListener}를 통해 이벤트를 발행하는 트랜잭션 커밋 이후에 핸들러가 실행됩니다.
* <br/>
* 이벤트를 발행한 트랜잭션과 별개의 트랜잭션으로 실행됩니다.
*
* @param event {@link AnnouncementEvent}
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
public void handleAnnouncementEvent(AnnouncementEvent event) {
log.info("handleAnnouncementEvent: {}", event);
List<DeviceToken> deviceTokens = deviceTokenSearchService.findAll();

Set<Notification> notifications = new HashSet<>();
deviceTokens.forEach(deviceToken -> {
String token = deviceToken.getDeviceToken();
Long toId = deviceToken.getMember().getId();

notifications.add(event.toEntity(toId));
});
notificationSaveService.saveAll(notifications);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package kr.co.fitapet.domain.domains.device.repository;

import kr.co.fitapet.domain.domains.device.domain.DeviceToken;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface DeviceTokenRepository extends JpaRepository<DeviceToken, Long> {
List<DeviceToken> findAllByMember_Id(Long userId);
boolean existsByDeviceToken(String deviceToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package kr.co.fitapet.domain.domains.device.service;

import kr.co.fitapet.common.annotation.DomainService;
import kr.co.fitapet.domain.domains.device.domain.DeviceToken;
import kr.co.fitapet.domain.domains.device.repository.DeviceTokenRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@DomainService
@RequiredArgsConstructor
public class DeviceTokenSearchService {
private final DeviceTokenRepository deviceTokenRepository;

@Transactional(readOnly = true)
public List<DeviceToken> findDeviceTokensByMemberId(Long userId) {
return deviceTokenRepository.findAllByMember_Id(userId);
}

@Transactional(readOnly = true)
public List<DeviceToken> findAll() {
return deviceTokenRepository.findAll();
}

@Transactional(readOnly = true)
public boolean isExistDeviceToken(String deviceToken) {
return deviceTokenRepository.existsByDeviceToken(deviceToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static NotificationSetting of(Boolean isCare, Boolean isMemo, Boolean isS

public void updateNotificationFromType(NotificationType type) {
switch (type) {
case NOTICE -> this.isNotice = !this.isNotice;
case ANNOUNCEMENT -> this.isNotice = !this.isNotice;
case CARE -> this.isCare = !this.isCare;
case MEMO -> this.isMemo = !this.isMemo;
case SCHEDULE -> this.isSchedule = !this.isSchedule;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,47 @@
import kr.co.fitapet.domain.domains.member.domain.Member;
import kr.co.fitapet.domain.domains.notification.type.NotificationType;
import kr.co.fitapet.domain.common.converter.NotificationTypeConverter;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.DynamicInsert;

@Getter
@Entity
@Table(name = "NOTIFICATION")
@DynamicInsert
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"title", "content", "ctype", "checked"})
@EqualsAndHashCode(of = {"id", "fromId", "toId", "domainId", "subjectId"}, callSuper = false)
public class Notification extends DateAuditable {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
private String imageUrl;
@Convert(converter = NotificationTypeConverter.class)
private NotificationType ctype;
@ColumnDefault("false")
private boolean checked;

private Long fromId;
private Long toId;
private Long domainId;
private Long subjectId;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

@Builder
private Notification(String title, String content, NotificationType ctype) {
@Builder(builderClassName = "NotificationBuilder")
private Notification(String title, String content, NotificationType ctype, String imageUrl, Long fromId, Long toId, Long domainId, Long subjectId, Member member) {
this.title = title;
this.content = content;
this.ctype = ctype;
}

public static Notification of(String title, String content, NotificationType ctype) {
return Notification.builder()
.title(title)
.content(content)
.ctype(ctype)
.build();
this.imageUrl = imageUrl;
this.fromId = fromId;
this.toId = toId;
this.domainId = domainId;
this.subjectId = subjectId;
this.member = member;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kr.co.fitapet.domain.domains.notification.repository;

import kr.co.fitapet.domain.domains.notification.domain.Notification;
import org.springframework.data.jpa.repository.JpaRepository;

public interface NotificationRepository extends JpaRepository<Notification, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package kr.co.fitapet.domain.domains.notification.service;

import kr.co.fitapet.common.annotation.DomainService;
import kr.co.fitapet.domain.domains.notification.domain.Notification;
import kr.co.fitapet.domain.domains.notification.repository.NotificationRepository;
import lombok.RequiredArgsConstructor;

@DomainService
@RequiredArgsConstructor
public class NotificationSaveService {
private final NotificationRepository notificationRepository;

public void save(Notification notification) {
notificationRepository.save(notification);
}

public void saveAll(Iterable<Notification> notifications) {
notificationRepository.saveAll(notifications);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

@RequiredArgsConstructor
public enum NotificationType implements LegacyCommonType {
NOTICE("1", "공지사항"),
ANNOUNCEMENT("1", "공지사항"),
MEMBER("2", "멤버"),
MANAGER("3", "매니저 활동"),
PET("4", "반려동물"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import com.google.api.core.ApiFuture;
import com.google.firebase.messaging.*;
import kr.co.fitapet.infra.client.fcm.request.NotificationMulticastRequest;
import kr.co.fitapet.infra.client.fcm.request.NotificationRequest;
import kr.co.fitapet.infra.client.fcm.request.NotificationSingleRequest;
import kr.co.fitapet.infra.client.fcm.request.NotificationTopicRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand All @@ -15,24 +19,24 @@ public class FcmNotificationServiceImpl implements NotificationService {
private final FirebaseMessaging firebaseMessaging;

@Override
public void sendMessage(NotificationRequest request) throws FirebaseMessagingException {
Message message = request.buildSendMessageToToken().setApnsConfig(getApnsConfigToTopic(request)).build();
public void sendMessage(NotificationSingleRequest request) throws FirebaseMessagingException {
Message message = request.buildSendMessage().setApnsConfig(getApnsConfigToTopic(request)).build();

ApiFuture<String> response = firebaseMessaging.sendAsync(message);
log.info("Successfully sent message: " + response);
}

@Override
public void sendMessages(NotificationRequest request) throws FirebaseMessagingException {
MulticastMessage messages = request.buildSendMessagesToTokens().setApnsConfig(getApnsConfigToTopic(request)).build();
public void sendMessages(NotificationMulticastRequest request) throws FirebaseMessagingException {
MulticastMessage messages = request.buildSendMessage().setApnsConfig(getApnsConfigToTopic(request)).build();

ApiFuture<BatchResponse> response = firebaseMessaging.sendEachForMulticastAsync(messages);
log.info("Successfully sent message: " + response);
}

@Override
public void sendMessagesToTopic(NotificationRequest request) throws FirebaseMessagingException {
Message message = request.buildSendMessageToTopic().setApnsConfig(getApnsConfigToTopic(request)).build();
public void sendMessagesToTopic(NotificationTopicRequest request) throws FirebaseMessagingException {
Message message = request.buildSendMessage().setApnsConfig(getApnsConfigToTopic(request)).build();

ApiFuture<String> response = firebaseMessaging.sendAsync(message);
log.info("Successfully sent message: " + response);
Expand All @@ -54,7 +58,7 @@ private ApnsConfig getApnsConfigToTopic(NotificationRequest request) {
.setAlert(
ApsAlert.builder()
.setTitle(request.title())
.setBody(request.body())
.setBody(request.content())
.setLaunchImage(request.imageUrl())
.build())
.setSound("default")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ public String getField() {
return field;
}

public List<String> getNameFields() {
public static List<String> getIdFields() {
return List.of("fromId", "toId", "domainId", "subjectId");
}

public static List<String> getNameFields() {
return List.of("fromName", "toName", "domainName", "subjectName");
}
}

This file was deleted.

Loading

0 comments on commit e408684

Please sign in to comment.