From 29e73aa347b34e39167b77342fa3e7c0dfb09241 Mon Sep 17 00:00:00 2001 From: choi-jin-wook Date: Tue, 15 Sep 2026 14:01:50 +0900 Subject: [PATCH] =?UTF-8?q?fix(notification):=20FCM=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EA=B3=84=EC=A0=95=20=EC=84=A4=EC=A0=95=20=EC=A3=BC?= =?UTF-8?q?=EC=9E=85=20=EB=B0=A9=EC=8B=9D=20=EA=B0=9C=EC=84=A0=20=20=20=20?= =?UTF-8?q?=20=20=20-=20FCM=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B3=84?= =?UTF-8?q?=EC=A0=95=20=ED=82=A4=EB=A5=BC=20Base64=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=A1=9C=20=EC=A3=BC=EC=9E=85=20=20=20=20=20?= =?UTF-8?q?=20=20-=20=EB=A1=9C=EC=BB=AC=20=ED=99=98=EA=B2=BD=EC=97=90?= =?UTF-8?q?=EC=84=9C=20classpath=20=EC=9E=90=EA=B2=A9=20=EC=A6=9D=EB=AA=85?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=EC=9D=84=20=ED=8F=B4=EB=B0=B1=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=82=AC=EC=9A=A9=20=20=20=20=20=20=20-=20?= =?UTF-8?q?=EC=9A=B4=EC=98=81=20=ED=99=98=EA=B2=BD=EC=9D=98=20=ED=82=A4=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=20=EC=8B=9C=20=EC=95=A0=ED=94=8C=EB=A6=AC?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=85=98=20=EA=B8=B0=EB=8F=99=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=EC=B2=98=EB=A6=AC=20=20=20=20=20=20=20-=20Spring?= =?UTF-8?q?=20Value=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20FCM=20=EC=84=A4=EC=A0=95=EA=B0=92=20?= =?UTF-8?q?=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notification/global/config/FCMConfig.java | 53 ++++++++++++++++--- .../src/main/resources/application-aws.yml | 7 +++ .../src/main/resources/application.yml | 7 +++ 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/notification/src/main/java/com/comatching/notification/global/config/FCMConfig.java b/notification/src/main/java/com/comatching/notification/global/config/FCMConfig.java index 1388e8e..9569b65 100644 --- a/notification/src/main/java/com/comatching/notification/global/config/FCMConfig.java +++ b/notification/src/main/java/com/comatching/notification/global/config/FCMConfig.java @@ -1,37 +1,74 @@ package com.comatching.notification.global.config; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; +import java.util.Base64; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import com.google.auth.oauth2.GoogleCredentials; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; - +import org.springframework.beans.factory.annotation.Value; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.StringUtils; @Slf4j @Configuration +@RequiredArgsConstructor public class FCMConfig { + private final ResourceLoader resourceLoader; + + // 운영: base64 인코딩된 서비스 계정 JSON 을 env 로 주입받는다. + // (.env 파일이 멀티라인 값을 지원하지 않아 JSON 원문 대신 base64 한 줄로 넣는다) + @Value("${fcm.service-account-b64:}") + private String serviceAccountB64; + + // 로컬 폴백: 기존과 같은 classpath 파일. + @Value("${fcm.credentials:classpath:serviceAccountKey.json}") + private String credentialsLocation; + + // 운영(aws 프로파일)에서만 true. 키가 없으면 기동을 깨서 배포 시점에 드러낸다. + @Value("${fcm.required:false}") + private boolean required; + @PostConstruct public void init() { - try { + try (InputStream serviceAccount = openCredentials()) { + if (serviceAccount == null) { + if (required) { + throw new IllegalStateException("FCM 서비스 계정 키가 없다 (env FCM_SERVICE_ACCOUNT_B64 미설정)"); + } + log.warn("FCM 키 없음 - 푸시 비활성화 상태로 기동"); + return; + } - InputStream serviceAccount = new ClassPathResource("serviceAccountKey.json").getInputStream(); FirebaseOptions options = FirebaseOptions.builder() - .setCredentials(GoogleCredentials.fromStream(serviceAccount)) - .build(); + .setCredentials(GoogleCredentials.fromStream(serviceAccount)) + .build(); if (FirebaseApp.getApps().isEmpty()) { FirebaseApp.initializeApp(options); log.info("🔥 FirebaseApp Initialized"); } + } catch (IOException | IllegalArgumentException e) { + // 키가 "있는데" 깨진 것(base64 오류·JSON 파싱 실패)은 환경 불문 잘못된 상태다. 삼키지 않는다. + throw new IllegalStateException("FirebaseApp 초기화 실패", e); + } + } - } catch (Exception e) { - log.error("❌ FirebaseApp Init Failed", e); + private InputStream openCredentials() throws IOException { + if (StringUtils.hasText(serviceAccountB64)) { + return new ByteArrayInputStream(Base64.getDecoder().decode(serviceAccountB64.trim())); } + Resource resource = resourceLoader.getResource(credentialsLocation); + return resource.exists() ? resource.getInputStream() : null; } -} +} \ No newline at end of file diff --git a/notification/src/main/resources/application-aws.yml b/notification/src/main/resources/application-aws.yml index 84aac07..23e7904 100644 --- a/notification/src/main/resources/application-aws.yml +++ b/notification/src/main/resources/application-aws.yml @@ -64,3 +64,10 @@ comatching: # 비우면 내부 API 전체가 401 로 닫힌다(fail-closed). internal: service-token: ${INTERNAL_SERVICE_TOKEN:} + + +# FCM 서비스 계정 키. 이미지에 굽지 않고 .env.prod 의 env 로 주입받는다. +# required: 키가 없으면 기동을 깨서 배포 시점에 드러낸다(kafka.admin.fail-fast 와 같은 철학). +fcm: + service-account-b64: ${FCM_SERVICE_ACCOUNT_B64:} + required: true \ No newline at end of file diff --git a/notification/src/main/resources/application.yml b/notification/src/main/resources/application.yml index c986404..ca93b3e 100644 --- a/notification/src/main/resources/application.yml +++ b/notification/src/main/resources/application.yml @@ -60,3 +60,10 @@ comatching: # 비우면 내부 API 전체가 401 로 닫힌다(fail-closed). internal: service-token: ${INTERNAL_SERVICE_TOKEN:} + + +# FCM 서비스 계정 키. 이미지에 굽지 않고 .env.prod 의 env 로 주입받는다. +# required: 키가 없으면 기동을 깨서 배포 시점에 드러낸다(kafka.admin.fail-fast 와 같은 철학). +fcm: + service-account-b64: ${FCM_SERVICE_ACCOUNT_B64:} + required: true \ No newline at end of file