UNPKG

@nexe/config-manager

Version:

Nexe Config Manager - A flexible configuration management solution with multiple sources and hot reload support

332 lines 12.4 kB
import { __decorate, __metadata } from "tslib"; import { createLogger } from '@nexe/logger'; import Consul from 'consul'; import { injectable } from 'tsyringe'; const logger = createLogger('ConsulConfigSource'); /** * 支持动态路径的 Consul 配置源 * 允许在运行时指定不同的配置路径前缀 */ let ConsulConfigSource = class ConsulConfigSource { /** * @param options Consul配置选项 */ constructor(options) { // 按路径前缀分别缓存配置 this.pathCaches = new Map(); this.callbacks = new Map(); // eslint-disable-next-line @typescript-eslint/no-explicit-any this.watchers = new Map(); this.recreatingWatchers = new Set(); // 防止重复创建 this.options = { secure: false, defaultKeyPrefix: '', ...options, }; // 初始化Consul客户端 this.consul = new Consul({ host: this.options.host, port: this.options.port, secure: this.options.secure, defaults: { token: this.options.token, dc: this.options.dc, }, }); logger.info(`🔗 已连接到Consul: ${this.options.host}:${this.options.port}`); } /** * 从Consul加载配置 */ async load(key, pathPrefix) { const actualPrefix = pathPrefix || this.options.defaultKeyPrefix || ''; try { // 检查是否已缓存该路径的配置 if (!this.pathCaches.has(actualPrefix)) { logger.info(`🔄 正在从Consul加载配置路径: ${actualPrefix}`); await this.fetchKeysForPrefix(actualPrefix); } const cache = this.pathCaches.get(actualPrefix) || {}; if (!key) { return cache; } const value = this.getNestedValue(cache, key); return value; } catch (error) { logger.error(`从Consul加载配置失败 (路径: ${actualPrefix}, 键: ${key}): ${error instanceof Error ? error.message : String(error)}`); return undefined; } } /** * 获取指定前缀的所有配置键 */ async fetchKeysForPrefix(keyPrefix) { try { const cache = {}; // 首先尝试获取前缀作为单个JSON对象(兼容原版) try { const singleResult = await this.consul.kv.get({ key: keyPrefix, recurse: false, }); if (singleResult && !Array.isArray(singleResult) && singleResult.Value) { // 如果存在单个JSON对象,解析它 const jsonData = this.decodeConsulValue(singleResult.Value); if (jsonData && typeof jsonData === 'object') { // logger.debug(`发现单个JSON配置对象 (${keyPrefix}),使用原版兼容模式`); Object.assign(cache, jsonData); this.pathCaches.set(keyPrefix, cache); // logger.info( // `✅ 已缓存配置路径 ${keyPrefix},包含 ${Object.keys(cache).length} 个配置项`, // ); return; } } } catch (error) { logger.debug(`单个JSON模式失败 (${keyPrefix}),尝试递归模式:`, error); } // 如果单个JSON模式失败,使用递归模式获取所有子键 const result = await this.consul.kv.get({ key: keyPrefix, recurse: true, }); if (Array.isArray(result)) { // 递归模式:处理多个键值对 logger.debug(`使用递归模式处理配置路径 (${keyPrefix})`); for (const item of result) { if (item.Key && item.Value !== undefined) { const relativePath = this.getRelativePath(item.Key, keyPrefix); const value = this.decodeConsulValue(item.Value); if (relativePath) { this.setNestedValue(cache, relativePath, value); } } } } this.pathCaches.set(keyPrefix, cache); logger.info(`✅ 已缓存配置路径 ${keyPrefix},包含 ${Object.keys(cache).length} 个配置项`); } catch (error) { logger.error(`获取Consul配置前缀 ${keyPrefix} 失败:`, error); this.pathCaches.set(keyPrefix, {}); } } /** * 获取相对路径 */ getRelativePath(fullKey, prefix) { if (!prefix) { return fullKey; } const relativePath = fullKey.startsWith(prefix) ? fullKey.slice(prefix.length) : fullKey; return relativePath.replace(/^\//, ''); } /** * 解码Consul值 */ decodeConsulValue(value) { if (!value) { return null; } try { // 尝试解析为JSON return JSON.parse(value); } catch { // 如果不是JSON,返回原始字符串 return value; } } /** * 获取嵌套值 */ getNestedValue(obj, path) { return path.split('.').reduce((current, key) => { return current && typeof current === 'object' && !Array.isArray(current) ? current[key] : undefined; }, obj); } /** * 设置嵌套值 */ setNestedValue(obj, path, value) { const keys = path.split('.'); const lastKey = keys.pop(); if (!lastKey) { return; } const target = keys.reduce((current, key) => { if (!(key in current)) { current[key] = {}; } return current[key]; }, obj); target[lastKey] = value; } /** * 获取配置源名称 */ getName() { return `ConsulConfigSource(${this.options.host}:${this.options.port})`; } /** * 是否支持热更新 */ supportsHotReload() { return true; } /** * 是否支持动态路径 */ supportsDynamicPath() { return true; } /** * 订阅配置变更 */ subscribe(key, callback, pathPrefix) { const actualPrefix = pathPrefix || this.options.defaultKeyPrefix || ''; const watchKey = `${actualPrefix}:${key}`; logger.info(`🔔 订阅配置变更: ${watchKey}`); // 添加回调到列表 const callbacks = this.callbacks.get(watchKey) ?? []; callbacks.push(callback); this.callbacks.set(watchKey, callbacks); // 如果还没有为这个路径+键创建watcher且不在重新创建中,则创建一个 if (!this.watchers.has(watchKey) && !this.recreatingWatchers.has(watchKey)) { logger.info(`🚀 创建新的 watcher: ${watchKey}`); this.createWatcher(actualPrefix, key); } else if (this.watchers.has(watchKey)) { logger.info(`♻️ watcher 已存在: ${watchKey}`); } else if (this.recreatingWatchers.has(watchKey)) { logger.info(`⏳ watcher 正在重新创建中: ${watchKey}`); } } /** * 取消订阅配置变更 */ unsubscribe(key, pathPrefix) { const actualPrefix = pathPrefix || this.options.defaultKeyPrefix || ''; const watchKey = `${actualPrefix}:${key}`; logger.info(`🚫 取消订阅配置变更: ${watchKey}`); // 删除回调 this.callbacks.delete(watchKey); // 取消重新创建标记 this.recreatingWatchers.delete(watchKey); // 关闭对应的watcher const watcher = this.watchers.get(watchKey); if (watcher) { try { watcher.end(); logger.info(`✅ 已关闭 watcher: ${watchKey}`); } catch (error) { logger.warn(`⚠️ 关闭 watcher 时出错 (${watchKey}):`, error); } this.watchers.delete(watchKey); } } /** * 创建配置监听器 */ createWatcher(pathPrefix, key) { const watchKey = `${pathPrefix}:${key}`; // 双重检查,确保不重复创建 if (this.watchers.has(watchKey)) { logger.warn(`⚠️ watcher 已存在,跳过创建: ${watchKey}`); return; } try { logger.info(`🔍 创建watcher: ${watchKey} -> 监听路径: ${pathPrefix}`); const watcher = this.consul.watch({ method: this.consul.kv.get, options: { key: pathPrefix, recurse: true, }, }); // 先注册到 watchers,防止重复创建 this.watchers.set(watchKey, watcher); watcher.on('change', async (_data) => { try { // logger.info(`🔄 配置变更检测: ${watchKey}`); // 更新缓存 await this.fetchKeysForPrefix(pathPrefix); // 通知订阅者 const cache = this.pathCaches.get(pathPrefix) || {}; const value = this.getNestedValue(cache, key); // logger.info(`📢 通知订阅者: ${watchKey}, 新值:`, value); const callbacks = this.callbacks.get(watchKey) ?? []; callbacks.forEach(callback => callback(value)); } catch (error) { logger.error('处理配置变更失败:', error); } }); watcher.on('error', (error) => { logger.error(`Consul watch错误 (${watchKey}):`, error); // 清理当前 watcher this.watchers.delete(watchKey); // 检查是否是限流错误 const isRateLimited = error.message.includes('too many requests'); if (isRateLimited) { logger.warn(`⚠️ Consul 限流,暂停重新创建 watcher: ${watchKey}`); return; // 不重新创建,避免进一步限流 } // 标记为正在重新创建 this.recreatingWatchers.add(watchKey); setTimeout(() => { this.recreatingWatchers.delete(watchKey); // 只有在还有回调的情况下才重新创建 if (this.callbacks.has(watchKey) && this.callbacks.get(watchKey)?.length) { logger.info(`🔄 重新创建 watcher: ${watchKey}`); this.createWatcher(pathPrefix, key); } }, 10000); // 增加到10秒,避免频繁重试 }); logger.info(`✅ watcher 创建成功: ${watchKey}`); } catch (error) { logger.error(`创建watcher失败 (${watchKey}):`, error); // 如果创建失败,从 watchers 中移除 this.watchers.delete(watchKey); } } /** * 清理资源 */ dispose() { logger.info('🧹 开始清理所有资源...'); // 清理重新创建标记 this.recreatingWatchers.clear(); // 关闭所有watchers this.watchers.forEach((watcher, watchKey) => { try { watcher.end(); logger.info(`✅ 已关闭 watcher: ${watchKey}`); } catch (error) { logger.warn(`⚠️ 关闭 watcher 时出错 (${watchKey}):`, error); } }); this.watchers.clear(); this.callbacks.clear(); this.pathCaches.clear(); logger.info('🧹 已清理所有资源'); } }; ConsulConfigSource = __decorate([ injectable(), __metadata("design:paramtypes", [Object]) ], ConsulConfigSource); export { ConsulConfigSource }; //# sourceMappingURL=consul-config-source.js.map