-
Notifications
You must be signed in to change notification settings - Fork 2
feat: FCM 병렬 동기 전송 메소드 추가 #2263
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
Merged
+69
−0
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
a1a9a26
feat: FcmSendRequest DTO 추가
Soundbar91 9a63002
fix: FcmSendRequest 네이밍 수정
Soundbar91 5bc7192
feat: sendMessages 메소드 추가
Soundbar91 a09422f
fix: FcmSendCommand 네이밍 수정
Soundbar91 96c31a1
feat: FCM 로깅 추가
Soundbar91 92347bd
fix: 배치사이즈 별로 FCM 알림 전송 로직으로 수정
Soundbar91 eb11ed3
Merge branch 'develop' into feat/2262-add-multicast-message
Soundbar91 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| import static com.google.firebase.messaging.AndroidConfig.Priority.HIGH; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| import org.springframework.scheduling.annotation.Async; | ||
|
|
@@ -24,6 +25,8 @@ | |
| @Component | ||
| public class FcmClient { | ||
|
|
||
| private static final int FCM_MESSAGE_BATCH_SIZE = 500; | ||
|
|
||
| @Async | ||
| public void sendMessage( | ||
| String targetDeviceToken, | ||
|
|
@@ -70,6 +73,39 @@ public boolean sendMessageWithResult( | |
| } | ||
| } | ||
|
|
||
| public void sendMessages(List<FcmSendRequest> requests) { | ||
| try { | ||
| for (int start = 0; start < requests.size(); start += FCM_MESSAGE_BATCH_SIZE) { | ||
| int end = Math.min(start + FCM_MESSAGE_BATCH_SIZE, requests.size()); | ||
| List<Message> messages = requests.subList(start, end).stream() | ||
| .map(request -> Message.builder() | ||
| .setToken(request.targetDeviceToken()) | ||
| .setApnsConfig(generateAppleConfig( | ||
| request.title(), | ||
| request.content(), | ||
| request.imageUrl(), | ||
| request.path(), | ||
| request.type(), | ||
| request.schemeUri() | ||
| )) | ||
| .setAndroidConfig(generateAndroidConfig( | ||
| request.title(), | ||
| request.content(), | ||
| request.imageUrl(), | ||
| request.schemeUri(), | ||
| request.type() | ||
| )) | ||
| .build() | ||
| ) | ||
| .toList(); | ||
|
|
||
| FirebaseMessaging.getInstance().sendEach(messages); | ||
| } | ||
| } catch (Exception e) { | ||
| log.warn("FCM 알림 전송 실패", e); | ||
| } | ||
|
Comment on lines
+104
to
+106
Collaborator
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. 개별 실패를 체크하면 좋긴할텐데 로그 노이즈가 많이 발생할거 같으니,
Collaborator
Author
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. |
||
| } | ||
|
|
||
| private ApnsConfig generateAppleConfig( | ||
| String title, | ||
| String content, | ||
|
|
||
33 changes: 33 additions & 0 deletions
33
src/main/java/in/koreatech/koin/infrastructure/fcm/FcmSendRequest.java
This file contains hidden or 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,33 @@ | ||
| package in.koreatech.koin.infrastructure.fcm; | ||
|
|
||
| import in.koreatech.koin.common.model.MobileAppPath; | ||
|
|
||
| public record FcmSendRequest( | ||
| String targetDeviceToken, | ||
| String title, | ||
| String content, | ||
| String imageUrl, | ||
| MobileAppPath path, | ||
| String schemeUri, | ||
| String type | ||
| ) { | ||
| public static FcmSendRequest of( | ||
| String targetDeviceToken, | ||
| String title, | ||
| String content, | ||
| String imageUrl, | ||
| MobileAppPath path, | ||
| String schemeUri, | ||
| String type | ||
| ) { | ||
| return new FcmSendRequest( | ||
| targetDeviceToken, | ||
| title, | ||
| content, | ||
| imageUrl, | ||
| path, | ||
| schemeUri, | ||
| type | ||
| ); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.

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.
🧩 Analysis chain
🌐 Web query:
In Firebase Admin Java SDK, forFirebaseMessaging.sendEach(List), do individual message failures throw an exception or only appear inBatchResponse? Also confirm recommended failure-handling patterns.💡 Result:
In the Firebase Admin Java SDK, the
FirebaseMessaging.sendEach()method does not throw an exception for individual message failures [1]. Instead, it processes the entire batch and returns aBatchResponseobject, which contains the results for each individual message [1][2]. If a specific message fails, that failure is encapsulated within theBatchResponse[1]. You must inspect theBatchResponseto determine the outcome of each message in the batch [2]. Recommended failure-handling patterns include: 1. Inspect the BatchResponse: Always checkBatchResponse.getFailureCount()to see if any messages failed [2]. 2. Iterate through Responses: UseBatchResponse.getResponses()to iterate through the list ofSendResponseobjects [2]. Note that the order of these responses corresponds exactly to the order of the messages provided in the input list [2]. 3. Identify and Handle Failures: For eachSendResponse, callisSuccessful(). If it returnsfalse, usegetException()to retrieve the specific error details [1][3]. 4. Token Management: Common errors, such asmessaging/registration-token-not-registeredormessaging/invalid-argument, often indicate that a token is stale or invalid [4][5][6]. When these errors are identified in theSendResponse, you should remove the associated token from your database to prevent future failed attempts [3][4][6]. 5. Retry Strategy: For errors indicating temporary issues (e.g.,messaging/server-unavailableormessaging/internal-error), implement an exponential backoff retry strategy [5]. Do not retry messages that failed due to permanent client-side errors [3][7].Citations:
sendEach,sendEachAsync,sendEachForMulticastandsendEachForMulticastAsync(#785) firebase/firebase-admin-java#815🏁 Script executed:
Repository: BCSDLab/KOIN_API_V2
Length of output: 312
🏁 Script executed:
Repository: BCSDLab/KOIN_API_V2
Length of output: 3970
🏁 Script executed:
Repository: BCSDLab/KOIN_API_V2
Length of output: 6534
🏁 Script executed:
Repository: BCSDLab/KOIN_API_V2
Length of output: 270
Handle per-message failures in
FcmClient.sendMessagesand filter blank tokensFirebaseMessaging.getInstance().sendEach(messages)does not throw per-message errors; it returns aBatchResponsethat must be inspected, so current code hides partial failures.sendMessageWithResult,sendMessagesdoesn’t guard against blank/invalidrequest.targetDeviceToken(), so invalid tokens can degrade the whole batch.Proposed fix
public void sendMessages(List<FcmSendRequest> requests) { + if (requests == null || requests.isEmpty()) { + return; + } try { - List<Message> messages = requests.stream() + List<FcmSendRequest> validRequests = requests.stream() + .filter(request -> request != null && StringUtils.hasText(request.targetDeviceToken())) + .toList(); + + if (validRequests.isEmpty()) { + return; + } + + List<Message> messages = validRequests.stream() .map(request -> Message.builder() .setToken(request.targetDeviceToken()) .setApnsConfig(generateAppleConfig( request.title(), request.content(), @@ .build() ) .toList(); - FirebaseMessaging.getInstance().sendEach(messages); + var batchResponse = FirebaseMessaging.getInstance().sendEach(messages); + if (batchResponse.getFailureCount() > 0) { + for (int i = 0; i < batchResponse.getResponses().size(); i++) { + var response = batchResponse.getResponses().get(i); + if (!response.isSuccessful()) { + log.warn("FCM batch send failed. token: {}, error: {}", + validRequests.get(i).targetDeviceToken(), + response.getException() != null ? response.getException().getMessage() : "unknown"); + } + } + } } catch (Exception e) { log.warn("FCM 알림 전송 실패", e); } }🤖 Prompt for AI Agents