이어서 구현한 것이다.
Front는 플러터로 이미 구현이 되어 있고, 나는 토큰을 이용해 알림을 전송하는 로직을 작성하고 푸시를 보내면 된다.
여기에서 나는 백엔드 개발 서버입장을 구현하면 된다. 헷갈리지 말아야 할 것은 FCM 백엔드가 아닌 자체 앱 서버이다.
그러니까, 나는 유저의 토큰을 받아서 알림을 보내달라!는 요청을 FCM 백엔드에게 보내면 되는 것이다.
즉, 내가 한 흐름은 다음과 같다.
1. 사용자마다 고유한 기기 토큰이 있는데, 이 Token을 Client가 앱 서버(나)에게 보낸다.
2. 서버에서 FCM 서버에 푸시 알림을 해달라고 요청한다.
3. 사용자에게 알림이 전송된다.
https://console.firebase.google.com/
접속해서 프로젝트를 생성한다.
나는 HTTP v1으로 구현했다.
필요한 구성은 다음과 같다.
https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?hl=ko#Notification
여기에 들어가서 보면 어떻게 Dto를 짜야하는지 나와 있다.
해당 양식에 맞게 짜지 않으면, 오류가 날 수 있기에 저대로 알림 템플릿을 짜야한다.
json 형식으로 보내야하고 받아야하기 때문에 inner class 처럼 하면 된다.
@Builder
@AllArgsConstructor
@Getter
public class FCMMessageDto {
private boolean validateOnly;
private Message message;
@Builder
@AllArgsConstructor
@Getter
public static class Message {
private Notification notification;
private String token;
private Data data;
}
@Builder
@AllArgsConstructor
@Getter
public static class Notification {
private String title;
private String body;
}
@Builder
@AllArgsConstructor
@Getter
public static class Data{
private String name;
private String description;
}
}
이런 식으로 했고, 이미지가 필요하다면 적절히 넣어주면 된다. 공식문서에 다 나와 있다.
빌더 패턴을 쓴 이유는?
빌더 패턴은 객체 생성을 보다 유연하게, 가독성 있게, 그리고 여러 옵션을 갖춘 객체를 생성하기 위한 패턴이다.
FCM 메시지 Dto는 많은 매개변수를 가졌고, 상황마다 그 구성이 다르다.
매개변수가 많거나 선택적인 경우, 생성자의 매개변수 순서를 기억하기 어려운 경우에 빌더 패턴을 사용하면 객체를 더 직관적으로 생성할 수 있다.
나는 원래 컨트롤러 상에서 3개를 RequestBody를 받는 걸로 짰는데....
@RequestBody는 한 메소드 안에 여러개의 RequestBody를 받는 거가 안되는 걸 나중에 깨달았다. 그 쉬운걸..ㅠ
암튼
@RequestBody String token,
@RequestBody String title,
@RequestBody String body
이렇게 하면 안된다. 그러면 400대 에러가 뜬다. 요청을 잘못 보냈기 때문!
즉, RequestBody로 한번에 처리하기 위해서는 requestDto가 필요하다.
public class PushNotificationRequest {
private String token;
private String title;
private String body;
}
이런식으로 짜면 된다.
그러면 요청하기 위한 Dto, 응답 템플릿에 맞는 Dto가 다 만들어졌으니
이제 서비스 단을 짜면 된다.
만들어야 하는 로직은 다음과 같다.
private String getAccessToken() throws IOException {
String firebaseConfigPath = "firebase/발급받은 키파일.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();
}
Firebase 서비스 키를 사용하여 GoogleCredentials 객체를 만들고, 해당 키를 이용하여 액세스 토큰을 얻어오는 것이다.
firebaseConfigPath는 Firebase 서비스 키 파일의 경로를 나타낸다. 이 경로에서 서비스 키 파일을 가져와서 이를 사용하여 Firebase 서비스와 통신할 것이다. -> 자신이 해당 키 파일을 넣어둔 데를 잡아주면 된다.
GoogleCredentials.fromStream()은 Firebase 서비스 키 파일을 읽어와 GoogleCredentials 객체를 생성한다. 이는 Firebase 서비스에 인증된 토큰을 얻기 위한 준비 작업이다.
createScoped()는 GoogleCredentials 객체에 인증 범위(scope)를 부여한다. 여기서는 Cloud Platform에 대한 권한(scope)을 지정하고 있다.
googleCredentials.refreshIfExpired()는 토큰의 만료 여부를 확인하고, 만료된 경우 토큰을 갱신한다.
googleCredentials.getAccessToken().getTokenValue()는 Firebase 서비스에 액세스하기 위한 액세스 토큰 값을 반환합니다. 이 토큰은 Firebase 서비스에 인증된 요청을 보낼 때 사용된다.
GoogleCredentials의 객체를 통해서, FCM을 이용할수 있는 권한이 부여된 Oauth2의 AccessToken을 받는다.
refreshIfExpired()
호출: AccessToken은 일정 기간이 지나면 만료된다. refreshIfExpired()
를 호출하여 AccessToken이 만료되었는지 확인하고, 필요한 경우에만 갱신한다.
getAccessToken()
메서드 호출: getAccessToken()
메서드를 사용하여 GoogleCredentials에서 AccessToken을 얻는다. 이 메서드는 AccessToken
객체를 반환한다.
getTokenValue()
호출: getTokenValue()
를 호출하여 최종적으로 AccessToken의 값을 얻어온다. 이 값은 나중에 FCM 요청을 보낼 때 Header에 설정하여 인증에 사용된다.
이러한 절차를 통해 Firebase 서비스에 인증된 AccessToken을 얻을 수 있으며, 이 토큰을 이용하여 FCM 요청의 인증에 사용할 수 있게 된다. 요청을 보낼 때는 HTTP 요청의 헤더에 Authorization: Bearer <AccessToken>
형식으로 토큰을 설정하여 FCM에 요청을 보내면 된다.
private String makeMessage(String token, String title, String body) throws JsonParseException, JsonProcessingException {
FCMMessageDto fcmMessage = FCMMessageDto.builder()
.message(FCMMessageDto.Message.builder()
.token(targetToken)
.notification(FCMMessageDto.Notification.builder()
.title(title)
.body(body)
.build()
).build()).validateOnly(false).build();
return objectMapper.writeValueAsString(fcmMessage);
}
https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages/send?hl=ko
이 템플릿에 맞는 형식이어야지 된다.
위에 말한 Dto를 생성했다면 문제 없다.
이 메소드는 토큰에 해당하는 디바이스로 푸시 알림을 전송하는 것을 요청한다.
그리고 여기서 바디에는 푸시알림의 내용이 들어간다. 스트링 형태를 메시지 템플릿에 맞게 변환해서 보내는 것이다.
public void sendMessageTo(String targetToken, String title, String body) throws IOException {
String message = makeMessage(targetToken, title, body);
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = RequestBody.create(message,
MediaType.get("application/json; charset=utf-8"));
Request request = new Request.Builder()
.url(API_URL)
.post(requestBody)
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken())
.addHeader(HttpHeaders.CONTENT_TYPE, "application/json; UTF-8")
.build();
Response response = client.newCall(request).execute();
}
이 메서드는 OkHttp 라이브러리를 사용하여 지정된 URL에 HTTP POST 요청을 보내는 기능을 수행한다.
메시지 생성: makeMessage
메서드를 사용하여 targetToken
, title
, body
를 이용해 JSON 형식의 메시지를 생성한다.
HTTP 클라이언트 설정: OkHttp 라이브러리의 OkHttpClient
를 생성하여 HTTP 요청을 처리할 클라이언트를 설정한다.
요청 구성: 생성된 JSON 메시지를 요청 본문으로 설정하고, 필요한 헤더(인증 및 콘텐츠 유형)와 함께 HTTP POST 요청을 만든다.
요청 전송: 생성된 요청을 실행하여 지정된 URL로 요청을 보낸다.
즉, 주어진 정보를 사용하여 API 엔드포인트로 메시지를 보내는 기능을 제공한다.
막간 꿀팁 : 이렇게 이미지 넣으면 크기 조절 쌉가능
<img src="" width="" height="">