@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,205 @@
|
|
|
1
|
+
import type { ServiceInstance } from '../service-registry/types';
|
|
2
|
+
import type { LoadBalancer, LoadBalanceStrategy } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 随机负载均衡器
|
|
6
|
+
*/
|
|
7
|
+
export class RandomLoadBalancer implements LoadBalancer {
|
|
8
|
+
public select(instances: ServiceInstance[]): ServiceInstance | null {
|
|
9
|
+
if (instances.length === 0) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const index = Math.floor(Math.random() * instances.length);
|
|
14
|
+
return instances[index] ?? null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 轮询负载均衡器
|
|
20
|
+
*/
|
|
21
|
+
export class RoundRobinLoadBalancer implements LoadBalancer {
|
|
22
|
+
private currentIndex: number = 0;
|
|
23
|
+
|
|
24
|
+
public select(instances: ServiceInstance[]): ServiceInstance | null {
|
|
25
|
+
if (instances.length === 0) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const instance = instances[this.currentIndex];
|
|
30
|
+
this.currentIndex = (this.currentIndex + 1) % instances.length;
|
|
31
|
+
return instance ?? null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 重置轮询索引
|
|
36
|
+
*/
|
|
37
|
+
public reset(): void {
|
|
38
|
+
this.currentIndex = 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 加权轮询负载均衡器
|
|
44
|
+
*/
|
|
45
|
+
export class WeightedRoundRobinLoadBalancer implements LoadBalancer {
|
|
46
|
+
private currentIndex: number = 0;
|
|
47
|
+
private currentWeight: number = 0;
|
|
48
|
+
private maxWeight: number = 0;
|
|
49
|
+
|
|
50
|
+
public select(instances: ServiceInstance[]): ServiceInstance | null {
|
|
51
|
+
if (instances.length === 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 过滤掉权重为 0 或未定义的实例
|
|
56
|
+
const validInstances = instances.filter((inst) => (inst.weight ?? 1) > 0);
|
|
57
|
+
if (validInstances.length === 0) {
|
|
58
|
+
return instances[0] ?? null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 计算最大权重
|
|
62
|
+
this.maxWeight = Math.max(...validInstances.map((inst) => inst.weight ?? 1));
|
|
63
|
+
|
|
64
|
+
// 加权轮询算法
|
|
65
|
+
while (true) {
|
|
66
|
+
this.currentIndex = (this.currentIndex + 1) % validInstances.length;
|
|
67
|
+
if (this.currentIndex === 0) {
|
|
68
|
+
this.currentWeight = this.currentWeight - 1;
|
|
69
|
+
if (this.currentWeight <= 0) {
|
|
70
|
+
this.currentWeight = this.maxWeight;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const instance = validInstances[this.currentIndex];
|
|
75
|
+
const weight = instance.weight ?? 1;
|
|
76
|
+
|
|
77
|
+
if (weight >= this.currentWeight) {
|
|
78
|
+
return instance;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 重置轮询状态
|
|
85
|
+
*/
|
|
86
|
+
public reset(): void {
|
|
87
|
+
this.currentIndex = 0;
|
|
88
|
+
this.currentWeight = 0;
|
|
89
|
+
this.maxWeight = 0;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 一致性哈希负载均衡器
|
|
95
|
+
*/
|
|
96
|
+
export class ConsistentHashLoadBalancer implements LoadBalancer {
|
|
97
|
+
private readonly virtualNodes: number = 160; // 虚拟节点数
|
|
98
|
+
|
|
99
|
+
public select(instances: ServiceInstance[], key?: string): ServiceInstance | null {
|
|
100
|
+
if (instances.length === 0) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!key) {
|
|
105
|
+
// 如果没有提供 key,使用随机选择
|
|
106
|
+
const index = Math.floor(Math.random() * instances.length);
|
|
107
|
+
return instances[index] ?? null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 构建虚拟节点环
|
|
111
|
+
const ring: Array<{ hash: number; instance: ServiceInstance }> = [];
|
|
112
|
+
|
|
113
|
+
for (const instance of instances) {
|
|
114
|
+
const instanceKey = `${instance.ip}:${instance.port}`;
|
|
115
|
+
for (let i = 0; i < this.virtualNodes; i++) {
|
|
116
|
+
const virtualKey = `${instanceKey}#${i}`;
|
|
117
|
+
const hash = this.hash(virtualKey);
|
|
118
|
+
ring.push({ hash, instance });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 排序
|
|
123
|
+
ring.sort((a, b) => a.hash - b.hash);
|
|
124
|
+
|
|
125
|
+
// 计算 key 的哈希值
|
|
126
|
+
const keyHash = this.hash(key);
|
|
127
|
+
|
|
128
|
+
// 找到第一个大于等于 keyHash 的节点
|
|
129
|
+
for (const node of ring) {
|
|
130
|
+
if (node.hash >= keyHash) {
|
|
131
|
+
return node.instance;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 如果没有找到,返回第一个节点(环)
|
|
136
|
+
return ring[0]?.instance ?? null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 简单的字符串哈希函数(FNV-1a)
|
|
141
|
+
*/
|
|
142
|
+
private hash(str: string): number {
|
|
143
|
+
let hash = 2166136261;
|
|
144
|
+
for (let i = 0; i < str.length; i++) {
|
|
145
|
+
hash ^= str.charCodeAt(i);
|
|
146
|
+
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
147
|
+
}
|
|
148
|
+
return hash >>> 0; // 转换为无符号整数
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 最少连接负载均衡器(简化版:使用权重和健康状态)
|
|
154
|
+
*/
|
|
155
|
+
export class LeastActiveLoadBalancer implements LoadBalancer {
|
|
156
|
+
public select(instances: ServiceInstance[]): ServiceInstance | null {
|
|
157
|
+
if (instances.length === 0) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 过滤健康实例
|
|
162
|
+
const healthyInstances = instances.filter((inst) => inst.healthy !== false);
|
|
163
|
+
|
|
164
|
+
if (healthyInstances.length === 0) {
|
|
165
|
+
// 如果没有健康实例,返回第一个
|
|
166
|
+
return instances[0] ?? null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 按权重排序,选择权重最大的(简化实现)
|
|
170
|
+
// 实际实现中应该跟踪每个实例的活跃连接数
|
|
171
|
+
const sorted = [...healthyInstances].sort((a, b) => {
|
|
172
|
+
const weightA = a.weight ?? 1;
|
|
173
|
+
const weightB = b.weight ?? 1;
|
|
174
|
+
return weightB - weightA;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return sorted[0] ?? null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 负载均衡器工厂
|
|
183
|
+
*/
|
|
184
|
+
export class LoadBalancerFactory {
|
|
185
|
+
/**
|
|
186
|
+
* 创建负载均衡器
|
|
187
|
+
*/
|
|
188
|
+
public static create(strategy: LoadBalanceStrategy): LoadBalancer {
|
|
189
|
+
switch (strategy) {
|
|
190
|
+
case 'random':
|
|
191
|
+
return new RandomLoadBalancer();
|
|
192
|
+
case 'roundRobin':
|
|
193
|
+
return new RoundRobinLoadBalancer();
|
|
194
|
+
case 'weightedRoundRobin':
|
|
195
|
+
return new WeightedRoundRobinLoadBalancer();
|
|
196
|
+
case 'consistentHash':
|
|
197
|
+
return new ConsistentHashLoadBalancer();
|
|
198
|
+
case 'leastActive':
|
|
199
|
+
return new LeastActiveLoadBalancer();
|
|
200
|
+
default:
|
|
201
|
+
return new RoundRobinLoadBalancer();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import type { ServiceRegistry, ServiceInstance } from '../service-registry/types';
|
|
2
|
+
import { LoadBalancerFactory } from './load-balancer';
|
|
3
|
+
import {
|
|
4
|
+
CircuitBreaker,
|
|
5
|
+
RateLimiter,
|
|
6
|
+
RetryStrategyImpl,
|
|
7
|
+
type CircuitBreakerOptions,
|
|
8
|
+
type RateLimiterOptions,
|
|
9
|
+
type RetryStrategy,
|
|
10
|
+
} from '../governance';
|
|
11
|
+
import {
|
|
12
|
+
Tracer,
|
|
13
|
+
SpanKind,
|
|
14
|
+
SpanStatus,
|
|
15
|
+
type TracingOptions,
|
|
16
|
+
type TraceCollector,
|
|
17
|
+
} from '../tracing';
|
|
18
|
+
import {
|
|
19
|
+
ServiceMetricsCollector,
|
|
20
|
+
type MonitoringOptions,
|
|
21
|
+
} from '../monitoring';
|
|
22
|
+
import {
|
|
23
|
+
ServiceCallError,
|
|
24
|
+
type ServiceCallOptions,
|
|
25
|
+
type ServiceCallResponse,
|
|
26
|
+
type ServiceRequestInterceptor,
|
|
27
|
+
type ServiceResponseInterceptor,
|
|
28
|
+
type LoadBalancer,
|
|
29
|
+
} from './types';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 服务调用客户端
|
|
33
|
+
* 依赖 ServiceRegistry 抽象接口,不依赖具体实现
|
|
34
|
+
*/
|
|
35
|
+
export class ServiceClient {
|
|
36
|
+
private readonly serviceRegistry: ServiceRegistry;
|
|
37
|
+
private readonly requestInterceptors: ServiceRequestInterceptor[] = [];
|
|
38
|
+
private readonly responseInterceptors: ServiceResponseInterceptor[] = [];
|
|
39
|
+
private readonly loadBalancers: Map<string, LoadBalancer> = new Map();
|
|
40
|
+
private readonly circuitBreakers: Map<string, CircuitBreaker> = new Map();
|
|
41
|
+
private readonly rateLimiters: Map<string, RateLimiter> = new Map();
|
|
42
|
+
private defaultCircuitBreakerOptions?: CircuitBreakerOptions;
|
|
43
|
+
private defaultRateLimiterOptions?: RateLimiterOptions;
|
|
44
|
+
private defaultRetryStrategy?: RetryStrategy;
|
|
45
|
+
private tracer?: Tracer;
|
|
46
|
+
private metricsCollector?: ServiceMetricsCollector;
|
|
47
|
+
|
|
48
|
+
public constructor(serviceRegistry: ServiceRegistry) {
|
|
49
|
+
this.serviceRegistry = serviceRegistry;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 设置默认熔断器配置
|
|
54
|
+
*/
|
|
55
|
+
public setDefaultCircuitBreakerOptions(options: CircuitBreakerOptions): void {
|
|
56
|
+
this.defaultCircuitBreakerOptions = options;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 设置默认限流器配置
|
|
61
|
+
*/
|
|
62
|
+
public setDefaultRateLimiterOptions(options: RateLimiterOptions): void {
|
|
63
|
+
this.defaultRateLimiterOptions = options;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 设置默认重试策略
|
|
68
|
+
*/
|
|
69
|
+
public setDefaultRetryStrategy(strategy: RetryStrategy): void {
|
|
70
|
+
this.defaultRetryStrategy = strategy;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 设置追踪器
|
|
75
|
+
*/
|
|
76
|
+
public setTracer(tracer: Tracer): void {
|
|
77
|
+
this.tracer = tracer;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 设置监控指标收集器
|
|
82
|
+
*/
|
|
83
|
+
public setMetricsCollector(collector: ServiceMetricsCollector): void {
|
|
84
|
+
this.metricsCollector = collector;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 添加请求拦截器
|
|
89
|
+
*/
|
|
90
|
+
public addRequestInterceptor(interceptor: ServiceRequestInterceptor): void {
|
|
91
|
+
this.requestInterceptors.push(interceptor);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 添加响应拦截器
|
|
96
|
+
*/
|
|
97
|
+
public addResponseInterceptor(interceptor: ServiceResponseInterceptor): void {
|
|
98
|
+
this.responseInterceptors.push(interceptor);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 调用服务
|
|
103
|
+
*/
|
|
104
|
+
public async call<T = unknown>(options: ServiceCallOptions): Promise<ServiceCallResponse<T>> {
|
|
105
|
+
// 应用请求拦截器
|
|
106
|
+
let finalOptions = options;
|
|
107
|
+
for (const interceptor of this.requestInterceptors) {
|
|
108
|
+
finalOptions = await Promise.resolve(interceptor.intercept(finalOptions));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 限流检查
|
|
112
|
+
if (finalOptions.enableRateLimit) {
|
|
113
|
+
const rateLimitKey = finalOptions.rateLimitKey ?? finalOptions.serviceName;
|
|
114
|
+
let rateLimiter = this.rateLimiters.get(rateLimitKey);
|
|
115
|
+
if (!rateLimiter) {
|
|
116
|
+
rateLimiter = new RateLimiter(this.defaultRateLimiterOptions);
|
|
117
|
+
this.rateLimiters.set(rateLimitKey, rateLimiter);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const allowed = await rateLimiter.allow(rateLimitKey);
|
|
121
|
+
if (!allowed) {
|
|
122
|
+
throw new ServiceCallError(`Rate limit exceeded for service: ${finalOptions.serviceName}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 获取服务实例
|
|
127
|
+
const instances = await this.serviceRegistry.getInstances(finalOptions.serviceName, {
|
|
128
|
+
namespaceId: finalOptions.instanceOptions?.namespaceId,
|
|
129
|
+
groupName: finalOptions.instanceOptions?.groupName,
|
|
130
|
+
clusterName: finalOptions.instanceOptions?.clusterName,
|
|
131
|
+
healthyOnly: finalOptions.instanceOptions?.healthyOnly ?? true,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (instances.length === 0) {
|
|
135
|
+
throw new ServiceCallError(`No instances found for service: ${finalOptions.serviceName}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 获取或创建负载均衡器
|
|
139
|
+
const strategy = finalOptions.loadBalanceStrategy ?? 'roundRobin';
|
|
140
|
+
let loadBalancer = this.loadBalancers.get(strategy);
|
|
141
|
+
if (!loadBalancer) {
|
|
142
|
+
loadBalancer = LoadBalancerFactory.create(strategy);
|
|
143
|
+
this.loadBalancers.set(strategy, loadBalancer);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 选择服务实例
|
|
147
|
+
const instance = loadBalancer.select(instances, finalOptions.consistentHashKey);
|
|
148
|
+
if (!instance) {
|
|
149
|
+
throw new ServiceCallError(`Failed to select instance for service: ${finalOptions.serviceName}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const startTime = Date.now();
|
|
153
|
+
|
|
154
|
+
// 开始追踪(如果启用)
|
|
155
|
+
const span = this.tracer?.startSpan(
|
|
156
|
+
`${finalOptions.method} ${finalOptions.serviceName}${finalOptions.path}`,
|
|
157
|
+
SpanKind.CLIENT,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
if (span && this.tracer) {
|
|
161
|
+
this.tracer.setSpanTags(span.context.spanId, {
|
|
162
|
+
'service.name': finalOptions.serviceName,
|
|
163
|
+
'service.instance': `${instance.ip}:${instance.port}`,
|
|
164
|
+
'http.method': finalOptions.method,
|
|
165
|
+
'http.path': finalOptions.path,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// 注入追踪上下文到请求头
|
|
169
|
+
const traceHeaders = this.tracer.injectToHeaders(span.context);
|
|
170
|
+
finalOptions.headers = {
|
|
171
|
+
...finalOptions.headers,
|
|
172
|
+
...traceHeaders,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 执行请求(带熔断器和重试)
|
|
177
|
+
const executeRequest = async (): Promise<ServiceCallResponse<T>> => {
|
|
178
|
+
const timeout = finalOptions.timeout ?? 5000;
|
|
179
|
+
let success = false;
|
|
180
|
+
let response: ServiceCallResponse<T>;
|
|
181
|
+
let error: Error | undefined;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
response = await this.executeRequest<T>(instance, finalOptions, timeout);
|
|
185
|
+
success = true;
|
|
186
|
+
|
|
187
|
+
// 应用响应拦截器
|
|
188
|
+
let finalResponse = response;
|
|
189
|
+
for (const interceptor of this.responseInterceptors) {
|
|
190
|
+
finalResponse = await Promise.resolve(interceptor.intercept(finalResponse));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return finalResponse;
|
|
194
|
+
} catch (e) {
|
|
195
|
+
error = e instanceof Error ? e : new Error(String(e));
|
|
196
|
+
throw error;
|
|
197
|
+
} finally {
|
|
198
|
+
const latency = Date.now() - startTime;
|
|
199
|
+
|
|
200
|
+
// 记录监控指标
|
|
201
|
+
if (this.metricsCollector) {
|
|
202
|
+
this.metricsCollector.recordCall(
|
|
203
|
+
finalOptions.serviceName,
|
|
204
|
+
`${instance.ip}:${instance.port}`,
|
|
205
|
+
success,
|
|
206
|
+
latency,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 结束追踪
|
|
211
|
+
if (span && this.tracer) {
|
|
212
|
+
this.tracer.endSpan(
|
|
213
|
+
span.context.spanId,
|
|
214
|
+
success ? SpanStatus.OK : SpanStatus.ERROR,
|
|
215
|
+
error,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// 如果启用熔断器
|
|
222
|
+
if (finalOptions.enableCircuitBreaker) {
|
|
223
|
+
const circuitBreakerKey = `${finalOptions.serviceName}:${instance.ip}:${instance.port}`;
|
|
224
|
+
let circuitBreaker = this.circuitBreakers.get(circuitBreakerKey);
|
|
225
|
+
if (!circuitBreaker) {
|
|
226
|
+
circuitBreaker = new CircuitBreaker(this.defaultCircuitBreakerOptions);
|
|
227
|
+
this.circuitBreakers.set(circuitBreakerKey, circuitBreaker);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return circuitBreaker.execute(
|
|
231
|
+
executeRequest,
|
|
232
|
+
finalOptions.fallback,
|
|
233
|
+
) as Promise<ServiceCallResponse<T>>;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 如果启用重试策略
|
|
237
|
+
if (this.defaultRetryStrategy || finalOptions.retryCount !== undefined) {
|
|
238
|
+
const retryStrategy = this.defaultRetryStrategy ?? {
|
|
239
|
+
maxRetries: finalOptions.retryCount ?? 0,
|
|
240
|
+
retryDelay: finalOptions.retryDelay ?? 1000,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const retryImpl = new RetryStrategyImpl(retryStrategy);
|
|
244
|
+
return retryImpl.execute(executeRequest);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 普通执行
|
|
248
|
+
return executeRequest();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 流式调用服务(返回 ReadableStream)
|
|
253
|
+
* @param options - 服务调用选项
|
|
254
|
+
* @returns ReadableStream
|
|
255
|
+
*/
|
|
256
|
+
public async callStream(options: ServiceCallOptions): Promise<ReadableStream> {
|
|
257
|
+
// 应用请求拦截器
|
|
258
|
+
let finalOptions = options;
|
|
259
|
+
for (const interceptor of this.requestInterceptors) {
|
|
260
|
+
finalOptions = await Promise.resolve(interceptor.intercept(finalOptions));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 获取服务实例
|
|
264
|
+
const instances = await this.serviceRegistry.getInstances(finalOptions.serviceName, {
|
|
265
|
+
namespaceId: finalOptions.instanceOptions?.namespaceId,
|
|
266
|
+
groupName: finalOptions.instanceOptions?.groupName,
|
|
267
|
+
clusterName: finalOptions.instanceOptions?.clusterName,
|
|
268
|
+
healthyOnly: finalOptions.instanceOptions?.healthyOnly ?? true,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
if (instances.length === 0) {
|
|
272
|
+
throw new ServiceCallError(`No instances found for service: ${finalOptions.serviceName}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 获取或创建负载均衡器
|
|
276
|
+
const strategy = finalOptions.loadBalanceStrategy ?? 'roundRobin';
|
|
277
|
+
let loadBalancer = this.loadBalancers.get(strategy);
|
|
278
|
+
if (!loadBalancer) {
|
|
279
|
+
loadBalancer = LoadBalancerFactory.create(strategy);
|
|
280
|
+
this.loadBalancers.set(strategy, loadBalancer);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// 选择服务实例
|
|
284
|
+
const instance = loadBalancer.select(instances, finalOptions.consistentHashKey);
|
|
285
|
+
if (!instance) {
|
|
286
|
+
throw new ServiceCallError(`Failed to select instance for service: ${finalOptions.serviceName}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 执行流式请求
|
|
290
|
+
return this.executeStreamRequest(instance, finalOptions);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* 执行流式 HTTP 请求
|
|
295
|
+
*/
|
|
296
|
+
private async executeStreamRequest(
|
|
297
|
+
instance: ServiceInstance,
|
|
298
|
+
options: ServiceCallOptions,
|
|
299
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
300
|
+
// 构建 URL
|
|
301
|
+
const url = new URL(options.path, `http://${instance.ip}:${instance.port}`);
|
|
302
|
+
|
|
303
|
+
// 添加查询参数
|
|
304
|
+
if (options.query) {
|
|
305
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
306
|
+
url.searchParams.append(key, String(value));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 构建请求头
|
|
311
|
+
const headers: Record<string, string> = {
|
|
312
|
+
'Accept': 'text/event-stream',
|
|
313
|
+
...options.headers,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// 构建请求体
|
|
317
|
+
let body: string | undefined;
|
|
318
|
+
if (options.body) {
|
|
319
|
+
if (typeof options.body === 'string') {
|
|
320
|
+
body = options.body;
|
|
321
|
+
} else {
|
|
322
|
+
body = JSON.stringify(options.body);
|
|
323
|
+
headers['Content-Type'] = 'application/json';
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 执行请求
|
|
328
|
+
const timeout = options.timeout ?? 30000; // 流式请求默认超时时间更长
|
|
329
|
+
const controller = new AbortController();
|
|
330
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
const response = await fetch(url.toString(), {
|
|
334
|
+
method: options.method,
|
|
335
|
+
headers,
|
|
336
|
+
body,
|
|
337
|
+
signal: controller.signal,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
clearTimeout(timeoutId);
|
|
341
|
+
|
|
342
|
+
if (!response.ok) {
|
|
343
|
+
throw new ServiceCallError(
|
|
344
|
+
`Service call failed with status ${response.status}`,
|
|
345
|
+
response.status,
|
|
346
|
+
await response.text().catch(() => undefined),
|
|
347
|
+
instance,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!response.body) {
|
|
352
|
+
throw new ServiceCallError('Response body is null', response.status, undefined, instance);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return response.body as ReadableStream<Uint8Array>;
|
|
356
|
+
} catch (error) {
|
|
357
|
+
clearTimeout(timeoutId);
|
|
358
|
+
if (error instanceof ServiceCallError) {
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
throw new ServiceCallError(
|
|
362
|
+
`Service call failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
363
|
+
undefined,
|
|
364
|
+
undefined,
|
|
365
|
+
instance,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* 执行 HTTP 请求
|
|
372
|
+
*/
|
|
373
|
+
private async executeRequest<T>(
|
|
374
|
+
instance: ServiceInstance,
|
|
375
|
+
options: ServiceCallOptions,
|
|
376
|
+
timeout: number,
|
|
377
|
+
): Promise<ServiceCallResponse<T>> {
|
|
378
|
+
// 构建 URL
|
|
379
|
+
const url = new URL(options.path, `http://${instance.ip}:${instance.port}`);
|
|
380
|
+
|
|
381
|
+
// 添加查询参数
|
|
382
|
+
if (options.query) {
|
|
383
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
384
|
+
url.searchParams.append(key, String(value));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// 构建请求头
|
|
389
|
+
const headers: Record<string, string> = {
|
|
390
|
+
'Content-Type': 'application/json',
|
|
391
|
+
...options.headers,
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// 构建请求体
|
|
395
|
+
let body: string | undefined;
|
|
396
|
+
if (options.body) {
|
|
397
|
+
if (typeof options.body === 'string') {
|
|
398
|
+
body = options.body;
|
|
399
|
+
} else {
|
|
400
|
+
body = JSON.stringify(options.body);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 执行请求
|
|
405
|
+
const controller = new AbortController();
|
|
406
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
const response = await fetch(url.toString(), {
|
|
410
|
+
method: options.method,
|
|
411
|
+
headers,
|
|
412
|
+
body,
|
|
413
|
+
signal: controller.signal,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
clearTimeout(timeoutId);
|
|
417
|
+
|
|
418
|
+
// 读取响应头
|
|
419
|
+
const responseHeaders: Record<string, string> = {};
|
|
420
|
+
response.headers.forEach((value, key) => {
|
|
421
|
+
responseHeaders[key] = value;
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// 先检查响应状态,再读取响应体
|
|
425
|
+
// 这样可以避免在错误响应时错误地解析响应体
|
|
426
|
+
if (!response.ok) {
|
|
427
|
+
// 对于错误响应,尝试读取错误信息(可能是 JSON 或文本)
|
|
428
|
+
let errorData: unknown;
|
|
429
|
+
try {
|
|
430
|
+
const contentType = response.headers.get('content-type');
|
|
431
|
+
if (contentType?.includes('application/json')) {
|
|
432
|
+
errorData = await response.json();
|
|
433
|
+
} else {
|
|
434
|
+
errorData = await response.text();
|
|
435
|
+
}
|
|
436
|
+
} catch (parseError) {
|
|
437
|
+
// 如果解析失败,使用状态文本作为错误信息
|
|
438
|
+
errorData = response.statusText || 'Unknown error';
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
throw new ServiceCallError(
|
|
442
|
+
`Service call failed with status ${response.status}`,
|
|
443
|
+
response.status,
|
|
444
|
+
errorData,
|
|
445
|
+
instance,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// 对于成功响应,根据 content-type 读取响应体
|
|
450
|
+
const contentType = response.headers.get('content-type');
|
|
451
|
+
let data: T;
|
|
452
|
+
|
|
453
|
+
if (contentType?.includes('application/json')) {
|
|
454
|
+
data = (await response.json()) as T;
|
|
455
|
+
} else {
|
|
456
|
+
data = (await response.text()) as T;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
status: response.status,
|
|
461
|
+
headers: responseHeaders,
|
|
462
|
+
data,
|
|
463
|
+
instance,
|
|
464
|
+
};
|
|
465
|
+
} catch (error) {
|
|
466
|
+
clearTimeout(timeoutId);
|
|
467
|
+
|
|
468
|
+
if (error instanceof ServiceCallError) {
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
throw new ServiceCallError(
|
|
473
|
+
`Service call failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
474
|
+
undefined,
|
|
475
|
+
undefined,
|
|
476
|
+
instance,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* 休眠(用于重试延迟)
|
|
483
|
+
*/
|
|
484
|
+
private sleep(ms: number): Promise<void> {
|
|
485
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|