Java

파이어베이스 다중 푸시

NaHyungMin 2023. 10. 16. 13:32

파이어베이스 홈페이지에서 json을 가져오는 형식과 처음 보내면 서비스 등록하라는 내용은 넘기고 구현체만 남김

단일 푸시지만 ios에서 개인 프로젝트로 만들었기 때문에 프로젝트 합치면서 오류가 날 경우 다른 정보로 보내도록 구현되어 있다.

한 프로젝트로 디바이스 등록하여 사용할거면 이렇게 처리 안해도 됨.

implementation 'com.google.firebase:firebase-admin:9.2.0' //230927 nhm, push

 

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.messaging.*;

 

@PostConstruct
private void initFireBaseApp() {
    try {
        ClassPathResource resource = new ClassPathResource("fcm/firebase.json");
        GoogleCredentials credentials = GoogleCredentials.fromStream(resource.getInputStream());

        FirebaseOptions options = FirebaseOptions.builder()
                .setCredentials(credentials)
                .build();

        //23년 -> 25년 안에 제거해야 한다.
        ClassPathResource resourceIosOldVersion = new ClassPathResource("fcm/ios-firebase.json");
        GoogleCredentials credentialsIosOldVersion = GoogleCredentials.fromStream(resourceIosOldVersion.getInputStream());

        FirebaseOptions optionsIosOldVersion = FirebaseOptions.builder()
                .setCredentials(credentialsIosOldVersion)
                .build();

        firebaseApp = FirebaseApp.initializeApp(options, "firebaseApp");
        fireBaseIosOldVersion = FirebaseApp.initializeApp(optionsIosOldVersion, "fireBaseIosOldVersion");
    } catch (FileNotFoundException ex) {
        오류처리("System fcm File Not Found : {}", ex.getMessage());
    } catch (IOException ex) {
        오류처리("System fcm io exception : {}", ex.getMessage());
    }
}

 

public synchronized void androidPush() {
    //android 푸시.
    List<PushAppInformation> androidPushList = pushV2Mapper.selectReservationsPushAppInformation(android);

    if(androidPushList.size() > 0) {
        sendPush(androidPushList);
    }
}

 

private void sendPush(List<PushAppInformation> pushList) {
    for(PushAppInformation pushAppInformation : pushList) {
        List<CompletableFuture> completableFutureList = new ArrayList<>();

        AtomicInteger successCount = new AtomicInteger();
        successCount.set(pushAppInformation.getSuccessCount());
        AtomicInteger failedCount = new AtomicInteger();
        failedCount.set(pushAppInformation.getFailedCount());

        String pushGroup = pushAppInformation.getPushGroup();
        String countryCode = pushAppInformation.getCountryCode();
        String deviceType = pushAppInformation.getDeviceType();

        int pushAppDataCount = pushV2Mapper.countReservationsPushAppData(pushGroup, countryCode, deviceType);

        if(pushAppDataCount > 0) { //0이라면 보내다가 배포로 인해 중단될 가능성 체크.
            try {
                int searchCount = (int) Math.ceil((double) pushAppDataCount / pushMaxCount);
                Queue<List<String>> queue = new LinkedList<>();

                for(int i = 0; i < searchCount; i++) {
                    //카운트 500 단위 0, 500 -> 501, 1000 -> 1001, 1500 ...
                    int startLimit = i * pushMaxCount;

                    queue.offer(selectReservationsPushAppToken(pushGroup, countryCode, deviceType, startLimit, pushMaxCount));
                }

                while (!queue.isEmpty()) {
                    List<String> pushAppDataList = queue.poll();

                    if(pushAppDataList.size() > 0) {
                        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                            try {
                                sendReservationsPushAppData(pushAppInformation, pushAppDataList, successCount, failedCount);
                            } catch (FirebaseMessagingException ex) {
                                오류처리("sendPush device : {}, FirebaseMessagingException exception : {}",  pushAppInformation.getDeviceType(), ex.getMessage());
                            }
                        });

                        completableFutureList.add(future);
                    }
                }

                if(completableFutureList.size() > 0) {
                    CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[0]));
                }
            } catch (Exception ex) {
                오류처리("PUSH V2 updateReservationsPushAppInformationComplete ERROR seq : {}, successCount : {}, failedCount : {}, message : {}"
                        , pushAppInformation.getSeq(), successCount, failedCount, ex.getMessage());

                //Message 발송.

                //Information isSend 처리.
                pushV2Mapper.updateReservationsPushAppInformationComplete(pushAppInformation.getSeq(), successCount.get(), failedCount.get());
            }
        }

        로그업데이트("PUSH V2 updateReservationsPushAppInformationComplete seq : {}, successCount : {}, failedCount : {}"
                , pushAppInformation.getSeq(), successCount, failedCount);

        //Information isSend 처리.
        pushV2Mapper.updateReservationsPushAppInformationComplete(pushAppInformation.getSeq(), successCount.get(), failedCount.get());
    }
}

 

