@nexe/config-manager
Version:
Nexe Config Manager - A flexible configuration management solution with multiple sources and hot reload support
332 lines • 12.4 kB
JavaScript
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