본문 바로가기

개발

[스프링부트] FCM (Firebase Cloud Messaging) - 개별 전송

 

Firebase는 Google이 제공하는 모바일 및 웹 애플리케이션 개발 플랫폼으로, 다음과 같은 다양한 기능을 제공합니다:

  • 실시간 데이터베이스: 클라우드에서 실시간으로 데이터를 저장하고 동기화할 수 있는 NoSQL 데이터베이스입니다.
  • 인증: 사용자 인증을 간편하게 처리할 수 있는 다양한 인증 방법을 지원합니다.
  • 호스팅: 정적 웹 사이트 및 앱을 배포하고 호스팅할 수 있는 기능을 제공합니다.
  • Analytics: 사용자 행동 분석 및 앱 성과를 측정할 수 있는 도구입니다.
  • Storage: 파일을 안전하게 저장하고 관리할 수 있는 클라우드 스토리지 서비스입니다.

 

 

FCM (Firebase Cloud Messaging)은 Firebase의 일부로, 다음과 같은 기능을 제공합니다:

  • 푸시 알림: 앱 사용자에게 실시간으로 푸시 알림을 전송할 수 있는 서비스입니다.
  • 메시지 전송: 앱 내 메시지를 전송하고 수신할 수 있는 기능을 제공합니다.
  • 안정성: Google의 인프라를 통해 안정적이고 신뢰성 높은 메시지 전송을 지원합니다.

 

[FCM의 동작 방식]

+ Firebase Cloud Messaging(FCM)을 사용하는 경우, 액세스 토큰 발급에 대한 과정을 별도로 처리할 필요는 없습니다. Firebase Admin SDK는 자동으로 Google Cloud Platform(GCP)의 인증 및 권한 관리를 처리합니다.

즉, FCM에서 서버가 클라이언트에게 메시지를 보내기 위해 Firebase Admin SDK를 사용할 때, Google 인증 파일(.json 파일)을 이용해 GCP와 통신을 자동으로 처리하며, 액세스 토큰 발급 및 갱신도 내부적으로 관리합니다. 따라서 명시적으로 액세스 토큰을 발급받는 로직을 작성하지 않아도 됩니다.

 

 

 

Firebase에 프로젝트 등록 

 

 

스프링부트 Firebase 의존성 추가

build.gradle

// Firebase 
    implementation 'com.google.firebase:firebase-admin:9.3.0'

 

 

Firebase → 프로젝트 설정 → 서비스 계정 → Firebase Admin SDK → 새 비공개 키 생성

 

발급받은 key resources파일 아래에 등록

 

※ .gitignore에도 추가하여 git에 업로드되지 않도록 주의

 

 

FCMConfig 설정 추가

: Firebase Cloud Messaging을 사용하기 위한 Spring Boot 설정 클래스를 정의

@Configuration
public class FCMConfig {

    @Bean
    FirebaseMessaging firebaseMessaging() throws IOException {
        ClassPathResource resource = new ClassPathResource("firebase/firebase_service_key.json");
        InputStream serviceAccount = resource.getInputStream();

        FirebaseOptions options = FirebaseOptions.builder()
                .setCredentials(GoogleCredentials.fromStream(serviceAccount))
                .build();

        FirebaseApp firebaseApp;
        if (FirebaseApp.getApps().isEmpty()) {
            firebaseApp = FirebaseApp.initializeApp(options);
        } else {
            firebaseApp = FirebaseApp.getInstance();
        }

        return FirebaseMessaging.getInstance(firebaseApp);
    }
}

 

 

FCM을 통해 알림을 전송하기 위해서느 해당 타겟 유저 기기를 식별할 수 있는 토큰이 필요하다.

 

FCMToken

: member_id와 fcm_id로 고유 키 제약을 걸어줌 

 

@NoArgsConstructor
@Getter
@Entity
@Table(name = "FCMToken",
        uniqueConstraints =  {
                @UniqueConstraint(columnNames = {"member_id", "fcm_id"})
        }
)
public class FCMToken {

    @Id
    @Column(name = "fcm_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    private String token;

    private LocalDate createdAt;

    public void setToken(String token) {
        this.token = token;
    }

    @Builder
    public FCMToken(Member member, String token, LocalDate createdAt) {
        this.member = member;
        this.token = token;
        this.createdAt = createdAt;
    }
}

 

DTO - MessagePushServiceRequest

record?

Java에서 record는 불변(immutable) 데이터 클래스를 간편하게 생성할 수 있도록 도입된 새로운 기능입니다. record는 Java 14에서 미리보기로 소개되었고, Java 16에서 정식으로 추가되었습니다. 

  1. 간결함: record를 사용하면 필드, 생성자, 접근자(getter) 메소드, equals(), hashCode(), toString() 메소드 등을 자동으로 생성해 줍니다. 이로 인해 일반적인 데이터 클래스를 작성할 때 필요한 보일러플레이트 코드가 줄어듭니다.
  2. 불변성: record로 생성된 객체는 기본적으로 불변입니다. 즉, 객체를 생성한 후에는 상태를 변경할 수 없습니다. 이는 데이터의 일관성을 유지하는 데 도움이 됩니다.
  3. 데이터 중심 프로그래밍: record는 데이터를 중심으로 한 프로그래밍 스타일을 장려합니다. 데이터 클래스는 주로 데이터를 표현하는 역할을 하며, 이를 통해 코드의 가독성을 높이고 유지보수를 용이하게 합니다.
  4. 패턴 매칭의 가능성: Java에서 record는 향후 패턴 매칭과 같은 고급 기능과 함께 사용할 수 있도록 설계되었습니다. 이는 코드를 더 간결하고 읽기 쉽게 만들어줍니다.
@Builder(access = PRIVATE)
public record MessagePushServiceRequest(
        String targetToken,
        String title,
        String body
) {

    public static MessagePushServiceRequest of(String token, String title, String body) {
        return MessagePushServiceRequest.builder()
                .targetToken(token)
                .title(title)
                .body(body)
                .build();
    }
}

 

 

Controller - FCM 전송 요청

@PostMapping("/send")
    public ResponseEntity<String> sendNotification(@RequestBody MessagePushServiceRequest request) {
        try {
            // FCM 메시지 전송 요청
            String response = fcmService.sendNotification(request);
            return ResponseEntity.ok("Message sent successfully: " + response);
        } catch (Exception e) {
            return ResponseEntity.status(500).body("Message failed: " + e.getMessage());
        }
    }

 

Service - FCM 전송 요청

 public String sendNotification(MessagePushServiceRequest request) {
        try {
            Message message = Message.builder()
                    .setToken(request.targetToken())
                    .setNotification(Notification.builder()
                            .setTitle(request.title())
                            .setBody(request.body())
                            .build())
                    .build();

            // 메시지 전송
            return firebaseMessaging.send(message);
        } catch (Exception e) {
            throw new RuntimeException("FCM 알림 전송에 실패했습니다.", e);
        }
    }

 

 

테스트

완료!