Spring/FCM

FCM 알림 전송

초코chip 2024. 4. 1. 15:19

배경

프로젝트 진행 중에 스프링에서 프론트(앱)으로 알림을 보내야하는 경우가 생겼음

그래서 

 

 

개념

 

구현 순서 요약

  1. Firebase 프로젝트 생성
  2. restTemplate 준비
  3. 프론트에 알림을 전송하는 Util 클래스 작성
    • Firebase 의존성 추가
    • resource 폴더 + .gitignore에 .json 파일 추가
    • 설정파일(application.yml)에 프로젝트 id 추가
    • config 패키지에 FirebaseConfig 클래스 작성
    • common/fcm 패키지에 FCMessage DTO 작성
    • common/fcm 패키지에 FCMUtil 클래스 작성
  4. 프론트에서 디바이스 토큰을 받아 DB에 저장하는 API 코드 작성
    • domain 패키지에 FCMToken Entitiy 작성
    • 디바이스 토큰 저장 API 작성  (repository + service + request DTO + controller)
  5. 알림 관련 API 코드 작성
    • domain 패키지에 Notification Entitiy 작성 
    • 알림 리스트 조회 API 작성  (repository + service + response DTO + controller)
    • 알림 삭제 API  (repository + service  + controller)

 

1. Firebase 프로젝트 생성

https://console.firebase.google.com/u/0/

 

로그인 - Google 계정

이메일 또는 휴대전화

accounts.google.com

 

프로젝트 생성 후, 설정 -> 서비스 계정 -> Admin SDK 구성 스니펫을 자바를 선택하여 다운로드

설정 -> 서비스 계정 자바 -> 새 비공개 키 생성

 

설정 -> 일반 -> 프로젝트 ID 기억

 

2. restTemplate 준비

config/RestTemplateConfig 클래스 작성

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

 

 

3. 프론트에 알림을 전송하는 Util 클래스 작성

Firebase 의존성 추가

dependencies {
	....
    
	implementation 'com.google.firebase:firebase-admin:9.2.0'
}

 

resource 폴더 + .gitignore에 .json 파일 추가

위에서 발급받은 .json 파일을 아래에 추가 ( 이때, 이름은 상관 없음 )

resource 폴더 추가 .gitignore에 추가

 

 

설정파일(application.yml)에 프로젝트 id 추가

위에서 봤던 프로젝트 id를 저장

 

config 패키지에 FirebaseConfig 클래스 작성

위에서 저장한 .json 파일의 경로를 입력해야함

@Configuration
public class FirebaseConfig {

    @PostConstruct
    public void init(){
        try{
            FileInputStream serviceAccount =
                    new FileInputStream("src/main/resources/firebaseKey.json");
            FirebaseOptions options = new FirebaseOptions.Builder()
                    .setCredentials(GoogleCredentials.fromStream(serviceAccount))
                    .build();
            FirebaseApp.initializeApp(options);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

 

common/fcm 패키지에 FCMessage DTO 작성

알림 서버에 전송할 메세지(DTO) 내용을 작성해야함

 

메세지 형식

{
  "validateOnly" : "false",
  "message": {
    "token": "target_device_token_here",
    "notification": {
      "title": "알림 제목",
      "body": "알림 내용",
      "img": "img url"
    },
    "data": {
      "url": "알림을 눌렀을때 이동할 페이지 url"
    }
  }
}

 

코드

public record FCMessage(boolean validateOnly, Message message) {

    public record Message(String token, Notification notification, Data data) {
    }

    public record Notification(String title, String body) {
    }

    public record Data(String url) {
    }
}

 

common/fcm 패키지에 FCMUtil 클래스 작성

@Component
@RequiredArgsConstructor
@Slf4j
public class FCMUtil { ... }

FCM 메세지 생성 메서드 - makeMessage

아래 정보를 입력받아 FCMessage(DTO)를 생성한 후 json 형식으로 변경

  • 디바이스 토큰(String), 알림 제목(String), 알림 내용(String), 알림 클릭시 이동 위치(String)
private final ObjectMapper objectMapper;

private String makeMessage(String targetToken, String title, String body, String targetUrl) {
    try {
        FCMessage fcMessage = new FCMessage(false, new FCMessage.Message(
                        targetToken,
                        new FCMessage.Notification(title, body),
                        new FCMessage.Data(targetUrl)
                ));

        return objectMapper.writeValueAsString(fcMessage);
    } catch (JsonProcessingException e) {
        throw new RuntimeException("FCM 메세지 내용 생성 실패");
    }
}

 

 

AccessToken 발급 메서드 - getAccessToken

FCM 알림 요청을 보낼 때, AccessToken을 발급받아 헤더에 추가해서 요청을 보내야 한다.

private static final String GOOGLE_CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
private final String firebaseConfigPath = "FCMAccountKey.json";

private String getAccessToken(){
    try {
        GoogleCredentials googleCredentials = GoogleCredentials
                .fromStream(new ClassPathResource(firebaseConfigPath).getInputStream())
                .createScoped(List.of(GOOGLE_CLOUD_PLATFORM_SCOPE));

        googleCredentials.refreshIfExpired();
        return googleCredentials.getAccessToken().getTokenValue();
    } catch (IOException e) {
        throw new RuntimeException("AccessToken 얻기 실패");
    }
}

 

 

FCM 메시지 전송 메서드 - sendMessageTo

HTTP 요청 메시지 생성 후, restTemplate을 통해 Firebase 프로젝트에 요청 전송

  • body: makeMessage()를 통해 생성
  • header: getAccessToken()을 통해 생성
@Value("${fcm.project-name}")
private String projectName;
private static final String API_URL = "https://fcm.googleapis.com/v1/projects/%s/messages:send";

private final RestTemplate restTemplate;

public void sendMessageTo(String targetToken, String title, String body, String targetUrl) {
    try {
        String message = makeMessage(targetToken, title, body, targetUrl);

        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(getAccessToken());
        headers.setContentType(MediaType.APPLICATION_JSON);

        HttpEntity<String> entity = new HttpEntity<>(message, headers);

        ResponseEntity<String> response = restTemplate.exchange(
                String.format(API_URL, projectName),
                HttpMethod.POST,
                entity,
                String.class
        );

        log.info("FCM Response: {}", response.getBody());
    } catch (Exception e) {
        throw new RuntimeException("FCM 전송 실패"); //custom exception 으로 변경 가능
    }
}

 

 

4. 프론트에서 디바이스 토큰을 받아 DB에 저장하는 API 코드 작성

spring-data-jpa 의존성 추가해서 해야함

domain 패키지에 FCMToken Entitiy 작성

  • 디바이스 토큰 저장 API 작성  (repository + service + request DTO + controller)

 

 

참고자료

https://velog.io/@dionisos198/스프링-푸시-알림-구현예제FCM-사용