gongxiangp2/lib/env-validation.ts
chenpei 4e89b57798 feat(upload): optimize upload UI and performance
- Expand batch upload list area and optimize preview image layout
- Improve upload speed tracking and display (server-side & client-side)
- Reduce network detection log noise
- Optimize Docker build process (caching, env validation)
- Add shadow environment deployment support
2026-01-28 15:07:50 +08:00

259 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

/**
* R47修复:环境变量启动验证
*
* 目的:在应用启动时验证所有必需的环境变量,提供清晰的错误提示
* 时机:在 lib/prisma.ts 和其他关键模块中调用
*/
/**
* 环境变量验证错误
*/
export class EnvValidationError extends Error {
public missingVars: string[]
constructor(missingVars: string[]) {
const varList = missingVars.map(v => ` - ${v}`).join('\n')
super(
`缺少必需的环境变量:\n${varList}\n\n` +
`请在 .env 文件或 docker-compose.yml 中配置这些变量。\n` +
`详情请参考:.env.example 或项目文档。`
)
this.name = 'EnvValidationError'
this.missingVars = missingVars
}
}
/**
* 必需的环境变量列表
*/
const REQUIRED_ENV_VARS = [
// 数据库
'DATABASE_URL',
// NextAuth
'NEXTAUTH_SECRET',
'NEXTAUTH_URL',
// S3/MinIO(如果使用S3存储)
// 'S3_ENDPOINT',
// 'S3_BUCKET',
// 'S3_ACCESS_KEY_ID',
// 'S3_SECRET_ACCESS_KEY',
// Redis(如果使用Redis)
// 'REDIS_URL',
// 'USE_REDIS',
// 管理员API密钥
'ADMIN_API_KEY',
]
/**
* 可选但建议配置的环境变量
*/
const OPTIONAL_ENV_VARS = [
'S3_ENDPOINT',
'S3_BUCKET',
'S3_ACCESS_KEY_ID',
'S3_SECRET_ACCESS_KEY',
'REDIS_URL',
'USE_REDIS',
'DINGTALK_APP_KEY',
'DINGTALK_APP_SECRET',
'DOWNLOAD_TOKEN_SECRET',
'DISABLE_CACHE',
]
/**
* 验证所有必需的环境变量
* @throws {EnvValidationError} 如果缺少必需的环境变量
*/
export function validateRequiredEnvVars(): void {
const missing: string[] = []
for (const varName of REQUIRED_ENV_VARS) {
if (!process.env[varName]) {
missing.push(varName)
}
}
if (missing.length > 0) {
throw new EnvValidationError(missing)
}
console.log('[Env] ✅ 所有必需的环境变量已配置')
}
/**
* 验证环境变量的格式
* @throws {Error} 如果环境变量格式不正确
*/
export function validateEnvFormats(): void {
const errors: string[] = []
// 验证 DATABASE_URL 格式
const databaseUrl = process.env.DATABASE_URL
if (databaseUrl) {
try {
const url = new URL(databaseUrl)
if (url.protocol !== 'postgresql:' && url.protocol !== 'postgres:') {
errors.push(`DATABASE_URL 格式错误:应该使用 postgresql:// 或 postgres:// 协议`)
}
} catch (e) {
errors.push(`DATABASE_URL 格式错误:${e}`)
}
}
// 验证 NEXTAUTH_URL 格式
const nextauthUrl = process.env.NEXTAUTH_URL
if (nextauthUrl) {
try {
new URL(nextauthUrl)
} catch (e) {
errors.push(`NEXTAUTH_URL 格式错误:${e}`)
}
}
// 验证 REDIS_URL 格式(如果配置了)
const redisUrl = process.env.REDIS_URL
if (redisUrl) {
try {
const url = new URL(redisUrl)
if (url.protocol !== 'redis:') {
errors.push(`REDIS_URL 格式错误:应该使用 redis:// 协议`)
}
} catch (e) {
errors.push(`REDIS_URL 格式错误:${e}`)
}
}
// 验证 S3_ENDPOINT 格式(如果配置了)
const s3Endpoint = process.env.S3_ENDPOINT
if (s3Endpoint) {
try {
const url = new URL(s3Endpoint)
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
errors.push(`S3_ENDPOINT 格式错误:应该使用 http:// 或 https:// 协议`)
}
} catch (e) {
errors.push(`S3_ENDPOINT 格式错误:${e}`)
}
}
if (errors.length > 0) {
const errorList = errors.map(e => ` - ${e}`).join('\n')
throw new Error(
`环境变量格式错误:\n${errorList}\n\n` +
`请检查 .env 文件或 docker-compose.yml 中的配置。`
)
}
console.log('[Env] ✅ 环境变量格式验证通过')
}
/**
* 检查可选环境变量并给出提示
*/
export function checkOptionalEnvVars(): void {
const configured: string[] = []
const notConfigured: string[] = []
for (const varName of OPTIONAL_ENV_VARS) {
if (process.env[varName]) {
configured.push(varName)
} else {
notConfigured.push(varName)
}
}
if (configured.length > 0) {
console.log(`[Env] ℹ️ 已配置的可选变量:${configured.join(', ')}`)
}
if (notConfigured.length > 0) {
console.log(`[Env] ⚠️ 未配置的可选变量:${notConfigured.join(', ')}`)
}
}
/**
* 检测是否在 Next.js 构建阶段
* 构建时不应该验证环境变量,因为构建环境没有运行时环境变量
*/
function isBuildPhase(): boolean {
// 方式1:检测 NEXT_PHASE 环境变量
if (process.env.NEXT_PHASE === 'phase-production-build') {
return true
}
// 方式2:检测 npm_lifecycle_event(构建脚本中会设置)
if (process.env.npm_lifecycle_event === 'build') {
return true
}
// 方式3:检测 __NEXT_PRIVATE_PREBUNDLED_REACT(Next.js 内部变量)
if (process.env.__NEXT_PRIVATE_PREBUNDLED_REACT) {
return true
}
return false
}
/**
* 完整的环境变量验证(在应用启动时调用)
* @throws {Error} 如果验证失败且在生产运行时环境
*/
export function validateEnvironment(): void {
// 构建阶段跳过验证
if (isBuildPhase()) {
return
}
console.log('[Env] 🔍 开始验证环境变量...')
try {
validateRequiredEnvVars()
validateEnvFormats()
checkOptionalEnvVars()
console.log('[Env] ✅ 环境变量验证完成')
} catch (error) {
console.error('[Env] ❌ 环境变量验证失败:', error)
// 只在生产运行时抛出错误,开发环境只记录警告
if (process.env.NODE_ENV === 'production') {
throw error
}
console.warn('[Env] ⚠️ 非 production 环境,跳过环境变量验证错误')
}
}
/**
* 安全获取环境变量(带默认值和类型转换)
*/
export function getEnvVar(key: string, defaultValue?: string): string | undefined {
const value = process.env[key]
if (value === undefined || value === '') {
return defaultValue
}
return value
}
/**
* 获取布尔型环境变量
*/
export function getBoolEnvVar(key: string, defaultValue: boolean = false): boolean {
const value = process.env[key]
if (value === undefined || value === '') {
return defaultValue
}
return value === 'true' || value === '1'
}
/**
* 获取数字型环境变量
*/
export function getNumberEnvVar(key: string, defaultValue: number = 0): number {
const value = process.env[key]
if (value === undefined || value === '') {
return defaultValue
}
const num = parseInt(value, 10)
return isNaN(num) ? defaultValue : num
}