配置管理 #

一、配置概述 #

良好的配置管理是应用开发的基础,Fastify支持多种配置方式。

1.1 配置类型 #

配置类型 说明 适用场景
环境变量 系统级配置 敏感信息、环境区分
配置文件 项目级配置 复杂配置、默认值
命令行参数 启动时配置 临时覆盖
代码配置 硬编码配置 固定配置

1.2 配置优先级 #

text
命令行参数 > 环境变量 > 配置文件 > 代码默认值

二、环境变量 #

2.1 使用dotenv #

安装

bash
npm install dotenv

使用

javascript
require('dotenv').config()

const fastify = require('fastify')({
  logger: {
    level: process.env.LOG_LEVEL || 'info'
  }
})

fastify.listen({
  port: process.env.PORT || 3000,
  host: process.env.HOST || '0.0.0.0'
})

2.2 .env文件 #

.env

env
NODE_ENV=development
PORT=3000
HOST=0.0.0.0
LOG_LEVEL=debug

DATABASE_URL=mongodb://localhost:27017/myapp
DATABASE_NAME=myapp_dev

REDIS_URL=redis://localhost:6379

JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=7d

API_KEY=your-api-key

.env.example

env
NODE_ENV=development
PORT=3000
HOST=0.0.0.0
LOG_LEVEL=info

DATABASE_URL=
DATABASE_NAME=

REDIS_URL=

JWT_SECRET=
JWT_EXPIRES_IN=7d

API_KEY=

2.3 环境区分 #

javascript
require('dotenv').config({
  path: `.env.${process.env.NODE_ENV || 'development'}`
})

.env.development

env
NODE_ENV=development
LOG_LEVEL=debug
DATABASE_URL=mongodb://localhost:27017/myapp_dev

.env.production

env
NODE_ENV=production
LOG_LEVEL=info
DATABASE_URL=mongodb://production-server:27017/myapp

.env.test

env
NODE_ENV=test
LOG_LEVEL=error
DATABASE_URL=mongodb://localhost:27017/myapp_test

2.4 类型转换 #

javascript
const config = {
  port: parseInt(process.env.PORT, 10) || 3000,
  host: process.env.HOST || '0.0.0.0',
  debug: process.env.DEBUG === 'true',
  timeout: parseInt(process.env.TIMEOUT, 10) || 5000,
  features: (process.env.FEATURES || '').split(',').filter(Boolean)
}

三、配置文件 #

3.1 JSON配置文件 #

config/default.json

json
{
  "app": {
    "name": "My Fastify App",
    "version": "1.0.0"
  },
  "server": {
    "port": 3000,
    "host": "0.0.0.0"
  },
  "database": {
    "url": "mongodb://localhost:27017",
    "name": "myapp"
  },
  "redis": {
    "url": "redis://localhost:6379"
  },
  "jwt": {
    "secret": "default-secret",
    "expiresIn": "7d"
  }
}

config/development.json

json
{
  "server": {
    "port": 3000
  },
  "database": {
    "name": "myapp_dev"
  }
}

config/production.json

json
{
  "server": {
    "port": 80
  },
  "database": {
    "name": "myapp"
  }
}

加载配置

javascript
const fs = require('fs')
const path = require('path')

function loadConfig() {
  const env = process.env.NODE_ENV || 'development'
  
  const defaultConfig = JSON.parse(
    fs.readFileSync(path.join(__dirname, 'config/default.json'), 'utf8')
  )
  
  const envConfig = JSON.parse(
    fs.readFileSync(path.join(__dirname, `config/${env}.json`), 'utf8')
  )
  
  return deepMerge(defaultConfig, envConfig)
}

function deepMerge(target, source) {
  const result = { ...target }
  
  for (const key in source) {
    if (source[key] instanceof Object && key in target) {
      result[key] = deepMerge(target[key], source[key])
    } else {
      result[key] = source[key]
    }
  }
  
  return result
}

const config = loadConfig()

3.2 JavaScript配置文件 #

config/index.js

javascript
const env = process.env.NODE_ENV || 'development'

