@be-link/shield-for-tcb-node-sdk 1.0.11 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -126,6 +126,68 @@ const frontendConfig = await frontendConfigService.fetchConfig({ key: 'your-key'
126
126
  | 版本追踪 | ❌ | ✅ |
127
127
  | 审计日志 | ❌ | ✅ |
128
128
 
129
+ ---
130
+
131
+ ## ShieldConfigManager(配置管理器)
132
+
133
+ `ShieldConfigManager` 提供配置自动拉取、缓存、定时刷新功能,适合需要持续使用配置的服务。
134
+
135
+ ### 使用方式
136
+
137
+ ```typescript
138
+ import { ShieldConfigManager } from '@be-link/shield-for-tcb-node-sdk'
139
+
140
+ const manager = new ShieldConfigManager({
141
+ serviceName: 'BackendBff',
142
+ env: 'local',
143
+ enableInfoLog: true, // 可选,默认 false
144
+ })
145
+
146
+ await manager.setup()
147
+ const config = manager.getConfig<MyConfig>()
148
+ manager.stop()
149
+ ```
150
+
151
+ ### 配置参数
152
+
153
+ | 参数 | 类型 | 默认值 | 说明 |
154
+ |------|------|--------|------|
155
+ | `serviceName` | `string` | **必填** | 服务名称 |
156
+ | `env` | `string` | `NODE_ENV` | 环境 |
157
+ | `refreshIntervalMs` | `number` | `5000` | 刷新间隔(毫秒) |
158
+ | `fetchTimeoutMs` | `number` | `10000` | 请求超时(毫秒) |
159
+ | `enableInfoLog` | `boolean` | `false` | 是否打印 info 日志 |
160
+ | `logger` | `Logger` | `console` | 自定义日志函数 |
161
+
162
+ ### 日志说明
163
+
164
+ `enableInfoLog` 默认为 `false`,不打印 info 级别日志。设置为 `true` 时会打印以下日志:
165
+
166
+ - 初始化成功
167
+ - 配置刷新(包含配置的 key 列表)
168
+ - 配置最终内容
169
+ - 定时器启动信息
170
+ - 停止信息
171
+
172
+ error 级别日志不受 `enableInfoLog` 影响,始终打印:
173
+
174
+ - 初始刷新失败
175
+ - 配置为空警告
176
+ - 刷新错误
177
+ - 定时器刷新错误
178
+
179
+ ### API
180
+
181
+ | 方法 | 说明 |
182
+ |------|------|
183
+ | `setup()` | 初始化并启动定时刷新,幂等操作 |
184
+ | `getConfig<T>()` | 获取类型化的配置对象 |
185
+ | `refresh()` | 手动触发一次配置刷新 |
186
+ | `stop()` | 停止定时刷新,清理资源 |
187
+ | `waitForSetup()` | 等待初始化完成 |
188
+ | `isSetup` | 是否已完成初始化(只读属性) |
189
+ | `config` | 当前缓存的配置(原始 Record,只读属性) |
190
+
129
191
  ## 发布流程
130
192
 
131
193
  ### 自动发布(推荐)
@@ -45,6 +45,32 @@ export interface Logger {
45
45
  warn: (message: string, ...args: any[]) => void;
46
46
  error: (message: string, ...args: any[]) => void;
47
47
  }
48
+ /**
49
+ * 流量控制配置
50
+ *
51
+ * @example
52
+ * ```json
53
+ * {
54
+ * "enabled": true,
55
+ * "totalBuckets": 100,
56
+ * "effectiveBuckets": 10,
57
+ * "whitelist": ["user_001", "user_002"],
58
+ * "blacklist": ["test_*"]
59
+ * }
60
+ * ```
61
+ */
62
+ export interface TrafficControlConfig {
63
+ /** 总开关 */
64
+ enabled: boolean;
65
+ /** 总桶数 */
66
+ totalBuckets: number;
67
+ /** 生效桶数(bucket 0 到 effectiveBuckets-1 生效) */
68
+ effectiveBuckets: number;
69
+ /** 白名单(命中后视为生效) */
70
+ whitelist?: string[];
71
+ /** 黑名单(命中后视为不生效) */
72
+ blacklist?: string[];
73
+ }
48
74
  /**
49
75
  * 配置管理器
50
76
  *
@@ -76,6 +102,52 @@ export declare class ShieldConfigManager<T = Record<string, any>> {
76
102
  * @returns 泛型 T 类型的配置对象
77
103
  */
78
104
  getConfig(): T | null;
105
+ /**
106
+ * 根据 config_key 获取对应的配置值
107
+ * @param key 配置键名(如 'REDIS', 'SQL')
108
+ * @param defaultValue 默认值(可选)
109
+ * @returns 配置值,如果不存在则返回默认值或 undefined
110
+ * @example
111
+ * ```ts
112
+ * // 获取配置
113
+ * const port = manager.getValue('API_PORT', 8090)
114
+ *
115
+ * // 获取配置并指定类型
116
+ * const isEnabled = manager.getValue<boolean>('FEATURE.ENABLED', false)
117
+ * ```
118
+ */
119
+ getValue<T = string>(key: string, defaultValue?: T): T | undefined;
120
+ /**
121
+ * 根据 config_key 获取开关状态(布尔值)
122
+ * @param key 配置键名
123
+ * @param defaultValue 默认值(可选),默认为 false
124
+ * @returns 开关状态,true 表示开启,false 表示关闭
125
+ * @example
126
+ * ```ts
127
+ * // 检查功能是否开启
128
+ * const isEnabled = manager.isSwitchEnabled('FEATURE.ENABLED')
129
+ *
130
+ * // 指定默认值为 true
131
+ * const isDebug = manager.isSwitchEnabled('DEBUG_MODE', true)
132
+ * ```
133
+ */
134
+ isSwitchEnabled(key: string, defaultValue?: boolean): boolean;
135
+ /**
136
+ * 判断请求是否命中灰度流量
137
+ * @param configKey 配置键名(如 'TRAFFIC_SPLIT')
138
+ * @param identifier 路由标识(如用户ID)
139
+ * @param defaultValue 默认值(可选),默认为 false
140
+ * @returns 是否命中灰度流量
141
+ * @example
142
+ * ```ts
143
+ * // 检查用户是否命中灰度流量
144
+ * const isHit = manager.isHitGrey('TRAFFIC_SPLIT', userId, false)
145
+ * if (isHit) {
146
+ * // 灰度流量
147
+ * }
148
+ * ```
149
+ */
150
+ isHitGrey(configKey: string, identifier: string, defaultValue?: boolean): boolean;
79
151
  /**
80
152
  * 停止定时刷新,清理资源
81
153
  */
@@ -71,7 +71,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
71
71
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
72
72
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
73
73
  };
74
- var _ShieldConfigManager_instances, _ShieldConfigManager_serviceName, _ShieldConfigManager_env, _ShieldConfigManager_refreshIntervalMs, _ShieldConfigManager_fetchTimeoutMs, _ShieldConfigManager_enableInfoLog, _ShieldConfigManager_logger, _ShieldConfigManager_config, _ShieldConfigManager_refreshTimer, _ShieldConfigManager_isSetup, _ShieldConfigManager_setupPromise, _ShieldConfigManager_doSetup, _ShieldConfigManager_startRefreshTimer;
74
+ var _ShieldConfigManager_instances, _ShieldConfigManager_serviceName, _ShieldConfigManager_env, _ShieldConfigManager_refreshIntervalMs, _ShieldConfigManager_fetchTimeoutMs, _ShieldConfigManager_enableInfoLog, _ShieldConfigManager_logger, _ShieldConfigManager_config, _ShieldConfigManager_refreshTimer, _ShieldConfigManager_isSetup, _ShieldConfigManager_setupPromise, _ShieldConfigManager_doSetup, _ShieldConfigManager_hashString, _ShieldConfigManager_startRefreshTimer;
75
75
  Object.defineProperty(exports, "__esModule", { value: true });
76
76
  exports.ShieldConfigManager = void 0;
77
77
  const serviceV2_1 = require("./serviceV2");
@@ -155,14 +155,14 @@ class ShieldConfigManager {
155
155
  const config = await withTimeout(serviceV2_1.backendConfigServiceV2.fetchAllConfigs(__classPrivateFieldGet(this, _ShieldConfigManager_serviceName, "f"), __classPrivateFieldGet(this, _ShieldConfigManager_env, "f")), __classPrivateFieldGet(this, _ShieldConfigManager_fetchTimeoutMs, "f"));
156
156
  if (config && Object.keys(config).length > 0) {
157
157
  __classPrivateFieldSet(this, _ShieldConfigManager_config, config, "f");
158
- __classPrivateFieldGet(this, _ShieldConfigManager_logger, "f").info(`ShieldConfigManager config refreshed (keys: ${JSON.stringify(Object.keys(config))})`);
158
+ __classPrivateFieldGet(this, _ShieldConfigManager_logger, "f").info(`ShieldConfigManager config refreshed (keys: ${JSON.stringify(config)})`);
159
159
  }
160
160
  else {
161
161
  __classPrivateFieldGet(this, _ShieldConfigManager_logger, "f").warn('ShieldConfigManager fetched empty config');
162
162
  }
163
163
  }
164
164
  catch (error) {
165
- __classPrivateFieldGet(this, _ShieldConfigManager_logger, "f").error(`ShieldConfigManager refresh error: ${error}`);
165
+ __classPrivateFieldGet(this, _ShieldConfigManager_logger, "f").error(`ShieldConfigManager refresh error: ${error instanceof Error ? error.message : JSON.stringify(error)}`);
166
166
  }
167
167
  // 兼容旧的逻辑
168
168
  try {
@@ -172,9 +172,9 @@ class ShieldConfigManager {
172
172
  }
173
173
  }
174
174
  catch (error) {
175
- __classPrivateFieldGet(this, _ShieldConfigManager_logger, "f").error(`fetch dynamic config error: ${error}`);
175
+ __classPrivateFieldGet(this, _ShieldConfigManager_logger, "f").error(`fetch dynamic config error: ${error instanceof Error ? error.message : JSON.stringify(error)}`);
176
176
  }
177
- __classPrivateFieldGet(this, _ShieldConfigManager_logger, "f").info(`ShieldConfigManager config refreshed finally (config: ${__classPrivateFieldGet(this, _ShieldConfigManager_config, "f")})`);
177
+ __classPrivateFieldGet(this, _ShieldConfigManager_logger, "f").info(`ShieldConfigManager config refreshed finally (config: ${JSON.stringify(__classPrivateFieldGet(this, _ShieldConfigManager_config, "f"))})`);
178
178
  }
