[Spring] 나만의 게시판 만들기 6 - FCM 설정

최진민·2022년 2월 25일
0

게시판 만들기

목록 보기
6/9
post-thumbnail

FCM 를 활용하여 댓글 알림 기능을 구현하고자 했습니다.

FCM ?


< 참고 > https://firebase.google.com/docs/cloud-messaging?hl=ko

  • 정의 : FCM (Firebase Cloud Messaging) : Firebase의 API를 활용하여 클라이언트에게 메시지를 전송합니다.
  • 주요 기능
    • 알림 또는 데이터 메시지 전송
      • 알림 메시지를 보내기도 하며, 애플리케이션 내에서 수행되는 비즈니스 로직을 거친 데이터를 전송할 수도 있습니다.
    • 다양한 메시지 Targeting
      • 3가지 방법으로 클라이언트에게 메시지를 전송할 수 있습니다.
        • 1) 단일 기기 (token) - 현재의 프로젝트에서 사용할 방법
          • 특정 기기 한 대(한 클라이언트)에 메시지를 전송
        • 2) 여러 기기 (tokens)
          • 여러 기기(여러 클라이언트)에 메시지를 전송
        • 3) 주제 구독 기기 (topic)
          • 주제를 선정해 해당 주제를 구독한 기기(클라이언트)들에게 전송
    • 클라이언트 앱에서 메시지 전송
      • 클라이언트의 기기에서 서버로 전송도 가능합니다.
  • 메시지 송수신을 위한 2가지 구성 요소
    • 메시지 작성, 타켓팅, 전송할 환경(앱 서버, Firebase용 Cloud Functions)
    • 메시지를 수신할 수 있는 앱(Android, IOS, 웹(js))

구현 (서버 사이드)

< 참고 > https://firebase.google.com/docs/cloud-messaging/server?hl=ko


프로젝트 생성 및 앱 등록

  • https://console.firebase.google.com/?hl=ko 에서 프로젝트를 생성하여 앱(IOS, Android, Web)을 등록할 수 있습니다.
  • 프로젝트를 생성한 후 프로젝트 설정서비스 계정 탭에서 새로운 비공개 키 생성으로 .json 파일을 다운로드 할 수 있습니다.
    • 해당 프로젝트는 서버 사이드만을 구현하기 때문에 별도로 앱(프론트)에 관련한 내용은 직접 해보길 권장드립니다.
  • jinminboard-firebase-adminsdk-9ataf-770210e8eb.json
    {
      "type": "service_account",
      "project_id": "jinminboard",
      "private_key_id": "{private_key_id}",
      "private_key": "{private_key}",
      "client_email": "firebase-adminsdk-9ataf@jinminboard.iam.gserviceaccount.com",
      "client_id": "108834495148772815532",
      "auth_uri": "https://accounts.google.com/o/oauth2/auth",
      "token_uri": "https://oauth2.googleapis.com/token",
      "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
      "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-9ataf%40jinminboard.iam.gserviceaccount.com"
    }
    • private 관련된 내용은 숨김 필수!!

설정

< 참고 > https://firebase.google.com/docs/cloud-messaging/migrate-v1?hl=ko

