mirror of
https://github.com/Silentely/eSIM-Tools.git
synced 2026-09-03 06:24:20 +08:00
🐛 fix: 重构健康检查功能以支持分层认证与环境变量监控
- 将健康检查从单一响应优化为公开/私有分层架构,通过 `detail=true` 参数控制是否返回详细环境变量检查状态 - 引入 `CRITICAL_ENV_VARS` 和 `OPTIONAL_ENV_VARS` 配置列表,统一环境变量健康检查逻辑,增强可维护性与可扩展性 - 移除 `notifications-internal.js` 废弃文件,保持函数目录结构清晰,避免路由冗余 - 在 BFF Edge Function 中将 `health` 路由加入 `ALLOWED_ORIGIN` 免验证白名单,支持前端直接调用健康检查接口
This commit is contained in:
@@ -14,7 +14,8 @@ const BFF_ROUTES = new Map([
|
||||
['auto-activate-esim', ['POST', 'OPTIONS']],
|
||||
['qrcode-generate', ['POST', 'OPTIONS']],
|
||||
['verify-cookie', ['POST', 'OPTIONS']],
|
||||
['public-config', ['GET', 'OPTIONS']]
|
||||
['public-config', ['GET', 'OPTIONS']],
|
||||
['health', ['GET', 'OPTIONS']]
|
||||
]);
|
||||
|
||||
// 注意:此文件运行在 Deno(Edge Function),无法直接引用 Node.js 的 _shared/cors.js。
|
||||
@@ -80,7 +81,7 @@ export default async (request, context) => {
|
||||
const sameOrigin = requestOrigin === url.origin;
|
||||
const configuredOrigin = allowedOrigins.includes(requestOrigin);
|
||||
const corsOrigin = sameOrigin || configuredOrigin ? requestOrigin : '';
|
||||
const allowMissingOriginForPublicGet = targetName === 'public-config' && request.method === 'GET';
|
||||
const allowMissingOriginForPublicGet = (targetName === 'public-config' || targetName === 'health') && request.method === 'GET';
|
||||
const fallbackCorsOrigin = allowedOrigins[0] || DEFAULT_ALLOWED_ORIGIN;
|
||||
const errorCorsHeaders = buildCorsHeaders(corsOrigin || fallbackCorsOrigin);
|
||||
|
||||
|
||||
@@ -1,48 +1,73 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Netlify Function: Health Check
|
||||
* 提供健康检查端点,用于监控服务状态
|
||||
* 提供分层健康检查端点,用于监控服务状态
|
||||
*
|
||||
* 公开层(无需认证):返回基础状态信息
|
||||
* 私有层(通过 BFF + ACCESS_KEY 认证):返回环境变量检查计数(隐藏变量名)
|
||||
*/
|
||||
|
||||
exports.handler = async (event, context) => {
|
||||
const health = {
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
service: 'eSIM-Tools',
|
||||
version: process.env.APP_VERSION || '2.0.0',
|
||||
environment: process.env.NODE_ENV || 'production',
|
||||
uptime: process.uptime(),
|
||||
checks: {
|
||||
accessKey: !!process.env.ACCESS_KEY,
|
||||
giffgaffClientId: !!process.env.GIFFGAFF_CLIENT_ID,
|
||||
giffgaffClientSecret: !!process.env.GIFFGAFF_CLIENT_SECRET
|
||||
}
|
||||
};
|
||||
const { withAuth } = require('./_shared/middleware');
|
||||
|
||||
// 检查关键环境变量
|
||||
const missingConfigs = [];
|
||||
if (!process.env.ACCESS_KEY) {
|
||||
missingConfigs.push('ACCESS_KEY');
|
||||
}
|
||||
if (!process.env.GIFFGAFF_CLIENT_ID) {
|
||||
missingConfigs.push('GIFFGAFF_CLIENT_ID');
|
||||
}
|
||||
if (!process.env.GIFFGAFF_CLIENT_SECRET) {
|
||||
missingConfigs.push('GIFFGAFF_CLIENT_SECRET');
|
||||
}
|
||||
// 需要检查的关键环境变量列表(仅内部使用,不对外暴露)
|
||||
const CRITICAL_ENV_VARS = [
|
||||
'ACCESS_KEY',
|
||||
'ALLOWED_ORIGIN',
|
||||
'GIFFGAFF_CLIENT_ID',
|
||||
'GIFFGAFF_CLIENT_SECRET'
|
||||
];
|
||||
|
||||
if (missingConfigs.length > 0) {
|
||||
health.status = 'degraded';
|
||||
health.warnings = missingConfigs.map(key => `${key} not configured`);
|
||||
}
|
||||
const OPTIONAL_ENV_VARS = [
|
||||
'SENTRY_DSN',
|
||||
'CAPTCHA_PROVIDER'
|
||||
];
|
||||
|
||||
const statusCode = health.status === 'healthy' ? 200 : 503;
|
||||
/**
|
||||
* 检查环境变量配置状态
|
||||
* @returns {{ total: number, configured: number, missing: number, optional: { total: number, configured: number } }}
|
||||
*/
|
||||
function checkEnvStatus() {
|
||||
const criticalMissing = CRITICAL_ENV_VARS.filter(key => !process.env[key]);
|
||||
const optionalConfigured = OPTIONAL_ENV_VARS.filter(key => !!process.env[key]);
|
||||
|
||||
return {
|
||||
statusCode,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate'
|
||||
},
|
||||
total: CRITICAL_ENV_VARS.length,
|
||||
configured: CRITICAL_ENV_VARS.length - criticalMissing.length,
|
||||
missing: criticalMissing.length,
|
||||
optional: {
|
||||
total: OPTIONAL_ENV_VARS.length,
|
||||
configured: optionalConfigured.length
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handler = async (event, context, { auth }) => {
|
||||
// 基础健康信息(公开)
|
||||
const envStatus = checkEnvStatus();
|
||||
const isHealthy = envStatus.missing === 0;
|
||||
const health = {
|
||||
status: isHealthy ? 'healthy' : 'degraded',
|
||||
service: 'eSIM-Tools',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
// 私有层:通过 BFF 认证后返回详细检查(BFF 已注入 ACCESS_KEY 做认证)
|
||||
const isDetailRequest = event.queryStringParameters && event.queryStringParameters.detail === 'true';
|
||||
if (isDetailRequest) {
|
||||
health.version = process.env.APP_VERSION || '2.0.0';
|
||||
health.environment = process.env.NODE_ENV || 'production';
|
||||
health.uptime = Math.floor(process.uptime());
|
||||
health.checks = {
|
||||
env: envStatus
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: isHealthy ? 200 : 503,
|
||||
body: JSON.stringify(health, null, 2)
|
||||
};
|
||||
};
|
||||
|
||||
// 公开端点:无需 ACCESS_KEY,仅做 CORS 校验
|
||||
exports.handler = withAuth(handler, { requireAuth: false });
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 内部通知API(兼容层)
|
||||
* 已合并至 notifications.js,此文件仅为向后兼容保留路由。
|
||||
* 前端应迁移至 /.netlify/functions/notifications
|
||||
*/
|
||||
|
||||
module.exports = require('./notifications');
|
||||
Reference in New Issue
Block a user