Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
163 lines
4.0 KiB
TypeScript
163 lines
4.0 KiB
TypeScript
/**
|
||
* R22修复:健康检查端点
|
||
*
|
||
* 用途:
|
||
* - Kubernetes liveness/readiness probe
|
||
* - 负载均衡器健康检查
|
||
* - Docker 容器健康检查
|
||
* - 运维监控系统
|
||
*
|
||
* 特点:
|
||
* - 无需认证(公开访问)
|
||
* - 轻量级(快速响应)
|
||
* - 检查核心依赖(数据库、Redis)
|
||
*/
|
||
|
||
import { NextRequest, NextResponse } from 'next/server'
|
||
import { readFileSync } from 'fs'
|
||
import { prisma } from '@/lib/prisma'
|
||
|
||
const DEPLOYMENT_COMMIT_FILE = '/app/DEPLOYMENT_COMMIT'
|
||
|
||
function readBakedDeploymentCommit(): string | null {
|
||
try {
|
||
const value = readFileSync(DEPLOYMENT_COMMIT_FILE, 'utf8').trim()
|
||
return /^[0-9a-f]{40}$/.test(value) ? value : null
|
||
} catch {
|
||
return null
|
||
}
|
||
}
|
||
|
||
// 健康检查结果类型
|
||
interface HealthStatus {
|
||
status: 'healthy' | 'degraded' | 'unhealthy'
|
||
timestamp: string
|
||
uptime: number
|
||
version: string
|
||
deploymentCommit: string | null
|
||
checks: {
|
||
database: CheckResult
|
||
redis?: CheckResult
|
||
s3?: CheckResult
|
||
}
|
||
}
|
||
|
||
interface CheckResult {
|
||
status: 'ok' | 'error'
|
||
latency?: number
|
||
error?: string
|
||
}
|
||
|
||
/**
|
||
* GET /api/health
|
||
*
|
||
* 返回系统健康状态
|
||
*
|
||
* 响应示例:
|
||
* {
|
||
* "status": "healthy",
|
||
* "timestamp": "2026-01-28T10:00:00.000Z",
|
||
* "uptime": 3600,
|
||
* "version": "1.0.0",
|
||
* "checks": {
|
||
* "database": { "status": "ok", "latency": 5 },
|
||
* "redis": { "status": "ok", "latency": 2 }
|
||
* }
|
||
* }
|
||
*/
|
||
export async function GET(req: NextRequest) {
|
||
const startTime = Date.now()
|
||
|
||
const health: HealthStatus = {
|
||
status: 'healthy',
|
||
timestamp: new Date().toISOString(),
|
||
uptime: process.uptime(),
|
||
version: process.env.npm_package_version || '1.0.0',
|
||
deploymentCommit: readBakedDeploymentCommit(),
|
||
checks: {
|
||
database: { status: 'ok' },
|
||
},
|
||
}
|
||
|
||
// 检查数据库连接
|
||
try {
|
||
const dbStart = Date.now()
|
||
await prisma.$queryRaw`SELECT 1`
|
||
health.checks.database = {
|
||
status: 'ok',
|
||
latency: Date.now() - dbStart,
|
||
}
|
||
} catch (error) {
|
||
health.checks.database = {
|
||
status: 'error',
|
||
error: error instanceof Error ? error.message : 'Database connection failed',
|
||
}
|
||
health.status = 'unhealthy'
|
||
}
|
||
|
||
// 检查 Redis 连接(如果配置了)
|
||
if (process.env.USE_REDIS === 'true' && process.env.REDIS_URL) {
|
||
try {
|
||
const redisStart = Date.now()
|
||
// 动态导入 Redis 客户端,避免在未配置时加载
|
||
const { redis } = await import('@/lib/upload-sessions-redis')
|
||
|
||
if (redis) {
|
||
await redis.ping()
|
||
health.checks.redis = {
|
||
status: 'ok',
|
||
latency: Date.now() - redisStart,
|
||
}
|
||
} else {
|
||
health.checks.redis = {
|
||
status: 'error',
|
||
error: 'Redis client not initialized',
|
||
}
|
||
// Redis 不可用时降级为 degraded(因为有内存 fallback)
|
||
if (health.status === 'healthy') {
|
||
health.status = 'degraded'
|
||
}
|
||
}
|
||
} catch (error) {
|
||
health.checks.redis = {
|
||
status: 'error',
|
||
error: error instanceof Error ? error.message : 'Redis connection failed',
|
||
}
|
||
// Redis 不可用时降级为 degraded(因为有内存 fallback)
|
||
if (health.status === 'healthy') {
|
||
health.status = 'degraded'
|
||
}
|
||
}
|
||
}
|
||
|
||
// 根据状态返回不同的 HTTP 状态码
|
||
// healthy: 200, degraded: 200, unhealthy: 503
|
||
const httpStatus = health.status === 'unhealthy' ? 503 : 200
|
||
|
||
return NextResponse.json(health, {
|
||
status: httpStatus,
|
||
headers: {
|
||
// 禁止缓存健康检查结果
|
||
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
||
// 允许跨域访问(用于外部监控系统)
|
||
'Access-Control-Allow-Origin': '*',
|
||
},
|
||
})
|
||
}
|
||
|
||
/**
|
||
* HEAD /api/health
|
||
*
|
||
* 简化的健康检查(仅返回状态码,无响应体)
|
||
* 用于简单的存活检查
|
||
*/
|
||
export async function HEAD(req: NextRequest) {
|
||
try {
|
||
// 只检查数据库连接
|
||
await prisma.$queryRaw`SELECT 1`
|
||
return new NextResponse(null, { status: 200 })
|
||
} catch {
|
||
return new NextResponse(null, { status: 503 })
|
||
}
|
||
}
|