const config = {
  development: {
    app: {
      name: 'My App (Dev)'
    },
    server: {
      port: 3000,
      host: 'localhost'
    },
    database: {
      url: 'mongodb://localhost:27017',
      name: 'myapp_dev'
    },
    log: {
      level: 'debug',
      prettyPrint: true
    }
  },
  
  production: {
    app: {
      name: 'My App'
    },
    server: {
      port: process.env.PORT || 80,
      host: '0.0.0.0'
    },
    database: {
      url: process.env.DATABASE_URL,
      name: 'myapp'
    },
    log: {
      level: 'info',
      prettyPrint: false
    }
  },
  
  test: {
    app: {
      name: 'My App (Test)'
    },
    server: {
      port: 0,
      host: 'localhost'
    },
    database: {
      url: 'mongodb://localhost:27017',
      name: 'myapp_test'
    },
    log: {
      level: 'error',
      prettyPrint: false
    }
  }
}

module.exports = config[env]

3.3 使用配置 #

javascript
const config = require('./config')

const fastify = require('fastify')({
  logger: {
    level: config.log.level,
    transport: config.log.prettyPrint 
      ? { target: 'pino-pretty' }
      : undefined
  }
})

fastify.register(require('@fastify/mongodb'), {
  url: `${config.database.url}/${config.database.name}`
})

fastify.listen({
  port: config.server.port,
  host: config.server.host
})

四、配置插件 #

4.1 配置插件封装 #

javascript
const fp = require('fastify-plugin')

async function configPlugin(fastify, opts) {
  const env = process.env.NODE_ENV || 'development'
  
  const config = {
    env,
    isDev: env === 'development',
    isProd: env === 'production',
    isTest: env === 'test',
    
    app: {
      name: process.env.APP_NAME || 'My App',
      version: process.env.APP_VERSION || '1.0.0'
    },
    
    server: {
      port: parseInt(process.env.PORT, 10) || 3000,
      host: process.env.HOST || '0.0.0.0'
    },
    
    database: {
      url: process.env.DATABASE_URL || 'mongodb://localhost:27017',
      name: process.env.DATABASE_NAME || 'myapp'
    },
    
    jwt: {
      secret: process.env.JWT_SECRET || 'secret',
      expiresIn: process.env.JWT_EXPIRES_IN || '7d'
    }
  }
  
  fastify.decorate('config', config)
}

module.exports = fp(configPlugin, {
  name: 'config'
})

4.2 使用配置插件 #

javascript
const fastify = require('fastify')()

fastify.register(require('./plugins/config'))

fastify.register(require('@fastify/mongodb'), {
  url: `${fastify.config.database.url}/${fastify.config.database.name}`
})

fastify.listen({
  port: fastify.config.server.port,
  host: fastify.config.server.host
})

五、配置验证 #

5.1 必需配置检查 #

javascript
function validateConfig(config) {
  const required = [
    'DATABASE_URL',
    'JWT_SECRET'
  ]
  
  const missing = required.filter(key => !process.env[key])
  
  if (missing.length > 0) {
    throw new Error(`Missing required environment variables: ${missing.join(', ')}`)
  }
}

validateConfig(process.env)

5.2 配置Schema验证 #

javascript
const Ajv = require('ajv')
const ajv = new Ajv()

const configSchema = {
  type: 'object',
  required: ['server', 'database'],
  properties: {
    server: {
      type: 'object',
      required: ['port', 'host'],
      properties: {
        port: { type: 'integer', minimum: 1, maximum: 65535 },
        host: { type: 'string' }
      }
    },
    database: {
      type: 'object',
      required: ['url', 'name'],
      properties: {
        url: { type: 'string', format: 'uri' },
        name: { type: 'string', minLength: 1 }
      }
    }
  }
}

function validateConfig(config) {
  const validate = ajv.compile(configSchema)
  
  if (!validate(config)) {
    throw new Error(`Invalid config: ${ajv.errorsText(validate.errors)}`)
  }
}

validateConfig(config)

六、敏感配置管理 #

6.1 加密配置 #

javascript
const crypto = require('crypto')

