[spring] fcm server 측 구현, Firebase Cloud Messaging

orca·2023년 1월 12일
0

Spring

목록 보기
9/13
post-thumbnail

fcm은 firebase cloud message를 의미한다
클라이언트에게 푸시 알림을 보낼 수 있는 플랫폼인데,
간단하게 말하면 서버가 메세지를 생성해서 fcm 백엔드에 보내기 요청을 하면 클라이언트에게 알림이 간다

파이어베이스 프로젝트 설정

파이어베이스 콘솔로 접속해서 프로젝트를 만들어야 한다
또 해당 프로젝트에 FE 멤버도 초대해야함


프로젝트 설정 > 서비스 계정 > 서비스 계정 만들기

새 비공개 키 생성
스크린샷과 같이 .json 키 파일이 다운로드된다

프로젝트 설정 > 일반
프로젝트 ID를 기억하자

spring 기본 설정

spring project에도 기본 세팅을 해줘야 한다

firebase DOCS 를 참고했다

위에서 다운로드 받은 키 파일을
src/main/resources 하위에 import 해주기

build.gradle

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

application.yml

firebase:
  project-id: fir-d61fb
  key-path: fir-d61fb-firebase-adminsdk-58xi7-8db4ff70b2.json

FcmInitializer.java

서버가 firebase 서비스 계정임을 인증하는 작업이 필요하다
@Component : 서버 띄울 때 기본적으로 필요한 작업임으로 빈으로 등록해줬고,
@PostConstruct : WAS가 올라가면서 bean이 생성될 때 한번 초기화하도록 설정했다

@Slf4j
@Component
public class FcmInitializer {

    @Value("${firebase.key-path}")
    String fcmKeyPath;

