-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[BE] feat: 스태프 인증 처리시 FCM 을 통해 유저 핸드폰에 입장 처리 완료 메시지를 보낸다(#444) #449
Changes from 30 commits
0b1bdb2
fb75c77
1ef3906
2b8200b
4c5c2a5
f2ffd3f
c20c05e
bff5dd5
a304bce
7105313
ceb3126
e55658a
72c452d
06d02b3
da113b8
062cce6
c594282
e5586c6
40f4374
06b8c9d
7ea3fb0
67a58e0
9eb300d
0eb447a
e112d0e
5aaf339
29c5c55
1c77fc9
894bbe0
903fc5f
b40a7a3
cc98d6e
38476b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,10 +8,12 @@ | |
import com.festago.entry.dto.EntryCodeResponse; | ||
import com.festago.entry.dto.TicketValidationRequest; | ||
import com.festago.entry.dto.TicketValidationResponse; | ||
import com.festago.entry.dto.event.EntryProcessEvent; | ||
import com.festago.ticketing.domain.MemberTicket; | ||
import com.festago.ticketing.repository.MemberTicketRepository; | ||
import java.time.Clock; | ||
import java.time.LocalDateTime; | ||
import org.springframework.context.ApplicationEventPublisher; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
|
@@ -21,11 +23,14 @@ public class EntryService { | |
|
||
private final EntryCodeManager entryCodeManager; | ||
private final MemberTicketRepository memberTicketRepository; | ||
private final ApplicationEventPublisher publisher; | ||
private final Clock clock; | ||
|
||
public EntryService(EntryCodeManager entryCodeManager, MemberTicketRepository memberTicketRepository, Clock clock) { | ||
public EntryService(EntryCodeManager entryCodeManager, MemberTicketRepository memberTicketRepository, | ||
ApplicationEventPublisher publisher, Clock clock) { | ||
this.entryCodeManager = entryCodeManager; | ||
this.memberTicketRepository = memberTicketRepository; | ||
this.publisher = publisher; | ||
this.clock = clock; | ||
} | ||
|
||
|
@@ -50,6 +55,7 @@ public TicketValidationResponse validate(TicketValidationRequest request) { | |
EntryCodePayload entryCodePayload = entryCodeManager.extract(request.code()); | ||
MemberTicket memberTicket = findMemberTicket(entryCodePayload.getMemberTicketId()); | ||
memberTicket.changeState(entryCodePayload.getEntryState()); | ||
publisher.publishEvent(new EntryProcessEvent(memberTicket.getOwner().getId())); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 어떤 memberTicket에 대한 fcm인지는 구분해주지 않아도 되나요?! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금은 이벤트가 딱 하나니까 그냥 이벤트가 날라왔다? -> 다음 화면으로 이동 이렇게 구현되어 있어요!! |
||
return TicketValidationResponse.from(memberTicket); | ||
} | ||
} |
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) { | ||
|
||
} |
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 logger = LoggerFactory.getLogger(FCMNotificationEventListener.class); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
아래 코드와 통일하면 좋을 것 같아요! |
||||||
|
||||||
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) { | ||||||
logger.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(); | ||||||
|
||||||
logger.warn("member {} 에 대한 다음 요청들이 실패했습니다. {}", memberId, failSend); | ||||||
throw new InternalServerException(ErrorCode.FAIL_SEND_FCM_MESSAGE); | ||||||
} | ||||||
} |
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); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
사소한건데 이거 어떤가요 ㅋㅋㅋ |
||||||
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)); | ||||||
} | ||||||
} |
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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.festago.fcm.domain; | ||
|
||
public enum FCMChannel { | ||
NOT_DEFINED; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.