chenpei 7326e273e1 fix: harden media upload and release safety
Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
2026-08-12 18:04:08 +08:00

163 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 })
}
}