From ef5fa484afd2c62ea811d56af1b7e2503e66fc0f Mon Sep 17 00:00:00 2001 From: dev-Crayon Date: Tue, 30 Jan 2024 18:41:01 +0900 Subject: [PATCH] =?UTF-8?q?[Feat]:=20FCM=20=EC=97=B0=EB=8F=99=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 푸시 알림을 위한 FCM 관련 로직 작성 admin-sdk파일 등록 Related to: #59 --- .gitignore | 5 +- build.gradle | 3 ++ .../io/sobok/SobokSobok/config/FCMConfig.java | 47 +++++++++++++++++ .../sobok/SobokSobok/exception/ErrorCode.java | 1 + .../firebase/FCMNotificationService.java | 51 +++++++++++++++++++ .../firebase/dto/FCMNotificationRequest.java | 16 ++++++ 6 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/sobok/SobokSobok/config/FCMConfig.java create mode 100644 src/main/java/io/sobok/SobokSobok/external/firebase/FCMNotificationService.java create mode 100644 src/main/java/io/sobok/SobokSobok/external/firebase/dto/FCMNotificationRequest.java diff --git a/.gitignore b/.gitignore index 3596642..05b151e 100644 --- a/.gitignore +++ b/.gitignore @@ -178,4 +178,7 @@ gradle-app.setting # Environment Variables application-local.yml application-dev.yml -application-prod.yml \ No newline at end of file +application-prod.yml + +# firebase admin sdk +sobok-76d0a-firebase-adminsdk-qb2ez-50cf49a817.json \ No newline at end of file diff --git a/build.gradle b/build.gradle index 0424d60..e1a6dc3 100644 --- a/build.gradle +++ b/build.gradle @@ -49,6 +49,9 @@ dependencies { implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2' implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2' + // firebase + implementation 'com.google.firebase:firebase-admin:9.2.0' + // Swagger implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.3.0' diff --git a/src/main/java/io/sobok/SobokSobok/config/FCMConfig.java b/src/main/java/io/sobok/SobokSobok/config/FCMConfig.java new file mode 100644 index 0000000..7e91d39 --- /dev/null +++ b/src/main/java/io/sobok/SobokSobok/config/FCMConfig.java @@ -0,0 +1,47 @@ +package io.sobok.SobokSobok.config; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.messaging.FirebaseMessaging; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +@Configuration +public class FCMConfig { + + @Value("${firebase.admin-sdk}") + String adminSdkFileName; + + @Bean + FirebaseMessaging firebaseMessaging() throws IOException { + ClassPathResource resource = new ClassPathResource("firebase/" + adminSdkFileName); + + InputStream refreshToken = resource.getInputStream(); + + FirebaseApp firebaseApp = null; + List firebaseAppList = FirebaseApp.getApps(); + + if (firebaseAppList != null && !firebaseAppList.isEmpty()) { + for (FirebaseApp app : firebaseAppList) { + if (app.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) { + firebaseApp = app; + } + } + } else { + FirebaseOptions options = FirebaseOptions.builder() + .setCredentials(GoogleCredentials.fromStream(refreshToken)) + .build(); + + firebaseApp = FirebaseApp.initializeApp(options); + } + + return FirebaseMessaging.getInstance(firebaseApp); + } +} diff --git a/src/main/java/io/sobok/SobokSobok/exception/ErrorCode.java b/src/main/java/io/sobok/SobokSobok/exception/ErrorCode.java index 0fd7c78..2cc6508 100644 --- a/src/main/java/io/sobok/SobokSobok/exception/ErrorCode.java +++ b/src/main/java/io/sobok/SobokSobok/exception/ErrorCode.java @@ -20,6 +20,7 @@ public enum ErrorCode { NOT_LOGGED_IN_USER(HttpStatus.NOT_FOUND, "로그인되지 않은 사용자입니다."), ALREADY_EXISTS_USER(HttpStatus.CONFLICT, "이미 회원가입이 완료된 사용자입니다."), ALREADY_USING_USERNAME(HttpStatus.CONFLICT, "이미 사용중인 username입니다."), + EMPTY_DEVICE_TOKEN(HttpStatus.NOT_FOUND, "디바이스 토큰이 존재하지 않습니다."), // jwt EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."), diff --git a/src/main/java/io/sobok/SobokSobok/external/firebase/FCMNotificationService.java b/src/main/java/io/sobok/SobokSobok/external/firebase/FCMNotificationService.java new file mode 100644 index 0000000..47f3aaa --- /dev/null +++ b/src/main/java/io/sobok/SobokSobok/external/firebase/FCMNotificationService.java @@ -0,0 +1,51 @@ +package io.sobok.SobokSobok.external.firebase; + +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingException; +import com.google.firebase.messaging.Message; +import com.google.firebase.messaging.Notification; +import io.sobok.SobokSobok.auth.application.util.UserServiceUtil; +import io.sobok.SobokSobok.auth.domain.User; +import io.sobok.SobokSobok.auth.infrastructure.UserRepository; +import io.sobok.SobokSobok.exception.ErrorCode; +import io.sobok.SobokSobok.exception.model.NotFoundException; +import io.sobok.SobokSobok.external.firebase.dto.FCMNotificationRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class FCMNotificationService { + + private final FirebaseMessaging firebaseMessaging; + private final UserRepository userRepository; + + public void sendNotificationByDeviceToken(FCMNotificationRequest request) { + + User user = UserServiceUtil.findUserById(userRepository, request.userId()); + + if (user.getDeviceToken() == null) { + throw new NotFoundException(ErrorCode.EMPTY_DEVICE_TOKEN); + } + + Notification notification = Notification.builder() + .setTitle(request.title()) + .setBody(request.body()) + .setImage(request.image()) + .build(); + + Message message = Message.builder() + .setToken(user.getDeviceToken()) + .setNotification(notification) + .putAllData(request.data()) + .build(); + + try { + firebaseMessaging.send(message); + } catch (FirebaseMessagingException e) { + log.error("푸시알림 전송에 실패했습니다. userId: " + user.getId() + "\n" + e.getMessage());; + } + } +} diff --git a/src/main/java/io/sobok/SobokSobok/external/firebase/dto/FCMNotificationRequest.java b/src/main/java/io/sobok/SobokSobok/external/firebase/dto/FCMNotificationRequest.java new file mode 100644 index 0000000..766dd61 --- /dev/null +++ b/src/main/java/io/sobok/SobokSobok/external/firebase/dto/FCMNotificationRequest.java @@ -0,0 +1,16 @@ +package io.sobok.SobokSobok.external.firebase.dto; + +import lombok.Builder; + +import java.util.Map; + +@Builder +public record FCMNotificationRequest( + + Long userId, + String title, + String body, + String image, + Map data +) { +}