Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

Expand All @@ -11,27 +16,60 @@

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;
}
}
7 changes: 7 additions & 0 deletions notification/src/main/resources/application-aws.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 7 additions & 0 deletions notification/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +65 to +69

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fcm.required: true가 application-aws.yml뿐 아니라 이 base application.yml에도 그대로 들어가 있습니다. application.yml은 프로파일과 무관하게 항상 로드되는 base 설정이므로, FCMConfig.java의 주석("운영(aws 프로파일)에서만 true. 키가 없으면 기동을 깨서 배포 시점에 드러낸다")과 기본값(@Value("${fcm.required:false}"))의 의도와 달리 로컬 개발/CI 환경에서도 fail-fast가 적용됩니다.

이 저장소에는 serviceAccountKey.json 파일이 존재하지 않고(.gitignore에 등록됨), FCM_SERVICE_ACCOUNT_B64 환경변수도 로컬에는 보통 없으므로, openCredentials()가 null을 반환 → required == true → IllegalStateException으로 @PostConstruct가 실패해 애플리케이션 컨텍스트 로딩 자체가 깨집니다. 이 PR이 새로 추가한 "키 없으면 경고 로그만 남기고 계속 부팅" 폴백 경로(log.warn("FCM 키 없음 - 푸시 비활성화 상태로 기동"))가 모든 프로파일에서 도달 불가능한 죽은 코드가 되는 셈입니다.

이 저장소는 fail-fast류 설정(예: spring.kafka.admin.fail-fast: true)을 prod(application-aws.yml) 전용으로만 두는 관례가 이미 있는데, 이번 변경은 그 관례와도 어긋납니다. application.yml(base)의 required는 false로 두고, true는 application-aws.yml 쪽에만 남겨야 합니다.

Suggested change
# FCM 서비스 계정 키. 이미지에 굽지 않고 .env.prod 의 env 로 주입받는다.
# required: 키가 없으면 기동을 깨서 배포 시점에 드러낸다(kafka.admin.fail-fast 와 같은 철학).
fcm:
service-account-b64: ${FCM_SERVICE_ACCOUNT_B64:}
required: true
fcm:
service-account-b64: ${FCM_SERVICE_ACCOUNT_B64:}
required: false

Loading