Firebase 서비스에 대한 서버 요청을 승인하기 위해서 아래 방법들을 조합하여 사용할 수 있습니다.

  • Google 애플리케이션 기본 사용자 인증 정보(ADC)
  • 서비스 계정 JSON 파일
  • 서비스 계정에서 생성된 수명이 짧은 OAuth 2.0 액세스 토큰
  • FCM의 설정 파일입니다.
    @Configuration
    public class FirebaseConfig {
    
        @Bean
        public GoogleCredentials getGoogleCredentials() throws IOException {
            return GoogleCredentials
                    .fromStream(new ClassPathResource("firebase/jinminboard-firebase-adminsdk-9ataf-770210e8eb.json").getInputStream())
                    .createScoped(Arrays.asList("https://www.googleapis.com/auth/cloud-platform"));
        }
    
        @Bean
        public FirebaseApp firebaseApp() throws IOException {
            FirebaseOptions options = FirebaseOptions.builder()
                    .setCredentials(getGoogleCredentials())
                    .build();
    
            return FirebaseApp.initializeApp(options);
        }
    
        //비동기 통신을 위함
        @Bean
        public ListeningExecutorService firebaseAppExecutor() {
            return MoreExecutors.newDirectExecutorService();
        }
    
        @Bean
        public OkHttpClient okHttpClient() {
            return new OkHttpClient();
        }
    }
    • @Configuration : 클래스를 설정 파일로 저장(in Bean Factory)합니다.
    • 위의 설정 파일은 ADC를 사용한 사용자의 인증 정보를 제공하도록 합니다.
      • getGoogleCredentials() : Google의 인증 라이브러리와 Firebase 사용자 인증 정보를 사용하여 인증 정보를 반환하도록 합니다.
      • firebaseApp() : 반환된 Google의 인증 정보를 통해 Firebase의 설정을 초기화 합니다.
    • @Bean : 메서드를 빈 타입으로 팩토리에 정의합니다. (싱글톤)

전송 서비스 구현

< 참고 > https://firebase.google.com/docs/cloud-messaging/send-message?hl=ko

메시지를 다음과 같은 타겟 유형으로 전송할 수 있습니다.

  • 주제 이름
  • 조건
  • 기기 등록 토큰
  • 기기 그룹 이름(기존 프로토콜 및 Node.js용 Firebase Admin SDK만 해당)
  • FcmMessage
    @Getter
    public class FcmMessage {
    
        @Key("validate_only")
        @JsonIgnore
        private boolean validateOnly;
    
        // Message 에 해당하는 데이터는 많지만,
        // 사용할 데이터는 notification(Notification), token(String) 뿐
        // Notification => title(String), body(String)
    
        @Key("message")
        private Message message;
    
        @Builder
        public FcmMessage(boolean validateOnly, Message message) {
            this.validateOnly = validateOnly;
            this.message = message;
        }
    }
    • 주석에 설명되어 있는 대로, Message 클래스에는 수많은 데이터가 담겨 있지만, 필요한 데이터만 사용하기 위해 FcmMessage 객체를 생성하도록 클래스를 작성했습니다.
  • FCM을 사용하기 위한 서비스 클래스 (FirebaseCloudMessageService)
    @Slf4j
    @Service
    @RequiredArgsConstructor
    public class FirebaseCloudMessageService {
    
        private static final String API_URI = "https://fcm.googleapis.com/v1/projects/jinminboard/messages:send";
    
        private final GoogleCredentials googleCredentials;
        private final OkHttpClient client;
        private final ObjectMapper objectMapper;
    
        /**
         * `targetToken`에 해당하는 기기로 푸시 알림 전송 요청
         * (targetToken 은 프론트 사이드에서 얻기!)
         */
        public void sendMessageTo(String targetToken, String title, String body) throws FirebaseMessagingException, IOException, ExecutionException, InterruptedException {
    
            //Request + Response 사용
            /*RequestBody requestBody = getRequestBody(makeFcmMessage(targetToken, title, body));
            Request request = getRequest(requestBody);
            Response response = getResponse(request);
            log.info(response.body().string());*/
    
            //FirebaseMessaging 사용
            Message message = makeMessage(targetToken, title, body);
            String response = FirebaseMessaging.getInstance().send(message);
            log.info(response);
    
            //비동기
            String asyncMessage = FirebaseMessaging.getInstance().sendAsync(message).get();
    
        }
    
        private Response getResponse(Request request) throws IOException {
            return client.newCall(request).execute();
        }
    
        private Request getRequest(RequestBody requestBody) {
            return new Request.Builder()
                    .url(API_URI)
                    .post(requestBody)
                    .addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + googleCredentials.getAccessToken().getTokenValue())
                    .addHeader(HttpHeaders.CONTENT_TYPE, "application/json; UTF-8")
                    .build();
        }
    
        private RequestBody getRequestBody(String message) {
            return RequestBody.create(message, MediaType.get("application/json; charset=utf-8"));
        }
    
        private String makeFcmMessage(String targetToken, String title, String body) throws JsonProcessingException {
            FcmMessage fcmMessage = FcmMessage.builder()
                    .message(Message.builder()
                            .setToken(targetToken)
                            .setNotification(new Notification(title, body))
                            .build())
                    .validateOnly(false)
                    .build();
    
            //log.info("message 변환(Object -> String) \n" + objectMapper.writeValueAsString(fcmMessage));
            return objectMapper.writeValueAsString(fcmMessage);
        }
    
        private Message makeMessage(String targetToken, String title, String body) {
            FcmMessage fcmMessage = FcmMessage.builder()
                    .message(Message.builder()
                            .setToken(targetToken)
                            .setNotification(new Notification(title, body))
                            .build())
                    .validateOnly(false)
                    .build();
    
            return fcmMessage.getMessage();
        }
    }
    • 위의 코드에서는 tokenFirebase 서버를 사용합니다. (Request, Response X)
    • sendMessageTo(token, title, body) : Token(기기, 클라이언트)에게 메시지를 전송하는 메서드입니다.
      • makeMessage(token, title, body) : 메시지를 생성합니다.
        • 특히, FcmMessage 객체와 Builder를 활용하여 토큰알림 내용을 설정하여 생성합니다.

