[캡스톤 디자인] FCM 채팅 푸시 알람 구현 [SpringBoot, html, javascript 사용] (2)

Dev_Sanizzang·2023년 10월 11일
1

캡스톤디자인

목록 보기
15/15

📕 개요

전 포스팅에서 푸시 알람에 대해 정리해보았다. 이번 포스팅에서는 SpringBoot, html, javascript를 통해서 FCM을 구현해보는 시간을 가져보겠다.

FCM 구현 (프론트엔드)

1. SDK 설정값 확인

Firebase Console 프로젝트 생성 후에 앱 등록까지 마치게 되면
아래와 같이 SDK 코드를 제공해준다.

그중 firebaseConfig의 값이 중요한 값이다.

2. VAPID KEY 생성하기

1) Firebase Console 접속
2) 프로젝트 개요 옆의 설절 아이콘(톱니바퀴) > 프로젝트 설정 클릭
3) 클라우드 메시징 클릭
4) 웹구성 -> Generate key pair

3. 토큰 발급을 위한 html 코드 작성

<body>
    <button id="enableNotifications">알림 활성화</button>

    <script src="https://www.gstatic.com/firebasejs/8.10.0/firebase.js"></script>
    <script type="module" src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script>
    <script type="module" src="https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.1/sockjs.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
    <script>
        const firebaseConfig = {
            apiKey: "본인 apikey",
            authDomain: "본인 authDomain",
            projectId: "본인 projectId",
            storageBucket: "본인 storageBucket",
            messagingSenderId: "본인 messagingSenderId",
            appId: "본인 appId",
            measurementId: "본인 measurementId"
        };

        const app = firebase.initializeApp(firebaseConfig);
        const messaging = firebase.messaging();

        const enableNotificationsButton = document.getElementById('enableNotifications');
        enableNotificationsButton.addEventListener('click', () => {
            Notification.requestPermission().then((permission) => {
                if (permission === 'granted') {
                    messaging.getToken({ vapidKey: "BN4UXj5_xn7lEegLHRDDlVlNQPbHV26-I9HCz_RR-ucOJAw_LpP78EmEmqt9DVhZiO5SxtBP0gH1a0NrjjtC-xw" })
                        .then((tokenValue) => {
                            console.log(`푸시 토큰 발급 완료 : ${tokenValue}`)
                        })
                        .catch((err) => {
                            console.log('푸시 토큰 가져오는 중에 에러 발생')
                        })
                } else {
                    console.log('푸시 알림 거부됨');
                }
            })
            .catch((error) => {
                console.error('Firebase 토큰 가져오기 실패:', error);
            });
        });
    </script>
</body>

4. 서비스워커 생성

서비스워커는 백그라운드에서 실행되는 스크립트로, 서비스를 실행하지 않거나 브라우저가 닫혔을 때 등에도 푸시 알림을 받을 수 있도록 한다.

FCM의 서비스 워커는 public/firebase-messaging-sw.js의 경로로 만들어놔야 한다.

  • public/firebase-messaging-sw.js
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js");

const firebaseConfig = {
            apiKey: "본인 apikey",
            authDomain: "본인 authDomain",
            projectId: "본인 projectId",
            storageBucket: "본인 storageBucket",
            messagingSenderId: "본인 messagingSenderId",
            appId: "본인 appId",
            measurementId: "본인 measurementId"
};

const app = firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();

5. Node 웹 서버 구축

npm i cors express
  • app.js
const express = require('express')

const app = express()

const cors = require('cors');

app.use(express.static(__dirname + "/public", {
    setHeaders: (res, path) => {
        if (path.endsWith('.js')) {
            res.set('Content-Type', 'application/javascript');
        }
    },
}));

app.use(cors());

app.get('/', function(req, res) {
    res.sendFile(__dirname + "/index.html")
})

app.listen(5001, function() {
    console.log("start! express server on port 5001")
})

테스팅을 위한 express 웹 서버를 구축하였다.

  • 웹 서버 실행
node app

테스트 해보기

FCM을 위한 기본적인 설정은 마친상태로 이제 테스트를 해보겠다.

1) 프로젝트 접속
2) Cloud Messaging 접속
3) 첫 번째 캠페인 만들기 클릭

4) Firebase 알림 메시지 선택 -> 만들기 클릭

5) 알림 내용 적고 테스트 메시지 전송

6) FCM 등록 토큰 추가 -> 테스트

성공 🤭🤗

푸시 알람이 정상적으로 받아와 짐을 확인할 수 있다.

나같은 경우 FreeDOS 노트북을 샀는데 정품 Window를 사지않고 기본으로 제공하는 Window를 쓰고 있어서 Windows 정품인증이 뜨고있다. 😅

FCM 푸시 알람 시나리오

일단 나는 채팅 알람을 위해 FCM을 사용하는 것이다. 이를 위해 하나의 상황을 가정하고 해당 상황을 기반으로 코드를 구현해보겠다.

1) 모임에 하나의 채팅방이 있다.
2) 만약 해당 모임에 채팅이라는 이벤트가 발생하면
3) 해당 모임의 회원들에게 채팅 알람을 보내준다.

-> 이를 위해서 나는 가입한 모임을 주제(Topic)으로 해당 주제(Topic)을 구독(subscribe)하고 해당 주제(Topic)에 대해서 채팅(이벤트)가 발생하면 구독하고 있는 사람들(모임원들)에게 알람을 보낼 것이다.

FCM 구현 (백엔드)

1. 의존성 추가

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

2. Firebase 프로젝트, 비공개 키 생성

