컴퓨터과학/Spring

Firebase Cloud Message(FCM) 스프링 부트 프로젝트에 적용하기

waspy 2022. 6. 20. 14:48

FCM이란?☁️

Firebase 클라우드 메시징(FCM)은 무료로 메시지를 안정적으로 전송할 수 있는 교차 플랫폼 메시징 솔루션입니다. - firebase 공식 홈페이지 참고문서

이 글에선 FCM에 대한 원리 설명, 설정보다 스프링 프로젝트에 적용하는 과정에 대해 설명하고자 한다.
초기설정 방법과 원리설명 등은 공식문서에 잘 정리되어 있으니 링크를 참고하길 바란다.

스프링 부트 프로젝트에 적용하기

요약

스프링 프로젝트에서의 FCM 사용 방법은 다음과 같다
1. 유저 테이블에 FCM 토큰 저장하는 컬럼 추가
2. 토큰 저장하는 REST API 생성
3. 특정 유저의 저장된 토큰으로 알림메시지 보내는 FCM 서비스 구현 +
4. 알림을 보내기 원하는 동작의 컨트롤러에서 비동기로 FCM 서비스 호출 +
5. 토큰의 유무, 유효 토큰 여부를 따져 에러 핸들링 후 전송 +

본 글에선 + 에 해당하는 부분만 설명한다. 1,2는 일반 REST API를 구현하는 방법과 동일하니 직접 구현해보길 바란다.

인증키 저장 및 설정

파이어베이스 프로젝트를 추가한 뒤
프로젝트 설정 - 서비스 계정 에서 Firebase Admin SDK 구성 스니펫 비공개 키를 다운받는다.
스프링에 적용할 것 이기 때문에 언어는 자바 로 설정한다.

다운받은 파일 .json을 spring 프로젝트의 resources 폴더 안에 넣어둔다.

이후 application.yml 에 해당 파일을 참조하기 위한 환경변수로 다음과 같은 설정을 추가한다.

fcm:
  certification: clip-b0e37-firebase-adminsdk-d0p4v-f8da8698c3.json

의존성 설정

본 프로젝트에선 gradle로 의존성을 관리한다
build.gradle에 다음 내용을 추가하여 firebase fcm 관련 라이브러리 의존성을 추가한다.

implementation 'com.google.firebase:firebase-admin:6.8.1'

파이어베이스 설정 초기화

firebase 패키지를 추가하고, FCMInitializer.class 클래스 파일을 추가한다.
파이어베이스 설정 초기화는 다음과 같다.

@Component
@Slf4j
public class FCMInitializer {

    @Value("${fcm.certification}")
    private String credential;

    @PostConstruct
    public void initialize(){
        ClassPathResource resource = new ClassPathResource(credential);

        try (InputStream stream = resource.getInputStream()) {
            FirebaseOptions options = FirebaseOptions.builder()
                    .setCredentials(GoogleCredentials.fromStream(stream))
                    .build();
ㄴ
            if (FirebaseApp.getApps().isEmpty()) {
                FirebaseApp.initializeApp(options);
                log.info("FirebaseApp initialization complete");
            }
        }catch (Exception e){
            e.printStackTrace();
            throw new ApiException(ExceptionEnum.INTERNAL_SERVER_ERROR);
        }

    }
}

비공개키를 json파일에서 읽어와 firebase 설정을 초기화 하였다.
@PostConstruct 어노테이션을 적어 스프링 실행단계에서 실행된다.

++ 여기서 ApiException은 커스텀하게 만든 에러이므로 이 부분만 변경하면 된다.

서비스 구현

FCM에서 메시지는 두가지 유형이 있다

  1. Message
  2. Notification

1은 데이터 바디만 전달하며, 푸시알람 기능이 아닌 포그라운드에서 이용하기에 적합한 메시지를 전달한다.

2는 알림과 데이터바디를 함께 전달하며, 백그라운드에서 이벤트 리스너를 통해 푸시알림을 구현한다. 포 그라운드에서 메시지 수신도 역시 가능하긴하다.

본 글에선 1을 이용해 채팅방을 구현, 2를 이용해 푸시알림 기능을 구현할 예정이다.

알림전송은 다음과 같다.

    @Async
    @Transactional
    public void sendNotification(UserVO user, String title, String body, String route) {

        if(user.getFcm() == null)
            return;

        Notification notification = new Notification(title, body);

        Message message = Message.builder()
                .setNotification(notification)
                .setToken(user.getFcm())
                .putData("route", route)
                .build();

        try{
            String response = FirebaseMessaging.getInstance().send(message);

            NoticeVO notice = NoticeVO.builder()
                    .user(user)
                    .content(body)
                    .route(route)
                    .state(1)
                    .title(title)
                    .build();

            noticeUpdateService.save(notice);
        }catch (Exception e){
            log.warn(user.getEmail() + ": 알림 전송에 실패하였습니다.");
        }


    }
  1. @Async를 이용해 비동기로 동작하도록 설정하였다
    스프링에서 비동기를 이용하는 방법은 다음글을 참고하면 된다 아직안씀
  2. Notification에는 title, body만 들어가면 된다. 위 함수에선 알림을 통해서 이동할 route를 추가로 body에 넣어주었다.
  3. UserVO는 유저의 JPA 엔티티로, fcm은 토큰을 의미하며 별도 rest api를 통해 저장된다. 이부분은 본인의 프로젝트에 알맞게 별도로 토큰을 가져오는 로직을 짜면 된다.
  4. 알림을 보낸 뒤 NoticeVO를 추가로 서버 db에 저장하는 로직이 있다. 이부분 또한 개별 구현하거나 생략하면 된다.

알림이 아닌 데이터만 전송은 message에서 setNotification만 생략하면 되니 생략하겠다.

위에서 구현한 메소드를 이용해 컨트롤러에서 호출할 메소드를 다음과 같이 구현해보았다.

    @Async
    @Transactional
    public void sendItemActivatedNotice(Long itemIdx){
        ItemVO item = itemFindService.findByIdx(itemIdx);

        sendNotification(item.getOwner(),
                "아이템이 활성화 되었습니다.",
                "아이템 " + item.getName() + "이 활성화 되었습니다.",
                "/");
    }

아이템 엔티티를 가져와 아이템의 소유주에게 알림을 보내는 함수이다.
트랜잭션 내부에서 동작하여 jpa로 아이템의 유저를 데려와 유저의 fcm 토큰으로 알림을 보낸다. route는 "/" 의미없는 값으로 작성하였다.

컨트롤러에 적용

아이템을 활성화 하면 아이템의 유저에게 알림을 보내는 로직을 위에서 작성하였다,
컨트롤러 단에서 호출하는 부분은 다음과 같다.

    @PutMapping("/v2/admin/items/{item-idx}/activation")
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    public ResponseEntity<MessageDTO> activateItem(
            @PathVariable(value = "item-idx") Long itemIdx
    ) {
        itemUpdateService.activateItem(itemIdx, true);
        fcmService.sendItemActivatedNotice(itemIdx);

        return new ResponseEntity<>(new MessageDTO("item activated"), HttpStatus.OK);
    }

fcmService의 sendItemActivatedNotice를 호출하였다.
이 메소드는 비동기로 작동하므로 딜레이 없이 즉시 message를 리턴하게 된다.

클라이언트 구현

웹 클라이언트에서 푸시알림을 수신하는 방법은 다음을 참고하길 바란다.

공식문서

구현한 컨트롤러의 api를 호출하면 클라이언트에서 다음과 같은 푸시알림을 확인할 수 있다.

반응형