    @PostConstruct
    public void getFcmCredential(){
        try {
            InputStream refreshToken = new ClassPathResource(fcmKeyPath).getInputStream();

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

            FirebaseApp.initializeApp(options);
            log.info("Fcm Setting Completed");
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

}

아마 서버가 started 되기 전에 아래와 같은 로그가 뜰 것이다

fcm 백엔드에 메세지 보내는 코드 작성

일단 서비스 dto 작성
클라이언트 측으로 보내고 싶은 정보를 다 담았다
FcmServiceDto.java

@Getter
@NoArgsConstructor
public class FcmServiceDto {
    private String username;
    private Long contentId;
    private NotifyType type;
    private String title;
    private String content;

    public static FcmServiceDto of(String username, Long contentId, NotifyType type, String title, String content){
        FcmServiceDto dto = new FcmServiceDto();
        dto.username = username;
        dto.contentId = contentId;
        dto.type = type;
        dto.title = title;
        dto.content = content;
        return dto;
    }
}

메세지 생성해서 fcm 서버로 보내는 부분이다
firebase-cloud-messaging DOCS 를 참고했다
클라이언트 측에서 푸시 알림 클릭시 액션을 구현할 수 있도록 관련 데이터와
연결된 동작을 담아 보낸다

연결된 동작은 ios는 category, aos는 click_action으로 지정할 수 있음

public void sendByToken(FcmServiceDto dto){
        String token = getToken(dto.getUsername());

        Message message = Message.builder()
                .setToken(token)
                .setNotification(
                        Notification.builder()
                                .setTitle(dto.getTitle())
                                .setBody(dto.getContent())
                                .build()
                )
                .setAndroidConfig(
                        AndroidConfig.builder()
                                .setNotification(
                                        AndroidNotification.builder()
                                                .setTitle(dto.getTitle())
                                                .setBody(dto.getContent())
                                                .setClickAction("push_click")
                                                .build()
                                )
                                .build()
                )
                .setApnsConfig(
                        ApnsConfig.builder()
                                .setAps(Aps.builder()
                                        .setCategory("push_click")
                                        .build())
                                .build()
                )
                .putData("type",dto.getType().name())
                .putData("contentId",dto.getContentId().toString())
                .build();

        try {
            String response = FirebaseMessaging.getInstance().send(message);
            log.info("FCMsend-"+response);
        } catch (FirebaseMessagingException e) {
            log.info("FCMexcept-"+ e.getMessage());
        }
    }

fcm 서버에 전달될 json은 결국 아래와 같을 것이다

POST https://fcm.googleapis.com/v1/projects/fir-d61fb/messages:send HTTP/1.1

Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
  "message":{
     "token":"fpO9B9HkQs-5nUrb0t-2de:APA91bHSK_JgiUJDL4re0dSfEyVAgNwp7C6Cugc05vUoXxg3WiHv6fubih7_czuJggVq6Yl4DUwwQIlzvz2qkVHh7IGPwQnQprd6ZN-6MXmo1jfogRX1t4WQK5ABmT-XaKOkVw_Wa39q",
     "data":{
         "type":"COMMENT",
         "contentId":"2"
     },
     "notification":{
       "title":"타이틀",
       "body":"콘텐츠"
     },
     "android":{
       "notification":{
         "title":"타이틀",
         "body":"콘텐츠"
         "click_action":"push_click"
       }
     },
     "apns":{
       "payload":{
         "aps":{
           "category" : "push_click"
         }
       }
     }
   }

참고

getToken() 부분은 클라이언트가 등록한 fcmToken을 레파지토리에서 가져오는 부분이다

따라서 FcmService.java 는 아래와 같다

@Slf4j
@Service
public class FcmService {

    private final TokenRepository tokenRepository;

    @Autowired
    public FcmService(TokenRepository tokenRepository) {
        this.tokenRepository = tokenRepository;
    }

    public void sendByToken(FcmServiceDto dto){
        String token = getToken(dto.getUsername());

        Message message = Message.builder()
                .setToken(token)
                .setNotification(
                        Notification.builder()
                                .setTitle(dto.getTitle())
                                .setBody(dto.getContent())
                                .build()
                )
                .setAndroidConfig(
                        AndroidConfig.builder()
                                .setNotification(
                                        AndroidNotification.builder()
                                                .setTitle(dto.getTitle())
                                                .setBody(dto.getContent())
                                                .setClickAction("push_click")
                                                .build()
                                )
                                .build()
                )
                .setApnsConfig(
                        ApnsConfig.builder()
                                .setAps(Aps.builder()
                                        .setCategory("push_click")
                                        .build())
                                .build()
                )
                .putData("type",dto.getType().name())
                .putData("contentId",dto.getContentId().toString())
                .build();

        try {
            String response = FirebaseMessaging.getInstance().send(message);
            log.info("FCMsend-"+response);
        } catch (FirebaseMessagingException e) {
            log.info("FCMexcept-"+ e.getMessage());
        }
    }

    private String getToken(String username) {
        Token token = tokenRepository.findByUsername(username).orElse(null);
        return token.getTokenValue();
    }

}

테스트

api를 만들어서 테스트해봤다
이게 참 힘든게 ㅎ 서버 쪽에서 클라이언트 토큰 없이 테스트할 수 있는 방법이 없다

@GetMapping("/push")
    public void fcmTest(@AuthenticationPrincipal UserDetailsImpl userDetails){
        FcmServiceDto dto = FcmServiceDto.of(userDetails.getUsername(),1L, NotifyType.NOTICE,"제목", "콘텐츠");
        fcmService.sendByToken(dto);
    }

토큰값을 null로 두고 테스트한 경우
토큰이나 토픽이 하나라도 지정되어야 한다는 exception이 난다
java.lang.IllegalArgumentException: Exactly one of token, topic or condition must be specified

만료된 토큰으로 테스트 한 경우
Requested entity was not found. 가 뜬다
서비스에 등록하지 않은 클라이언트 토큰일 경우 나는 에러인데,
만료된 토큰이니까 당연하다

두 경우 다 서버 쪽에서는 fcm 백엔드에 메세지를 잘 전달한 것이므로 받으면 잘 구현되었다는 걸로 생각하자

이건 동일한 코드로 진행했을 때 AOS 측으로 전달된 fcm 메세지이다

0개의 댓글