1) Firebase 콘솔에 접속하여 프로젝트 생성
2) 프로젝트 설정 > 서비스 계정 항목 에서 비공개 키를 생성
3) json 파일로 생성된 admin sdk를 Spring 플젝트의 resources 디렉토리로 이동
4) application.secret.yml에 json 파일의 위치를 저장 후 @Value를 통해 불러오도록 설정

3. FCM 초기화

// 비공개 키 파일의 인증정보를 이용한 FirebaseApp 초기화
@Slf4j
@Component
public class FCMInitializer {

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

    // 빈 객체가 생성되고 의존성 주입이 완료된 후에 초기화가 실행될 수 있도록
    @PostConstruct
    public void initialize() throws IOException {
        ClassPathResource resource = new ClassPathResource(googleApplicationCredentials);

        try (InputStream is = resource.getInputStream()) {
            FirebaseOptions options = FirebaseOptions.builder()
                    .setCredentials(GoogleCredentials.fromStream(is))
                    .build();

            if (FirebaseApp.getApps().isEmpty()) {
                FirebaseApp.initializeApp(options);
                log.info("FirebaseApp initialization complete");
            }
        }
    }
}

어플리케이션이 실행되는 시점에 비공개 키 파일의 인증정보를 통해 FirebaseApp 초기화

4. Controller 작성

RequestSubscribe.java

@Getter
@AllArgsConstructor
public class RequestSubscribe {
    String token;
    List<String> topicList;
}

RequestUnsubscribe.java

@Getter
@AllArgsConstructor
public class RequestUnsubscribe {
    String token;
    List<String> topicList;
}

RequestChatMessage.java

@Getter
@AllArgsConstructor
public class RequestChatMessage {
    String clubName;
    String userName;
    String chatMessage;
    String topic;
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/")
public class FCMController {

    private final FCMService fcmService;

    @GetMapping("/test")
    public String test() {
        return "test";
    }

    @PostMapping("/subscribe")
    public ResponseEntity<Void> subscribe(@RequestBody RequestSubscribe requestSubscribe) {
        fcmService.subscribeTopic(requestSubscribe);

        return ResponseEntity.noContent().build();
    }

    @DeleteMapping("/unsubscribe")
    public ResponseEntity<Void> unsubscribe(@RequestBody RequestUnsubscribe requestUnsubscribe) {
        fcmService.unsubscribeTopic(requestUnsubscribe);

        return ResponseEntity.noContent().build();
    }

    @PostMapping("/sendChatMessage")
    public ResponseEntity<Void> sendChatMessage(@RequestBody RequestChatMessage requestChatMessage) {
        fcmService.sendChatMessage(requestChatMessage);

        return ResponseEntity.noContent().build();
    }
}
  • /subscribe : 구독(subscribe)를 위한 API 이다. 해당 기기의 token과 구독할 주제(topic)들을 담아 구독을 요청할 수 있다.
  • /unsubscribe : 구독(subscribe) 취소를 위한 API 이다.
  • /sendChatMessage : 채팅 알람 요청 API 이다. clubName(클럽 이름), userName(유저 이름), chatMessage(채팅 메시지), topic(클럽 ID) 를 넘겨주면 해당 topic(클럽 ID)를 구독한 사람들(모임원들)에게 채팅 알람을 보내준다.

Service 구현

public interface MessageService {

    void sendChatMessage(RequestChatMessage requestChatMessage);
}
@Slf4j
@Service
public class FCMService implements MessageService{

    @Override
    public void sendChatMessage(RequestChatMessage requestChatMessage) {
        Message message = Message.builder()
                .setNotification(Notification.builder()
                        .setTitle(requestChatMessage.getClubName())
                        .setBody(requestChatMessage.getUserName() + ": " + requestChatMessage.getChatMessage())
                        .build())
                .setTopic(requestChatMessage.getTopic())
                .build();
        
        send(message);
    }

    public void subscribeTopic(RequestSubscribe requestSubscribe){
        List<String> registrationTokens = Arrays.asList(
                requestSubscribe.getToken()
        );
        
        try {
            for(String topic : requestSubscribe.getTopicList()) {
                TopicManagementResponse response = FirebaseMessaging
                        .getInstance()
                        .subscribeToTopic(registrationTokens, topic);

                log.info(response.getSuccessCount() + " tokens were subscribed successfully");
            }
        } catch (FirebaseMessagingException e) {
            throw new RuntimeException(e);
        }
    }

    public void unsubscribeTopic(RequestUnsubscribe requestUnsubscribe) {
        List<String> registrationTokens = Arrays.asList(
                requestUnsubscribe.getToken()
        );

        try {
            for(String topic : requestUnsubscribe.getTopicList()) {
                FirebaseMessaging
                        .getInstance()
                        .subscribeToTopic(registrationTokens, topic);
            }
        } catch (FirebaseMessagingException e) {
            throw new RuntimeException(e);
        }
    }

    public void send(Message message) {
        String response = String.valueOf(FirebaseMessaging.getInstance().sendAsync(message));
        log.info("Successfully sent message: " + response);
    }
}

FCM 공식문서를 기반으로 짠 코드이다.

참고 자료

💡 [FrontEnd] FCM을 이용해 웹 푸시 알림 적용하기
💡 FCM 공식문서

🚪 마무리

이로써 FCM 토큰 발급과 푸시 알람 이벤트를 위한 백엔드단 까지 만들어보았다. 이를 바탕으로 현재 프론트를 담당하고 있는 친구에게 내가 구상한 로직을 설명해주고 진행중인 프로젝트에 합칠 수 있도록 할 것이다.

profile
기록을 통해 성장합니다.

0개의 댓글