@be-link/shield-for-tcb-node-sdk 1.0.16 → 1.0.18
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 +40 -0
- package/index.d.ts +2 -0
- package/index.js +4 -1
- package/modules/config/ShieldCloudFunctionConfig.d.ts +32 -0
- package/modules/config/ShieldCloudFunctionConfig.js +107 -0
- package/modules/config/ShieldConfigManager.d.ts +2 -13
- package/modules/config/ShieldConfigManager.js +3 -29
- package/modules/config/greyMatcher.d.ts +18 -0
- package/modules/config/greyMatcher.js +31 -0
- package/modules/config/serviceV2.d.ts +9 -0
- package/modules/config/serviceV2.js +10 -0
- package/modules/config/typesV2.d.ts +30 -0
- package/package.json +1 -1
- package/utils/cloudFunctionHttp.d.ts +5 -0
- package/utils/cloudFunctionHttp.js +19 -0
package/README.md
CHANGED
|
@@ -254,6 +254,46 @@ const values = await backendConfigServiceV2.getConfigValues(
|
|
|
254
254
|
// }
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
+
## 云函数场景:按单 Key 读取配置
|
|
258
|
+
|
|
259
|
+
云函数等短生命周期场景不适合在每次请求中初始化 `ShieldConfigManager` 并拉取全量配置。可以使用 `ShieldCloudFunctionConfig` 按需读取单个 key:构造不发请求、不 `setup()`、不启动定时器、SDK 侧不维护本地缓存,每次 `fetchKey()` 只发一次单 key HTTP 请求,缓存集中在 Shield 服务端(本地内存 + Redis + MySQL)。
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import { ShieldCloudFunctionConfig } from '@be-link/shield-for-tcb-node-sdk'
|
|
263
|
+
|
|
264
|
+
const configClient = new ShieldCloudFunctionConfig({
|
|
265
|
+
serviceName: 'TheVitality',
|
|
266
|
+
env: 'prod',
|
|
267
|
+
fetchTimeoutMs: 2000,
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
export async function handler() {
|
|
271
|
+
const redisConfig = await configClient.fetchKey<{
|
|
272
|
+
HOST: string
|
|
273
|
+
PORT: number
|
|
274
|
+
}>('REDIS')
|
|
275
|
+
|
|
276
|
+
const timeout = await configClient.fetchKey<number>('API_TIMEOUT', 3000)
|
|
277
|
+
const enabled = await configClient.isSwitchEnabled('FEATURE_ENABLED', false)
|
|
278
|
+
const isHit = await configClient.isHitGrey('TRAFFIC_SPLIT', 'user_001', false)
|
|
279
|
+
|
|
280
|
+
return { redisConfig, timeout, enabled, isHit }
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### 行为说明
|
|
285
|
+
|
|
286
|
+
- 构造 `ShieldCloudFunctionConfig` 不会发起 HTTP 请求。
|
|
287
|
+
- 不需要调用 `setup()`。
|
|
288
|
+
- 不会启动定时器。
|
|
289
|
+
- SDK 侧不维护本地缓存。
|
|
290
|
+
- 每次 `fetchKey()` 都会请求 Shield 服务端单 key 接口 `POST /shield/v2/config/cloud-function/fetch-key`。
|
|
291
|
+
- 默认超时时间为 2 秒,可以通过 `fetchTimeoutMs` 覆盖。
|
|
292
|
+
- 配置不存在、HTTP 超时、网络异常、Shield 5xx 时,`fetchKey()` 返回 `defaultValue` 或 `undefined`。
|
|
293
|
+
- `defaultValue` 同时适用于「配置不存在」和「远程读取失败」,安全敏感开关应选择 fail-safe 默认值。
|
|
294
|
+
- 多个 key 会产生多次 HTTP 请求;如果同一次云函数调用需要多个 key,调用方应评估延迟预算。
|
|
295
|
+
- `isSwitchEnabled()` / `isHitGrey()` 与 `ShieldConfigManager` 保持一致的解析与灰度规则,灰度判断复用同一套逻辑,结果不会漂移。
|
|
296
|
+
|
|
257
297
|
## 发布流程
|
|
258
298
|
|
|
259
299
|
### 自动发布(推荐)
|
package/index.d.ts
CHANGED
|
@@ -4,3 +4,5 @@ export { backendConfigServiceV2, ConfigServiceV2 } from './modules/config/servic
|
|
|
4
4
|
export type { ServiceV2 as ConfigControllerTypesV2 } from './modules/config/typesV2';
|
|
5
5
|
export { ShieldConfigManager } from './modules/config/ShieldConfigManager';
|
|
6
6
|
export type { ShieldConfigManagerOptions, Logger } from './modules/config/ShieldConfigManager';
|
|
7
|
+
export { ShieldCloudFunctionConfig } from './modules/config/ShieldCloudFunctionConfig';
|
|
8
|
+
export type { ShieldCloudFunctionConfigOptions } from './modules/config/ShieldCloudFunctionConfig';
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ShieldConfigManager = exports.ConfigServiceV2 = exports.backendConfigServiceV2 = exports.backendConfigService = void 0;
|
|
3
|
+
exports.ShieldCloudFunctionConfig = exports.ShieldConfigManager = exports.ConfigServiceV2 = exports.backendConfigServiceV2 = exports.backendConfigService = void 0;
|
|
4
4
|
// V1 SDK (YAML 数据源)
|
|
5
5
|
var service_1 = require("./modules/config/service");
|
|
6
6
|
Object.defineProperty(exports, "backendConfigService", { enumerable: true, get: function () { return service_1.backendConfigService; } });
|
|
@@ -11,3 +11,6 @@ Object.defineProperty(exports, "ConfigServiceV2", { enumerable: true, get: funct
|
|
|
11
11
|
// ShieldConfigManager
|
|
12
12
|
var ShieldConfigManager_1 = require("./modules/config/ShieldConfigManager");
|
|
13
13
|
Object.defineProperty(exports, "ShieldConfigManager", { enumerable: true, get: function () { return ShieldConfigManager_1.ShieldConfigManager; } });
|
|
14
|
+
// Cloud Function SDK
|
|
15
|
+
var ShieldCloudFunctionConfig_1 = require("./modules/config/ShieldCloudFunctionConfig");
|
|
16
|
+
Object.defineProperty(exports, "ShieldCloudFunctionConfig", { enumerable: true, get: function () { return ShieldCloudFunctionConfig_1.ShieldCloudFunctionConfig; } });
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Logger } from './ShieldConfigManager';
|
|
2
|
+
export interface ShieldCloudFunctionConfigOptions {
|
|
3
|
+
serviceName: string;
|
|
4
|
+
env?: string;
|
|
5
|
+
fetchTimeoutMs?: number;
|
|
6
|
+
logger?: Logger;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 云函数专用单 Key 配置客户端
|
|
10
|
+
*
|
|
11
|
+
* 面向云函数等短生命周期场景:构造函数不发 HTTP、不 setup、不启动定时器、不维护 SDK 本地缓存。
|
|
12
|
+
* 每次 fetchKey 只按指定 key 发一次 HTTP 请求到 Shield 服务,缓存集中在服务端。
|
|
13
|
+
*/
|
|
14
|
+
export declare class ShieldCloudFunctionConfig {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(options: ShieldCloudFunctionConfigOptions);
|
|
17
|
+
/**
|
|
18
|
+
* 按单 Key 读取配置
|
|
19
|
+
* @param key 配置键
|
|
20
|
+
* @param defaultValue 配置不存在或读取异常时的默认值
|
|
21
|
+
* @returns 解析后的配置值,配置不存在或异常时返回 defaultValue / undefined
|
|
22
|
+
*/
|
|
23
|
+
fetchKey<T = any>(key: string, defaultValue?: T): Promise<T | undefined>;
|
|
24
|
+
/**
|
|
25
|
+
* 开关判断:按 ShieldConfigManager.isSwitchEnabled 兼容规则解析布尔值
|
|
26
|
+
*/
|
|
27
|
+
isSwitchEnabled(key: string, defaultValue?: boolean): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* 灰度判断:复用共享 greyMatcher,保证与 ShieldConfigManager.isHitGrey 结果一致
|
|
30
|
+
*/
|
|
31
|
+
isHitGrey(configKey: string, identifier: string, defaultValue?: boolean): Promise<boolean>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
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");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _ShieldCloudFunctionConfig_serviceName, _ShieldCloudFunctionConfig_env, _ShieldCloudFunctionConfig_fetchTimeoutMs, _ShieldCloudFunctionConfig_logger;
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ShieldCloudFunctionConfig = void 0;
|
|
16
|
+
const serviceV2_1 = require("./serviceV2");
|
|
17
|
+
const greyMatcher_1 = require("./greyMatcher");
|
|
18
|
+
const defaultLogger = {
|
|
19
|
+
info: (msg, ...args) => console.log(`[ShieldCloudFunctionConfig] ${msg}`, ...args),
|
|
20
|
+
warn: (msg, ...args) => console.warn(`[ShieldCloudFunctionConfig] ${msg}`, ...args),
|
|
21
|
+
error: (msg, ...args) => console.error(`[ShieldCloudFunctionConfig] ${msg}`, ...args),
|
|
22
|
+
};
|
|
23
|
+
const DEFAULT_FETCH_TIMEOUT_MS = 2 * 1000;
|
|
24
|
+
/**
|
|
25
|
+
* 云函数专用单 Key 配置客户端
|
|
26
|
+
*
|
|
27
|
+
* 面向云函数等短生命周期场景:构造函数不发 HTTP、不 setup、不启动定时器、不维护 SDK 本地缓存。
|
|
28
|
+
* 每次 fetchKey 只按指定 key 发一次 HTTP 请求到 Shield 服务,缓存集中在服务端。
|
|
29
|
+
*/
|
|
30
|
+
class ShieldCloudFunctionConfig {
|
|
31
|
+
constructor(options) {
|
|
32
|
+
_ShieldCloudFunctionConfig_serviceName.set(this, void 0);
|
|
33
|
+
_ShieldCloudFunctionConfig_env.set(this, void 0);
|
|
34
|
+
_ShieldCloudFunctionConfig_fetchTimeoutMs.set(this, void 0);
|
|
35
|
+
_ShieldCloudFunctionConfig_logger.set(this, void 0);
|
|
36
|
+
const serviceName = (options.serviceName || '').trim();
|
|
37
|
+
if (!serviceName) {
|
|
38
|
+
throw new Error('[ShieldCloudFunctionConfig] serviceName is required');
|
|
39
|
+
}
|
|
40
|
+
if (options.env !== undefined && options.env.trim() === '') {
|
|
41
|
+
throw new Error('[ShieldCloudFunctionConfig] env must not be empty');
|
|
42
|
+
}
|
|
43
|
+
__classPrivateFieldSet(this, _ShieldCloudFunctionConfig_serviceName, serviceName, "f");
|
|
44
|
+
__classPrivateFieldSet(this, _ShieldCloudFunctionConfig_env, (options.env && options.env.trim()) || process.env.NODE_ENV, "f");
|
|
45
|
+
__classPrivateFieldSet(this, _ShieldCloudFunctionConfig_fetchTimeoutMs, options.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS, "f");
|
|
46
|
+
__classPrivateFieldSet(this, _ShieldCloudFunctionConfig_logger, options.logger || defaultLogger, "f");
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 按单 Key 读取配置
|
|
50
|
+
* @param key 配置键
|
|
51
|
+
* @param defaultValue 配置不存在或读取异常时的默认值
|
|
52
|
+
* @returns 解析后的配置值,配置不存在或异常时返回 defaultValue / undefined
|
|
53
|
+
*/
|
|
54
|
+
async fetchKey(key, defaultValue) {
|
|
55
|
+
if (!key) {
|
|
56
|
+
throw new Error('[ShieldCloudFunctionConfig] key is required');
|
|
57
|
+
}
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
try {
|
|
60
|
+
const result = await serviceV2_1.backendConfigServiceV2.fetchCloudFunctionConfigKey({ service: __classPrivateFieldGet(this, _ShieldCloudFunctionConfig_serviceName, "f"), key, env: __classPrivateFieldGet(this, _ShieldCloudFunctionConfig_env, "f") }, { timeoutMs: __classPrivateFieldGet(this, _ShieldCloudFunctionConfig_fetchTimeoutMs, "f") });
|
|
61
|
+
if (!result) {
|
|
62
|
+
return defaultValue;
|
|
63
|
+
}
|
|
64
|
+
return result.value;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const durationMs = Date.now() - startTime;
|
|
68
|
+
const message = error instanceof Error ? error.message : JSON.stringify(error);
|
|
69
|
+
__classPrivateFieldGet(this, _ShieldCloudFunctionConfig_logger, "f").error(`[ShieldCloudFunctionConfig] fetch key error service=${__classPrivateFieldGet(this, _ShieldCloudFunctionConfig_serviceName, "f")} env=${__classPrivateFieldGet(this, _ShieldCloudFunctionConfig_env, "f") || ''} key=${key} durationMs=${durationMs} error=${message}`);
|
|
70
|
+
return defaultValue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 开关判断:按 ShieldConfigManager.isSwitchEnabled 兼容规则解析布尔值
|
|
75
|
+
*/
|
|
76
|
+
async isSwitchEnabled(key, defaultValue = false) {
|
|
77
|
+
const value = await this.fetchKey(key);
|
|
78
|
+
if (value === undefined || value === null) {
|
|
79
|
+
return defaultValue;
|
|
80
|
+
}
|
|
81
|
+
if (typeof value === 'boolean') {
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
if (typeof value === 'number') {
|
|
85
|
+
return value !== 0;
|
|
86
|
+
}
|
|
87
|
+
if (typeof value === 'string') {
|
|
88
|
+
const lower = value.trim().toLowerCase();
|
|
89
|
+
if (lower === 'true' || lower === '1' || lower === 'yes' || lower === 'on') {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (lower === 'false' || lower === '0' || lower === 'no' || lower === 'off') {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return defaultValue;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 灰度判断:复用共享 greyMatcher,保证与 ShieldConfigManager.isHitGrey 结果一致
|
|
100
|
+
*/
|
|
101
|
+
async isHitGrey(configKey, identifier, defaultValue = false) {
|
|
102
|
+
const config = await this.fetchKey(configKey);
|
|
103
|
+
return (0, greyMatcher_1.isHitGreyConfig)(config, identifier, defaultValue);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.ShieldCloudFunctionConfig = ShieldCloudFunctionConfig;
|
|
107
|
+
_ShieldCloudFunctionConfig_serviceName = new WeakMap(), _ShieldCloudFunctionConfig_env = new WeakMap(), _ShieldCloudFunctionConfig_fetchTimeoutMs = new WeakMap(), _ShieldCloudFunctionConfig_logger = new WeakMap();
|
|
@@ -46,7 +46,7 @@ export interface Logger {
|
|
|
46
46
|
error: (message: string, ...args: any[]) => void;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
49
|
+
* 流量控制配置(类型从 greyMatcher 复用,保证灰度判断逻辑统一)
|
|
50
50
|
*
|
|
51
51
|
* @example
|
|
52
52
|
* ```json
|
|
@@ -59,18 +59,7 @@ export interface Logger {
|
|
|
59
59
|
* }
|
|
60
60
|
* ```
|
|
61
61
|
*/
|
|
62
|
-
export
|
|
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
|
-
}
|
|
62
|
+
export type { TrafficControlConfig } from './greyMatcher';
|
|
74
63
|
/**
|
|
75
64
|
* 配置管理器
|
|
76
65
|
*
|
|
@@ -71,11 +71,12 @@ 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,
|
|
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;
|
|
75
75
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
76
76
|
exports.ShieldConfigManager = void 0;
|
|
77
77
|
const serviceV2_1 = require("./serviceV2");
|
|
78
78
|
const shieldClient = __importStar(require("./service"));
|
|
79
|
+
const greyMatcher_1 = require("./greyMatcher");
|
|
79
80
|
const defaultLogger = {
|
|
80
81
|
info: (msg, ...args) => console.log(`[ShieldConfigManager] ${msg}`, ...args),
|
|
81
82
|
warn: (msg, ...args) => console.warn(`[ShieldConfigManager] ${msg}`, ...args),
|
|
@@ -264,26 +265,7 @@ class ShieldConfigManager {
|
|
|
264
265
|
*/
|
|
265
266
|
isHitGrey(configKey, identifier, defaultValue = false) {
|
|
266
267
|
const config = this.getValue(configKey);
|
|
267
|
-
|
|
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;
|
|
268
|
+
return (0, greyMatcher_1.isHitGreyConfig)(config, identifier, defaultValue);
|
|
287
269
|
}
|
|
288
270
|
/**
|
|
289
271
|
* 停止定时刷新,清理资源
|
|
@@ -312,14 +294,6 @@ _ShieldConfigManager_serviceName = new WeakMap(), _ShieldConfigManager_env = new
|
|
|
312
294
|
__classPrivateFieldGet(this, _ShieldConfigManager_instances, "m", _ShieldConfigManager_startRefreshTimer).call(this);
|
|
313
295
|
__classPrivateFieldSet(this, _ShieldConfigManager_isSetup, true, "f");
|
|
314
296
|
__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);
|
|
323
297
|
}, _ShieldConfigManager_startRefreshTimer = function _ShieldConfigManager_startRefreshTimer() {
|
|
324
298
|
__classPrivateFieldSet(this, _ShieldConfigManager_refreshTimer, setInterval(() => {
|
|
325
299
|
this.refresh().catch((error) => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 流量控制配置
|
|
3
|
+
* Shared by ShieldConfigManager and ShieldCloudFunctionConfig.
|
|
4
|
+
*/
|
|
5
|
+
export interface TrafficControlConfig {
|
|
6
|
+
/** 总开关 */
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
/** 总桶数 */
|
|
9
|
+
totalBuckets: number;
|
|
10
|
+
/** 生效桶数(bucket 0 到 effectiveBuckets-1 生效) */
|
|
11
|
+
effectiveBuckets: number;
|
|
12
|
+
/** 白名单(命中后视为生效) */
|
|
13
|
+
whitelist?: string[];
|
|
14
|
+
/** 黑名单(命中后视为不生效) */
|
|
15
|
+
blacklist?: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function hashString(str: string): number;
|
|
18
|
+
export declare function isHitGreyConfig(config: TrafficControlConfig | null | undefined, identifier: string, defaultValue?: boolean): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hashString = hashString;
|
|
4
|
+
exports.isHitGreyConfig = isHitGreyConfig;
|
|
5
|
+
function hashString(str) {
|
|
6
|
+
let hash = 0;
|
|
7
|
+
for (let i = 0; i < str.length; i++) {
|
|
8
|
+
const char = str.charCodeAt(i);
|
|
9
|
+
hash = (hash << 5) - hash + char;
|
|
10
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
11
|
+
}
|
|
12
|
+
return Math.abs(hash);
|
|
13
|
+
}
|
|
14
|
+
function isHitGreyConfig(config, identifier, defaultValue = false) {
|
|
15
|
+
if (!config) {
|
|
16
|
+
return defaultValue;
|
|
17
|
+
}
|
|
18
|
+
if (!config.enabled) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (config.blacklist && config.blacklist.includes(identifier)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (config.whitelist && config.whitelist.includes(identifier)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
const totalBuckets = config.totalBuckets || 100;
|
|
28
|
+
const effectiveBuckets = config.effectiveBuckets || 0;
|
|
29
|
+
const hash = hashString(identifier);
|
|
30
|
+
return hash % totalBuckets < effectiveBuckets;
|
|
31
|
+
}
|
|
@@ -18,6 +18,15 @@ export declare class ConfigServiceV2 extends BaseService implements ServiceV2.Co
|
|
|
18
18
|
* @returns 配置数据映射
|
|
19
19
|
*/
|
|
20
20
|
fetchConfigs(req: ServiceV2.Request.FetchConfigs): Promise<ServiceV2.Response.FetchConfigsResponse>;
|
|
21
|
+
/**
|
|
22
|
+
* 云函数单 Key 配置读取
|
|
23
|
+
* @param req 请求参数
|
|
24
|
+
* @param options 请求选项
|
|
25
|
+
* @returns 解析后的配置数据,如果配置不存在返回 null
|
|
26
|
+
*/
|
|
27
|
+
fetchCloudFunctionConfigKey(req: ServiceV2.Request.FetchCloudFunctionConfigKey, options?: {
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
}): Promise<ServiceV2.Response.FetchCloudFunctionConfigKeyResponse | null>;
|
|
21
30
|
/**
|
|
22
31
|
* 获取所有服务列表
|
|
23
32
|
* @param env 环境名称(可选,默认使用当前环境)
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.backendConfigServiceV2 = exports.ConfigServiceV2 = void 0;
|
|
7
7
|
const http_1 = require("../../utils/http");
|
|
8
|
+
const cloudFunctionHttp_1 = require("../../utils/cloudFunctionHttp");
|
|
8
9
|
const BaseService_1 = __importDefault(require("../BaseService"));
|
|
9
10
|
/**
|
|
10
11
|
* V2 配置服务
|
|
@@ -33,6 +34,15 @@ class ConfigServiceV2 extends BaseService_1.default {
|
|
|
33
34
|
fetchConfigs(req) {
|
|
34
35
|
return (0, http_1.callApi)(this.getApiUrlV2('/fetch-batch'), req).catch(() => ({}));
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* 云函数单 Key 配置读取
|
|
39
|
+
* @param req 请求参数
|
|
40
|
+
* @param options 请求选项
|
|
41
|
+
* @returns 解析后的配置数据,如果配置不存在返回 null
|
|
42
|
+
*/
|
|
43
|
+
fetchCloudFunctionConfigKey(req, options) {
|
|
44
|
+
return (0, cloudFunctionHttp_1.quietPost)(this.getApiUrlV2('/cloud-function/fetch-key'), req, { timeoutMs: options?.timeoutMs ?? 2000 });
|
|
45
|
+
}
|
|
36
46
|
/**
|
|
37
47
|
* 获取所有服务列表
|
|
38
48
|
* @param env 环境名称(可选,默认使用当前环境)
|
|
@@ -47,6 +47,14 @@ export declare namespace ServiceV2 {
|
|
|
47
47
|
serviceName: string;
|
|
48
48
|
env?: string;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* 云函数单 Key 配置请求参数
|
|
52
|
+
*/
|
|
53
|
+
interface FetchCloudFunctionConfigKey {
|
|
54
|
+
service: string;
|
|
55
|
+
key: string;
|
|
56
|
+
env?: string;
|
|
57
|
+
}
|
|
50
58
|
}
|
|
51
59
|
namespace Response {
|
|
52
60
|
/**
|
|
@@ -72,6 +80,20 @@ export declare namespace ServiceV2 {
|
|
|
72
80
|
* 获取服务配置键列表响应
|
|
73
81
|
*/
|
|
74
82
|
type GetServiceKeysResponse = string[];
|
|
83
|
+
/**
|
|
84
|
+
* 云函数单 Key 配置缓存命中来源
|
|
85
|
+
*/
|
|
86
|
+
type CloudFunctionConfigKeySource = 'local' | 'redis' | 'mysql';
|
|
87
|
+
/**
|
|
88
|
+
* 云函数单 Key 配置响应
|
|
89
|
+
*/
|
|
90
|
+
interface FetchCloudFunctionConfigKeyResponse {
|
|
91
|
+
key: string;
|
|
92
|
+
value: any;
|
|
93
|
+
value_type: 'string' | 'number' | 'boolean' | 'object' | 'array' | string;
|
|
94
|
+
updated_at?: string;
|
|
95
|
+
source: CloudFunctionConfigKeySource;
|
|
96
|
+
}
|
|
75
97
|
}
|
|
76
98
|
/**
|
|
77
99
|
* V2 配置控制器接口
|
|
@@ -102,5 +124,13 @@ export declare namespace ServiceV2 {
|
|
|
102
124
|
* @returns 配置键列表
|
|
103
125
|
*/
|
|
104
126
|
getServiceKeys(serviceName: string, env?: string): Promise<Response.GetServiceKeysResponse>;
|
|
127
|
+
/**
|
|
128
|
+
* 云函数单 Key 配置读取
|
|
129
|
+
* @param req 请求参数
|
|
130
|
+
* @returns 解析后的配置数据,配置不存在时返回 null
|
|
131
|
+
*/
|
|
132
|
+
fetchCloudFunctionConfigKey(req: Request.FetchCloudFunctionConfigKey, options?: {
|
|
133
|
+
timeoutMs?: number;
|
|
134
|
+
}): Promise<Response.FetchCloudFunctionConfigKeyResponse | null>;
|
|
105
135
|
}
|
|
106
136
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.quietPost = quietPost;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const uuid_1 = require("uuid");
|
|
9
|
+
// 云函数单 Key 接口专用 axios 实例:不挂载 axios-retry,避免对每次单 key 读取做重试放大请求。
|
|
10
|
+
const cloudFunctionAxios = axios_1.default.create();
|
|
11
|
+
async function quietPost(url, request, options) {
|
|
12
|
+
const requestId = (0, uuid_1.v4)();
|
|
13
|
+
const response = await cloudFunctionAxios.post(url, request || {}, {
|
|
14
|
+
timeout: options.timeoutMs,
|
|
15
|
+
headers: { 'x-request-id': requestId, 'content-type': 'application/json' },
|
|
16
|
+
});
|
|
17
|
+
const responseData = response.data;
|
|
18
|
+
return responseData.data;
|
|
19
|
+
}
|