diff --git a/build.gradle b/build.gradle
index a46f524..4762e23 100644
--- a/build.gradle
+++ b/build.gradle
@@ -51,6 +51,10 @@ dependencies {
implementation("org.flywaydb:flyway-core")
implementation("org.flywaydb:flyway-mysql")
+ // FCM
+ implementation 'com.google.firebase:firebase-admin:9.2.0'
+ implementation("com.squareup.okhttp3:okhttp")
+
asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testAnnotationProcessor 'org.projectlombok:lombok'
testImplementation("com.h2database:h2")
diff --git a/src/main/java/com/polzzak/domain/notification/dto/FcmMessage.java b/src/main/java/com/polzzak/domain/notification/dto/FcmMessage.java
new file mode 100644
index 0000000..bb8113e
--- /dev/null
+++ b/src/main/java/com/polzzak/domain/notification/dto/FcmMessage.java
@@ -0,0 +1,45 @@
+package com.polzzak.domain.notification.dto;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+@Getter
+public class FcmMessage {
+
+ private boolean validateOnly;
+ private Message message;
+ private String to;
+
+ public FcmMessage(boolean validateOnly, Message message, String to) {
+ this.validateOnly = validateOnly;
+ this.message = message;
+ this.to = to;
+ }
+
+ @NoArgsConstructor
+ @Getter
+ public static class Message {
+ private Notification notification;
+ private String token;
+
+ public Message(Notification notification, String token) {
+ this.notification = notification;
+ this.token = token;
+ }
+ }
+
+ @NoArgsConstructor
+ @Getter
+ public static class Notification {
+ private String title;
+ private String body;
+ private String image;
+
+ public Notification(String title, String body, String image) {
+ this.title = title;
+ this.body = body;
+ this.image = image;
+ }
+ }
+}
diff --git a/src/main/java/com/polzzak/domain/notification/entity/NotificationType.java b/src/main/java/com/polzzak/domain/notification/entity/NotificationType.java
index b8391ba..2dd24d9 100644
--- a/src/main/java/com/polzzak/domain/notification/entity/NotificationType.java
+++ b/src/main/java/com/polzzak/domain/notification/entity/NotificationType.java
@@ -38,6 +38,15 @@ public String getMessageWithParameter(final String parameter) {
return String.format(this.message, "" + parameter + "");
}
+ public String getParameterWithoutBold(final String parameter) {
+ if (this == STAMP_REQUEST || this == REWARD_REQUEST || this == STAMP_BOARD_COMPLETE || this == REWARDED
+ || this == REWARD_REQUEST_AGAIN || this == REWARD_FAIL || this == CREATED_STAMP_BOARD
+ || this == ISSUED_COUPON || this == REWARDED_REQUEST) {
+ return String.format(this.message, "'" + parameter + "'");
+ }
+ return String.format(this.message, parameter);
+ }
+
public String getLinkWithParameter(final String parameter) {
if (link == null) {
return null;
diff --git a/src/main/java/com/polzzak/domain/notification/handler/NotificationEventHandler.java b/src/main/java/com/polzzak/domain/notification/handler/NotificationEventHandler.java
index b389541..43d0d14 100644
--- a/src/main/java/com/polzzak/domain/notification/handler/NotificationEventHandler.java
+++ b/src/main/java/com/polzzak/domain/notification/handler/NotificationEventHandler.java
@@ -5,10 +5,14 @@
import org.springframework.stereotype.Component;
import com.polzzak.domain.notification.dto.NotificationCreateEvent;
+import com.polzzak.domain.notification.dto.NotificationDto;
import com.polzzak.domain.notification.dto.NotificationSettingDto;
import com.polzzak.domain.notification.entity.Notification;
import com.polzzak.domain.notification.entity.NotificationType;
import com.polzzak.domain.notification.service.NotificationService;
+import com.polzzak.domain.user.entity.Member;
+import com.polzzak.domain.user.service.UserService;
+import com.polzzak.global.infra.firebase.FirebaseCloudMessageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@@ -19,6 +23,8 @@
public class NotificationEventHandler {
private final NotificationService notificationService;
+ private final FirebaseCloudMessageService firebaseCloudMessageService;
+ private final UserService userService;
@Async
@EventListener
@@ -91,7 +97,9 @@ public void addNotification(NotificationCreateEvent event) {
}
}
- notificationService.addNotification(event.senderId(), event.receiverId(), event.type(), event.data());
+ Notification notification = notificationService.addNotification(event.senderId(), event.receiverId(),
+ event.type(), event.data());
+ sendPushNotification(event.senderId(), notification);
log.info("[NotificationEvent] info. sender_id : {}, receiver_id : {}, type : {}, data : {}",
event.senderId(), event.receiverId(), event.type(), event.data());
@@ -99,4 +107,12 @@ public void addNotification(NotificationCreateEvent event) {
log.error("[NotificationEvent] error.", e);
}
}
+
+ private void sendPushNotification(long memberId, Notification notification) {
+ Member member = userService.findMemberByMemberId(memberId);
+ NotificationDto notificationDto = notificationService.getNotificationDto(member, notification, false);
+
+ firebaseCloudMessageService.sendPushNotification(member, notificationDto.title(), notificationDto.message(),
+ notificationDto.link());
+ }
}
diff --git a/src/main/java/com/polzzak/domain/notification/service/NotificationService.java b/src/main/java/com/polzzak/domain/notification/service/NotificationService.java
index 349e814..b5f0e21 100644
--- a/src/main/java/com/polzzak/domain/notification/service/NotificationService.java
+++ b/src/main/java/com/polzzak/domain/notification/service/NotificationService.java
@@ -41,7 +41,7 @@ public class NotificationService {
private final NotificationSettingRepository notificationSettingRepository;
@Transactional
- public void addNotification(final Long senderId, final Long receiverId, final NotificationType type,
+ public Notification addNotification(final Long senderId, final Long receiverId, final NotificationType type,
final String data) {
Member sender = userService.findMemberByMemberId(senderId);
Member receiver = userService.findMemberByMemberId(receiverId);
@@ -53,6 +53,7 @@ public void addNotification(final Long senderId, final Long receiverId, final No
.data(data)
.build();
notificationRepository.save(notification);
+ return notification;
}
@Transactional
@@ -126,18 +127,26 @@ private NotificationResponse getNotificationResponse(final Long memberId, final
pageRequest);
List notificationDtoList = notifications.getContent().stream()
- .map(notification -> getNotificationDto(member, notification))
+ .map(notification -> getNotificationDto(member, notification, true))
.toList();
return NotificationResponse.from(pageRequest, notificationDtoList, notifications.hasNext());
}
- private NotificationDto getNotificationDto(final Member member, final Notification notification) {
+ public NotificationDto getNotificationDto(final Member member, final Notification notification,
+ final boolean isBold) {
Member sender = notification.getSender();
MemberDtoForNotification senderDto = sender == null ? null : MemberDtoForNotification.from(sender,
fileClient.getSignedUrl(sender.getProfileKey()));
- String message = notification.getType()
- .getMessageWithParameter(getMessageParameter(member.getId(), notification));
+ String message;
+ if (isBold) {
+ message = notification.getType()
+ .getMessageWithParameter(getMessageParameter(member.getId(), notification));
+ } else {
+ message = notification.getType()
+ .getParameterWithoutBold(getMessageParameter(member.getId(), notification));
+ }
+
String link = notification.getType().getLinkWithParameter(getLinkParameter(member.getId(), notification));
return NotificationDto.from(notification, message, link, senderDto);
diff --git a/src/main/java/com/polzzak/domain/pushtoken/repository/PushTokenRepository.java b/src/main/java/com/polzzak/domain/pushtoken/repository/PushTokenRepository.java
index 52e249e..d65e43e 100644
--- a/src/main/java/com/polzzak/domain/pushtoken/repository/PushTokenRepository.java
+++ b/src/main/java/com/polzzak/domain/pushtoken/repository/PushTokenRepository.java
@@ -1,9 +1,13 @@
package com.polzzak.domain.pushtoken.repository;
+import java.util.List;
+
import org.springframework.data.jpa.repository.JpaRepository;
import com.polzzak.domain.pushtoken.model.PushToken;
+import com.polzzak.domain.user.entity.Member;
public interface PushTokenRepository extends JpaRepository {
+ List getPushTokensByMember(Member member);
}
diff --git a/src/main/java/com/polzzak/domain/pushtoken/service/PushTokenService.java b/src/main/java/com/polzzak/domain/pushtoken/service/PushTokenService.java
index e8f7718..daab872 100644
--- a/src/main/java/com/polzzak/domain/pushtoken/service/PushTokenService.java
+++ b/src/main/java/com/polzzak/domain/pushtoken/service/PushTokenService.java
@@ -1,5 +1,7 @@
package com.polzzak.domain.pushtoken.service;
+import java.util.List;
+
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
@@ -17,7 +19,7 @@ public class PushTokenService {
private final UserService userService;
private final PushTokenRepository pushTokenRepository;
- public void addToken(Long memberId, String token) {
+ public void addToken(final Long memberId, final String token) {
Member member = userService.findMemberByMemberId(memberId);
PushToken pushToken = PushToken.createPushToken()
@@ -31,4 +33,8 @@ public void addToken(Long memberId, String token) {
}
}
+
+ public List getPushTokens(final Member member) {
+ return pushTokenRepository.getPushTokensByMember(member);
+ }
}
diff --git a/src/main/java/com/polzzak/global/infra/firebase/FirebaseCloudMessageService.java b/src/main/java/com/polzzak/global/infra/firebase/FirebaseCloudMessageService.java
new file mode 100644
index 0000000..98c601c
--- /dev/null
+++ b/src/main/java/com/polzzak/global/infra/firebase/FirebaseCloudMessageService.java
@@ -0,0 +1,68 @@
+package com.polzzak.global.infra.firebase;
+
+import java.io.FileInputStream;
+import java.util.List;
+
+import org.springframework.stereotype.Service;
+
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.FirebaseOptions;
+import com.google.firebase.messaging.BatchResponse;
+import com.google.firebase.messaging.FirebaseMessaging;
+import com.google.firebase.messaging.FirebaseMessagingException;
+import com.google.firebase.messaging.MulticastMessage;
+import com.google.firebase.messaging.Notification;
+import com.polzzak.domain.pushtoken.model.PushToken;
+import com.polzzak.domain.pushtoken.service.PushTokenService;
+import com.polzzak.domain.user.entity.Member;
+
+import lombok.RequiredArgsConstructor;
+
+@Service
+@RequiredArgsConstructor
+public class FirebaseCloudMessageService {
+
+ private final PushTokenService pushTokenService;
+
+ public void sendPushNotification(Member member, String title, String body, String link) {
+ try {
+ FileInputStream serviceAccount = new FileInputStream(
+ "src/main/resources/firebase/firebase_service_key.json");
+
+ FirebaseOptions options = new FirebaseOptions.Builder()
+ .setCredentials(GoogleCredentials.fromStream(serviceAccount))
+ .build();
+
+ if (FirebaseApp.getApps().isEmpty()) {
+ FirebaseApp.initializeApp(options);
+ }
+
+ List registrationTokens = pushTokenService.getPushTokens(member).stream()
+ .map(PushToken::getToken)
+ .toList();
+
+ MulticastMessage message = MulticastMessage.builder()
+ .setNotification(Notification.builder()
+ .setTitle(title)
+ .setBody(body)
+ .build())
+ .addAllTokens(registrationTokens)
+ .putData("link", link)
+ .build();
+ BatchResponse response = null;
+ try {
+ response = FirebaseMessaging.getInstance().sendEachForMulticast(message);
+ } catch (FirebaseMessagingException e) {
+ e.printStackTrace();
+ }
+ // See the BatchResponse reference documentation
+ // for the contents of response.
+ System.out.println(response.getSuccessCount() + " messages were sent successfully");
+ System.out.println(response);
+
+ } catch (Exception e) {
+
+ }
+ }
+}
diff --git a/src/main/resources/firebase/firebase_service_key.json b/src/main/resources/firebase/firebase_service_key.json
new file mode 100644
index 0000000..f0043c0
--- /dev/null
+++ b/src/main/resources/firebase/firebase_service_key.json
@@ -0,0 +1,13 @@
+{
+ "type": "service_account",
+ "project_id": "polzzak-57648",
+ "private_key_id": "ead4311d449ac5facc6c507ec152a6d9f54a279d",
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCuffOTpdr5Z23/\nn6c37jA+TkjDj8MZxBr67ovNVgKSUchidJe3A8VYS+Ck6tMaMr7/YPUu6OfkkHOS\n0VlW3UQwYRqj5Rk5kH6ByBxVH9ZI7Sh0FO9pH71dsHZXwVLam6UOKcyrrbJhwcfX\nXrnksdCi4LxhvIxTsn0z11Q3CXtf5diKXgIt+Ewgqk/sKWFmCLSjIATD6mMn5VTl\nD8+QyOCV/U6ec3CmXHj2RAJMm1FiIE0vHDaGwNtc6hha/igibK6OoQ5+lyrnvuMr\ngGS3o2zH5TOBfMs0FI9KrHaUgxfa0p9jG4Oze1ibyZhK+6hl1RSBSWuxy5iY77vp\n4fdrzzexAgMBAAECggEACaYdWgTJ3xDBHGmPraAWOtvJWkcQ2tPlSgr24BvpeH3d\nPtSDrzMeLovDmFsD4Wb8+NI7vKRUbmcufOfmsM77flFgT7/TbUN4O2T9bBeemdnD\naufddUq0BgJECQY/tqb0sZvOHZA1VQKKMnaigOr0Ro123VC30ckE82Ds3z4+/EZ5\n4+KKxVxXMiV3seQOHIdiMHI/ClrUTrr7S7h67UNJ6ZFzuP/JD1j7XENO9lM2haCL\ny3uxa7KEHDJWs1DU6O8FsXaxdb/XCMAYYTnH33SBtnUxgHNmdbv6IyZfTi/Wm+rY\nC8Vrua52T+8VRav1btVCVNQE6RpNWgWFGGq8pAy1oQKBgQDyDUNyfnfK/xZJAZ9N\nKMe7uBhrS95PAF33fHF6Kc70WPO9JLxWnXFNJPiT9bCRer7Vc9fkB388N43BnGX9\nYDR68EySd2GA8C2BloM6/pBULg7u4CxOrbcaWgqkY8oEktM7cgn5I2EVOGVwTfwP\n420ApXUS5zdUIGR/XsUROBf+hwKBgQC4jAyHNzGhczEALhbLTB+4gZqRO3J1z2uH\n4msAyyD5dU/Vrpa+i+HwG/wWELAzIRbFbwL8pEa4qtbD+ofDN3ytpn86KQZrwxK2\n0m89ajadwppeOh2z051TGCeU1cyECkv3iUPfZP+z9fERnNdAt+cly462zgaH+uJT\n2+/luP0uBwKBgQCJBhEkg4t1EyqecZioqWlIT1MjinNy7ZZEP+JNcdWCZci1TlKA\nBejZ7w/5UqB9+qqFU2rn34abpCdPbyYdZZTP87ClSYec4logfgAUKX+y58/0UltC\nvvxkooxbu1HlfOivQkN7EhgnVyG1jbAfnnNaZk/8P4AG07+QiymsMcEDiQKBgAyx\ntXrnlQZiAhDdGrxJNDVg1N0AldL8vYzPSkT3tAD0zNUJ+VyKCrSVeDWcWEJsGEDk\nbfQq6KJzPeqlJQmMm4rmVQIPKF3pQTRKLVSwJamcZTnuDXT9LWk11CMswbCjdK5G\nRuDq9ZvPYxGvFC9jdwbmhZ6VdWWNIFxcWJgYrXGpAoGBAOwyu9ycczHwhaiITVnH\nDmG7S2LCj8Jq3u3351c6dv/Lt8wuZ6u2FmTuMh8id9hD7u2TO9QzIF8+S10Rc/J8\nzb9GI3KLg67bCCrReZQ0B1L2cjKUObpx5om2icxYXTV1k3GyVg7RJwQ5NA8ncQSP\nvXysN6nV8rcLOSuK8iGsS9Je\n-----END PRIVATE KEY-----\n",
+ "client_email": "firebase-adminsdk-mzmu4@polzzak-57648.iam.gserviceaccount.com",
+ "client_id": "101961288027838881996",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-mzmu4%40polzzak-57648.iam.gserviceaccount.com",
+ "universe_domain": "googleapis.com"
+}