일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 데이터 전달
- plcrashreporter
- EUC-KR
- 블루투스 헤드셋
- API
- .net
- Font
- MFC
- 기념일관리
- ClickOnce
- self-signed ssl
- 와이브로
- C#
- GDI
- 크래시로그
- 자바스크립트
- VS2008
- protobuf-c
- C/C++
- php
- 설치제거
- phpmailer
- crashlog
- net
- Antialiasing
- M8200
- docker
- 한 번만 실행
- JavaScript
- PDA
- Today
- Total
~☆~ 우하하!!~ 개발블로그
[SpringBoot] FCM 메시지 발송 본문
Firebase 를 이용하여 앱에 푸시메시지를 발송하는 기능의 웹 프로젝트이다.
전제조건
Firebase 에 회원가입된 상태여야 한다.
Firebase 에 프로젝트를 생성하고, 해당 프로젝트의 모바일 앱(iOS 또는 Android) 이 개발된 상태여야 한다.
모바일 앱은 FCM 푸시 메시지 수신 기능이 구현되어 있어야 한다.
프로젝트 설정으로 이동한다.
서비스 계정 탭으로 이동한다.
Firebase Admin SDK 항목을 선택한다.
"새 비공개 키 생성" 버튼을 클릭하여 비공개 키를 생성한다.
"키 생성" 버튼을 클릭하면 파일이 다운로드된다.
다운로드된 파일은 learnfirebase-9fed8-firebase-adminsdk-6x1xl-c8415d6104.json 이다.
SpringBoot 프로젝트의 resources 디렉토리 아래에 firebase 라는 이름으로 디렉토리를 생성한 뒤에 이곳에 넣어주되, 파일의 이름은 learnfirebase-adminsdk-key.json 정도로 단순하게 변경해준다.
build.gradle 에 의존성 패키지를 추가한다.
위 정보는 Firebase Admin SDK 화면의 링크에서 얻었다.
우선 푸시메시지 데이터 객체부터 설계하자.
데이터 객체 클래스명은 MessageDto 로 한다.
푸시메시지를 위한 데이터 항목으로는 대상기기의 푸시토큰값, 푸시메시지의 제목과 본문내용 등 3가지 항목이다.
package com.woohahaapps.example.dto;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class MessageDto {
private String pushToken;
private String title;
private String body;
}
푸시메시지를 발송하는 서비스 클래스를 구현한다.
package com.woohahaapps.example.service;
import com.woohahaapps.example.dto.MessageDto;
import org.springframework.stereotype.Service;
@Service
public class FcmPushService {
public void sendMessage(MessageDto messageDto) {
}
}
SpringBoot 프로그램에서 Firebase 를 연동하여 푸시메시지를 발송하기 위해서는 Firebase 가 제공하는 api 를 이용해야 한다.
FCM(Firebase Cloud Messaging) API 는 아래 링크에서 소개되고 있다.
https://firebase.google.com/docs/reference/fcm/rest?hl=ko
서비스 엔드포인트는 https://fcm.googleapis.com 이다.
REST API 주소는 /v1/{parent=projects/*}/messages:send 이다.
데이터 전송 방식은 POST 이다.
REST API 리소스의 send 함수 링크를 클릭하면 {parent=projects/*} 를 어떻게 구성해야 하는지에 대한 방법이 있다.
REST API 주소에서 {parent=projects/*} 부분은 Firebase Console 의 프로젝트 설정 - 일반 에서 구할 수 있다.
최종적인 REST API 주소는 https://fcm.googleapis.com/v1/projects/learnfirebase-9fed8/messages:send 이다.
요청 본문 데이터 구조는 해당 문서 아래쪽에 다음과 같이 설명되고 있다.
RestTemplate 를 이용한 동기식 연동 방법
RestTemplate 은 Spring에서 제공하는 HTTP 요청/응답을 처리하는 데 사용되는 클래스입니다. 주로 RESTful 웹 서비스와 상호작용하기 위해 사용되며, 외부 API와 통신하거나 서버 간 데이터를 주고받는 데 적합한 도구이다. RestTemplate을 사용하면 HTTP GET, POST, PUT, DELETE 요청을 쉽게 보낼 수 있고, JSON이나 XML과 같은 응답을 객체로 변환하여 받을 수 있다.
RestTemplate 의 여러 메소드 중에서 exchange 를 사용해서 요청을 보내고 ResponseEntity 로 응답을 받을 수 있다. HttpHeaders 와 HttpEntity 를 사용해 요청 헤더를 추가할 수 있어 좀 더 유연하게 사용할 수 있다.
exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables)
아래 코드는 RestTemplate 의 exchange 메소드를 사용하여 FCM 발송 API 에 접속하여 요청을 보내고 응답을 받는 내용의 코드이다.
public void sendMessage(MessageDto messageDto) throws IOException {
RestTemplate restTemplate = new RestTemplate();
String message = makeMessage(messageDto);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + getAccessToken());
HttpEntity<String> entity = new HttpEntity<>(message, headers);
String API_URL = "https://fcm.googleapis.com/v1/projects/learnfirebase-9fed8/messages:send";
ResponseEntity<String> response = restTemplate.exchange(API_URL, HttpMethod.POST, entity, String.class);
System.out.println(response.getStatusCode());
}
sendMessage 가 파라미터로 받는 MessageDto 는 대상 기기의 FCM 토큰, 푸시메시지의 제목과 본문 으로 구성된 데이터형이다.
makeMessage 함수를 이용하여 FCM 메시지 API 용 데이터 구조로 변환을 처리하고 있다.
HttpHeaders 를 이용하여 FCM 메시지 API 를 사용하는데 필요한 인증 절차를 처리하고 있다.
makeMessage 함수와 getAccessToken 함수의 내용은 다음과 같다.
makeMessage 함수에서는 Message 데이터형식의 데이터를 만들어내고 있는데,
이 데이터형식은 FcmMessageDto 클래스로 아래와 같이 먼저 정의해둔다.
package com.woohahaapps.example.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class FcmMessageDto {
private boolean validateOnly;
private FcmMessageDto.Message message;
@Builder
@AllArgsConstructor
@Getter
public static class Message {
private FcmMessageDto.Notification notification;
private String token;
}
@Builder
@AllArgsConstructor
@Getter
public static class Notification {
private String title;
private String body;
private String image;
}
}
/**
* Firebase Admin SDK의 비공개 키를 참조하여 Bearer 토큰을 발급 받습니다.
*
* @return Bearer token
*/
private String getAccessToken() throws IOException {
String firebaseConfigPath = "firebase/learnfirebase-adminsdk-key.json";
GoogleCredentials googleCredentials = GoogleCredentials
.fromStream(new ClassPathResource(firebaseConfigPath).getInputStream())
.createScoped(List.of("https://www.googleapis.com/auth/cloud-platform"));
googleCredentials.refreshIfExpired();
return googleCredentials.getAccessToken().getTokenValue();
}
/**
* FCM 전송 정보를 기반으로 메시지를 구성합니다. (Object -> String)
*
* @param messageDto MessageDto
* @return String
*/
private String makeMessage(MessageDto messageDto) throws JsonProcessingException {
ObjectMapper om = new ObjectMapper();
FcmMessageDto fcmMessageDto = FcmMessageDto.builder()
.message(FcmMessageDto.Message.builder()
.token(messageDto.getPushToken())
.notification(FcmMessageDto.Notification.builder()
.title(messageDto.getTitle())
.body(messageDto.getBody())
.image(null)
.build()
).build()).validateOnly(false).build();
return om.writeValueAsString(fcmMessageDto);
}
FcmPushService 클래스의 전체 소스코드는 다음과 같다.
package com.woohahaapps.example.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.auth.oauth2.GoogleCredentials;
import com.woohahaapps.example.dto.FcmMessageDto;
import com.woohahaapps.example.dto.MessageDto;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.util.List;
@Service
public class FcmPushService {
public void sendMessage(MessageDto messageDto) throws IOException {
RestTemplate restTemplate = new RestTemplate();
String message = makeMessage(messageDto);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + getAccessToken());
HttpEntity<String> entity = new HttpEntity<>(message, headers);
String API_URL = "https://fcm.googleapis.com/v1/projects/learnfirebase-9fed8/messages:send";
ResponseEntity<String> response = restTemplate.exchange(API_URL, HttpMethod.POST, entity, String.class);
System.out.println(response.getStatusCode());
}
/**
* Firebase Admin SDK의 비공개 키를 참조하여 Bearer 토큰을 발급 받습니다.
*
* @return Bearer token
*/
private String getAccessToken() throws IOException {
String firebaseConfigPath = "firebase/learnfirebase-adminsdk-key.json";
GoogleCredentials googleCredentials = GoogleCredentials
.fromStream(new ClassPathResource(firebaseConfigPath).getInputStream())
.createScoped(List.of("https://www.googleapis.com/auth/cloud-platform"));
googleCredentials.refreshIfExpired();
return googleCredentials.getAccessToken().getTokenValue();
}
/**
* FCM 전송 정보를 기반으로 메시지를 구성합니다. (Object -> String)
*
* @param messageDto MessageDto
* @return String
*/
private String makeMessage(MessageDto messageDto) throws JsonProcessingException {
ObjectMapper om = new ObjectMapper();
FcmMessageDto fcmMessageDto = FcmMessageDto.builder()
.message(FcmMessageDto.Message.builder()
.token(messageDto.getPushToken())
.notification(FcmMessageDto.Notification.builder()
.title(messageDto.getTitle())
.body(messageDto.getBody())
.image(null)
.build()
).build()).validateOnly(false).build();
return om.writeValueAsString(fcmMessageDto);
}
}
FcmPushService 클래스의 테스트를 만들어서 푸시 메시지 발송을 테스트해보자.
package com.woohahaapps.example.service;
import com.woohahaapps.example.dto.FcmMessageDto;
import com.woohahaapps.example.dto.MessageDto;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class FcmPushServiceTest {
@Autowired
private FcmPushService fcmPushService;
@Test
void sendMessage() throws IOException {
MessageDto messageDto = MessageDto.builder()
.pushToken("dnChR27YIEaSgLuGt59dsZ:APA91bF67edTpQ5o6h0ujg_kSQUzDPQvlMT8AZxcMo0MZIxsxY58M5kC4pqvWFPd_EXkGz8WY3_eC7LszbwRobWvTVf8nft8IqvF7-H2u1PfTy2LXnUQWGg")
.title("테스트 메시지 제목입니다.")
.body("테스트로 발송하는 메시지입니다.")
.build();
fcmPushService.sendMessage(messageDto);
}
}
위와 같은 테스트코드로 기기에 설치한 앱에 푸시메시지가 정상적으로 들어오는 것을 확인하였다.
FcmPushService 에 작성한 또 다른 푸시메시지 발송 함수를 소개한다.
public void sendNotification(MessageDto messageDto) {
Notification notification = Notification.builder()
.setTitle(messageDto.getTitle())
.setBody(messageDto.getBody())
.build();
Message message = Message.builder()
.setToken(messageDto.getPushToken())
.setNotification(notification)
.build();
try {
String response = FirebaseMessaging.getInstance().send(message);
logger.info("Successfully sent message: {}", response);
} catch (Exception e) {
logger.error("An error occurred", e);
logger.error("Failed to send message");
}
}
public CompletableFuture<String> sendNotificationAsync(MessageDto messageDto) {
Notification notification = Notification.builder()
.setTitle(messageDto.getTitle())
.setBody(messageDto.getBody())
.build();
Message message = Message.builder()
.setToken(messageDto.getPushToken())
.setNotification(notification)
.build();
// ApiFuture 를 CompletableFuture 로 변환
return apiFutureToCompletable(FirebaseMessaging.getInstance().sendAsync(message))
.thenApply(response -> "Successfully sent message: " + response)
.exceptionally(e -> {
logger.error("An error occurred", e);
return "Failed to send message";
});
}
// ApiFuture 를 CompletableFuture 로 변환하는 메서드
private <T> CompletableFuture<T> apiFutureToCompletable(ApiFuture<T> apiFuture) {
CompletableFuture<T> completableFuture = new CompletableFuture<>();
apiFuture.addListener(() -> {
try {
completableFuture.complete(apiFuture.get());
} catch (Exception e) {
completableFuture.completeExceptionally(e);
}
}, Runnable::run);
return completableFuture;
}
FcmPushService 의 Full Source 는 아래와 같다.
package com.woohahaapps.example.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.api.core.ApiFuture;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.Notification;
import com.woohahaapps.example.dto.FcmMessageDto;
import com.woohahaapps.example.dto.MessageDto;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Slf4j
@Service
public class FcmPushService {
private static final Logger logger = LoggerFactory.getLogger(FcmPushService.class);
public void sendMessage(MessageDto messageDto) throws IOException {
RestTemplate restTemplate = new RestTemplate();
String message = makeMessage(messageDto);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + getAccessToken());
HttpEntity<String> entity = new HttpEntity<>(message, headers);
String API_URL = "https://fcm.googleapis.com/v1/projects/learnfirebase-9fed8/messages:send";
ResponseEntity<String> response = restTemplate.exchange(API_URL, HttpMethod.POST, entity, String.class);
System.out.println(response.getStatusCode());
}
/**
* Firebase Admin SDK의 비공개 키를 참조하여 Bearer 토큰을 발급 받습니다.
*
* @return Bearer token
*/
private String getAccessToken() throws IOException {
String firebaseConfigPath = "firebase/learnfirebase-adminsdk-key.json";
GoogleCredentials googleCredentials = GoogleCredentials
.fromStream(new ClassPathResource(firebaseConfigPath).getInputStream())
.createScoped(List.of("https://www.googleapis.com/auth/cloud-platform"));
googleCredentials.refreshIfExpired();
return googleCredentials.getAccessToken().getTokenValue();
}
/**
* FCM 전송 정보를 기반으로 메시지를 구성합니다. (Object -> String)
*
* @param messageDto MessageDto
* @return String
*/
private String makeMessage(MessageDto messageDto) throws JsonProcessingException {
ObjectMapper om = new ObjectMapper();
FcmMessageDto fcmMessageDto = FcmMessageDto.builder()
.message(FcmMessageDto.Message.builder()
.token(messageDto.getPushToken())
.notification(FcmMessageDto.Notification.builder()
.title(messageDto.getTitle())
.body(messageDto.getBody())
.image(null)
.build()
).build()).validateOnly(false).build();
return om.writeValueAsString(fcmMessageDto);
}
public void sendNotification(MessageDto messageDto) {
Notification notification = Notification.builder()
.setTitle(messageDto.getTitle())
.setBody(messageDto.getBody())
.build();
Message message = Message.builder()
.setToken(messageDto.getPushToken())
.setNotification(notification)
.build();
try {
String response = FirebaseMessaging.getInstance().send(message);
logger.info("Successfully sent message: {}", response);
} catch (Exception e) {
logger.error("An error occurred", e);
logger.error("Failed to send message");
}
}
public CompletableFuture<String> sendNotificationAsync(MessageDto messageDto) {
Notification notification = Notification.builder()
.setTitle(messageDto.getTitle())
.setBody(messageDto.getBody())
.build();
Message message = Message.builder()
.setToken(messageDto.getPushToken())
.setNotification(notification)
.build();
// ApiFuture 를 CompletableFuture 로 변환
return apiFutureToCompletable(FirebaseMessaging.getInstance().sendAsync(message))
.thenApply(response -> "Successfully sent message: " + response)
.exceptionally(e -> {
logger.error("An error occurred", e);
return "Failed to send message";
});
}
// ApiFuture 를 CompletableFuture 로 변환하는 메서드
private <T> CompletableFuture<T> apiFutureToCompletable(ApiFuture<T> apiFuture) {
CompletableFuture<T> completableFuture = new CompletableFuture<>();
apiFuture.addListener(() -> {
try {
completableFuture.complete(apiFuture.get());
} catch (Exception e) {
completableFuture.completeExceptionally(e);
}
}, Runnable::run);
return completableFuture;
}
}
FcmPushService 의 함수를 테스트하는 코드를 아래와 같이 작성해서 모두 푸시메시지가 잘 들어오는 것도 확인하였다.
package com.woohahaapps.example.service;
import com.google.api.core.ApiFuture;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.Message;
import com.woohahaapps.example.dto.FcmMessageDto;
import com.woohahaapps.example.dto.MessageDto;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@SpringBootTest
class FcmPushServiceTest {
@Autowired
private FcmPushService fcmPushService;
@Test
void sendMessage() throws IOException {
MessageDto messageDto = MessageDto.builder()
.pushToken("dnChR27YIEaSgLuGt59dsZ:APA91bF67edTpQ5o6h0ujg_kSQUzDPQvlMT8AZxcMo0MZIxsxY58M5kC4pqvWFPd_EXkGz8WY3_eC7LszbwRobWvTVf8nft8IqvF7-H2u1PfTy2LXnUQWGg")
.title("테스트 메시지 제목입니다.")
.body("테스트로 발송하는 메시지입니다.")
.build();
fcmPushService.sendMessage(messageDto);
}
@Test
void sendNotification() {
MessageDto messageDto = MessageDto.builder()
.pushToken("dnChR27YIEaSgLuGt59dsZ:APA91bF67edTpQ5o6h0ujg_kSQUzDPQvlMT8AZxcMo0MZIxsxY58M5kC4pqvWFPd_EXkGz8WY3_eC7LszbwRobWvTVf8nft8IqvF7-H2u1PfTy2LXnUQWGg")
.title("테스트 메시지 제목입니다2222.")
.body("테스트로 발송하는 메시지입니다2222.")
.build();
fcmPushService.sendNotification(messageDto);
}
@Test
void sendNotificationAsync() throws ExecutionException, InterruptedException {
// Arrange
MessageDto messageDto = MessageDto.builder()
.pushToken("dnChR27YIEaSgLuGt59dsZ:APA91bF67edTpQ5o6h0ujg_kSQUzDPQvlMT8AZxcMo0MZIxsxY58M5kC4pqvWFPd_EXkGz8WY3_eC7LszbwRobWvTVf8nft8IqvF7-H2u1PfTy2LXnUQWGg")
.title("테스트 메시지 제목입니다3333.")
.body("테스트로 발송하는 메시지입니다3333.")
.build();
// Act
CompletableFuture<String> resultFuture = fcmPushService.sendNotificationAsync(messageDto);
String result = resultFuture.get(); // Blocking get() for test simplicity
}
}
'개발환경' 카테고리의 다른 글
React 프로젝트 생성 (macOS) (0) | 2024.11.08 |
---|---|
pfSense 내부의 VM에서 Let's Encrypt 인증서 발급 및 갱신하기 (0) | 2024.11.07 |
오랜만에 건드려보는 IntelliJ spring boot 프로젝트 (신규) (0) | 2024.10.30 |
AWS SES (Simple Email Service) (0) | 2024.10.30 |
AWS 계정 가입하여 12개월 프리 티어 사용해보기 (0) | 2024.10.18 |