private void sendReservationsPushAppData(PushAppInformation pushAppInformation, List<String> pushAppFcmTokenList, AtomicInteger successCount, AtomicInteger failedCount) throws FirebaseMessagingException {
    String pushGroup = pushAppInformation.getPushGroup();
    String deviceType = pushAppInformation.getDeviceType();
    String countryCode = pushAppInformation.getCountryCode();

    BatchResponse response = FirebaseMessaging.getInstance(firebaseApp).sendEachForMulticast(getSendMessage(pushAppInformation, pushAppFcmTokenList));

    Map<String, Object> updateMap = new HashMap<>();
    updateMap.put("infoPushGroup", pushGroup);
    updateMap.put("deviceType", deviceType);
    updateMap.put("countryCode", countryCode);
    updateMap.put("list", pushAppFcmTokenList);

    //update. send
    pushV2Mapper.updateSendReservationsPushAppData(updateMap);

    successCount.set(successCount.get() + response.getSuccessCount());
    failedCount.set(failedCount.get() + response.getFailureCount());

    if (response.getFailureCount() > 0) {
        List<SendResponse> responses = response.getResponses();
        List<String> failedTokens = new ArrayList<>();
        List<String> failedIosTokens = new ArrayList<>();

        for (int i = 0; i < responses.size(); i++) {
            if (!responses.get(i).isSuccessful()) {
                MessagingErrorCode messagingErrorCode = responses.get(i).getException().getMessagingErrorCode();

                if(deviceType.equalsIgnoreCase(ios) && messagingErrorCode.name().equals(MessagingErrorCode.SENDER_ID_MISMATCH.name())) {
                    failedIosTokens.add(pushAppFcmTokenList.get(i));
                } else {
                    failedTokens.add(pushAppFcmTokenList.get(i));
                }
            }
        }

        if(failedIosTokens.size() > 0) {
            //231013 nhm, ios 프로젝트가 개인으로 만들어져 과거 버전 호환해야 한다. failedTokens update
            failedTokens.addAll(sendIosOldVersion(pushAppInformation, failedIosTokens, successCount, failedCount));
        }

        //update. failed
        if(failedTokens.size() > 0) {
            updateMap.put("list", failedTokens);
            pushV2Mapper.updateFailedReservationsPushAppData(updateMap);
        }
    }
}

 

private List<String> sendIosOldVersion(PushAppInformation pushAppInformation, List<String> failedIosTokens, AtomicInteger successCount, AtomicInteger failedCount) throws FirebaseMessagingException {
    List<String> failedTokens = new ArrayList<>();

    try {
        BatchResponse response = FirebaseMessaging.getInstance(fireBaseIosOldVersion).sendEachForMulticast(getSendMessage(pushAppInformation, failedIosTokens));

        successCount.set(successCount.get() + response.getSuccessCount());
        failedCount.set(failedCount.get() - response.getSuccessCount());

        List<SendResponse> responses = response.getResponses();

        for (int i = 0; i < responses.size(); i++) {
            if (!responses.get(i).isSuccessful()) {
                failedTokens.add(failedIosTokens.get(i));
            }
        }
    } catch (Exception ex) {
        ex.printStackTrace();
        오류처리("sendIosOldVersion ERROR : {}", ex.getMessage());
    }

    return failedTokens;
}

 

데이터베이스는 조회용 모테이블과 실제 발송 토큰 자식테이블로 되어 있음.

내부 정보가 있어 비공개 처리.