firebase 연동해서 fcm 사용하기이란!

🦥 FCM 사용

fcm 을 처음에는 코프링으로 구현하다가, App Check 가 스프링에서는 구현이 안된다는 이슈로 node 로 다시 구현하기 시작했다.

내가 구현한 기능은 조금 많고 복잡하다. 내가 실력이 부족한건지, 아님 공통으로 줄일 수 있는데, 내가 못줄인것인지,, 이렇게 작성해놓으면 , 미래의 내가 보고 깨달음을 주겠지!

참고로 이게시글에는 백엔드 관련해서만 작성하겠다!

왜냐, node 를 하면서 오랜만에 백엔드 비즈니스 로직을 작성을 했고, 그 흐름을 잊어버리지 않기 위함이다.

  1. 데이터베이스는 fcm 예약 DB, fcm 히스토리 DB 2개 존재한다.

토큰, 주제 의 전송방식으로 즉시 전송과 예약 전송 두가지 메소드가 존재한다.

즉시전송!

즉시전송은 바로 fcm 이 전송되기때문에, 전송 성공시 히스토리 DB에 남기는 로직으로 진행했다.

 // 토큰으로 보내기
    public async sendByToken(fcm: FcmModel): Promise<boolean> {
        try {
            const message = {
                notification: {
                    title: fcm.title ?? '',
                    body: fcm.content,
                },
                token: fcm.target,
            }

            // 즉시 발송인 경우, 히스토리 디비에 저장
            return await admin.messaging().send(message)
                .then(_ => {
                    return fcmHistoryService.insertToHistory(fcm, '토큰')
                }).catch(err => {
                    console.error(err);
                    return false
                });
        } catch (err) {
            console.error('sendBy Token : ', err)
        }
    }

    // 주제(토픽)로 보내기
    public async sendByTopic(fcm): Promise<boolean> {
        try {
            const msg = {
                notification: {
                    title: fcm.title ?? '',
                    body: fcm.content,
                },
                topic: fcm.topic!,
            }

            // 즉시 발송인 경우, 히스토리 디비에 저장
            return await admin.messaging().send(msg).then(_ => {
                return fcmHistoryService.insertToHistory(fcm, '주제')
            }).catch(err => {
                console.error(err);
                return false
            })
        } catch (err) {
            console.error('sendBy Token : ', err)
        }
    }

예약 전송

예약전송은 우선 예약 DB 에 등록을 하고 node-cron 을 통해서 정해진 시간에 DB 를 읽어와 예약시간에 맞춰 전송해주는 방식으로 진행했다.

async insertToReservation(fcm: FcmModel, type: String): Promise<boolean> {
        const db: Firestore = admin.firestore();
        try {
            let timeStamp = String(Date.now());
            dayjs.extend(utc);
            dayjs.extend(timezone);

            const randomString = Array.from({length: 5}, () => (Math.random().toString(36)[2].toUpperCase())).join('');
            const documentId = `${timeStamp}${randomString}`;

            const obj = {
                title: fcm.title,
                content: fcm.content,
                createdAt: fcm.createdAt,
                reservationAt: fcm.reservationAt,
                docId: documentId,
                messageType: type,
                target: fcm.target ?? null,
                topic: fcm.topic ?? null,
                isReservation: fcm.isReservation,
            };

            const result = await db.collection('reservation').doc(documentId).set(obj)
                .then(_ => {
                    return true;
                }).catch(err => {
                    console.error(err);
                    return false;
                });

            return result;
        } catch (err) {
            console.error(err);
        }
    }

cron_utils 코드

index.ts 에 require() 해주었다.

import Timestamp = firestore.Timestamp;

const cron = require('node-cron');

const fcmReservationService = new FcmReservationService();

const task = cron.schedule('*/30 * * * *', async function () {
    try {
        let reservationList: FcmModel[] = [];
        const timeStamp = Timestamp.now();
        const result = await fcmReservationService.lists(null, null);

        if (result) {
            result.map((e) => {
                if (e.reservationAt?.seconds == timeStamp.seconds) {
                    reservationList.push(e);
                }
            })

            reservationList.map(async (tempElement) => {
                if (tempElement.messageType === '토큰') {
                    await fcmReservationService.sendByToken(tempElement).then(async _ => {
                        // 예약 서비스에서 삭제!
                        await fcmReservationService.deleteReservation(tempElement.docId);
                    });
                } else {
                    await fcmReservationService.sendByTopic(tempElement).then(async _ => {
                        // 예약 서비스에서 삭제!
                        await fcmReservationService.deleteReservation(tempElement.docId);
                    });
                }
            })
        }
    } catch (err) {
        console.error(err);
    } finally {
        reservationList = [];
    }
}, {
    scheduled: true
})

module.exports = task;

Categories:

Updated:

Leave a comment