-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feat: FCMConfig 생성 * chore: FCMConfig 패키지 이동 * feat: MemberFCM 생성 * feat: MemberFCMService 생성 * feat: NotificationFacade 생성 * feat: 로그인 시 fcm 을 등록한다. * feat: 입장 처리 시 EntryProcessEvent 를 발행한다. * feat: 유저 탈퇴시 유저의 FCM 을 모두 삭제한다. * feat: 테스트 환경에서 FirebaseMessaging 을 MockBean 으로 등록한다 * chore: 테스트 컨벤션 적용 * chore: Notification -> FCMNotificationFacade 네이밍 변견 * feat: flyway 추가 * chore: 마지막 줄 개행 추가 * feat: submodule 업데이트 * refactor: 메서드 네이밍, 메서드 순서, 파라미터 순서 변경 * refactor: fcm bean 을 테스트에서 제외 * feat: EventListener phase 명시 * chore: FCMEventListener 네이밍 변경 * feat: MemberFCM 의 Member 의존성 제거 * chore: EntryProcessEvent 패키지 분리 * refactor: AuthService 가 MemberFCM 을 의존하지 않도록 변경 * feat: MemberFCM 빈 생성자 추가 * chore: flyway version 변경 * feat: FCMChannel 을 Enum 으로 관리 * chore: 메서드 접근자 및 네이밍 변경, log 메세지 변경 * feat: local prod, dev 환경에서만 FCM Bean 들이 생성되도록 변경 * refactor: eventListen 로직을 비동기적으로 처리한다 * refactor: LoginService 와 MemberFCMService 를 분리한다 * chore: 파라미터 네이밍 변경 * chore: logger -> log 네이밍 변경 * chore: log 메시지 변경 * chore: flyway 버전 변경
- Loading branch information
Showing
24 changed files
with
564 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
backend/src/main/java/com/festago/entry/dto/event/EntryProcessEvent.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.festago.entry.dto.event; | ||
|
||
public record EntryProcessEvent( | ||
Long memberId) { | ||
|
||
} |
90 changes: 90 additions & 0 deletions
90
backend/src/main/java/com/festago/fcm/application/FCMNotificationEventListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package com.festago.fcm.application; | ||
|
||
import com.festago.common.exception.ErrorCode; | ||
import com.festago.common.exception.InternalServerException; | ||
import com.festago.entry.dto.event.EntryProcessEvent; | ||
import com.festago.fcm.domain.FCMChannel; | ||
import com.festago.fcm.dto.MemberFCMResponse; | ||
import com.google.firebase.messaging.AndroidConfig; | ||
import com.google.firebase.messaging.AndroidNotification; | ||
import com.google.firebase.messaging.BatchResponse; | ||
import com.google.firebase.messaging.FirebaseMessaging; | ||
import com.google.firebase.messaging.FirebaseMessagingException; | ||
import com.google.firebase.messaging.Message; | ||
import com.google.firebase.messaging.SendResponse; | ||
import java.util.List; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.context.annotation.Profile; | ||
import org.springframework.scheduling.annotation.Async; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.transaction.event.TransactionPhase; | ||
import org.springframework.transaction.event.TransactionalEventListener; | ||
|
||
@Component | ||
@Profile({"dev", "prod"}) | ||
public class FCMNotificationEventListener { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(FCMNotificationEventListener.class); | ||
|
||
private final FirebaseMessaging firebaseMessaging; | ||
private final MemberFCMService memberFCMService; | ||
|
||
public FCMNotificationEventListener(FirebaseMessaging firebaseMessaging, MemberFCMService memberFCMService) { | ||
this.firebaseMessaging = firebaseMessaging; | ||
this.memberFCMService = memberFCMService; | ||
} | ||
|
||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||
@Async | ||
public void sendFcmNotification(EntryProcessEvent event) { | ||
List<Message> messages = createMessages(getMemberFCMToken(event.memberId()), FCMChannel.NOT_DEFINED.name()); | ||
try { | ||
BatchResponse batchResponse = firebaseMessaging.sendAll(messages); | ||
checkAllSuccess(batchResponse, event.memberId()); | ||
} catch (FirebaseMessagingException e) { | ||
log.error("fail send FCM message", e); | ||
throw new InternalServerException(ErrorCode.FAIL_SEND_FCM_MESSAGE); | ||
} | ||
} | ||
|
||
private List<String> getMemberFCMToken(Long memberId) { | ||
return memberFCMService.findMemberFCM(memberId).memberFCMs().stream() | ||
.map(MemberFCMResponse::fcmToken) | ||
.toList(); | ||
} | ||
|
||
private List<Message> createMessages(List<String> tokens, String channelId) { | ||
return tokens.stream() | ||
.map(token -> createMessage(token, channelId)) | ||
.toList(); | ||
} | ||
|
||
private Message createMessage(String token, String channelId) { | ||
return Message.builder() | ||
.setAndroidConfig(createAndroidConfig(channelId)) | ||
.setToken(token) | ||
.build(); | ||
} | ||
|
||
private AndroidConfig createAndroidConfig(String channelId) { | ||
return AndroidConfig.builder() | ||
.setNotification(createAndroidNotification(channelId)) | ||
.build(); | ||
} | ||
|
||
private AndroidNotification createAndroidNotification(String channelId) { | ||
return AndroidNotification.builder() | ||
.setChannelId(channelId) | ||
.build(); | ||
} | ||
|
||
private void checkAllSuccess(BatchResponse batchResponse, Long memberId) { | ||
List<SendResponse> failSend = batchResponse.getResponses().stream() | ||
.filter(sendResponse -> !sendResponse.isSuccessful()) | ||
.toList(); | ||
|
||
log.warn("member {} 에 대한 다음 요청들이 실패했습니다. {}", memberId, failSend); | ||
throw new InternalServerException(ErrorCode.FAIL_SEND_FCM_MESSAGE); | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
backend/src/main/java/com/festago/fcm/application/MemberFCMService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package com.festago.fcm.application; | ||
|
||
import com.festago.auth.application.AuthExtractor; | ||
import com.festago.auth.domain.AuthPayload; | ||
import com.festago.common.exception.ErrorCode; | ||
import com.festago.common.exception.InternalServerException; | ||
import com.festago.fcm.domain.MemberFCM; | ||
import com.festago.fcm.dto.MemberFCMsResponse; | ||
import com.festago.fcm.repository.MemberFCMRepository; | ||
import java.util.List; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.scheduling.annotation.Async; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@Transactional | ||
public class MemberFCMService { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(MemberFCMService.class); | ||
private static final int LEAST_MEMBER_FCM = 1; | ||
|
||
private final MemberFCMRepository memberFCMRepository; | ||
private final AuthExtractor authExtractor; | ||
|
||
public MemberFCMService(MemberFCMRepository memberFCMRepository, AuthExtractor authExtractor) { | ||
this.memberFCMRepository = memberFCMRepository; | ||
this.authExtractor = authExtractor; | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public MemberFCMsResponse findMemberFCM(Long memberId) { | ||
List<MemberFCM> memberFCM = memberFCMRepository.findByMemberId(memberId); | ||
if (memberFCM.size() < LEAST_MEMBER_FCM) { | ||
log.error("member {} 의 FCM 토큰이 발급되지 않았습니다.", memberId); | ||
throw new InternalServerException(ErrorCode.FCM_NOT_FOUND); | ||
} | ||
return MemberFCMsResponse.from(memberFCM); | ||
} | ||
|
||
@Async | ||
public void saveMemberFCM(String accessToken, String fcmToken) { | ||
AuthPayload authPayload = authExtractor.extract(accessToken); | ||
Long memberId = authPayload.getMemberId(); | ||
memberFCMRepository.save(new MemberFCM(memberId, fcmToken)); | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
backend/src/main/java/com/festago/fcm/config/FCMConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package com.festago.fcm.config; | ||
|
||
import com.google.auth.oauth2.GoogleCredentials; | ||
import com.google.firebase.FirebaseApp; | ||
import com.google.firebase.FirebaseOptions; | ||
import com.google.firebase.messaging.FirebaseMessaging; | ||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.context.annotation.Profile; | ||
import org.springframework.core.io.ClassPathResource; | ||
|
||
@Configuration | ||
@Profile({"prod", "dev"}) | ||
public class FCMConfig { | ||
|
||
@Value("${fcm.key.path}") | ||
private String fcmPrivateKeyPath; | ||
|
||
@Value("${fcm.key.scope}") | ||
private String fireBaseScope; | ||
|
||
@Bean | ||
public FirebaseMessaging firebaseMessaging() throws IOException { | ||
Optional<FirebaseApp> defaultFirebaseApp = defaultFirebaseApp(); | ||
if (defaultFirebaseApp.isPresent()) { | ||
return FirebaseMessaging.getInstance(defaultFirebaseApp.get()); | ||
} | ||
return FirebaseMessaging.getInstance( | ||
FirebaseApp.initializeApp(createFirebaseOption()) | ||
); | ||
} | ||
|
||
private Optional<FirebaseApp> defaultFirebaseApp() { | ||
List<FirebaseApp> firebaseAppList = FirebaseApp.getApps(); | ||
if (firebaseAppList == null || firebaseAppList.isEmpty()) { | ||
return Optional.empty(); | ||
} | ||
return firebaseAppList.stream() | ||
.filter(firebaseApp -> firebaseApp.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) | ||
.findAny(); | ||
} | ||
|
||
private FirebaseOptions createFirebaseOption() throws IOException { | ||
return FirebaseOptions.builder() | ||
.setCredentials(createGoogleCredentials()) | ||
.build(); | ||
} | ||
|
||
private GoogleCredentials createGoogleCredentials() throws IOException { | ||
return GoogleCredentials | ||
.fromStream(new ClassPathResource(fcmPrivateKeyPath).getInputStream()) | ||
.createScoped(fireBaseScope); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.festago.fcm.domain; | ||
|
||
public enum FCMChannel { | ||
NOT_DEFINED; | ||
} |
Oops, something went wrong.