Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PC-414] FCM 푸시 모듈 구현 #40

Merged
merged 10 commits into from
Feb 8, 2025
24 changes: 24 additions & 0 deletions core/domain/src/main/java/org/yapp/core/domain/fcm/FcmToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.yapp.core.domain.fcm;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;

@Entity
@Getter
@Table(name = "fcm_token")
public class FcmToken {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "user_id")
private Long userId;
@Column(name = "token")
private String token;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.yapp.core.exception.error.code;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor
public enum NotificationErrorCode implements ErrorCode {
FCM_SEND_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "FCM 전송에 실패했습니다."),
FCM_INIT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "FCM 초기화에 실패했습니다"),

NOTIFICATION_CLIENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "알림 클라이언트를 찾을 수 없습니다.");

private final HttpStatus httpStatus;
private final String message;
}
22 changes: 22 additions & 0 deletions core/notification/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
id 'java'
}

group = 'org.yapp'
version = '0.0.1-SNAPSHOT'

repositories {
mavenCentral()
}

dependencies {
implementation project(':core:exception')
implementation project(':infra:fcm')

testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
}

test {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package application;

import client.NotificationClient;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import provider.NotificationProvider;

@Service
@RequiredArgsConstructor
public class NotificationService {

private final NotificationProvider notificationProvider;

public void sendNotification(String clientName, String token, String title, String message) {
NotificationClient notificationClient = notificationProvider.getNotificationClient(
clientName);
notificationClient.sendNotification(token, title, message);
}
}
19 changes: 19 additions & 0 deletions core/notification/src/main/java/client/ApnClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package client;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ApnClient implements NotificationClient {

@Override
public boolean match(String clientName) {
return false;
}

@Override
public void sendNotification(String token, String title, String message) {

}
}
23 changes: 23 additions & 0 deletions core/notification/src/main/java/client/FcmClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package client;

import application.FcmSendService;
import enums.NotificationClientName;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class FcmClient implements NotificationClient {

private final FcmSendService fcmSendService;

@Override
public boolean match(String clientName) {
return NotificationClientName.FCM_CLIENT.getClientName().equals(clientName);
}

@Override
public void sendNotification(String token, String title, String message) {
fcmSendService.sendNotificationWithToken(token, title, message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package client;

public interface NotificationClient {

boolean match(String clientName);

void sendNotification(String token, String title, String message);
}
14 changes: 14 additions & 0 deletions core/notification/src/main/java/enums/NotificationClientName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package enums;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum NotificationClientName {
FCM_CLIENT("fcm"),
APM_CLIENT("apn"),
;

private final String clientName;
}
21 changes: 21 additions & 0 deletions core/notification/src/main/java/provider/NotificationProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package provider;

import client.NotificationClient;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.yapp.core.exception.ApplicationException;
import org.yapp.core.exception.error.code.NotificationErrorCode;

@Component
@RequiredArgsConstructor
public class NotificationProvider {

private final List<NotificationClient> notificationClients;

public NotificationClient getNotificationClient(String clientName) {
return notificationClients.stream().filter(client -> client.match(clientName)).findFirst()
.orElseThrow(
() -> new ApplicationException(NotificationErrorCode.NOTIFICATION_CLIENT_NOT_FOUND));
}
}
22 changes: 22 additions & 0 deletions infra/fcm/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
id 'java'
}

group = 'org.yapp'
version = '0.0.1-SNAPSHOT'

repositories {
mavenCentral()
}

dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'

implementation project(':core:exception')
implementation 'com.google.firebase:firebase-admin:9.4.3' // Google Firebase Admin
}

test {
useJUnitPlatform()
}
99 changes: 99 additions & 0 deletions infra/fcm/src/main/java/application/FcmSendService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package application;

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 java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.yapp.core.exception.ApplicationException;
import org.yapp.core.exception.error.code.NotificationErrorCode;

@Service
@RequiredArgsConstructor
public class FcmSendService {

public void sendNotificationWithToken(String token, String title, String body) {
try {
FirebaseMessaging.getInstance().send(Message.builder()
.setNotification(Notification.builder()
.setTitle(title)
.setBody(body)
.build())
.setToken(token)
.build());
} catch (FirebaseMessagingException e) {
throw new ApplicationException(NotificationErrorCode.FCM_SEND_ERROR);
}
}

public void sendNotificationWithTopic(String topic, String title, String body) {
try {
FirebaseMessaging.getInstance().send(Message.builder()
.setNotification(Notification.builder()
.setTitle(title)
.setBody(body)
.build())
.setTopic(topic)
.build());
} catch (FirebaseMessagingException e) {
throw new ApplicationException(NotificationErrorCode.FCM_SEND_ERROR);
}
}

public void sendDataWithToken(String token, Map<String, String> data) {
try {
FirebaseMessaging.getInstance().send(Message.builder()
.setToken(token)
.putAllData(data)
.build());
} catch (FirebaseMessagingException e) {
throw new ApplicationException(NotificationErrorCode.FCM_SEND_ERROR);
}
}

public void sendDataWithTopic(String topic, Map<String, String> data) {
try {
FirebaseMessaging.getInstance().send(Message.builder()
.setTopic(topic)
.putAllData(data)
.build());
} catch (FirebaseMessagingException e) {
throw new ApplicationException(NotificationErrorCode.FCM_SEND_ERROR);
}
}

public void sendNotificationAndDataWithToken(String token, Map<String, String> data, String title,
String body) {
try {
FirebaseMessaging.getInstance().send(Message.builder()
.setNotification(Notification.builder()
.setTitle(title)
.setBody(body)
.build())
.setToken(token)
.putAllData(data)
.build());
} catch (FirebaseMessagingException e) {
throw new ApplicationException(NotificationErrorCode.FCM_SEND_ERROR);
}
}

public void sendNotificationAndDataWithTopic(String topic, Map<String, String> data, String title,
String body) {
try {
FirebaseMessaging.getInstance().send(Message.builder()
.setNotification(Notification.builder()
.setTitle(title)
.setBody(body)
.build())
.setTopic(topic)
.putAllData(data)
.build());
} catch (FirebaseMessagingException e) {
throw new ApplicationException(NotificationErrorCode.FCM_SEND_ERROR);
}
}
}

34 changes: 34 additions & 0 deletions infra/fcm/src/main/java/config/FcmConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package config;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import jakarta.annotation.PostConstruct;
import java.io.InputStream;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.yapp.core.exception.ApplicationException;
import org.yapp.core.exception.error.code.NotificationErrorCode;

@Slf4j
@Configuration
public class FcmConfig {

@PostConstruct
public void init() {
try {
InputStream serviceAccount = new ClassPathResource(
"firebasekey.json").getInputStream();
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.build();

if (FirebaseApp.getApps().isEmpty()) {
FirebaseApp.initializeApp(options);
}
} catch (Exception e) {
throw new ApplicationException(NotificationErrorCode.FCM_INIT_ERROR);
}
}
}
5 changes: 4 additions & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ include 'infra:redis'
findProject(':infra:redis')?.name = 'redis'
include 'infra:sms'
findProject(':infra:sms')?.name = 'sms'
include 'infra:fcm'
findProject(':infra:fcm')?.name = 'fcm'
include 'core:notification'
findProject(':core:notification')?.name = 'notification'
include 'core:sse'
findProject(':core:sse')?.name = 'sse'

Loading