애플리케이션에 푸시 알림을 특정 시간대에(9시, 13시, 19시) 보내야할 일이 있어서 파이어베이스 자체에서 푸시 알림을 예약해서 사용하고 있었는데, 이게 파이어베이스 자체 문제인지 제대로 알림이 가지 않았다.
그래서 아얘 백엔드에서 FCM 서버를 만들어 특정 시간대에 파이어베이스에 푸시알림을 직접 요청하기로 했다.
본 포스팅에서는 Spring으로 FCM PushNotification(푸시알림) 서버를 구축해보겠다.
+) FCM은 사용자 개개인의 토큰을 사용하는 방법과 사용자 그룹인 토픽을 사용하는 방법이 있는데 본 포스팅에서는 토픽을 사용하는 방법을 택했다.
Firebase 비공개 키 생성
백엔드에서 푸시 알림을 파이어베이스에 요청하기 위해서는 파이어베이스의 비공개 키 파일이 필요하다.
각자 파이어베이스 콘솔에서 아래 절차대로 비공개 키를 생성한다.
이제 Spring 프로젝트의 resources/firebase/ 하위에 비공개 키를 넣는다.
비공개 키는 json 형식으로 이름은 각자 콘솔 설정마다 다를 수 있다.
의존성 추가
build.gradle 파일에 firebase sdk 의존성을 추가한다.
dependencies {
...
// firebase sdk
implementation 'com.google.firebase:firebase-admin:7.1.0'
...
}
스케줄러 설정
특정 시간대에 알림을 보내주기 위해 Spring이 제공하는 TaskScheduler를 빈 등록 한다.
POOL_SIZE는 한 번에 처리할 건수로 여기서는 10으로 설정했다.
@Configuration
@EnableScheduling
public class ScheduledConfig {
private final int POOL_SIZE = 10;
public TaskScheduler scheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(POOL_SIZE);
return scheduler;
}
}
메시지 형식 설정
FCM 메시지 형식을 확장성을 위해 enum 타입으로 개발했다. FCM 메시지는 title, body, image 등등 여러 필드를 받아서 처리할 수 있는데, 여기서는 간단하게 title, body만 입력했다.
아래 코드는 매 끼니 식사마다 알림을 보내주는 예시이다.
@Getter
@AllArgsConstructor
public enum RequestPushMessage {
MORNING_DIET("아침 식사 드셨나요?", "아침 식사를 드셨다면, 식사를 기록해주세요!"),
LUNCH_DIET("점심 식사 드셨나요?", "점심 식사를 드셨다면, 식사를 기록해주세요!"),
DINNER_DIET("저녁 식사 드셨나요?", "저녁 식사를 드셨다면, 식사를 기록해주세요!")
;
String title;
String body;
}
알림 스케줄러 개발
우선 application.yml 파일에 다음과 같이 세팅한다.
firebase-topic에는 각자 푸시알림하기 원하는 파이어베이스 토픽을 입력한다.
project:
properties:
firebase-create-scoped: "https://www.googleapis.com/auth/firebase.messaging"
firebase-topic: "diet_record_notification"
이제 스케줄러를 만들어보자. 부분 코드 설명은 아래에서 하겠다.
@Slf4j
@Service
public class NotificationScheduler {
@Value("${project.properties.firebase-create-scoped}")
String fireBaseCreateScoped;
@Value("${project.properties.firebase-topic}")
String topic;
private FirebaseMessaging instance;
@PostConstruct
public void firebaseSetting() throws IOException {
GoogleCredentials googleCredentials = GoogleCredentials.fromStream(new ClassPathResource("firebase/easy-yum-dev-firebase-adminsdk-dd3nm-ea5a60d154.json").getInputStream())
.createScoped((Arrays.asList(fireBaseCreateScoped)));
FirebaseOptions secondaryAppConfig = FirebaseOptions.builder()
.setCredentials(googleCredentials)
.build();
FirebaseApp app = FirebaseApp.initializeApp(secondaryAppConfig);
this.instance = FirebaseMessaging.getInstance(app);
}
@Scheduled(cron = "0 0 09 * * ?")
public void pushMorningDietAlarm() throws FirebaseMessagingException {
log.info("아침 식사 알림");
pushAlarm(MORNING_DIET);
}
@Scheduled(cron = "0 0 13 * * ?")
public void pushLunchDietAlarm() throws FirebaseMessagingException {
log.info("점심 식사 알림");
pushAlarm(LUNCH_DIET);
}
@Scheduled(cron = "0 0 19 * * ?")
public void pushDinnerDietAlarm() throws FirebaseMessagingException {
log.info("저녁 식사 알림");
pushAlarm(DINNER_DIET);
}
private void pushAlarm(RequestPushMessage data) throws FirebaseMessagingException {
Message message = getMessage(data);
sendMessage(message);
}
private Message getMessage(RequestPushMessage data) {
Notification notification = Notification.builder().setTitle(data.getTitle()).setBody(data.getBody()).build();
Message.Builder builder = Message.builder();
Message message = builder.setTopic(topic).setNotification(notification).build();
return message;
}
public String sendMessage(Message message) throws FirebaseMessagingException {
return this.instance.send(message);
}
}
먼저 처음에 저장해둔 파이어베이스 비공개 키 파일을 통해 백엔드에서 파이어베이스에 접속한다.
private FirebaseMessaging instance;
@PostConstruct
public void firebaseSetting() throws IOException {
GoogleCredentials googleCredentials = GoogleCredentials.fromStream(new ClassPathResource("firebase/easy-yum-dev-firebase-adminsdk-dd3nm-ea5a60d154.json").getInputStream())
.createScoped((Arrays.asList(fireBaseCreateScoped)));
FirebaseOptions secondaryAppConfig = FirebaseOptions.builder()
.setCredentials(googleCredentials)
.build();
FirebaseApp app = FirebaseApp.initializeApp(secondaryAppConfig);
this.instance = FirebaseMessaging.getInstance(app);
}
다음은 특정 시간에 맞게 푸시 알림을 스케줄링하는 코드이다.
Linux의 cron 시간대를 사용했고 각각 9시, 13시, 19시 정각에 푸시 알림을 보내도록 설정했다.
@Scheduled(cron = "0 0 09 * * ?")
public void pushMorningDietAlarm() throws FirebaseMessagingException {
log.info("아침 식사 알림");
pushAlarm(MORNING_DIET);
}
@Scheduled(cron = "0 0 13 * * ?")
public void pushLunchDietAlarm() throws FirebaseMessagingException {
log.info("점심 식사 알림");
pushAlarm(LUNCH_DIET);
}
@Scheduled(cron = "0 0 19 * * ?")
public void pushDinnerDietAlarm() throws FirebaseMessagingException {
log.info("저녁 식사 알림");
pushAlarm(DINNER_DIET);
}
cron에 대한 양식은 다음 글을 참고했다.
https://huskdoll.tistory.com/819
[Spring] @Scheduled 정해진 시간에 맞춰서 모듈 실행하기
Linux에서 cron 을 사용하는것 처럼 웹서비스에서도 정해진 시간에 무언가를 실행해야 하는 경우가 종종 있습니다. 자바에도 그런 기능을 제공해주고 있고, Spring에서는 손쉽게 사용할 수가 있습
huskdoll.tistory.com
나머지는 푸시 알림을 보내는 코드이다. 이 때 application.yml에 입력한 topic 값이 사용된다.
private void pushAlarm(RequestPushMessage data) throws FirebaseMessagingException {
Message message = getMessage(data);
sendMessage(message);
}
private Message getMessage(RequestPushMessage data) {
Notification notification = Notification.builder().setTitle(data.getTitle()).setBody(data.getBody()).build();
Message.Builder builder = Message.builder();
Message message = builder.setTopic(topic).setNotification(notification).build();
return message;
}
public String sendMessage(Message message) throws FirebaseMessagingException {
return this.instance.send(message);
}
이제 서버를 백그라운드에 계속 돌려 놓으면 특정 시간대에 맞게 FCM 푸시 알림을 사용자들에게 보낼 수 있다.
'java > spring' 카테고리의 다른 글
[Spring] AWS Parameter Store를 사용해 변수 불러오기 (0) | 2022.01.09 |
---|---|
[Spring] 개발, 운영 환경 별 profile 설정 (0) | 2022.01.09 |
[Spring] Spring Jdbc - batchUpdate()를 사용한 bulk Insert 최적화 (0) | 2021.08.06 |
[Spring] SpringBatch를 사용해 csv 파일 읽어 DB에 저장 (4) | 2021.08.06 |
[Spring] AWS S3, csv 파일 읽어서 DB에 저장 (0) | 2021.08.05 |