function encrypt(text, secret) {
  const iv = crypto.randomBytes(16)
  const key = crypto.scryptSync(secret, 'salt', 32)
  const cipher = crypto.createCipheriv('aes-256-cbc', key, iv)
  
  let encrypted = cipher.update(text, 'utf8', 'hex')
  encrypted += cipher.final('hex')
  
  return iv.toString('hex') + ':' + encrypted
}

function decrypt(text, secret) {
  const [ivHex, encrypted] = text.split(':')
  const iv = Buffer.from(ivHex, 'hex')
  const key = crypto.scryptSync(secret, 'salt', 32)
  const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv)
  
  let decrypted = decipher.update(encrypted, 'hex', 'utf8')
  decrypted += decipher.final('utf8')
  
  return decrypted
}

6.2 使用密钥管理服务 #

javascript
async function loadSecrets() {
  if (process.env.NODE_ENV === 'production') {
    const secrets = await fetchSecretsFromVault()
    return secrets
  }
  
  return {
    JWT_SECRET: process.env.JWT_SECRET,
    DATABASE_PASSWORD: process.env.DATABASE_PASSWORD
  }
}

七、配置热更新 #

7.1 监听配置文件变化 #

javascript
const fs = require('fs')
const path = require('path')

let config = JSON.parse(
  fs.readFileSync(path.join(__dirname, 'config.json'), 'utf8')
)

fs.watch(path.join(__dirname, 'config.json'), (eventType, filename) => {
  if (eventType === 'change') {
    try {
      config = JSON.parse(
        fs.readFileSync(path.join(__dirname, 'config.json'), 'utf8')
      )
      console.log('Config reloaded')
    } catch (err) {
      console.error('Failed to reload config:', err)
    }
  }
})

7.2 配置更新通知 #

javascript
const EventEmitter = require('events')

class ConfigManager extends EventEmitter {
  constructor() {
    super()
    this.config = {}
  }
  
  update(newConfig) {
    const oldConfig = this.config
    this.config = newConfig
    this.emit('update', { oldConfig, newConfig })
  }
}

const configManager = new ConfigManager()

configManager.on('update', ({ oldConfig, newConfig }) => {
  console.log('Config updated')
})

八、最佳实践 #

8.1 配置分层 #

javascript
const config = {
  defaults: {
    port: 3000,
    host: 'localhost'
  },
  
  env: {
    port: parseInt(process.env.PORT, 10),
    host: process.env.HOST
  },
  
  file: loadConfigFile(),
  
  get merged() {
    return {
      ...this.defaults,
      ...this.file,
      ...Object.fromEntries(
        Object.entries(this.env).filter(([_, v]) => v !== undefined)
      )
    }
  }
}

8.2 配置文档 #

javascript
const configSchema = {
  PORT: {
    type: 'integer',
    default: 3000,
    description: 'Server port',
    env: 'PORT'
  },
  HOST: {
    type: 'string',
    default: '0.0.0.0',
    description: 'Server host',
    env: 'HOST'
  },
  DATABASE_URL: {
    type: 'string',
    required: true,
    description: 'Database connection URL',
    env: 'DATABASE_URL'
  }
}

8.3 配置日志 #

javascript
function logConfig(config) {
  const safeConfig = { ...config }
  
  if (safeConfig.jwt) {
    safeConfig.jwt = { ...safeConfig.jwt, secret: '***' }
  }
  
  if (safeConfig.database) {
    safeConfig.database = { ...safeConfig.database, password: '***' }
  }
  
  console.log('Loaded config:', JSON.stringify(safeConfig, null, 2))
}

九、总结 #

本章我们学习了:

  1. 配置类型:环境变量、配置文件、命令行参数
  2. 环境变量:dotenv使用、.env文件、环境区分
  3. 配置文件:JSON配置、JavaScript配置
  4. 配置插件:封装配置插件
  5. 配置验证:必需检查、Schema验证
  6. 敏感配置:加密、密钥管理
  7. 配置热更新:监听变化、更新通知
  8. 最佳实践:分层、文档、日志

接下来让我们学习日志系统!

最后更新:2026-03-28