You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
4.4 KiB
165 lines
4.4 KiB
import { logger } from "@/logger.js"
|
|
|
|
/**
|
|
* 环境变量验证配置
|
|
* required: 必需的环境变量
|
|
* optional: 可选的环境变量(提供默认值)
|
|
*/
|
|
const ENV_CONFIG = {
|
|
required: [
|
|
"SESSION_SECRET",
|
|
"JWT_SECRET"
|
|
],
|
|
optional: {
|
|
"NODE_ENV": "development",
|
|
"PORT": "3000",
|
|
"LOG_DIR": "logs",
|
|
"HTTPS_ENABLE": "off"
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 验证必需的环境变量
|
|
* @returns {Object} 验证结果
|
|
*/
|
|
function validateRequiredEnv() {
|
|
const missing = []
|
|
const valid = {}
|
|
|
|
for (const key of ENV_CONFIG.required) {
|
|
const value = process.env[key]
|
|
if (!value || value.trim() === '') {
|
|
missing.push(key)
|
|
} else {
|
|
valid[key] = value
|
|
}
|
|
}
|
|
|
|
return { missing, valid }
|
|
}
|
|
|
|
/**
|
|
* 设置可选环境变量的默认值
|
|
* @returns {Object} 设置的默认值
|
|
*/
|
|
function setOptionalDefaults() {
|
|
const defaults = {}
|
|
|
|
for (const [key, defaultValue] of Object.entries(ENV_CONFIG.optional)) {
|
|
if (!process.env[key]) {
|
|
process.env[key] = defaultValue
|
|
defaults[key] = defaultValue
|
|
}
|
|
}
|
|
|
|
return defaults
|
|
}
|
|
|
|
/**
|
|
* 验证环境变量的格式和有效性
|
|
* @param {Object} env 环境变量对象
|
|
* @returns {Array} 错误列表
|
|
*/
|
|
function validateEnvFormat(env) {
|
|
const errors = []
|
|
|
|
// 验证 PORT 是数字
|
|
if (env.PORT && isNaN(parseInt(env.PORT))) {
|
|
errors.push("PORT must be a valid number")
|
|
}
|
|
|
|
// 验证 NODE_ENV 的值
|
|
const validNodeEnvs = ['development', 'production', 'test']
|
|
if (env.NODE_ENV && !validNodeEnvs.includes(env.NODE_ENV)) {
|
|
errors.push(`NODE_ENV must be one of: ${validNodeEnvs.join(', ')}`)
|
|
}
|
|
|
|
// 验证 SESSION_SECRET 至少包含一个密钥
|
|
if (env.SESSION_SECRET) {
|
|
const secrets = env.SESSION_SECRET.split(',').filter(s => s.trim())
|
|
if (secrets.length === 0) {
|
|
errors.push("SESSION_SECRET must contain at least one non-empty secret")
|
|
}
|
|
}
|
|
|
|
// 验证 JWT_SECRET 长度
|
|
if (env.JWT_SECRET && env.JWT_SECRET.length < 32) {
|
|
errors.push("JWT_SECRET must be at least 32 characters long for security")
|
|
}
|
|
|
|
return errors
|
|
}
|
|
|
|
/**
|
|
* 初始化和验证所有环境变量
|
|
* @returns {boolean} 验证是否成功
|
|
*/
|
|
export function validateEnvironment() {
|
|
logger.info("🔍 开始验证环境变量...")
|
|
|
|
// 1. 验证必需的环境变量
|
|
const { missing, valid } = validateRequiredEnv()
|
|
|
|
if (missing.length > 0) {
|
|
logger.error("❌ 缺少必需的环境变量:")
|
|
missing.forEach(key => {
|
|
logger.error(` - ${key}`)
|
|
})
|
|
logger.error("请设置这些环境变量后重新启动应用")
|
|
return false
|
|
}
|
|
|
|
// 2. 设置可选环境变量的默认值
|
|
const defaults = setOptionalDefaults()
|
|
if (Object.keys(defaults).length > 0) {
|
|
logger.info("⚙️ 设置默认环境变量:")
|
|
Object.entries(defaults).forEach(([key, value]) => {
|
|
logger.info(` - ${key}=${value}`)
|
|
})
|
|
}
|
|
|
|
// 3. 验证环境变量格式
|
|
const formatErrors = validateEnvFormat(process.env)
|
|
if (formatErrors.length > 0) {
|
|
logger.error("❌ 环境变量格式错误:")
|
|
formatErrors.forEach(error => {
|
|
logger.error(` - ${error}`)
|
|
})
|
|
return false
|
|
}
|
|
|
|
// 4. 记录有效的环境变量(敏感信息脱敏)
|
|
logger.info("✅ 环境变量验证成功:")
|
|
logger.info(` - NODE_ENV=${process.env.NODE_ENV}`)
|
|
logger.info(` - PORT=${process.env.PORT}`)
|
|
logger.info(` - LOG_DIR=${process.env.LOG_DIR}`)
|
|
logger.info(` - SESSION_SECRET=${maskSecret(process.env.SESSION_SECRET)}`)
|
|
logger.info(` - JWT_SECRET=${maskSecret(process.env.JWT_SECRET)}`)
|
|
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* 脱敏显示敏感信息
|
|
* @param {string} secret 敏感字符串
|
|
* @returns {string} 脱敏后的字符串
|
|
*/
|
|
export function maskSecret(secret) {
|
|
if (!secret) return "未设置"
|
|
if (secret.length <= 8) return "*".repeat(secret.length)
|
|
return secret.substring(0, 4) + "*".repeat(secret.length - 8) + secret.substring(secret.length - 4)
|
|
}
|
|
|
|
/**
|
|
* 获取环境变量配置(用于生成 .env.example)
|
|
* @returns {Object} 环境变量配置
|
|
*/
|
|
export function getEnvConfig() {
|
|
return ENV_CONFIG
|
|
}
|
|
|
|
export default {
|
|
validateEnvironment,
|
|
getEnvConfig,
|
|
maskSecret
|
|
}
|