Skip to content

Commit

Permalink
refactor: fcm 서버 비동기 처리 구현 (#499)
Browse files Browse the repository at this point in the history
* refactor: fcm sdk 적용 및 fcm 서버측 알림처리 비동기 설정

* refactor: FCM 초기화 로직 변경

* refactor: FCM 파일 경로 변경

* refactor: FCM 인스턴스 명 변경

* test: 알림 테스트 계정 변경

* chore: Async 스레드 풀 개수 변경

* chore: 알림 로깅 추가 및 불필요한 코드 삭제

* refactor: 파일 경로 정적 변수로 추출

* refactor: 비동기 스레드 풀 40으로 설정

* chore: 현재 필요하지 않는 테스트 메서드 주석 처리

* chore: FCM 정적 변수 빈 주입으로 변경

---------

Co-authored-by: 이건회 <[email protected]>
  • Loading branch information
Kim0914 and rawfishthelgh authored Jan 25, 2024
1 parent 0f13d5d commit 04f7066
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 106 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.official.pium.admin.service;

import com.official.pium.petPlant.domain.PetPlant;
import com.official.pium.petPlant.event.notification.NotificationEvent;
import com.official.pium.petPlant.repository.PetPlantRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
Expand Down Expand Up @@ -33,8 +30,8 @@ public class TestService {
// log.info("동기 알림 테스트 종료. Thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
// }

public void sendWaterNotificationAsyncRampTest() {
List<PetPlant> petPlants = petPlantRepository.findAllByMemberId(6L);
// public void sendWaterNotificationAsyncRampTest() {
// List<PetPlant> petPlants = petPlantRepository.findAllByMemberId(7L);
// List<NotificationEvent> events = petPlants.stream()
// .map(plant -> NotificationEvent.builder()
// .title(plant.getNickname())
Expand All @@ -43,39 +40,39 @@ public void sendWaterNotificationAsyncRampTest() {
// .build()
// ).toList();

for (int i = 0; i < 100; i++) {
PetPlant petPlant = petPlants.get(i);
NotificationEvent event = NotificationEvent.builder()
.title(petPlant.getNickname())
.body("물줘")
.deviceToken(petPlant.getMember().getDeviceToken())
.build();
publisher.publishEvent(event);
}
// for (int i = 0; i < 100; i++) {
// PetPlant petPlant = petPlants.get(i);
// NotificationEvent event = NotificationEvent.builder()
// .title(petPlant.getNickname())
// .body("물줘")
// .deviceToken(petPlant.getMember().getDeviceToken())
// .build();
// publisher.publishEvent(event);
// }

// log.info("비동기 테스트 램프업 시작");
// for (int i = 0; i < 100; i++) {
// NotificationEvent notificationEvent = events.get(i);
// publisher.publishEvent(notificationEvent);
// }
}

public void sendWaterNotificationAsyncTest() {
List<PetPlant> petPlants = petPlantRepository.findAllByMemberId(6L);
List<NotificationEvent> events = petPlants.stream()
.map(plant -> NotificationEvent.builder()
.title(plant.getNickname())
.body("(테스트 중) 물을 줄 시간이에요!")
.deviceToken(plant.getMember().getDeviceToken())
.build()
).toList();
// }

int i = 1;
log.info("비동기 알림 테스트 시작. Thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
for (NotificationEvent event : events) {
log.info(i++ + "번째 알림 이벤트");
publisher.publishEvent(event);
}
log.info("비동기 알림 테스트 종료. Thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
}
// public void sendWaterNotificationAsyncTest() {
// List<PetPlant> petPlants = petPlantRepository.findAllByMemberId(7L);
// List<NotificationEvent> events = petPlants.stream()
// .map(plant -> NotificationEvent.builder()
// .title(plant.getNickname())
// .body("(테스트 중) 물을 줄 시간이에요!")
// .deviceToken(plant.getMember().getDeviceToken())
// .build()
// ).toList();
//
// int i = 1;
// log.info("비동기 알림 테스트 시작. Thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
// for (NotificationEvent event : events) {
// log.info(i++ + "번째 알림 이벤트");
// publisher.publishEvent(event);
// }
// log.info("비동기 알림 테스트 종료. Thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
// }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.official.pium.admin.ui;

import com.official.pium.admin.service.TestService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -9,13 +10,7 @@
@RequestMapping("/test")
public class TestController {

// private final TestService testService;

// @GetMapping("/notifications")
// public ResponseEntity<String> notificationTest() {
// testService.sendWaterNotificationTest();
// return ResponseEntity.ok("알림 기능 테스트 성공");
// }
private final TestService testService;

// @GetMapping("/notifications/ramp")
// public ResponseEntity<String> notificationRampTest() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,81 +1,64 @@
package com.official.pium.notification.fcm.application;

import com.google.auth.oauth2.GoogleCredentials;
import com.official.pium.notification.fcm.dto.FcmMessageResponse;
import com.official.pium.notification.fcm.exception.FcmException;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.Notification;
import com.official.pium.notification.application.MessageSendManager;
import jakarta.annotation.PostConstruct;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;

@Slf4j
@Component
@RequiredArgsConstructor
public class FcmMessageSender implements MessageSendManager {

@Value("${fcm.api.url}")
private String apiUrl;

@Value("${fcm.key.path}")
private String keyPath;

@Value("${fcm.key.scope}")
private String keyScope;
@Value("${fcm.json.path}")
private String FCM_JSON_PATH;

private final RestTemplate restTemplate;

public void sendMessageTo(String targetToken, String title, String body) {
@PostConstruct
public void initialize() {
try {
FcmMessageResponse message = makeMessage(targetToken, title, body);

HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken());
headers.set(HttpHeaders.CONTENT_TYPE, "application/json; UTF-8");

HttpEntity<FcmMessageResponse> request = new HttpEntity<>(message, headers);
ClassPathResource resource = new ClassPathResource(FCM_JSON_PATH);
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(resource.getInputStream()))
.build();

ResponseEntity<FcmMessageResponse> postResult = restTemplate.postForEntity(
apiUrl,
request,
FcmMessageResponse.class
);

log.info("FCM 메시지 전송 성공: {}", postResult.getBody());

} catch (Exception e) {
log.error("FCM 메시지 전송 실패", e);
throw new FcmException.FcmMessageSendException(e.getMessage());
if (FirebaseApp.getApps().isEmpty()) {
FirebaseApp.initializeApp(options);
}
} catch (FileNotFoundException e) {
log.error("파일을 찾을 수 없습니다. ", e);
} catch (IOException e) {
log.error("FCM 인증이 실패했습니다. ", e);
}
}

private FcmMessageResponse makeMessage(String targetToken, String title, String body) {
return FcmMessageResponse.builder()
.message(FcmMessageResponse.Message.builder()
.token(targetToken)
.notification(FcmMessageResponse.Notification.builder()
.title(title)
.body(body)
.image(null)
.build()
)
.build()
)
.validate_only(false)
public void sendMessageTo(String targetToken, String title, String body) {
Notification notification = Notification.builder()
.setTitle(title)
.setBody(body)
.build();
}

private String getAccessToken() throws IOException {
GoogleCredentials googleCredentials = GoogleCredentials
.fromStream(new ClassPathResource(keyPath).getInputStream())
.createScoped(keyScope);
googleCredentials.refreshIfExpired();
return googleCredentials.getAccessToken().getTokenValue();
Message message = Message.builder()
.setToken(targetToken)
.setNotification(notification)
.build();
try {
String response = FirebaseMessaging.getInstance().sendAsync(message).get();
log.info("알림 전송 성공 : " + response);
} catch (InterruptedException e) {
log.error("FCM 알림 스레드에서 문제가 발생했습니다.", e);
} catch (ExecutionException e) {
log.error("FCM 알림 전송에 실패했습니다.", e);
}
}
}
8 changes: 3 additions & 5 deletions backend/pium/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,9 @@ management:
endpoints:
enabled-by-default: false
fcm:
key:
path: test/
scope: https://www.googleapis.com/auth/firebase.messaging
api:
url: https://fcm.googleapis.com/v1/projects/project-id/messages:send
json:
path: config/pium-fcm.json

petPlant:
image:
directory: test
Expand Down
5 changes: 1 addition & 4 deletions backend/pium/src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,8 @@ server:
max-http-form-post-size: 10MB

fcm:
key:
json:
path: config/pium-fcm.json
scope: https://www.googleapis.com/auth/firebase.messaging
api:
url: https://fcm.googleapis.com/v1/projects/pium-test/messages:send

management:
endpoint:
Expand Down

0 comments on commit 04f7066

Please sign in to comment.