179
179
  /**
180
180
  * 获取类型化的配置
@@ -183,6 +183,108 @@ class ShieldConfigManager {
183
183
  getConfig() {
184
184
  return __classPrivateFieldGet(this, _ShieldConfigManager_config, "f") ?? null;
185
185
  }
186
+ /**
187
+ * 根据 config_key 获取对应的配置值
188
+ * @param key 配置键名(如 'REDIS', 'SQL')
189
+ * @param defaultValue 默认值(可选)
190
+ * @returns 配置值,如果不存在则返回默认值或 undefined
191
+ * @example
192
+ * ```ts
193
+ * // 获取配置
194
+ * const port = manager.getValue('API_PORT', 8090)
195
+ *
196
+ * // 获取配置并指定类型
197
+ * const isEnabled = manager.getValue<boolean>('FEATURE.ENABLED', false)
198
+ * ```
199
+ */
200
+ getValue(key, defaultValue) {
201
+ if (!__classPrivateFieldGet(this, _ShieldConfigManager_config, "f")) {
202
+ return defaultValue;
203
+ }
204
+ const value = __classPrivateFieldGet(this, _ShieldConfigManager_config, "f")[key];
205
+ if (value === undefined || value === null) {
206
+ return defaultValue;
207
+ }
208
+ return value;
209
+ }
210
+ /**
211
+ * 根据 config_key 获取开关状态(布尔值)
212
+ * @param key 配置键名
213
+ * @param defaultValue 默认值(可选),默认为 false
214
+ * @returns 开关状态,true 表示开启,false 表示关闭
215
+ * @example
216
+ * ```ts
217
+ * // 检查功能是否开启
218
+ * const isEnabled = manager.isSwitchEnabled('FEATURE.ENABLED')
219
+ *
220
+ * // 指定默认值为 true
221
+ * const isDebug = manager.isSwitchEnabled('DEBUG_MODE', true)
222
+ * ```
223
+ */
224
+ isSwitchEnabled(key, defaultValue = false) {
225
+ if (!__classPrivateFieldGet(this, _ShieldConfigManager_config, "f")) {
226
+ return defaultValue;
227
+ }
228
+ const value = __classPrivateFieldGet(this, _ShieldConfigManager_config, "f")[key];
229
+ if (value === undefined || value === null) {
230
+ return defaultValue;
231
+ }
232
+ // 支持多种布尔值表示
233
+ if (typeof value === 'boolean') {
234
+ return value;
235
+ }
236
+ if (typeof value === 'number') {
237
+ return value !== 0;
238
+ }
239
+ if (typeof value === 'string') {
240
+ const lower = value.toLowerCase();
241
+ if (lower === 'true' || lower === '1' || lower === 'yes' || lower === 'on') {
242
+ return true;
243
+ }
244
+ if (lower === 'false' || lower === '0' || lower === 'no' || lower === 'off') {
245
+ return false;
246
+ }
247
+ }
248
+ return defaultValue;
249
+ }
250
+ /**
251
+ * 判断请求是否命中灰度流量
252
+ * @param configKey 配置键名(如 'TRAFFIC_SPLIT')
253
+ * @param identifier 路由标识(如用户ID)
254
+ * @param defaultValue 默认值(可选),默认为 false
255
+ * @returns 是否命中灰度流量
256
+ * @example
257
+ * ```ts
258
+ * // 检查用户是否命中灰度流量
259
+ * const isHit = manager.isHitGrey('TRAFFIC_SPLIT', userId, false)
260
+ * if (isHit) {
261
+ * // 灰度流量
262
+ * }
263
+ * ```
264
+ */
265
+ isHitGrey(configKey, identifier, defaultValue = false) {
266
+ const config = this.getValue(configKey);
267
+ if (!config) {
268
+ return defaultValue;
269
+ }
270
+ // 总开关关闭,返回 false
271
+ if (!config.enabled) {
272
+ return false;
273
+ }
274
+ // 黑名单检查(精确匹配)- 命中后返回 false
275
+ if (config.blacklist && config.blacklist.includes(identifier)) {
276
+ return false;
277
+ }
278
+ // 白名单检查(精确匹配)- 命中后返回 true
279
+ if (config.whitelist && config.whitelist.includes(identifier)) {
280
+ return true;
281
+ }
282
+ // 桶路由
283
+ const totalBuckets = config.totalBuckets || 100;
284
+ const effectiveBuckets = config.effectiveBuckets || 0;
285
+ const hash = __classPrivateFieldGet(this, _ShieldConfigManager_instances, "m", _ShieldConfigManager_hashString).call(this, identifier);
286
+ return hash % totalBuckets < effectiveBuckets;
287
+ }
186
288
  /**
187
289
  * 停止定时刷新,清理资源
188
290
  */