서비스 사용 (댓글 알림)

  • 실제로 위의 서비스를 사용하기 위해, 게시글에 댓글을 작성하면 게시물의 작성자에게 댓글이 달렸다는 알림을 주도록 코드를 수정합니다.
  • CommentWriteService
    @Slf4j
    @Service
    @RequiredArgsConstructor
    public class CommentWriteService {
    
        private final CommentRepository commentRepository;
        private final UserFindService userFindService;
        private final BoardFindService boardFindService;
        private final FirebaseCloudMessageService messageService;
    
        @Transactional
        public Long writeComment(Long userId, Long boardId, CommentWriteRequest commentWriteRequest) throws FirebaseMessagingException, IOException, ExecutionException, InterruptedException {
            Board board = boardFindService.findById(boardId);
            User user = userFindService.findById(userId);
    
            Comment comment = Comment.builder()
                    .content(commentWriteRequest.getContent())
                    .writer(user.getName())
                    .board(board)
                    .build();
    
            Comment savedComment = commentRepository.save(comment);
            user.writeComment(savedComment);
    
            String targetToken = board.getUser().getDeviceToken();
            sendMessageToBoardWriter(targetToken, "Comment Notification!", comment.getWriter(), comment.getContent());
            return savedComment.getComment_id();
        }
    
        private void sendMessageToBoardWriter(String targetToken, String title, String writer, String content) throws FirebaseMessagingException, IOException, ExecutionException, InterruptedException {
            messageService.sendMessageTo(targetToken, title, "[" + writer + "]" + "가 댓글 : <" + content + ">을 작성했습니다.");
        }
    }
    • 게시판을 작성한 사용자의 토큰값을 가져옵니다. (토큰 : 프론트 사이드에서 받아온 기기의 값)
    • sendMessageToBoardWriter()의 구현체는 FirebaseCloudMessageServicesendMessageTo()입니다.
profile
열심히 해보자9999

1개의 댓글

comment-user-thumbnail
2025년 2월 4일

Escort Delhi Hotels: Your Private Gateway to Unforgettable Intimate Experiences in Delhi's Finest Hotels. Unlock the door to unparalleled pleasure with Escort Delhi Hotels. We specialize in providing access to captivating and incredibly sexy escorts who are experts in creating deeply intimate experiences within the luxurious setting of Delhi hotels. Imagine surrendering to your deepest sexual desires in a safe and discreet environment, guided by a top-notch professional dedicated to your satisfaction.
Hotel Grand Imperial Escorts
Hotel Grand Park Inn Escorts

답글 달기