From 0dee45b71be31b69e811a2cb3d216e6c6ff4628e Mon Sep 17 00:00:00 2001 From: Abner <22141172+Silentely@users.noreply.github.com> Date: Wed, 24 Jun 2026 23:57:45 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=81=A5=E5=BA=B7=E6=A3=80=E6=9F=A5=E5=8A=9F=E8=83=BD=E4=BB=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=88=86=E5=B1=82=E8=AE=A4=E8=AF=81=E4=B8=8E?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E7=9B=91=E6=8E=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将健康检查从单一响应优化为公开/私有分层架构,通过 `detail=true` 参数控制是否返回详细环境变量检查状态 - 引入 `CRITICAL_ENV_VARS` 和 `OPTIONAL_ENV_VARS` 配置列表,统一环境变量健康检查逻辑,增强可维护性与可扩展性 - 移除 `notifications-internal.js` 废弃文件,保持函数目录结构清晰,避免路由冗余 - 在 BFF Edge Function 中将 `health` 路由加入 `ALLOWED_ORIGIN` 免验证白名单,支持前端直接调用健康检查接口 --- netlify/edge-functions/bff-proxy.js | 5 +- netlify/functions/health.js | 97 +++++++++++++-------- netlify/functions/notifications-internal.js | 9 -- 3 files changed, 64 insertions(+), 47 deletions(-) delete mode 100644 netlify/functions/notifications-internal.js diff --git a/netlify/edge-functions/bff-proxy.js b/netlify/edge-functions/bff-proxy.js index a6cecb4..28dfa89 100644 --- a/netlify/edge-functions/bff-proxy.js +++ b/netlify/edge-functions/bff-proxy.js @@ -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); diff --git a/netlify/functions/health.js b/netlify/functions/health.js index 1cb6bcb..32fc293 100644 --- a/netlify/functions/health.js +++ b/netlify/functions/health.js @@ -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 }); diff --git a/netlify/functions/notifications-internal.js b/netlify/functions/notifications-internal.js deleted file mode 100644 index 297c65d..0000000 --- a/netlify/functions/notifications-internal.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * 内部通知API(兼容层) - * 已合并至 notifications.js,此文件仅为向后兼容保留路由。 - * 前端应迁移至 /.netlify/functions/notifications - */ - -module.exports = require('./notifications');