Skip to content

Commit 55054f4

Browse files
committed
fix(ga4): 凭证文件缺失时降级为 stub client,不阻塞容器启动
根因:JVM 模式下 @configuration 的 Ga4ClientConfig 立即实例化, 读 ga4-sa-key.json 抛 FileNotFoundException 让整个 Spring ApplicationContext 起不来。 (之前 native image 模式可能因 lazy class loading 没立即触发,但部署也持续失败) 修复: - credPath 不存在时,注册一个 endpoint=invalid 的 stub BetaAnalyticsDataClient - Ga4ReportService 调用时 RPC 抛异常 → Ga4UnavailableException → 503 - 其他业务(auth / preferences / 埋点)不受影响 本地验证: - 有 ga4-sa-key.json 时走原逻辑 - mvn package + java -jar 5.6s 启动成功 - /actuator/health 503 是因为本地无 DB 凭据,DB 配好的环境会 UP
1 parent 7a57055 commit 55054f4

1 file changed

Lines changed: 50 additions & 12 deletions

File tree

‎src/main/java/com/involutionhell/backend/analytics/config/Ga4ClientConfig.java‎

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@
1010

1111
import java.io.FileInputStream;
1212
import java.io.IOException;
13+
import java.nio.file.Files;
14+
import java.nio.file.Path;
1315

1416
/**
1517
* 构造 BetaAnalyticsDataClient 单例 Bean 并注入 Service Account 凭证。
1618
* 客户端基于 gRPC,线程安全,在应用启动时创建一次即可;作用域仅申请 analytics.readonly,不写任何数据。
19+
*
20+
* 容错策略:凭证文件不存在不阻塞容器启动,而是注册一个"调用即抛 Ga4UnavailableException"的 stub
21+
* client。这样 GA4 接口运行时返回 503,其他业务(auth / preferences / 埋点)仍可用。
22+
* 之前直接 throw IOException 让 Spring ApplicationContext 起不来 → 整个后端崩,影响面过大。
1723
*/
1824
@Configuration
1925
public class Ga4ClientConfig {
@@ -22,26 +28,58 @@ public class Ga4ClientConfig {
2228

2329
/**
2430
* 从本地 JSON 文件加载 Google Service Account 密钥构造 GA4 Data API 客户端。
25-
* 启动失败(如凭证路径不存在或无访问权限)会让容器启动失败,及时暴露配置问题。
31+
* 文件不存在或读取失败时记录 warning 并抛 IOException 让 Spring 跳过 bean 注册前的提前 check —
32+
* 实际改为返回一个调用失败的 stub。
2633
*/
2734
@Bean
28-
public BetaAnalyticsDataClient betaAnalyticsDataClient(Ga4Properties ga4Properties) throws IOException {
35+
public BetaAnalyticsDataClient betaAnalyticsDataClient(Ga4Properties ga4Properties) {
2936
String credPath = ga4Properties.getCredentialsPath();
30-
log.info("初始化 GA4 客户端,凭证路径: {}", credPath);
3137

32-
// 只申请只读权限(最小权限原则);try-with-resources 显式关闭文件流,
33-
// GoogleCredentials.fromStream 并不保证替调用方关闭 InputStream
34-
GoogleCredentials credentials;
38+
if (credPath == null || credPath.isBlank() || !Files.exists(Path.of(credPath))) {
39+
log.warn("GA4 凭证文件不存在或未配置 ({}), GA4 接口将返回 503,其余功能不受影响。",
40+
credPath);
41+
return brokenClient(credPath);
42+
}
43+
44+
log.info("初始化 GA4 客户端,凭证路径: {}", credPath);
3545
try (FileInputStream credentialsStream = new FileInputStream(credPath)) {
36-
credentials = GoogleCredentials
46+
GoogleCredentials credentials = GoogleCredentials
3747
.fromStream(credentialsStream)
3848
.createScoped("https://www.googleapis.com/auth/analytics.readonly");
39-
}
4049

41-
BetaAnalyticsDataSettings settings = BetaAnalyticsDataSettings.newBuilder()
42-
.setCredentialsProvider(() -> credentials)
43-
.build();
50+
BetaAnalyticsDataSettings settings = BetaAnalyticsDataSettings.newBuilder()
51+
.setCredentialsProvider(() -> credentials)
52+
.build();
4453

45-
return BetaAnalyticsDataClient.create(settings);
54+
return BetaAnalyticsDataClient.create(settings);
55+
} catch (IOException e) {
56+
log.warn("GA4 客户端初始化失败 ({}): {}, 降级到 stub", credPath, e.getMessage());
57+
return brokenClient(credPath);
58+
}
59+
}
60+
61+
/**
62+
* 构造一个"调用即失败"的 BetaAnalyticsDataClient,让 Ga4ReportService.fetchTopPaths
63+
* 走异常分支抛 Ga4UnavailableException → GlobalExceptionHandler 转 503。
64+
*
65+
* 用 BetaAnalyticsDataClient.create(settings) 配一个无效 endpoint 即可;
66+
* 实际方法调用会在网络层 / auth 层失败。
67+
*/
68+
private BetaAnalyticsDataClient brokenClient(String credPath) {
69+
try {
70+
BetaAnalyticsDataSettings settings = BetaAnalyticsDataSettings.newBuilder()
71+
// 用一个明显无效的 endpoint,确保任何 RPC 立刻失败而不是真的去打 google
72+
.setEndpoint("invalid.localhost:0")
73+
.setCredentialsProvider(() -> {
74+
throw new IllegalStateException(
75+
"GA4 credentials file not configured: " + credPath);
76+
})
77+
.build();
78+
return BetaAnalyticsDataClient.create(settings);
79+
} catch (IOException e) {
80+
// 这里几乎不会发生(create 只是包对象不发请求),保险起见还是抛 RuntimeException
81+
throw new IllegalStateException(
82+
"Failed to construct stub GA4 client; both real and stub init failed", e);
83+
}
4684
}
4785
}

0 commit comments

Comments
 (0)