@dangao/bun-server 1.2.0 → 1.4.0
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 +15 -0
- package/dist/config/config-module.d.ts +17 -0
- package/dist/config/config-module.d.ts.map +1 -1
- package/dist/config/service.d.ts +18 -1
- package/dist/config/service.d.ts.map +1 -1
- package/dist/config/types.d.ts +25 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/controller/controller.d.ts +5 -0
- package/dist/controller/controller.d.ts.map +1 -1
- package/dist/controller/decorators.d.ts +30 -1
- package/dist/controller/decorators.d.ts.map +1 -1
- package/dist/controller/index.d.ts +2 -2
- package/dist/controller/index.d.ts.map +1 -1
- package/dist/controller/param-binder.d.ts +12 -0
- package/dist/controller/param-binder.d.ts.map +1 -1
- package/dist/core/application.d.ts +15 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/context.d.ts +1 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/server.d.ts +8 -0
- package/dist/core/server.d.ts.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6487 -4178
- package/dist/microservice/config-center/config-center-module.d.ts +43 -0
- package/dist/microservice/config-center/config-center-module.d.ts.map +1 -0
- package/dist/microservice/config-center/decorators.d.ts +58 -0
- package/dist/microservice/config-center/decorators.d.ts.map +1 -0
- package/dist/microservice/config-center/index.d.ts +9 -0
- package/dist/microservice/config-center/index.d.ts.map +1 -0
- package/dist/microservice/config-center/nacos-config-center.d.ts +37 -0
- package/dist/microservice/config-center/nacos-config-center.d.ts.map +1 -0
- package/dist/microservice/config-center/nacos-decorators.d.ts +24 -0
- package/dist/microservice/config-center/nacos-decorators.d.ts.map +1 -0
- package/dist/microservice/config-center/types.d.ts +63 -0
- package/dist/microservice/config-center/types.d.ts.map +1 -0
- package/dist/microservice/governance/circuit-breaker.d.ts +54 -0
- package/dist/microservice/governance/circuit-breaker.d.ts.map +1 -0
- package/dist/microservice/governance/decorators.d.ts +51 -0
- package/dist/microservice/governance/decorators.d.ts.map +1 -0
- package/dist/microservice/governance/index.d.ts +9 -0
- package/dist/microservice/governance/index.d.ts.map +1 -0
- package/dist/microservice/governance/rate-limiter.d.ts +26 -0
- package/dist/microservice/governance/rate-limiter.d.ts.map +1 -0
- package/dist/microservice/governance/redis-rate-limiter.d.ts +76 -0
- package/dist/microservice/governance/redis-rate-limiter.d.ts.map +1 -0
- package/dist/microservice/governance/retry-strategy.d.ts +21 -0
- package/dist/microservice/governance/retry-strategy.d.ts.map +1 -0
- package/dist/microservice/governance/types.d.ts +212 -0
- package/dist/microservice/governance/types.d.ts.map +1 -0
- package/dist/microservice/index.d.ts +10 -0
- package/dist/microservice/index.d.ts.map +1 -0
- package/dist/microservice/monitoring/index.d.ts +4 -0
- package/dist/microservice/monitoring/index.d.ts.map +1 -0
- package/dist/microservice/monitoring/metrics-collector.d.ts +54 -0
- package/dist/microservice/monitoring/metrics-collector.d.ts.map +1 -0
- package/dist/microservice/monitoring/metrics-integration.d.ts +24 -0
- package/dist/microservice/monitoring/metrics-integration.d.ts.map +1 -0
- package/dist/microservice/monitoring/types.d.ts +99 -0
- package/dist/microservice/monitoring/types.d.ts.map +1 -0
- package/dist/microservice/service-client/call-decorators.d.ts +52 -0
- package/dist/microservice/service-client/call-decorators.d.ts.map +1 -0
- package/dist/microservice/service-client/decorators.d.ts +35 -0
- package/dist/microservice/service-client/decorators.d.ts.map +1 -0
- package/dist/microservice/service-client/index.d.ts +7 -0
- package/dist/microservice/service-client/index.d.ts.map +1 -0
- package/dist/microservice/service-client/interceptors.d.ts +96 -0
- package/dist/microservice/service-client/interceptors.d.ts.map +1 -0
- package/dist/microservice/service-client/load-balancer.d.ts +59 -0
- package/dist/microservice/service-client/load-balancer.d.ts.map +1 -0
- package/dist/microservice/service-client/service-client.d.ts +74 -0
- package/dist/microservice/service-client/service-client.d.ts.map +1 -0
- package/dist/microservice/service-client/types.d.ts +155 -0
- package/dist/microservice/service-client/types.d.ts.map +1 -0
- package/dist/microservice/service-registry/decorators.d.ts +84 -0
- package/dist/microservice/service-registry/decorators.d.ts.map +1 -0
- package/dist/microservice/service-registry/discovery-decorators.d.ts +58 -0
- package/dist/microservice/service-registry/discovery-decorators.d.ts.map +1 -0
- package/dist/microservice/service-registry/health-integration.d.ts +32 -0
- package/dist/microservice/service-registry/health-integration.d.ts.map +1 -0
- package/dist/microservice/service-registry/index.d.ts +10 -0
- package/dist/microservice/service-registry/index.d.ts.map +1 -0
- package/dist/microservice/service-registry/nacos-service-registry.d.ts +68 -0
- package/dist/microservice/service-registry/nacos-service-registry.d.ts.map +1 -0
- package/dist/microservice/service-registry/service-registry-module.d.ts +48 -0
- package/dist/microservice/service-registry/service-registry-module.d.ts.map +1 -0
- package/dist/microservice/service-registry/types.d.ts +121 -0
- package/dist/microservice/service-registry/types.d.ts.map +1 -0
- package/dist/microservice/tracing/collectors.d.ts +27 -0
- package/dist/microservice/tracing/collectors.d.ts.map +1 -0
- package/dist/microservice/tracing/index.d.ts +4 -0
- package/dist/microservice/tracing/index.d.ts.map +1 -0
- package/dist/microservice/tracing/tracer.d.ts +59 -0
- package/dist/microservice/tracing/tracer.d.ts.map +1 -0
- package/dist/microservice/tracing/types.d.ts +179 -0
- package/dist/microservice/tracing/types.d.ts.map +1 -0
- package/dist/request/request.d.ts +1 -0
- package/dist/request/request.d.ts.map +1 -1
- package/docs/microservice-config-center.md +258 -0
- package/docs/microservice-nacos.md +346 -0
- package/docs/microservice-service-registry.md +306 -0
- package/docs/microservice.md +680 -0
- package/docs/troubleshooting.md +41 -0
- package/docs/zh/troubleshooting.md +41 -0
- package/package.json +5 -4
- package/src/config/config-module.ts +210 -0
- package/src/config/service.ts +52 -1
- package/src/config/types.ts +31 -0
- package/src/controller/controller.ts +8 -0
- package/src/controller/decorators.ts +55 -0
- package/src/controller/index.ts +16 -2
- package/src/controller/param-binder.ts +87 -1
- package/src/core/application.ts +100 -2
- package/src/core/context.ts +1 -0
- package/src/core/server.ts +14 -0
- package/src/index.ts +98 -2
- package/src/microservice/config-center/config-center-module.ts +98 -0
- package/src/microservice/config-center/decorators.ts +159 -0
- package/src/microservice/config-center/index.ts +13 -0
- package/src/microservice/config-center/nacos-config-center.ts +126 -0
- package/src/microservice/config-center/nacos-decorators.ts +34 -0
- package/src/microservice/config-center/types.ts +80 -0
- package/src/microservice/governance/circuit-breaker.ts +229 -0
- package/src/microservice/governance/decorators.ts +113 -0
- package/src/microservice/governance/index.ts +18 -0
- package/src/microservice/governance/rate-limiter.ts +72 -0
- package/src/microservice/governance/redis-rate-limiter.ts +154 -0
- package/src/microservice/governance/retry-strategy.ts +74 -0
- package/src/microservice/governance/types.ts +247 -0
- package/src/microservice/index.ts +12 -0
- package/src/microservice/monitoring/index.ts +8 -0
- package/src/microservice/monitoring/metrics-collector.ts +223 -0
- package/src/microservice/monitoring/metrics-integration.ts +154 -0
- package/src/microservice/monitoring/types.ts +118 -0
- package/src/microservice/service-client/call-decorators.ts +107 -0
- package/src/microservice/service-client/decorators.ts +87 -0
- package/src/microservice/service-client/index.ts +37 -0
- package/src/microservice/service-client/interceptors.ts +182 -0
- package/src/microservice/service-client/load-balancer.ts +205 -0
- package/src/microservice/service-client/service-client.ts +488 -0
- package/src/microservice/service-client/types.ts +186 -0
- package/src/microservice/service-registry/decorators.ts +238 -0
- package/src/microservice/service-registry/discovery-decorators.ts +156 -0
- package/src/microservice/service-registry/health-integration.ts +146 -0
- package/src/microservice/service-registry/index.ts +20 -0
- package/src/microservice/service-registry/nacos-service-registry.ts +259 -0
- package/src/microservice/service-registry/service-registry-module.ts +105 -0
- package/src/microservice/service-registry/types.ts +149 -0
- package/src/microservice/tracing/collectors.ts +50 -0
- package/src/microservice/tracing/index.ts +15 -0
- package/src/microservice/tracing/tracer.ts +293 -0
- package/src/microservice/tracing/types.ts +213 -0
- package/src/request/request.ts +1 -0
- package/tests/config/set-value-by-path.test.ts +53 -0
- package/tests/controller/param-map.test.ts +237 -0
- package/tests/microservice/config-center.test.ts +77 -0
- package/tests/microservice/governance.test.ts +157 -0
- package/tests/microservice/monitoring.test.ts +75 -0
- package/tests/microservice/service-client.test.ts +136 -0
- package/tests/microservice/service-registry.test.ts +80 -0
- package/tests/microservice/tracing.test.ts +143 -0
- package/tests/utils/test-port.ts +29 -19
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { ConfigCenterValue } from './decorators';
|
|
3
|
+
import type { Constructor } from '../../core/types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* NacosValue 装饰器
|
|
7
|
+
* Nacos 特定的配置值注入装饰器
|
|
8
|
+
* 这是 @ConfigCenterValue 的便捷别名,专门用于 Nacos
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* @Injectable()
|
|
13
|
+
* class MyService {
|
|
14
|
+
* @NacosValue('my-config', 'DEFAULT_GROUP')
|
|
15
|
+
* public configValue: string = '';
|
|
16
|
+
*
|
|
17
|
+
* @NacosValue('app-name', 'DEFAULT_GROUP', { defaultValue: 'MyApp' })
|
|
18
|
+
* public appName: string = '';
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function NacosValue(
|
|
23
|
+
dataId: string,
|
|
24
|
+
groupName: string = 'DEFAULT_GROUP',
|
|
25
|
+
options?: {
|
|
26
|
+
namespaceId?: string;
|
|
27
|
+
defaultValue?: string;
|
|
28
|
+
watch?: boolean;
|
|
29
|
+
},
|
|
30
|
+
): PropertyDecorator {
|
|
31
|
+
// 直接使用 ConfigCenterValue,因为 NacosConfigCenter 实现了 ConfigCenter 接口
|
|
32
|
+
return ConfigCenterValue(dataId, groupName, options);
|
|
33
|
+
}
|
|
34
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置中心抽象接口
|
|
3
|
+
* 定义配置中心的核心能力,不依赖具体实现
|
|
4
|
+
*/
|
|
5
|
+
export interface ConfigCenter {
|
|
6
|
+
/**
|
|
7
|
+
* 获取配置
|
|
8
|
+
* @param dataId - 配置 ID
|
|
9
|
+
* @param groupName - 配置分组
|
|
10
|
+
* @param namespaceId - 命名空间
|
|
11
|
+
* @returns 配置内容
|
|
12
|
+
*/
|
|
13
|
+
getConfig(
|
|
14
|
+
dataId: string,
|
|
15
|
+
groupName: string,
|
|
16
|
+
namespaceId?: string,
|
|
17
|
+
): Promise<ConfigResult>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 监听配置变更
|
|
21
|
+
* @param dataId - 配置 ID
|
|
22
|
+
* @param groupName - 配置分组
|
|
23
|
+
* @param listener - 变更监听器
|
|
24
|
+
* @param namespaceId - 命名空间(可选)
|
|
25
|
+
* @returns 取消监听的函数
|
|
26
|
+
*/
|
|
27
|
+
watchConfig(
|
|
28
|
+
dataId: string,
|
|
29
|
+
groupName: string,
|
|
30
|
+
listener: ConfigChangeListener,
|
|
31
|
+
namespaceId?: string,
|
|
32
|
+
): () => void;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 关闭配置中心连接
|
|
36
|
+
*/
|
|
37
|
+
close(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 配置查询结果
|
|
42
|
+
*/
|
|
43
|
+
export interface ConfigResult {
|
|
44
|
+
/**
|
|
45
|
+
* 配置内容
|
|
46
|
+
*/
|
|
47
|
+
content: string;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 配置 MD5 值(用于判断配置是否变更)
|
|
51
|
+
*/
|
|
52
|
+
md5: string;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 最后修改时间(时间戳)
|
|
56
|
+
*/
|
|
57
|
+
lastModified: number;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 内容类型
|
|
61
|
+
*/
|
|
62
|
+
contentType: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 配置变更监听器
|
|
67
|
+
*/
|
|
68
|
+
export interface ConfigChangeListener {
|
|
69
|
+
/**
|
|
70
|
+
* 配置变更回调
|
|
71
|
+
* @param result - 新的配置结果
|
|
72
|
+
*/
|
|
73
|
+
(result: ConfigResult): void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* ConfigCenter Token(用于依赖注入)
|
|
78
|
+
*/
|
|
79
|
+
export const CONFIG_CENTER_TOKEN = Symbol('CONFIG_CENTER_TOKEN');
|
|
80
|
+
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { CircuitBreakerState, type CircuitBreakerOptions, type CircuitBreakerStats } from './types';
|
|
2
|
+
|
|
3
|
+
export { CircuitBreakerState, type CircuitBreakerOptions, type CircuitBreakerStats } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 请求记录
|
|
7
|
+
*/
|
|
8
|
+
interface RequestRecord {
|
|
9
|
+
timestamp: number;
|
|
10
|
+
success: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 熔断器实现
|
|
15
|
+
*/
|
|
16
|
+
export class CircuitBreaker {
|
|
17
|
+
private state: CircuitBreakerState = CircuitBreakerState.CLOSED;
|
|
18
|
+
private readonly options: Required<CircuitBreakerOptions>;
|
|
19
|
+
private readonly records: RequestRecord[] = [];
|
|
20
|
+
private openTime?: number;
|
|
21
|
+
private halfOpenTestCount: number = 0;
|
|
22
|
+
|
|
23
|
+
public constructor(options: CircuitBreakerOptions = {}) {
|
|
24
|
+
this.options = {
|
|
25
|
+
failureThreshold: options.failureThreshold ?? 0.5,
|
|
26
|
+
timeWindow: options.timeWindow ?? 60000,
|
|
27
|
+
minimumRequests: options.minimumRequests ?? 10,
|
|
28
|
+
openDuration: options.openDuration ?? 60000,
|
|
29
|
+
halfOpenRequests: options.halfOpenRequests ?? 3,
|
|
30
|
+
timeout: options.timeout ?? 5000,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 执行请求(带熔断保护)
|
|
36
|
+
*/
|
|
37
|
+
public async execute<T>(
|
|
38
|
+
fn: () => Promise<T>,
|
|
39
|
+
fallback?: () => Promise<T> | T,
|
|
40
|
+
): Promise<T> {
|
|
41
|
+
// 检查熔断器状态
|
|
42
|
+
this.updateState();
|
|
43
|
+
|
|
44
|
+
if (this.state === CircuitBreakerState.OPEN) {
|
|
45
|
+
// 熔断器开启,执行降级逻辑
|
|
46
|
+
if (fallback) {
|
|
47
|
+
return Promise.resolve(fallback());
|
|
48
|
+
}
|
|
49
|
+
throw new Error('Circuit breaker is OPEN');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (this.state === CircuitBreakerState.HALF_OPEN) {
|
|
53
|
+
// 半开状态,限制请求数
|
|
54
|
+
if (this.halfOpenTestCount >= this.options.halfOpenRequests) {
|
|
55
|
+
if (fallback) {
|
|
56
|
+
return Promise.resolve(fallback());
|
|
57
|
+
}
|
|
58
|
+
throw new Error('Circuit breaker is HALF_OPEN, test requests limit reached');
|
|
59
|
+
}
|
|
60
|
+
this.halfOpenTestCount++;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const startTime = Date.now();
|
|
64
|
+
let success = false;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// 执行请求(带超时)
|
|
68
|
+
const result = await Promise.race([
|
|
69
|
+
fn(),
|
|
70
|
+
this.createTimeoutPromise(),
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
success = true;
|
|
74
|
+
this.recordRequest(success);
|
|
75
|
+
return result as T;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
success = false;
|
|
78
|
+
this.recordRequest(success);
|
|
79
|
+
throw error;
|
|
80
|
+
} finally {
|
|
81
|
+
// 更新状态
|
|
82
|
+
this.updateState();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 记录请求
|
|
88
|
+
*/
|
|
89
|
+
private recordRequest(success: boolean): void {
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
this.records.push({
|
|
92
|
+
timestamp: now,
|
|
93
|
+
success,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// 清理过期记录
|
|
97
|
+
this.cleanupRecords(now);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 清理过期记录
|
|
102
|
+
*/
|
|
103
|
+
private cleanupRecords(now: number): void {
|
|
104
|
+
const cutoff = now - this.options.timeWindow;
|
|
105
|
+
while (this.records.length > 0 && this.records[0]!.timestamp < cutoff) {
|
|
106
|
+
this.records.shift();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 更新熔断器状态
|
|
112
|
+
*/
|
|
113
|
+
private updateState(): void {
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
|
|
116
|
+
switch (this.state) {
|
|
117
|
+
case CircuitBreakerState.CLOSED:
|
|
118
|
+
// 检查是否需要开启熔断
|
|
119
|
+
if (this.shouldOpen()) {
|
|
120
|
+
this.state = CircuitBreakerState.OPEN;
|
|
121
|
+
this.openTime = now;
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case CircuitBreakerState.OPEN:
|
|
126
|
+
// 检查是否可以进入半开状态
|
|
127
|
+
if (this.openTime && now - this.openTime >= this.options.openDuration) {
|
|
128
|
+
this.state = CircuitBreakerState.HALF_OPEN;
|
|
129
|
+
this.halfOpenTestCount = 0;
|
|
130
|
+
this.openTime = undefined;
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
|
|
134
|
+
case CircuitBreakerState.HALF_OPEN:
|
|
135
|
+
// 半开状态下,根据测试结果决定状态
|
|
136
|
+
if (this.shouldClose()) {
|
|
137
|
+
this.state = CircuitBreakerState.CLOSED;
|
|
138
|
+
this.halfOpenTestCount = 0;
|
|
139
|
+
} else if (this.shouldOpen()) {
|
|
140
|
+
this.state = CircuitBreakerState.OPEN;
|
|
141
|
+
this.openTime = now;
|
|
142
|
+
this.halfOpenTestCount = 0;
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 判断是否应该开启熔断
|
|
150
|
+
*/
|
|
151
|
+
private shouldOpen(): boolean {
|
|
152
|
+
if (this.records.length < this.options.minimumRequests) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const failureCount = this.records.filter((r) => !r.success).length;
|
|
157
|
+
const failureRate = failureCount / this.records.length;
|
|
158
|
+
|
|
159
|
+
return failureRate >= this.options.failureThreshold;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 判断是否应该关闭熔断(半开状态下)
|
|
164
|
+
*/
|
|
165
|
+
private shouldClose(): boolean {
|
|
166
|
+
if (this.halfOpenTestCount === 0) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 检查最近的测试请求是否都成功
|
|
171
|
+
const recentRecords = this.records.slice(-this.halfOpenTestCount);
|
|
172
|
+
return recentRecords.every((r) => r.success);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 创建超时 Promise
|
|
177
|
+
*/
|
|
178
|
+
private createTimeoutPromise(): Promise<never> {
|
|
179
|
+
return new Promise((_, reject) => {
|
|
180
|
+
setTimeout(() => {
|
|
181
|
+
reject(new Error(`Request timeout after ${this.options.timeout}ms`));
|
|
182
|
+
}, this.options.timeout);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 获取当前状态
|
|
188
|
+
*/
|
|
189
|
+
public getState(): CircuitBreakerState {
|
|
190
|
+
this.updateState();
|
|
191
|
+
return this.state;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 获取统计信息
|
|
196
|
+
*/
|
|
197
|
+
public getStats(): CircuitBreakerStats {
|
|
198
|
+
const now = Date.now();
|
|
199
|
+
this.cleanupRecords(now);
|
|
200
|
+
|
|
201
|
+
const totalRequests = this.records.length;
|
|
202
|
+
const failureRequests = this.records.filter((r) => !r.success).length;
|
|
203
|
+
const successRequests = totalRequests - failureRequests;
|
|
204
|
+
const failureRate = totalRequests > 0 ? failureRequests / totalRequests : 0;
|
|
205
|
+
|
|
206
|
+
const lastFailure = this.records.findLast((r) => !r.success);
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
state: this.state,
|
|
210
|
+
totalRequests,
|
|
211
|
+
successRequests,
|
|
212
|
+
failureRequests,
|
|
213
|
+
failureRate,
|
|
214
|
+
lastFailureTime: lastFailure?.timestamp,
|
|
215
|
+
openTime: this.openTime,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 重置熔断器
|
|
221
|
+
*/
|
|
222
|
+
public reset(): void {
|
|
223
|
+
this.state = CircuitBreakerState.CLOSED;
|
|
224
|
+
this.records.length = 0;
|
|
225
|
+
this.openTime = undefined;
|
|
226
|
+
this.halfOpenTestCount = 0;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import type { CircuitBreakerOptions } from './types';
|
|
3
|
+
import type { Constructor } from '../../core/types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 熔断器装饰器元数据键
|
|
7
|
+
*/
|
|
8
|
+
const CIRCUIT_BREAKER_METADATA_KEY = Symbol('circuit-breaker:metadata');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 熔断器元数据
|
|
12
|
+
*/
|
|
13
|
+
export interface CircuitBreakerMetadata {
|
|
14
|
+
/**
|
|
15
|
+
* 熔断器选项
|
|
16
|
+
*/
|
|
17
|
+
options?: CircuitBreakerOptions;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 降级处理函数名(可选)
|
|
21
|
+
*/
|
|
22
|
+
fallbackMethod?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* CircuitBreaker 装饰器
|
|
27
|
+
* 用于在方法上自动应用熔断保护
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* @Injectable()
|
|
32
|
+
* class MyService {
|
|
33
|
+
* @CircuitBreaker({
|
|
34
|
+
* failureThreshold: 5,
|
|
35
|
+
* resetTimeout: 60000,
|
|
36
|
+
* })
|
|
37
|
+
* public async callExternalService() {
|
|
38
|
+
* // 自动应用熔断保护
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* @CircuitBreaker({
|
|
42
|
+
* failureThreshold: 5,
|
|
43
|
+
* }, 'fallbackMethod')
|
|
44
|
+
* public async callWithFallback() {
|
|
45
|
+
* // 自动应用熔断保护,失败时调用 fallbackMethod
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* private async fallbackMethod() {
|
|
49
|
+
* return { message: 'Fallback response' };
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function CircuitBreaker(
|
|
55
|
+
options?: CircuitBreakerOptions,
|
|
56
|
+
fallbackMethod?: string,
|
|
57
|
+
): MethodDecorator {
|
|
58
|
+
return function (
|
|
59
|
+
target: object,
|
|
60
|
+
propertyKey: string | symbol,
|
|
61
|
+
descriptor: PropertyDescriptor,
|
|
62
|
+
) {
|
|
63
|
+
const metadata: CircuitBreakerMetadata = {
|
|
64
|
+
options,
|
|
65
|
+
fallbackMethod,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// 保存元数据
|
|
69
|
+
const existingMetadata: Map<string | symbol, CircuitBreakerMetadata> =
|
|
70
|
+
Reflect.getMetadata(CIRCUIT_BREAKER_METADATA_KEY, target.constructor) ||
|
|
71
|
+
new Map();
|
|
72
|
+
existingMetadata.set(propertyKey, metadata);
|
|
73
|
+
Reflect.defineMetadata(
|
|
74
|
+
CIRCUIT_BREAKER_METADATA_KEY,
|
|
75
|
+
existingMetadata,
|
|
76
|
+
target.constructor,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// 保存原始方法
|
|
80
|
+
const originalMethod = descriptor.value;
|
|
81
|
+
|
|
82
|
+
// 包装方法,应用熔断保护
|
|
83
|
+
descriptor.value = async function (...args: any[]) {
|
|
84
|
+
// 动态导入 CircuitBreaker(避免循环依赖)
|
|
85
|
+
const { CircuitBreaker: CircuitBreakerImpl } = await import(
|
|
86
|
+
'./circuit-breaker'
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const circuitBreaker = new CircuitBreakerImpl(metadata.options);
|
|
90
|
+
|
|
91
|
+
const fallback = metadata.fallbackMethod
|
|
92
|
+
? (this as any)[metadata.fallbackMethod]?.bind(this)
|
|
93
|
+
: undefined;
|
|
94
|
+
|
|
95
|
+
return circuitBreaker.execute(
|
|
96
|
+
() => originalMethod.apply(this, args),
|
|
97
|
+
fallback,
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 获取方法的熔断器元数据
|
|
105
|
+
*/
|
|
106
|
+
export function getCircuitBreakerMetadata(
|
|
107
|
+
target: Constructor<unknown>,
|
|
108
|
+
): Map<string | symbol, CircuitBreakerMetadata> {
|
|
109
|
+
return (
|
|
110
|
+
Reflect.getMetadata(CIRCUIT_BREAKER_METADATA_KEY, target) || new Map()
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { CircuitBreaker } from './circuit-breaker';
|
|
2
|
+
export { CircuitBreakerState } from './types';
|
|
3
|
+
export { RateLimiter } from './rate-limiter';
|
|
4
|
+
export { RedisRateLimiter } from './redis-rate-limiter';
|
|
5
|
+
export { RetryStrategyImpl } from './retry-strategy';
|
|
6
|
+
export {
|
|
7
|
+
CircuitBreaker as CircuitBreakerDecorator,
|
|
8
|
+
getCircuitBreakerMetadata,
|
|
9
|
+
} from './decorators';
|
|
10
|
+
export type { CircuitBreakerMetadata } from './decorators';
|
|
11
|
+
export type {
|
|
12
|
+
CircuitBreakerOptions,
|
|
13
|
+
CircuitBreakerStats,
|
|
14
|
+
RateLimiterOptions,
|
|
15
|
+
RetryStrategy,
|
|
16
|
+
RedisRateLimiterOptions,
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { RateLimiterOptions } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 限流器实现(内存版)
|
|
5
|
+
*/
|
|
6
|
+
export class RateLimiter {
|
|
7
|
+
private readonly options: Required<Pick<RateLimiterOptions, 'requestsPerSecond' | 'timeWindow'>>;
|
|
8
|
+
private readonly requests: Map<string, number[]> = new Map();
|
|
9
|
+
|
|
10
|
+
public constructor(options: RateLimiterOptions = {}) {
|
|
11
|
+
this.options = {
|
|
12
|
+
requestsPerSecond: options.requestsPerSecond ?? 100,
|
|
13
|
+
timeWindow: options.timeWindow ?? 1000,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 检查是否允许请求
|
|
19
|
+
* @param key - 限流键(如服务名、IP 等)
|
|
20
|
+
* @returns 是否允许请求
|
|
21
|
+
*/
|
|
22
|
+
public async allow(key: string): Promise<boolean> {
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
const cutoff = now - this.options.timeWindow;
|
|
25
|
+
|
|
26
|
+
// 获取该键的请求记录
|
|
27
|
+
let timestamps = this.requests.get(key) ?? [];
|
|
28
|
+
|
|
29
|
+
// 清理过期记录
|
|
30
|
+
timestamps = timestamps.filter((ts) => ts > cutoff);
|
|
31
|
+
|
|
32
|
+
// 检查是否超过限制
|
|
33
|
+
if (timestamps.length >= this.options.requestsPerSecond) {
|
|
34
|
+
// 更新记录
|
|
35
|
+
this.requests.set(key, timestamps);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 添加新请求记录
|
|
40
|
+
timestamps.push(now);
|
|
41
|
+
this.requests.set(key, timestamps);
|
|
42
|
+
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 获取剩余请求数
|
|
48
|
+
* @param key - 限流键
|
|
49
|
+
* @returns 剩余请求数
|
|
50
|
+
*/
|
|
51
|
+
public getRemaining(key: string): number {
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
const cutoff = now - this.options.timeWindow;
|
|
54
|
+
|
|
55
|
+
const timestamps = this.requests.get(key) ?? [];
|
|
56
|
+
const validTimestamps = timestamps.filter((ts) => ts > cutoff);
|
|
57
|
+
|
|
58
|
+
return Math.max(0, this.options.requestsPerSecond - validTimestamps.length);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 重置限流器
|
|
63
|
+
*/
|
|
64
|
+
public reset(key?: string): void {
|
|
65
|
+
if (key) {
|
|
66
|
+
this.requests.delete(key);
|
|
67
|
+
} else {
|
|
68
|
+
this.requests.clear();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { RateLimiterOptions } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Redis 限流器选项
|
|
5
|
+
*/
|
|
6
|
+
export interface RedisRateLimiterOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Redis 客户端(需要用户提供)
|
|
9
|
+
* 支持任何兼容的 Redis 客户端接口
|
|
10
|
+
*/
|
|
11
|
+
client: {
|
|
12
|
+
/**
|
|
13
|
+
* 获取键值
|
|
14
|
+
*/
|
|
15
|
+
get(key: string): Promise<string | null>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 设置键值(带过期时间)
|
|
19
|
+
*/
|
|
20
|
+
set(
|
|
21
|
+
key: string,
|
|
22
|
+
value: string,
|
|
23
|
+
options?: { PX?: number; EX?: number },
|
|
24
|
+
): Promise<void>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 删除键
|
|
28
|
+
*/
|
|
29
|
+
del(key: string): Promise<void>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 递增键值(原子操作)
|
|
33
|
+
*/
|
|
34
|
+
incr(key: string): Promise<number>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 设置键的过期时间
|
|
38
|
+
*/
|
|
39
|
+
expire(key: string, seconds: number): Promise<void>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 检查键是否存在
|
|
43
|
+
*/
|
|
44
|
+
exists(key: string): Promise<number>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 键前缀
|
|
49
|
+
* @default 'ratelimit:'
|
|
50
|
+
*/
|
|
51
|
+
keyPrefix?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Redis 限流器实现(分布式限流)
|
|
56
|
+
* 支持多个服务实例共享限流状态
|
|
57
|
+
*/
|
|
58
|
+
export class RedisRateLimiter {
|
|
59
|
+
private readonly client: RedisRateLimiterOptions['client'];
|
|
60
|
+
private readonly keyPrefix: string;
|
|
61
|
+
private readonly options: Required<
|
|
62
|
+
Pick<RateLimiterOptions, 'requestsPerSecond' | 'timeWindow'>
|
|
63
|
+
>;
|
|
64
|
+
|
|
65
|
+
public constructor(
|
|
66
|
+
redisOptions: RedisRateLimiterOptions,
|
|
67
|
+
rateLimiterOptions: RateLimiterOptions = {},
|
|
68
|
+
) {
|
|
69
|
+
this.client = redisOptions.client;
|
|
70
|
+
this.keyPrefix = redisOptions.keyPrefix ?? 'ratelimit:';
|
|
71
|
+
this.options = {
|
|
72
|
+
requestsPerSecond: rateLimiterOptions.requestsPerSecond ?? 100,
|
|
73
|
+
timeWindow: rateLimiterOptions.timeWindow ?? 1000,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 检查是否允许请求
|
|
79
|
+
* @param key - 限流键(如服务名、IP 等)
|
|
80
|
+
* @returns 是否允许请求
|
|
81
|
+
*/
|
|
82
|
+
public async allow(key: string): Promise<boolean> {
|
|
83
|
+
const redisKey = this.getKey(key);
|
|
84
|
+
const windowSeconds = Math.ceil(this.options.timeWindow / 1000);
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// 使用 Redis INCR 原子操作递增计数器
|
|
88
|
+
const count = await this.client.incr(redisKey);
|
|
89
|
+
|
|
90
|
+
// 如果是第一次请求,设置过期时间
|
|
91
|
+
if (count === 1) {
|
|
92
|
+
await this.client.expire(redisKey, windowSeconds);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 检查是否超过限制
|
|
96
|
+
return count <= this.options.requestsPerSecond;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('[RedisRateLimiter] Failed to check rate limit:', error);
|
|
99
|
+
// 如果 Redis 操作失败,默认允许请求(fail-open 策略)
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 获取剩余请求数
|
|
106
|
+
* @param key - 限流键
|
|
107
|
+
* @returns 剩余请求数
|
|
108
|
+
*/
|
|
109
|
+
public async getRemaining(key: string): Promise<number> {
|
|
110
|
+
const redisKey = this.getKey(key);
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const countStr = await this.client.get(redisKey);
|
|
114
|
+
if (!countStr) {
|
|
115
|
+
return this.options.requestsPerSecond;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const count = parseInt(countStr, 10);
|
|
119
|
+
return Math.max(0, this.options.requestsPerSecond - count);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error('[RedisRateLimiter] Failed to get remaining:', error);
|
|
122
|
+
return this.options.requestsPerSecond;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 重置限流器
|
|
128
|
+
* @param key - 限流键(可选,如果不提供则重置所有)
|
|
129
|
+
*/
|
|
130
|
+
public async reset(key?: string): Promise<void> {
|
|
131
|
+
if (key) {
|
|
132
|
+
const redisKey = this.getKey(key);
|
|
133
|
+
try {
|
|
134
|
+
await this.client.del(redisKey);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error('[RedisRateLimiter] Failed to reset:', error);
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
// 注意:重置所有键需要遍历所有匹配的键,这在生产环境中可能很慢
|
|
140
|
+
// 建议使用具体的 key 进行重置
|
|
141
|
+
console.warn(
|
|
142
|
+
'[RedisRateLimiter] Resetting all keys is not supported. Please provide a specific key.',
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 生成 Redis 键
|
|
149
|
+
*/
|
|
150
|
+
private getKey(key: string): string {
|
|
151
|
+
return `${this.keyPrefix}${key}`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|