配置管理
📋 概述
配置管理是指系统化地处理应用程序和基础设施配置的过程,包括配置的存储、版本控制、分发、更新和安全管理。良好的配置管理能确保环境一致性、提高部署效率和增强安全性。
🎯 学习目标
- 理解配置管理的核心概念和重要性
- 掌握多环境配置管理策略
- 学会使用配置中心和密钥管理
- 了解配置变更的最佳实践
📚 配置管理基础
配置类型分类
mermaid
graph TB
A[应用配置] --> B[环境配置]
A --> C[功能配置]
A --> D[业务配置]
A --> E[安全配置]
B --> B1[数据库连接]
B --> B2[服务端点]
B --> B3[日志级别]
C --> C1[功能开关]
C --> C2[限流配置]
C --> C3[缓存策略]
D --> D1[业务规则]
D --> D2[计费配置]
D --> D3[工作流程]
E --> E1[API密钥]
E --> E2[证书]
E --> E3[加密配置]
配置管理原则
javascript
const ConfigManagementPrinciples = {
SEPARATION: '配置与代码分离',
ENVIRONMENT_SPECIFIC: '环境特定配置',
SECURITY: '敏感配置加密存储',
VERSION_CONTROL: '配置版本控制',
VALIDATION: '配置验证和校验',
ROLLBACK: '配置回滚能力',
AUDIT: '配置变更审计'
};
🛠 Node.js配置管理实现
分层配置系统
javascript
// config/config-manager.js
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
const Joi = require('joi');
class ConfigManager {
constructor() {
this.config = {};
this.schema = null;
this.watchers = [];
this.environment = process.env.NODE_ENV || 'development';
}
// 加载配置
async load() {
try {
// 1. 加载默认配置
await this.loadDefaultConfig();
// 2. 加载环境特定配置
await this.loadEnvironmentConfig();
// 3. 加载本地配置覆盖
await this.loadLocalConfig();
// 4. 加载环境变量
this.loadEnvironmentVariables();
// 5. 加载远程配置
await this.loadRemoteConfig();
// 6. 验证配置
this.validateConfig();
console.log(`✅ 配置加载完成 (${this.environment})`);
} catch (error) {
console.error('❌ 配置加载失败:', error);
throw error;
}
}
async loadDefaultConfig() {
const defaultConfigPath = path.join(__dirname, 'default.yaml');
if (fs.existsSync(defaultConfigPath)) {
const content = fs.readFileSync(defaultConfigPath, 'utf8');
const defaultConfig = yaml.load(content);
this.config = { ...defaultConfig };
}
}
async loadEnvironmentConfig() {
const envConfigPath = path.join(__dirname, `${this.environment}.yaml`);
if (fs.existsSync(envConfigPath)) {
const content = fs.readFileSync(envConfigPath, 'utf8');
const envConfig = yaml.load(content);
this.config = this.mergeDeep(this.config, envConfig);
}
}
async loadLocalConfig() {
const localConfigPath = path.join(__dirname, 'local.yaml');
if (fs.existsSync(localConfigPath)) {
const content = fs.readFileSync(localConfigPath, 'utf8');
const localConfig = yaml.load(content);
this.config = this.mergeDeep(this.config, localConfig);
}
}
loadEnvironmentVariables() {
// 映射环境变量到配置
const envMapping = {
'DATABASE_URL': 'database.url',
'REDIS_URL': 'redis.url',
'JWT_SECRET': 'auth.jwtSecret',
'LOG_LEVEL': 'logging.level',
'PORT': 'server.port'
};
for (const [envVar, configPath] of Object.entries(envMapping)) {
const value = process.env[envVar];
if (value !== undefined) {
this.setNestedValue(this.config, configPath, value);
}
}
}
async loadRemoteConfig() {
const remoteConfigUrl = process.env.CONFIG_SERVICE_URL;
if (!remoteConfigUrl) return;
try {
const axios = require('axios');
const response = await axios.get(`${remoteConfigUrl}/config/${this.environment}`, {
timeout: 5000,
headers: {
'Authorization': `Bearer ${process.env.CONFIG_SERVICE_TOKEN}`
}
});
const remoteConfig = response.data;
this.config = this.mergeDeep(this.config, remoteConfig);
console.log('✅ 远程配置加载成功');
} catch (error) {
console.warn('⚠️ 远程配置加载失败,使用本地配置:', error.message);
}
}
validateConfig() {
if (!this.schema) return;
const { error } = this.schema.validate(this.config);
if (error) {
throw new Error(`配置验证失败: ${error.details.map(d => d.message).join(', ')}`);
}
}
// 设置配置验证模式
setSchema(schema) {
this.schema = schema;
}
// 获取配置值
get(path, defaultValue = undefined) {
return this.getNestedValue(this.config, path, defaultValue);
}
// 设置配置值
set(path, value) {
this.setNestedValue(this.config, path, value);
this.notifyWatchers(path, value);
}
// 监听配置变更
watch(path, callback) {
this.watchers.push({ path, callback });
}
// 通知监听器
notifyWatchers(path, value) {
this.watchers
.filter(watcher => path.startsWith(watcher.path))
.forEach(watcher => watcher.callback(path, value));
}
// 深度合并对象
mergeDeep(target, source) {
const result = { ...target };
for (const key in source) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = this.mergeDeep(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
return result;
}
// 获取嵌套值
getNestedValue(obj, path, defaultValue) {
const keys = path.split('.');
let current = obj;
for (const key of keys) {
if (current === null || current === undefined || !(key in current)) {
return defaultValue;
}
current = current[key];
}
return current;
}
// 设置嵌套值
setNestedValue(obj, path, value) {
const keys = path.split('.');
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!(key in current) || typeof current[key] !== 'object') {
current[key] = {};
}
current = current[key];
}
current[keys[keys.length - 1]] = value;
}
// 热重载配置
async reload() {
console.log('🔄 重新加载配置...');
await this.load();
}
// 导出配置(用于调试)
export() {
return JSON.stringify(this.config, null, 2);
}
// 获取所有配置
getAll() {
return { ...this.config };
}
}
module.exports = ConfigManager;
配置文件结构
yaml
# config/default.yaml
server:
port: 3000
host: '0.0.0.0'
timeout: 30000
database:
type: 'postgresql'
host: 'localhost'
port: 5432
name: 'app'
username: 'postgres'
password: 'password'
pool:
min: 2
max: 10
idle: 10000
ssl: false
logging: false
redis:
host: 'localhost'
port: 6379
password: ''
db: 0
keyPrefix: 'app:'
retryDelayOnFailover: 100
maxRetriesPerRequest: 3
auth:
jwtSecret: 'default-secret'
jwtExpiresIn: '24h'
bcryptRounds: 12
sessionSecret: 'session-secret'
sessionMaxAge: 86400000
logging:
level: 'info'
file: './logs/app.log'
maxFiles: 5
maxSize: '10m'
colorize: true
timestamp: true
cache:
ttl: 3600
checkPeriod: 600
maxKeys: 1000
rateLimit:
windowMs: 900000 # 15分钟
max: 100
message: 'Too many requests'
cors:
origin: '*'
methods: ['GET', 'POST', 'PUT', 'DELETE']
allowedHeaders: ['Content-Type', 'Authorization']
credentials: true
features:
enableRegistration: true
enablePasswordReset: true
enableEmailVerification: false
enableTwoFactor: false
maintenanceMode: false
monitoring:
enabled: true
metricsPort: 9090
healthCheckPath: '/health'
external:
emailService:
provider: 'smtp'
host: 'smtp.gmail.com'
port: 587
secure: false
storage:
provider: 'local'
path: './uploads'
yaml
# config/production.yaml
server:
port: ${PORT:3000}
timeout: 60000
database:
host: ${DB_HOST}
port: ${DB_PORT:5432}
name: ${DB_NAME}
username: ${DB_USER}
password: ${DB_PASSWORD}
ssl: true
logging: false
pool:
min: 5
max: 20
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD}
auth:
jwtSecret: ${JWT_SECRET}
sessionSecret: ${SESSION_SECRET}
logging:
level: 'warn'
file: '/var/log/app/app.log'
features:
enableRegistration: false
maintenanceMode: false
monitoring:
enabled: true
external:
emailService:
provider: 'ses'
region: 'us-east-1'
storage:
provider: 's3'
bucket: ${S3_BUCKET}
region: 'us-east-1'
配置验证模式
javascript
// config/config-schema.js
const Joi = require('joi');
const configSchema = Joi.object({
server: Joi.object({
port: Joi.number().port().required(),
host: Joi.string().required(),
timeout: Joi.number().positive()
}).required(),
database: Joi.object({
type: Joi.string().valid('postgresql', 'mysql', 'mongodb').required(),
host: Joi.string().required(),
port: Joi.number().port().required(),
name: Joi.string().required(),
username: Joi.string().required(),
password: Joi.string().required(),
pool: Joi.object({
min: Joi.number().min(0),
max: Joi.number().min(1),
idle: Joi.number().positive()
}),
ssl: Joi.boolean(),
logging: Joi.boolean()
}).required(),
redis: Joi.object({
host: Joi.string().required(),
port: Joi.number().port().required(),
password: Joi.string().allow(''),
db: Joi.number().min(0).max(15),
keyPrefix: Joi.string()
}).required(),
auth: Joi.object({
jwtSecret: Joi.string().min(32).required(),
jwtExpiresIn: Joi.string().required(),
bcryptRounds: Joi.number().min(8).max(15),
sessionSecret: Joi.string().min(32).required(),
sessionMaxAge: Joi.number().positive()
}).required(),
logging: Joi.object({
level: Joi.string().valid('error', 'warn', 'info', 'debug', 'silly').required(),
file: Joi.string(),
maxFiles: Joi.number().positive(),
maxSize: Joi.string(),
colorize: Joi.boolean(),
timestamp: Joi.boolean()
}).required(),
features: Joi.object({
enableRegistration: Joi.boolean(),
enablePasswordReset: Joi.boolean(),
enableEmailVerification: Joi.boolean(),
enableTwoFactor: Joi.boolean(),
maintenanceMode: Joi.boolean()
}),
monitoring: Joi.object({
enabled: Joi.boolean(),
metricsPort: Joi.number().port(),
healthCheckPath: Joi.string()
})
});
module.exports = configSchema;
🔐 密钥管理系统
密钥管理器
javascript
// security/secret-manager.js
const crypto = require('crypto');
const fs = require('fs').promises;
const path = require('path');
class SecretManager {
constructor(config) {
this.config = config;
this.secrets = new Map();
this.encryptionKey = null;
this.providers = new Map();
this.initializeProviders();
}
initializeProviders() {
// 本地文件提供者
this.providers.set('file', new FileSecretProvider(this.config.file));
// 环境变量提供者
this.providers.set('env', new EnvSecretProvider());
// AWS Secrets Manager提供者
if (this.config.aws) {
this.providers.set('aws', new AWSSecretProvider(this.config.aws));
}
// HashiCorp Vault提供者
if (this.config.vault) {
this.providers.set('vault', new VaultSecretProvider(this.config.vault));
}
}
async initialize() {
// 初始化加密密钥
await this.initializeEncryptionKey();
// 加载所有密钥
await this.loadSecrets();
console.log('🔐 密钥管理器初始化完成');
}
async initializeEncryptionKey() {
const keyFile = this.config.encryptionKeyFile || './config/encryption.key';
try {
const keyData = await fs.readFile(keyFile);
this.encryptionKey = keyData;
} catch (error) {
if (error.code === 'ENOENT') {
// 生成新的加密密钥
this.encryptionKey = crypto.randomBytes(32);
await fs.writeFile(keyFile, this.encryptionKey);
console.log(`🔑 生成新的加密密钥: ${keyFile}`);
} else {
throw error;
}
}
}
async loadSecrets() {
const providers = this.config.providers || ['env', 'file'];
for (const providerName of providers) {
const provider = this.providers.get(providerName);
if (provider) {
try {
const secrets = await provider.loadSecrets();
for (const [key, value] of Object.entries(secrets)) {
this.secrets.set(key, value);
}
console.log(`✅ 从 ${providerName} 加载密钥完成`);
} catch (error) {
console.error(`❌ 从 ${providerName} 加载密钥失败:`, error.message);
}
}
}
}
// 获取密钥
getSecret(key, defaultValue = undefined) {
const secret = this.secrets.get(key);
if (secret === undefined) {
if (defaultValue !== undefined) {
return defaultValue;
}
throw new Error(`密钥不存在: ${key}`);
}
// 如果是加密的密钥,解密后返回
if (typeof secret === 'object' && secret.encrypted) {
return this.decrypt(secret.value);
}
return secret;
}
// 设置密钥
async setSecret(key, value, encrypt = true) {
let secretValue = value;
if (encrypt && typeof value === 'string') {
secretValue = {
encrypted: true,
value: this.encrypt(value)
};
}
this.secrets.set(key, secretValue);
// 持久化到主要提供者
const primaryProvider = this.providers.get(this.config.primaryProvider || 'file');
if (primaryProvider && primaryProvider.saveSecret) {
await primaryProvider.saveSecret(key, secretValue);
}
}
// 删除密钥
async deleteSecret(key) {
this.secrets.delete(key);
const primaryProvider = this.providers.get(this.config.primaryProvider || 'file');
if (primaryProvider && primaryProvider.deleteSecret) {
await primaryProvider.deleteSecret(key);
}
}
// 加密
encrypt(text) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher('aes-256-cbc', this.encryptionKey);
cipher.setAutoPadding(true);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return {
iv: iv.toString('hex'),
data: encrypted
};
}
// 解密
decrypt(encryptedData) {
const decipher = crypto.createDecipher('aes-256-cbc', this.encryptionKey);
decipher.setAutoPadding(true);
let decrypted = decipher.update(encryptedData.data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// 轮换密钥
async rotateSecret(key, newValue) {
const oldValue = this.getSecret(key);
await this.setSecret(key, newValue);
// 记录轮换历史
await this.logSecretRotation(key, oldValue, newValue);
}
async logSecretRotation(key, oldValue, newValue) {
const logEntry = {
timestamp: new Date().toISOString(),
key,
action: 'rotate',
oldValueHash: crypto.createHash('sha256').update(oldValue).digest('hex'),
newValueHash: crypto.createHash('sha256').update(newValue).digest('hex')
};
// 写入审计日志
const logFile = this.config.auditLogFile || './logs/secret-audit.log';
await fs.appendFile(logFile, JSON.stringify(logEntry) + '\n');
}
// 列出所有密钥名称
listSecrets() {
return Array.from(this.secrets.keys());
}
// 验证密钥完整性
async validateSecrets() {
const issues = [];
for (const [key, value] of this.secrets) {
try {
if (typeof value === 'object' && value.encrypted) {
this.decrypt(value.value);
}
} catch (error) {
issues.push(`密钥 ${key} 解密失败: ${error.message}`);
}
}
return issues;
}
}
// 文件密钥提供者
class FileSecretProvider {
constructor(config) {
this.config = config;
this.filePath = config.path || './config/secrets.json';
}
async loadSecrets() {
try {
const content = await fs.readFile(this.filePath, 'utf8');
return JSON.parse(content);
} catch (error) {
if (error.code === 'ENOENT') {
return {};
}
throw error;
}
}
async saveSecret(key, value) {
let secrets = {};
try {
secrets = await this.loadSecrets();
} catch (error) {
// 忽略文件不存在的错误
}
secrets[key] = value;
await fs.writeFile(this.filePath, JSON.stringify(secrets, null, 2));
}
async deleteSecret(key) {
const secrets = await this.loadSecrets();
delete secrets[key];
await fs.writeFile(this.filePath, JSON.stringify(secrets, null, 2));
}
}
// 环境变量密钥提供者
class EnvSecretProvider {
async loadSecrets() {
const secrets = {};
const prefix = 'SECRET_';
for (const [key, value] of Object.entries(process.env)) {
if (key.startsWith(prefix)) {
const secretKey = key.substring(prefix.length).toLowerCase();
secrets[secretKey] = value;
}
}
return secrets;
}
}
// AWS Secrets Manager提供者
class AWSSecretProvider {
constructor(config) {
this.config = config;
this.client = null;
}
async loadSecrets() {
if (!this.client) {
const AWS = require('aws-sdk');
this.client = new AWS.SecretsManager({
region: this.config.region || 'us-east-1'
});
}
const secrets = {};
try {
const response = await this.client.getSecretValue({
SecretId: this.config.secretId
}).promise();
const secretData = JSON.parse(response.SecretString);
return secretData;
} catch (error) {
console.error('AWS Secrets Manager 加载失败:', error.message);
return {};
}
}
}
// HashiCorp Vault提供者
class VaultSecretProvider {
constructor(config) {
this.config = config;
this.client = null;
}
async loadSecrets() {
if (!this.client) {
const vault = require('node-vault');
this.client = vault({
apiVersion: 'v1',
endpoint: this.config.endpoint,
token: this.config.token
});
}
try {
const response = await this.client.read(this.config.path);
return response.data.data || response.data;
} catch (error) {
console.error('Vault 加载失败:', error.message);
return {};
}
}
}
module.exports = SecretManager;
🔄 动态配置更新
配置热更新系统
javascript
// config/hot-reload-manager.js
const fs = require('fs');
const path = require('path');
const EventEmitter = require('events');
class HotReloadManager extends EventEmitter {
constructor(configManager) {
super();
this.configManager = configManager;
this.watchers = new Map();
this.updateQueue = [];
this.isProcessing = false;
}
// 启动热更新监控
start() {
console.log('🔥 启动配置热更新监控');
// 监控配置文件变化
this.watchConfigFiles();
// 监控远程配置变化
this.watchRemoteConfig();
// 处理更新队列
this.processUpdateQueue();
}
watchConfigFiles() {
const configDir = path.join(__dirname);
const configFiles = ['default.yaml', 'development.yaml', 'production.yaml', 'local.yaml'];
configFiles.forEach(filename => {
const filePath = path.join(configDir, filename);
if (fs.existsSync(filePath)) {
const watcher = fs.watch(filePath, (eventType) => {
if (eventType === 'change') {
console.log(`📝 检测到配置文件变化: ${filename}`);
this.queueUpdate('file', filename);
}
});
this.watchers.set(filePath, watcher);
console.log(`👁️ 监控配置文件: ${filename}`);
}
});
}
watchRemoteConfig() {
const remoteConfigUrl = process.env.CONFIG_SERVICE_URL;
if (!remoteConfigUrl) return;
// 定期检查远程配置变化
setInterval(async () => {
try {
await this.checkRemoteConfigChanges();
} catch (error) {
console.error('检查远程配置失败:', error.message);
}
}, 30000); // 30秒检查一次
}
async checkRemoteConfigChanges() {
const axios = require('axios');
const environment = process.env.NODE_ENV || 'development';
try {
const response = await axios.get(`${process.env.CONFIG_SERVICE_URL}/config/${environment}/version`, {
timeout: 5000,
headers: {
'Authorization': `Bearer ${process.env.CONFIG_SERVICE_TOKEN}`
}
});
const remoteVersion = response.data.version;
const currentVersion = this.configManager.get('_version', '0');
if (remoteVersion !== currentVersion) {
console.log(`🔄 检测到远程配置版本变化: ${currentVersion} -> ${remoteVersion}`);
this.queueUpdate('remote', remoteVersion);
}
} catch (error) {
// 静默处理错误,避免日志污染
}
}
queueUpdate(source, identifier) {
this.updateQueue.push({
source,
identifier,
timestamp: Date.now()
});
}
async processUpdateQueue() {
setInterval(async () => {
if (this.isProcessing || this.updateQueue.length === 0) {
return;
}
this.isProcessing = true;
try {
const update = this.updateQueue.shift();
await this.processUpdate(update);
} catch (error) {
console.error('处理配置更新失败:', error.message);
} finally {
this.isProcessing = false;
}
}, 1000);
}
async processUpdate(update) {
console.log(`🔄 处理配置更新: ${update.source}/${update.identifier}`);
try {
// 备份当前配置
const backupConfig = this.configManager.getAll();
// 重新加载配置
await this.configManager.reload();
// 验证新配置
const validationResult = await this.validateNewConfig();
if (validationResult.isValid) {
// 通知配置变更
this.emit('configUpdated', {
source: update.source,
identifier: update.identifier,
timestamp: update.timestamp,
changes: this.detectChanges(backupConfig, this.configManager.getAll())
});
console.log('✅ 配置热更新成功');
} else {
// 回滚配置
this.configManager.config = backupConfig;
console.error('❌ 配置验证失败,已回滚:', validationResult.errors);
this.emit('configUpdateFailed', {
source: update.source,
identifier: update.identifier,
errors: validationResult.errors
});
}
} catch (error) {
console.error('配置更新处理失败:', error.message);
this.emit('configUpdateError', error);
}
}
async validateNewConfig() {
try {
// 基本验证
this.configManager.validateConfig();
// 业务逻辑验证
const businessValidation = await this.performBusinessValidation();
return {
isValid: businessValidation.isValid,
errors: businessValidation.errors
};
} catch (error) {
return {
isValid: false,
errors: [error.message]
};
}
}
async performBusinessValidation() {
const errors = [];
// 数据库连接验证
try {
await this.validateDatabaseConnection();
} catch (error) {
errors.push(`数据库连接验证失败: ${error.message}`);
}
// Redis连接验证
try {
await this.validateRedisConnection();
} catch (error) {
errors.push(`Redis连接验证失败: ${error.message}`);
}
// 外部服务验证
try {
await this.validateExternalServices();
} catch (error) {
errors.push(`外部服务验证失败: ${error.message}`);
}
return {
isValid: errors.length === 0,
errors
};
}
async validateDatabaseConnection() {
// 实现数据库连接验证逻辑
const dbConfig = this.configManager.get('database');
// ... 验证逻辑
}
async validateRedisConnection() {
// 实现Redis连接验证逻辑
const redisConfig = this.configManager.get('redis');
// ... 验证逻辑
}
async validateExternalServices() {
// 实现外部服务验证逻辑
const externalConfig = this.configManager.get('external');
// ... 验证逻辑
}
detectChanges(oldConfig, newConfig) {
const changes = [];
this.compareObjects(oldConfig, newConfig, '', changes);
return changes;
}
compareObjects(obj1, obj2, path, changes) {
const keys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);
for (const key of keys) {
const fullPath = path ? `${path}.${key}` : key;
const val1 = obj1[key];
const val2 = obj2[key];
if (val1 === undefined) {
changes.push({ type: 'added', path: fullPath, value: val2 });
} else if (val2 === undefined) {
changes.push({ type: 'removed', path: fullPath, value: val1 });
} else if (typeof val1 === 'object' && typeof val2 === 'object') {
this.compareObjects(val1, val2, fullPath, changes);
} else if (val1 !== val2) {
changes.push({ type: 'changed', path: fullPath, oldValue: val1, newValue: val2 });
}
}
}
stop() {
console.log('⏹️ 停止配置热更新监控');
// 停止文件监控
for (const watcher of this.watchers.values()) {
watcher.close();
}
this.watchers.clear();
// 清空更新队列
this.updateQueue = [];
}
}
module.exports = HotReloadManager;
🌐 配置中心集成
配置中心客户端
javascript
// config/config-center-client.js
const axios = require('axios');
const WebSocket = require('ws');
const EventEmitter = require('events');
class ConfigCenterClient extends EventEmitter {
constructor(options) {
super();
this.options = {
endpoint: options.endpoint,
apiKey: options.apiKey,
namespace: options.namespace || 'default',
environment: options.environment || 'development',
timeout: options.timeout || 10000,
retryInterval: options.retryInterval || 30000,
enableWebSocket: options.enableWebSocket !== false
};
this.config = new Map();
this.isConnected = false;
this.ws = null;
this.retryTimer = null;
}
async connect() {
try {
console.log('🔌 连接配置中心...');
// 获取初始配置
await this.fetchInitialConfig();
// 建立WebSocket连接(用于实时更新)
if (this.options.enableWebSocket) {
await this.connectWebSocket();
}
this.isConnected = true;
console.log('✅ 配置中心连接成功');
this.emit('connected');
} catch (error) {
console.error('❌ 配置中心连接失败:', error.message);
this.scheduleRetry();
throw error;
}
}
async fetchInitialConfig() {
const response = await axios.get(`${this.options.endpoint}/api/config`, {
params: {
namespace: this.options.namespace,
environment: this.options.environment
},
headers: {
'Authorization': `Bearer ${this.options.apiKey}`,
'Content-Type': 'application/json'
},
timeout: this.options.timeout
});
const configData = response.data;
// 存储配置
for (const [key, value] of Object.entries(configData.config || {})) {
this.config.set(key, value);
}
// 存储元数据
this.configVersion = configData.version;
this.lastUpdated = configData.updatedAt;
console.log(`📥 获取配置完成: ${this.config.size} 项配置`);
}
async connectWebSocket() {
const wsUrl = `${this.options.endpoint.replace('http', 'ws')}/ws/config`;
this.ws = new WebSocket(wsUrl, {
headers: {
'Authorization': `Bearer ${this.options.apiKey}`
}
});
this.ws.on('open', () => {
console.log('🔗 WebSocket连接已建立');
// 订阅配置变更
this.ws.send(JSON.stringify({
type: 'subscribe',
namespace: this.options.namespace,
environment: this.options.environment
}));
});
this.ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
this.handleWebSocketMessage(message);
} catch (error) {
console.error('WebSocket消息解析失败:', error.message);
}
});
this.ws.on('close', () => {
console.log('🔌 WebSocket连接已断开');
this.scheduleReconnect();
});
this.ws.on('error', (error) => {
console.error('WebSocket错误:', error.message);
});
}
handleWebSocketMessage(message) {
switch (message.type) {
case 'config_updated':
this.handleConfigUpdate(message.data);
break;
case 'config_deleted':
this.handleConfigDeletion(message.data);
break;
case 'ping':
this.ws.send(JSON.stringify({ type: 'pong' }));
break;
default:
console.log('未知WebSocket消息类型:', message.type);
}
}
handleConfigUpdate(data) {
console.log(`📝 配置更新: ${data.key}`);
const oldValue = this.config.get(data.key);
this.config.set(data.key, data.value);
this.emit('configChanged', {
key: data.key,
oldValue,
newValue: data.value,
timestamp: data.timestamp
});
}
handleConfigDeletion(data) {
console.log(`🗑️ 配置删除: ${data.key}`);
const oldValue = this.config.get(data.key);
this.config.delete(data.key);
this.emit('configDeleted', {
key: data.key,
oldValue,
timestamp: data.timestamp
});
}
// 获取配置值
get(key, defaultValue = undefined) {
return this.config.get(key) || defaultValue;
}
// 获取所有配置
getAll() {
return Object.fromEntries(this.config);
}
// 设置配置值(推送到配置中心)
async set(key, value) {
try {
await axios.put(`${this.options.endpoint}/api/config/${key}`, {
value,
namespace: this.options.namespace,
environment: this.options.environment
}, {
headers: {
'Authorization': `Bearer ${this.options.apiKey}`,
'Content-Type': 'application/json'
},
timeout: this.options.timeout
});
// 本地也更新
this.config.set(key, value);
console.log(`✅ 配置设置成功: ${key}`);
} catch (error) {
console.error(`❌ 配置设置失败: ${key}`, error.message);
throw error;
}
}
// 删除配置
async delete(key) {
try {
await axios.delete(`${this.options.endpoint}/api/config/${key}`, {
params: {
namespace: this.options.namespace,
environment: this.options.environment
},
headers: {
'Authorization': `Bearer ${this.options.apiKey}`
},
timeout: this.options.timeout
});
// 本地也删除
this.config.delete(key);
console.log(`✅ 配置删除成功: ${key}`);
} catch (error) {
console.error(`❌ 配置删除失败: ${key}`, error.message);
throw error;
}
}
scheduleRetry() {
if (this.retryTimer) {
clearTimeout(this.retryTimer);
}
this.retryTimer = setTimeout(() => {
console.log('🔄 重试连接配置中心...');
this.connect().catch(() => {
// 连接失败会自动重试
});
}, this.options.retryInterval);
}
scheduleReconnect() {
if (this.options.enableWebSocket && !this.ws) {
setTimeout(() => {
this.connectWebSocket().catch(error => {
console.error('WebSocket重连失败:', error.message);
});
}, 5000);
}
}
disconnect() {
console.log('🔌 断开配置中心连接');
this.isConnected = false;
if (this.ws) {
this.ws.close();
this.ws = null;
}
if (this.retryTimer) {
clearTimeout(this.retryTimer);
this.retryTimer = null;
}
this.emit('disconnected');
}
// 健康检查
async healthCheck() {
try {
const response = await axios.get(`${this.options.endpoint}/api/health`, {
headers: {
'Authorization': `Bearer ${this.options.apiKey}`
},
timeout: 5000
});
return response.status === 200;
} catch (error) {
return false;
}
}
}
module.exports = ConfigCenterClient;
📊 配置监控和审计
配置变更审计
javascript
// config/config-audit.js
const fs = require('fs').promises;
const crypto = require('crypto');
class ConfigAudit {
constructor(options) {
this.options = {
auditLogFile: options.auditLogFile || './logs/config-audit.log',
enableEncryption: options.enableEncryption !== false,
maxLogSize: options.maxLogSize || 10 * 1024 * 1024, // 10MB
retentionDays: options.retentionDays || 90
};
this.encryptionKey = options.encryptionKey;
}
// 记录配置变更
async logConfigChange(change) {
const auditEntry = {
timestamp: new Date().toISOString(),
id: this.generateId(),
type: change.type, // 'create', 'update', 'delete', 'read'
key: change.key,
source: change.source, // 'file', 'api', 'ui', 'system'
user: change.user || 'system',
environment: change.environment || process.env.NODE_ENV,
metadata: {
userAgent: change.userAgent,
ip: change.ip,
sessionId: change.sessionId
}
};
// 处理敏感数据
if (change.oldValue !== undefined) {
auditEntry.oldValueHash = this.hashValue(change.oldValue);
}
if (change.newValue !== undefined) {
auditEntry.newValueHash = this.hashValue(change.newValue);
}
// 加密敏感信息(可选)
if (this.options.enableEncryption && this.encryptionKey) {
auditEntry.encrypted = true;
auditEntry.data = this.encrypt(JSON.stringify({
oldValue: change.oldValue,
newValue: change.newValue
}));
}
// 写入审计日志
await this.writeAuditLog(auditEntry);
console.log(`📋 配置变更已记录: ${change.type} ${change.key}`);
}
// 生成唯一ID
generateId() {
return crypto.randomBytes(16).toString('hex');
}
// 哈希值计算
hashValue(value) {
if (value === null || value === undefined) {
return null;
}
const valueStr = typeof value === 'string' ? value : JSON.stringify(value);
return crypto.createHash('sha256').update(valueStr).digest('hex');
}
// 加密数据
encrypt(text) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher('aes-256-cbc', this.encryptionKey);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return {
iv: iv.toString('hex'),
data: encrypted
};
}
// 解密数据
decrypt(encryptedData) {
const decipher = crypto.createDecipher('aes-256-cbc', this.encryptionKey);
let decrypted = decipher.update(encryptedData.data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
}
// 写入审计日志
async writeAuditLog(auditEntry) {
const logLine = JSON.stringify(auditEntry) + '\n';
try {
await fs.appendFile(this.options.auditLogFile, logLine);
// 检查日志文件大小
await this.checkLogRotation();
} catch (error) {
console.error('写入审计日志失败:', error.message);
throw error;
}
}
// 日志轮转
async checkLogRotation() {
try {
const stats = await fs.stat(this.options.auditLogFile);
if (stats.size > this.options.maxLogSize) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const rotatedFile = `${this.options.auditLogFile}.${timestamp}`;
await fs.rename(this.options.auditLogFile, rotatedFile);
console.log(`📁 审计日志已轮转: ${rotatedFile}`);
// 清理旧日志
await this.cleanupOldLogs();
}
} catch (error) {
if (error.code !== 'ENOENT') {
console.error('日志轮转检查失败:', error.message);
}
}
}
// 清理旧日志
async cleanupOldLogs() {
try {
const logDir = require('path').dirname(this.options.auditLogFile);
const logBasename = require('path').basename(this.options.auditLogFile);
const files = await fs.readdir(logDir);
const logFiles = files.filter(file => file.startsWith(logBasename));
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - this.options.retentionDays);
for (const file of logFiles) {
const filePath = require('path').join(logDir, file);
const stats = await fs.stat(filePath);
if (stats.mtime < cutoffDate) {
await fs.unlink(filePath);
console.log(`🗑️ 删除过期审计日志: ${file}`);
}
}
} catch (error) {
console.error('清理旧日志失败:', error.message);
}
}
// 查询审计日志
async queryAuditLogs(criteria) {
try {
const content = await fs.readFile(this.options.auditLogFile, 'utf8');
const lines = content.trim().split('\n').filter(line => line);
let entries = lines.map(line => JSON.parse(line));
// 应用过滤条件
if (criteria.startDate) {
entries = entries.filter(entry => new Date(entry.timestamp) >= new Date(criteria.startDate));
}
if (criteria.endDate) {
entries = entries.filter(entry => new Date(entry.timestamp) <= new Date(criteria.endDate));
}
if (criteria.type) {
entries = entries.filter(entry => entry.type === criteria.type);
}
if (criteria.key) {
entries = entries.filter(entry => entry.key === criteria.key);
}
if (criteria.user) {
entries = entries.filter(entry => entry.user === criteria.user);
}
// 排序
entries.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
// 限制结果数量
if (criteria.limit) {
entries = entries.slice(0, criteria.limit);
}
return entries;
} catch (error) {
console.error('查询审计日志失败:', error.message);
return [];
}
}
// 生成审计报告
async generateAuditReport(criteria) {
const entries = await this.queryAuditLogs(criteria);
const report = {
period: {
startDate: criteria.startDate,
endDate: criteria.endDate
},
summary: {
totalChanges: entries.length,
changesByType: this.groupBy(entries, 'type'),
changesByUser: this.groupBy(entries, 'user'),
changesByKey: this.groupBy(entries, 'key')
},
entries: entries.slice(0, 100) // 只包含前100条详细记录
};
return report;
}
groupBy(array, key) {
return array.reduce((groups, item) => {
const value = item[key];
groups[value] = (groups[value] || 0) + 1;
return groups;
}, {});
}
// 验证审计日志完整性
async verifyAuditIntegrity() {
try {
const entries = await this.queryAuditLogs({});
const issues = [];
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
// 检查必需字段
if (!entry.id || !entry.timestamp || !entry.type) {
issues.push(`条目 ${i} 缺少必需字段`);
}
// 检查时间戳格式
if (isNaN(Date.parse(entry.timestamp))) {
issues.push(`条目 ${i} 时间戳格式无效`);
}
// 检查加密数据
if (entry.encrypted && this.encryptionKey) {
try {
this.decrypt(entry.data);
} catch (error) {
issues.push(`条目 ${i} 解密失败`);
}
}
}
return {
isValid: issues.length === 0,
issues,
totalEntries: entries.length
};
} catch (error) {
return {
isValid: false,
issues: [`验证失败: ${error.message}`],
totalEntries: 0
};
}
}
}
module.exports = ConfigAudit;
📝 总结
配置管理为Node.js应用提供了:
- 环境隔离:不同环境使用不同配置
- 安全管理:敏感配置加密存储
- 动态更新:支持运行时配置变更
- 版本控制:配置变更可追踪和回滚
- 集中管理:统一的配置管理平台
- 审计跟踪:完整的配置变更历史
良好的配置管理是现代应用架构的重要组成部分,能够提高部署效率和运维安全性。