FCM은 Firebase Cloud Messaging의 약자로 기존 GCM에서 모든 플랫폼에 푸쉬를 쉽게 보낼 수 있도록 통합된 솔루션이다.
Notification 콘솔의 GUI나 Admin SDK를 이용해 서버 등에서 FCM의 백에 요청을 보내면, FCM에서 각 플랫폼 별로 메시지를 전송하는 방식이다.
이 글에서는 이 FCM을 활용해 Spring Boot에서 Notification을 보내는 방법에 대해 알아보겠다.
글 작성에 앞서 firebase 프로젝트 생성 부분은 생략하고 이미 되어있는 것을 가정하겠다.
1. Admin SDK
서버에서 Firebase 와 상호작용하기 위해서는 먼저 Admin SDK를 추가해야한다. (아래 공식문서 참고)
먼저 Spring Boot 프로젝트 build.gradle의 dependencies에 다음 코드를 추가한다.
dependencies {
implementation 'com.google.firebase:firebase-admin:9.1.1'
}
(Maven을 사용하여 빌드하는 경우 pom.xml에 아래 코드를 추가하면 된다.)
<dependency>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-admin</artifactId>
<version>9.1.1</version>
</dependency>
그리고 firebase 콘솔에 들어가 아래와 같이 Firebase Admin SDK의 새 비공개 키를 다운받는다.
다운 받은 json 파일은 프로젝트 resources 폴더에 firebase 폴더를 만들고 넣어주었다.
다 넣었다면, 소스코드 폴더에 FCMConfig.java 파일을 만들고 빈을 생성해준다.
싱글톤 객체로 생성하여 관리하기 위해 @Configuration 어노테이션과 함께 사용해준다.
@Configuration
public class FCMConfig {
@Bean
FirebaseMessaging firebaseMessaging() throws IOException {
ClassPathResource resource = new ClassPathResource("firebase/파일이름.json");
InputStream refreshToken = resource.getInputStream();
FirebaseApp firebaseApp = null;
List<FirebaseApp> firebaseAppList = FirebaseApp.getApps();
if (firebaseAppList != null && !firebaseAppList.isEmpty()) {
for (FirebaseApp app : firebaseAppList) {
if (app.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) {
firebaseApp = app;
}
}
} else {
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(refreshToken))
.build();
firebaseApp = FirebaseApp.initializeApp(options);
}
return FirebaseMessaging.getInstance(firebaseApp);
}
}
처음에는 공식문서에 적힌 코드를 그대로 사용했었는데 문제가 있었다.
firebaseapp name [default] already exists! 라는 오류였는데, 이는 firebase initializeApp을 중복 실행해서 발생하는 문제였다. 이를 해결하기 위해 이미 init 되어있는 경우 기존의 app을 사용할 수 있게 변경해주었다.
이후 ec2에 배포까지 진행하였는데 여기서 또 문제가 발생하였다.
공식문서에 제공된 예시 코드의 FileInputStream은 절대경로를 기준으로하여 src 폴더를 찾지 못하는 문제였다.
이를 해결하기 위해 resource 파일에 접근할 수 있는 ClassPathResource를 사용하였고 이를 통해 얻은 inputStream을 넘겨주는 것으로 코드를 수정했다.
2. 구현부 코드 작성
Admin SDK를 추가했으니 알림 기능 구현을 진행해보겠다.
필자는 특정 사용자에게 알림이 가는 기능이 필요했기에 해당 코드를 소개하겠다. (ex. 누군가 어떤 게시글에 댓글을 달면 게시글 작성자에게 알림 전송)
전체 사용자에게 알림을 보내는 기능은 콘솔을 이용하면 된다.
특정 사용자에게 알림을 보내기 위해서는 "해당 타겟 유저 기기를 식별"할 수 있는 토큰이 필요하다.
유저 기기 식별용 토큰은 클라이언트단에서 서비스 접속 시 발급하여 서버에 보내고 이를 저장해두어야한다. (이에 관한 로직도 따로 구현해야해 둬야 한다.)
이에 관한 내용은 이후 글에서 소개하겠다.
이 글에서는 사용자들의 토큰 값을 서버에 저장해둔 상태를 가정하고 소개하겠다.
먼저 데이터를 담을 DTO 를 만들겠다.
FCMNotificationRequestDto.java
@Getter
@NoArgsConstructor
public class FCMNotificationRequestDto {
private Long targetUserId;
private String title;
private String body;
// private String image;
// private Map<String, String> data;
@Builder
public FCMNotificationRequestDto(Long targetUserId, String title, String body) {
this.targetUserId = targetUserId;
this.title = title;
this.body = body;
// this.image = image;
// this.data = data;
}
}
필자는 image와 data가 필요없어 주석 처리 하였는데 필요하면 위와 같이 작성하여 쓰면 된다.
FCMNotificationService.java
@RequiredArgsConstructor
@Service
public class FCMNotificationService {
private final FirebaseMessaging firebaseMessaging;
private final UsersRepository usersRepository;
public String sendNotificationByToken(FCMNotificationRequestDto requestDto) {
Optional<Users> user = usersRepository.findById(requestDto.getTargetUserId());
if (user.isPresent()) {
if (user.get().getFirebaseToken() != null) {
Notification notification = Notification.builder()
.setTitle(requestDto.getTitle())
.setBody(requestDto.getBody())
// .setImage(requestDto.getImage())
.build();
Message message = Message.builder()
.setToken(user.get().getFirebaseToken())
.setNotification(notification)
// .putAllData(requestDto.getData())
.build();
try {
firebaseMessaging.send(message);
return "알림을 성공적으로 전송했습니다. targetUserId=" + requestDto.getTargetUserId();
} catch (FirebaseMessagingException e) {
e.printStackTrace();
return "알림 보내기를 실패하였습니다. targetUserId=" + requestDto.getTargetUserId();
}
} else {
return "서버에 저장된 해당 유저의 FirebaseToken이 존재하지 않습니다. targetUserId=" + requestDto.getTargetUserId();
}
} else {
return "해당 유저가 존재하지 않습니다. targetUserId=" + requestDto.getTargetUserId();
}
}
}
FirebaseMessaging은 앞서 만든 Bean에서 주입 받은 것이다.
requestDto에서 받은 타겟 유저의 아이디를 조회한 후, 존재하는 유저면 해당 유저의 firebaseToken 값을 조회한다.
만약 등록된 firebaseToken이 있다면 알림을 보낸다.
FCMNotificationApiController.java
다음으로 컨트롤러를 만들어준다.
@Tag(name = "Notification", description = "FCM Notification 관련 api 입니다.")
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/v1/notification")
public class FCMNotificationApiController {
private final FCMNotificationService fcmNotificationService;
@Operation(summary = "알림 보내기")
@PostMapping()
public String sendNotificationByToken(@RequestBody FCMNotificationRequestDto requestDto) {
return fcmNotificationService.sendNotificationByToken(requestDto);
}
}
모든 구현이 완료되었다.
이제 컨트롤러를 통해 프론트 단에서 api로 알림을 보낼 수도 있고, 서버상에서 알림을 보낼 수도 있다.
Flutter를 활용한 클라이언트단의 푸시알림 구현은 아래 글을 참고하면 된다.
'Back-End > Spring Boot' 카테고리의 다른 글
[Spring] Swagger https 설정하기 (springdoc) (0) | 2023.01.25 |
---|---|
[Spring] Test 중 발생한 Dto의 'Cannot construct instance of...' 에러 (0) | 2023.01.07 |
[Spring] @RequestBody 사용 시 boolean 변수 바인딩 에러 (Boolean variable binding error in requestbody annotation) (0) | 2023.01.05 |
[Spring] 스프링 웹 계층 (spring web layer) (0) | 2023.01.05 |
[Spring] DAO, DTO, VO의 개념과 차이점 (0) | 2022.12.14 |