@@ -210,10 +312,18 @@ _ShieldConfigManager_serviceName = new WeakMap(), _ShieldConfigManager_env = new
210
312
  __classPrivateFieldGet(this, _ShieldConfigManager_instances, "m", _ShieldConfigManager_startRefreshTimer).call(this);
211
313
  __classPrivateFieldSet(this, _ShieldConfigManager_isSetup, true, "f");
212
314
  __classPrivateFieldGet(this, _ShieldConfigManager_logger, "f").info('ShieldConfigManager initialized successfully');
315
+ }, _ShieldConfigManager_hashString = function _ShieldConfigManager_hashString(str) {
316
+ let hash = 0;
317
+ for (let i = 0; i < str.length; i++) {
318
+ const char = str.charCodeAt(i);
319
+ hash = (hash << 5) - hash + char;
320
+ hash = hash & hash; // Convert to 32bit integer
321
+ }
322
+ return Math.abs(hash);
213
323
  }, _ShieldConfigManager_startRefreshTimer = function _ShieldConfigManager_startRefreshTimer() {
214
324
  __classPrivateFieldSet(this, _ShieldConfigManager_refreshTimer, setInterval(() => {
215
325
  this.refresh().catch((error) => {
216
- __classPrivateFieldGet(this, _ShieldConfigManager_logger, "f").error(`ShieldConfigManager timer refresh error: ${error}`);
326
+ __classPrivateFieldGet(this, _ShieldConfigManager_logger, "f").error(`ShieldConfigManager timer refresh error: ${error instanceof Error ? error.message : JSON.stringify(error)}`);
217
327
  });
218
328
  }, __classPrivateFieldGet(this, _ShieldConfigManager_refreshIntervalMs, "f")), "f");
219
329
  __classPrivateFieldGet(this, _ShieldConfigManager_logger, "f").info(`ShieldConfigManager refresh timer started (interval: ${__classPrivateFieldGet(this, _ShieldConfigManager_refreshIntervalMs, "f")}ms)`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@be-link/shield-for-tcb-node-sdk",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "ShieldForTCB Node.js SDK",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",