@dangao/bun-server 1.0.1 → 1.1.2
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/dist/controller/controller.d.ts +1 -1
- package/dist/controller/controller.d.ts.map +1 -1
- package/dist/core/application.d.ts.map +1 -1
- package/dist/database/database-extension.d.ts.map +1 -1
- package/dist/database/database-module.d.ts.map +1 -1
- package/dist/database/orm/transaction-decorator.d.ts +1 -0
- package/dist/database/orm/transaction-decorator.d.ts.map +1 -1
- package/dist/database/orm/transaction-interceptor.d.ts +12 -3
- package/dist/database/orm/transaction-interceptor.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +678 -310
- package/dist/interceptor/base-interceptor.d.ts +94 -0
- package/dist/interceptor/base-interceptor.d.ts.map +1 -0
- package/dist/interceptor/builtin/cache-interceptor.d.ts +69 -0
- package/dist/interceptor/builtin/cache-interceptor.d.ts.map +1 -0
- package/dist/interceptor/builtin/index.d.ts +4 -0
- package/dist/interceptor/builtin/index.d.ts.map +1 -0
- package/dist/interceptor/builtin/log-interceptor.d.ts +56 -0
- package/dist/interceptor/builtin/log-interceptor.d.ts.map +1 -0
- package/dist/interceptor/builtin/permission-interceptor.d.ts +70 -0
- package/dist/interceptor/builtin/permission-interceptor.d.ts.map +1 -0
- package/dist/interceptor/index.d.ts +7 -0
- package/dist/interceptor/index.d.ts.map +1 -0
- package/dist/interceptor/interceptor-chain.d.ts +22 -0
- package/dist/interceptor/interceptor-chain.d.ts.map +1 -0
- package/dist/interceptor/interceptor-registry.d.ts +59 -0
- package/dist/interceptor/interceptor-registry.d.ts.map +1 -0
- package/dist/interceptor/metadata.d.ts +12 -0
- package/dist/interceptor/metadata.d.ts.map +1 -0
- package/dist/interceptor/types.d.ts +42 -0
- package/dist/interceptor/types.d.ts.map +1 -0
- package/dist/middleware/decorators.d.ts +2 -1
- package/dist/middleware/decorators.d.ts.map +1 -1
- package/dist/router/decorators.d.ts.map +1 -1
- package/dist/router/registry.d.ts +2 -1
- package/dist/router/registry.d.ts.map +1 -1
- package/dist/router/route.d.ts +3 -2
- package/dist/router/route.d.ts.map +1 -1
- package/dist/router/router.d.ts +2 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/websocket/decorators.d.ts +2 -1
- package/dist/websocket/decorators.d.ts.map +1 -1
- package/package.json +5 -3
- package/readme.md +163 -2
- package/src/auth/controller.ts +148 -0
- package/src/auth/decorators.ts +81 -0
- package/src/auth/index.ts +12 -0
- package/src/auth/jwt.ts +169 -0
- package/src/auth/oauth2.ts +244 -0
- package/src/auth/types.ts +248 -0
- package/src/cache/cache-module.ts +67 -0
- package/src/cache/decorators.ts +202 -0
- package/src/cache/index.ts +27 -0
- package/src/cache/service.ts +151 -0
- package/src/cache/types.ts +420 -0
- package/src/config/config-module.ts +76 -0
- package/src/config/index.ts +8 -0
- package/src/config/service.ts +93 -0
- package/src/config/types.ts +27 -0
- package/src/controller/controller.ts +278 -0
- package/src/controller/decorators.ts +84 -0
- package/src/controller/index.ts +7 -0
- package/src/controller/metadata.ts +27 -0
- package/src/controller/param-binder.ts +157 -0
- package/src/core/application.ts +239 -0
- package/src/core/context.ts +228 -0
- package/src/core/index.ts +4 -0
- package/src/core/server.ts +128 -0
- package/src/core/types.ts +2 -0
- package/src/database/connection-manager.ts +239 -0
- package/src/database/connection-pool.ts +322 -0
- package/src/database/database-extension.ts +83 -0
- package/src/database/database-module.ts +121 -0
- package/src/database/health-indicator.ts +51 -0
- package/src/database/index.ts +47 -0
- package/src/database/orm/decorators.ts +155 -0
- package/src/database/orm/drizzle-repository.ts +39 -0
- package/src/database/orm/index.ts +23 -0
- package/src/database/orm/repository-decorator.ts +39 -0
- package/src/database/orm/repository.ts +103 -0
- package/src/database/orm/service.ts +49 -0
- package/src/database/orm/transaction-decorator.ts +76 -0
- package/src/database/orm/transaction-interceptor.ts +263 -0
- package/src/database/orm/transaction-manager.ts +276 -0
- package/src/database/orm/transaction-types.ts +140 -0
- package/src/database/orm/types.ts +99 -0
- package/src/database/service.ts +221 -0
- package/src/database/types.ts +171 -0
- package/src/di/container.ts +398 -0
- package/src/di/decorators.ts +228 -0
- package/src/di/index.ts +4 -0
- package/src/di/module-registry.ts +188 -0
- package/src/di/module.ts +65 -0
- package/src/di/types.ts +67 -0
- package/src/error/error-codes.ts +222 -0
- package/src/error/filter.ts +43 -0
- package/src/error/handler.ts +66 -0
- package/src/error/http-exception.ts +115 -0
- package/src/error/i18n.ts +217 -0
- package/src/error/index.ts +16 -0
- package/src/extensions/index.ts +5 -0
- package/src/extensions/logger-extension.ts +31 -0
- package/src/extensions/logger-module.ts +69 -0
- package/src/extensions/types.ts +14 -0
- package/src/files/index.ts +5 -0
- package/src/files/static-middleware.ts +53 -0
- package/src/files/storage.ts +67 -0
- package/src/files/types.ts +33 -0
- package/src/files/upload-middleware.ts +45 -0
- package/src/health/controller.ts +76 -0
- package/src/health/health-module.ts +51 -0
- package/src/health/index.ts +12 -0
- package/src/health/types.ts +28 -0
- package/src/index.ts +292 -0
- package/src/interceptor/base-interceptor.ts +203 -0
- package/src/interceptor/builtin/cache-interceptor.ts +169 -0
- package/src/interceptor/builtin/index.ts +28 -0
- package/src/interceptor/builtin/log-interceptor.ts +178 -0
- package/src/interceptor/builtin/permission-interceptor.ts +173 -0
- package/src/interceptor/index.ts +26 -0
- package/src/interceptor/interceptor-chain.ts +79 -0
- package/src/interceptor/interceptor-registry.ts +132 -0
- package/src/interceptor/metadata.ts +40 -0
- package/src/interceptor/types.ts +52 -0
- package/src/metrics/collector.ts +209 -0
- package/src/metrics/controller.ts +40 -0
- package/src/metrics/index.ts +15 -0
- package/src/metrics/metrics-module.ts +58 -0
- package/src/metrics/middleware.ts +46 -0
- package/src/metrics/prometheus.ts +79 -0
- package/src/metrics/types.ts +103 -0
- package/src/middleware/builtin/cors.ts +60 -0
- package/src/middleware/builtin/error-handler.ts +90 -0
- package/src/middleware/builtin/file-upload.ts +42 -0
- package/src/middleware/builtin/index.ts +14 -0
- package/src/middleware/builtin/logger.ts +91 -0
- package/src/middleware/builtin/rate-limit.ts +252 -0
- package/src/middleware/builtin/static-file.ts +88 -0
- package/src/middleware/decorators.ts +92 -0
- package/src/middleware/index.ts +11 -0
- package/src/middleware/middleware.ts +13 -0
- package/src/middleware/pipeline.ts +93 -0
- package/src/queue/decorators.ts +110 -0
- package/src/queue/index.ts +26 -0
- package/src/queue/queue-module.ts +64 -0
- package/src/queue/service.ts +302 -0
- package/src/queue/types.ts +341 -0
- package/src/request/body-parser.ts +133 -0
- package/src/request/file-handler.ts +46 -0
- package/src/request/index.ts +5 -0
- package/src/request/request.ts +107 -0
- package/src/request/response.ts +150 -0
- package/src/router/decorators.ts +123 -0
- package/src/router/index.ts +6 -0
- package/src/router/registry.ts +99 -0
- package/src/router/route.ts +141 -0
- package/src/router/router.ts +242 -0
- package/src/router/types.ts +27 -0
- package/src/security/access-decision-manager.ts +34 -0
- package/src/security/authentication-manager.ts +47 -0
- package/src/security/context.ts +92 -0
- package/src/security/filter.ts +162 -0
- package/src/security/index.ts +8 -0
- package/src/security/providers/index.ts +3 -0
- package/src/security/providers/jwt-provider.ts +60 -0
- package/src/security/providers/oauth2-provider.ts +70 -0
- package/src/security/security-module.ts +145 -0
- package/src/security/types.ts +165 -0
- package/src/session/decorators.ts +45 -0
- package/src/session/index.ts +19 -0
- package/src/session/middleware.ts +143 -0
- package/src/session/service.ts +218 -0
- package/src/session/session-module.ts +69 -0
- package/src/session/types.ts +373 -0
- package/src/swagger/decorators.ts +133 -0
- package/src/swagger/generator.ts +234 -0
- package/src/swagger/index.ts +7 -0
- package/src/swagger/swagger-extension.ts +41 -0
- package/src/swagger/swagger-module.ts +83 -0
- package/src/swagger/types.ts +188 -0
- package/src/swagger/ui.ts +98 -0
- package/src/testing/harness.ts +96 -0
- package/src/validation/decorators.ts +95 -0
- package/src/validation/errors.ts +28 -0
- package/src/validation/index.ts +14 -0
- package/src/validation/types.ts +35 -0
- package/src/validation/validator.ts +63 -0
- package/src/websocket/decorators.ts +53 -0
- package/src/websocket/index.ts +12 -0
- package/src/websocket/registry.ts +133 -0
- package/tests/cache/cache-module.test.ts +212 -0
- package/tests/config/config-module.test.ts +151 -0
- package/tests/controller/controller.test.ts +189 -0
- package/tests/controller/path-combination.test.ts +207 -0
- package/tests/core/application.test.ts +57 -0
- package/tests/core/context-body.test.ts +44 -0
- package/tests/core/context.test.ts +86 -0
- package/tests/core/edge-cases.test.ts +432 -0
- package/tests/database/database-module.test.ts +385 -0
- package/tests/database/orm.test.ts +164 -0
- package/tests/database/postgres-mysql-integration.test.ts +395 -0
- package/tests/database/transaction.test.ts +238 -0
- package/tests/di/container.test.ts +264 -0
- package/tests/di/module.test.ts +128 -0
- package/tests/error/error-codes.test.ts +121 -0
- package/tests/error/error-handler.test.ts +68 -0
- package/tests/error/error-handling.test.ts +254 -0
- package/tests/error/http-exception.test.ts +37 -0
- package/tests/error/i18n-integration.test.ts +175 -0
- package/tests/extensions/logger-extension.test.ts +40 -0
- package/tests/files/static-middleware.test.ts +67 -0
- package/tests/files/upload-middleware.test.ts +43 -0
- package/tests/health/health-module.test.ts +116 -0
- package/tests/integration/application-router.test.ts +85 -0
- package/tests/integration/body-parsing.test.ts +88 -0
- package/tests/integration/cache-e2e.test.ts +114 -0
- package/tests/integration/oauth2-e2e.test.ts +615 -0
- package/tests/integration/session-e2e.test.ts +207 -0
- package/tests/interceptor/builtin/cache-interceptor.test.ts +137 -0
- package/tests/interceptor/builtin/permission-interceptor.test.ts +182 -0
- package/tests/interceptor/interceptor-advanced-integration.test.ts +592 -0
- package/tests/interceptor/interceptor-arg-modification.test.ts +76 -0
- package/tests/interceptor/interceptor-chain.test.ts +199 -0
- package/tests/interceptor/interceptor-integration.test.ts +230 -0
- package/tests/interceptor/interceptor-registry.test.ts +200 -0
- package/tests/interceptor/perf/interceptor-performance.test.ts +341 -0
- package/tests/metrics/metrics-module.test.ts +178 -0
- package/tests/middleware/builtin.test.ts +206 -0
- package/tests/middleware/file-upload.test.ts +41 -0
- package/tests/middleware/middleware.test.ts +120 -0
- package/tests/middleware/pipeline.test.ts +72 -0
- package/tests/middleware/rate-limit.test.ts +314 -0
- package/tests/middleware/static-file.test.ts +62 -0
- package/tests/perf/harness.test.ts +48 -0
- package/tests/perf/optimization.test.ts +183 -0
- package/tests/perf/regression.test.ts +120 -0
- package/tests/queue/queue-module.test.ts +217 -0
- package/tests/request/body-parser.test.ts +96 -0
- package/tests/request/response.test.ts +99 -0
- package/tests/router/decorators.test.ts +46 -0
- package/tests/router/registry.test.ts +51 -0
- package/tests/router/route.test.ts +71 -0
- package/tests/router/router-normalization.test.ts +106 -0
- package/tests/router/router.test.ts +133 -0
- package/tests/security/access-decision-manager.test.ts +84 -0
- package/tests/security/authentication-manager.test.ts +81 -0
- package/tests/security/context.test.ts +302 -0
- package/tests/security/filter.test.ts +225 -0
- package/tests/security/jwt-provider.test.ts +106 -0
- package/tests/security/oauth2-provider.test.ts +269 -0
- package/tests/security/security-module.test.ts +143 -0
- package/tests/session/session-module.test.ts +307 -0
- package/tests/stress/di-stress.test.ts +30 -0
- package/tests/swagger/decorators.test.ts +153 -0
- package/tests/swagger/generator.test.ts +202 -0
- package/tests/swagger/swagger-extension.test.ts +72 -0
- package/tests/swagger/swagger-module.test.ts +79 -0
- package/tests/utils/test-port.ts +10 -0
- package/tests/validation/controller-validation.test.ts +64 -0
- package/tests/validation/validation.test.ts +42 -0
- package/tests/websocket/gateway.test.ts +68 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Interceptor, InterceptorMetadata } from './types';
|
|
2
|
+
import { INTERCEPTOR_REGISTRY_TOKEN } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 拦截器注册表
|
|
6
|
+
* 管理所有注册的拦截器,支持按优先级排序
|
|
7
|
+
*/
|
|
8
|
+
export class InterceptorRegistry {
|
|
9
|
+
/**
|
|
10
|
+
* 拦截器存储
|
|
11
|
+
* key: 元数据键(Symbol)
|
|
12
|
+
* value: 拦截器元数据列表
|
|
13
|
+
*/
|
|
14
|
+
private readonly interceptors = new Map<symbol, InterceptorMetadata[]>();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 注册拦截器
|
|
18
|
+
* @param metadataKey - 元数据键(用于匹配装饰器)
|
|
19
|
+
* @param interceptor - 拦截器实例
|
|
20
|
+
* @param priority - 优先级(数字越小优先级越高,默认 100)
|
|
21
|
+
*/
|
|
22
|
+
public register(
|
|
23
|
+
metadataKey: symbol,
|
|
24
|
+
interceptor: Interceptor,
|
|
25
|
+
priority: number = 100,
|
|
26
|
+
): void {
|
|
27
|
+
if (!this.interceptors.has(metadataKey)) {
|
|
28
|
+
this.interceptors.set(metadataKey, []);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const metadataList = this.interceptors.get(metadataKey)!;
|
|
32
|
+
|
|
33
|
+
// 检查是否已注册相同的拦截器
|
|
34
|
+
const exists = metadataList.some(
|
|
35
|
+
(meta) => meta.interceptor === interceptor,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (!exists) {
|
|
39
|
+
metadataList.push({
|
|
40
|
+
metadataKey,
|
|
41
|
+
interceptor,
|
|
42
|
+
priority,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// 按优先级排序(数字越小优先级越高)
|
|
46
|
+
metadataList.sort((a, b) => a.priority - b.priority);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 获取拦截器元数据列表(按优先级排序)
|
|
52
|
+
* @param metadataKey - 元数据键
|
|
53
|
+
* @returns 拦截器元数据列表
|
|
54
|
+
*/
|
|
55
|
+
public getInterceptorMetadata(metadataKey: symbol): InterceptorMetadata[] {
|
|
56
|
+
const metadataList = this.interceptors.get(metadataKey);
|
|
57
|
+
if (!metadataList || metadataList.length === 0) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 返回已排序的拦截器元数据列表(包括优先级信息)
|
|
62
|
+
return [...metadataList];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 获取拦截器列表(按优先级排序)
|
|
67
|
+
* @param metadataKey - 元数据键
|
|
68
|
+
* @returns 拦截器列表
|
|
69
|
+
*/
|
|
70
|
+
public getInterceptors(metadataKey: symbol): Interceptor[] {
|
|
71
|
+
const metadataList = this.interceptors.get(metadataKey);
|
|
72
|
+
if (!metadataList || metadataList.length === 0) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 返回已排序的拦截器列表
|
|
77
|
+
return metadataList.map((meta) => meta.interceptor);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 检查是否有拦截器
|
|
82
|
+
* @param metadataKey - 元数据键
|
|
83
|
+
* @returns 是否有拦截器
|
|
84
|
+
*/
|
|
85
|
+
public hasInterceptor(metadataKey: symbol): boolean {
|
|
86
|
+
const metadataList = this.interceptors.get(metadataKey);
|
|
87
|
+
return metadataList !== undefined && metadataList.length > 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 获取所有已注册的元数据键
|
|
92
|
+
* @returns 元数据键迭代器
|
|
93
|
+
*/
|
|
94
|
+
public getAllMetadataKeys(): IterableIterator<symbol> {
|
|
95
|
+
return this.interceptors.keys();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 清除所有拦截器
|
|
100
|
+
*/
|
|
101
|
+
public clear(): void {
|
|
102
|
+
this.interceptors.clear();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 移除指定元数据键的所有拦截器
|
|
107
|
+
* @param metadataKey - 元数据键
|
|
108
|
+
*/
|
|
109
|
+
public remove(metadataKey: symbol): void {
|
|
110
|
+
this.interceptors.delete(metadataKey);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 获取拦截器数量
|
|
115
|
+
* @param metadataKey - 元数据键(可选)
|
|
116
|
+
* @returns 拦截器数量
|
|
117
|
+
*/
|
|
118
|
+
public count(metadataKey?: symbol): number {
|
|
119
|
+
if (metadataKey) {
|
|
120
|
+
const metadataList = this.interceptors.get(metadataKey);
|
|
121
|
+
return metadataList ? metadataList.length : 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 返回所有拦截器的总数
|
|
125
|
+
let total = 0;
|
|
126
|
+
for (const metadataList of this.interceptors.values()) {
|
|
127
|
+
total += metadataList.length;
|
|
128
|
+
}
|
|
129
|
+
return total;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import type { InterceptorRegistry } from './interceptor-registry';
|
|
3
|
+
import type { Interceptor, InterceptorMetadata } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 扫描方法上的所有拦截器元数据
|
|
7
|
+
* @param target - 目标对象(控制器实例的原型)
|
|
8
|
+
* @param propertyKey - 方法名
|
|
9
|
+
* @param registry - 拦截器注册表
|
|
10
|
+
* @returns 匹配的拦截器列表(按优先级排序)
|
|
11
|
+
*/
|
|
12
|
+
export function scanInterceptorMetadata(
|
|
13
|
+
target: object,
|
|
14
|
+
propertyKey: string | symbol,
|
|
15
|
+
registry: InterceptorRegistry,
|
|
16
|
+
): Interceptor[] {
|
|
17
|
+
const interceptorMetadataList: InterceptorMetadata[] = [];
|
|
18
|
+
|
|
19
|
+
// 扫描所有已注册的元数据键
|
|
20
|
+
for (const metadataKey of registry.getAllMetadataKeys()) {
|
|
21
|
+
// 检查方法上是否有该元数据
|
|
22
|
+
const metadata = Reflect.getMetadata(metadataKey, target, propertyKey);
|
|
23
|
+
|
|
24
|
+
if (metadata !== undefined && metadata !== null) {
|
|
25
|
+
// 找到匹配的元数据,获取对应的拦截器元数据
|
|
26
|
+
// 使用公共方法获取完整的元数据(包括优先级)
|
|
27
|
+
const metadataList = registry.getInterceptorMetadata(metadataKey);
|
|
28
|
+
if (metadataList.length > 0) {
|
|
29
|
+
interceptorMetadataList.push(...metadataList);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 按优先级排序(数字越小优先级越高)
|
|
35
|
+
interceptorMetadataList.sort((a, b) => a.priority - b.priority);
|
|
36
|
+
|
|
37
|
+
// 返回排序后的拦截器列表
|
|
38
|
+
return interceptorMetadataList.map((meta) => meta.interceptor);
|
|
39
|
+
}
|
|
40
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Container } from '../di/container';
|
|
2
|
+
import type { Context } from '../core/context';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 拦截器接口
|
|
6
|
+
* 定义拦截器的核心执行方法
|
|
7
|
+
*/
|
|
8
|
+
export interface Interceptor {
|
|
9
|
+
/**
|
|
10
|
+
* 执行拦截器逻辑
|
|
11
|
+
* @param target - 目标对象(控制器实例的原型)
|
|
12
|
+
* @param propertyKey - 方法名
|
|
13
|
+
* @param originalMethod - 原始方法
|
|
14
|
+
* @param args - 方法参数
|
|
15
|
+
* @param container - DI 容器
|
|
16
|
+
* @param context - 请求上下文(可选)
|
|
17
|
+
* @returns 方法执行结果
|
|
18
|
+
*/
|
|
19
|
+
execute<T>(
|
|
20
|
+
target: unknown,
|
|
21
|
+
propertyKey: string | symbol,
|
|
22
|
+
originalMethod: (...args: unknown[]) => T | Promise<T>,
|
|
23
|
+
args: unknown[],
|
|
24
|
+
container: Container,
|
|
25
|
+
context?: Context,
|
|
26
|
+
): Promise<T>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 拦截器元数据
|
|
31
|
+
* 用于存储拦截器的注册信息
|
|
32
|
+
*/
|
|
33
|
+
export interface InterceptorMetadata {
|
|
34
|
+
/**
|
|
35
|
+
* 元数据键(用于匹配装饰器)
|
|
36
|
+
*/
|
|
37
|
+
metadataKey: symbol;
|
|
38
|
+
/**
|
|
39
|
+
* 拦截器实例
|
|
40
|
+
*/
|
|
41
|
+
interceptor: Interceptor;
|
|
42
|
+
/**
|
|
43
|
+
* 优先级(数字越小优先级越高,默认 100)
|
|
44
|
+
*/
|
|
45
|
+
priority: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 拦截器注册表 Token
|
|
50
|
+
*/
|
|
51
|
+
export const INTERCEPTOR_REGISTRY_TOKEN = Symbol('@dangao/bun-server:interceptor-registry');
|
|
52
|
+
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CustomMetric,
|
|
3
|
+
MetricDataPoint,
|
|
4
|
+
MetricLabels,
|
|
5
|
+
MetricType,
|
|
6
|
+
MetricValue,
|
|
7
|
+
} from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 指标收集器
|
|
11
|
+
*/
|
|
12
|
+
export class MetricsCollector {
|
|
13
|
+
private counters: Map<string, Map<string, number>> = new Map();
|
|
14
|
+
private gauges: Map<string, Map<string, number>> = new Map();
|
|
15
|
+
private histograms: Map<string, Map<string, number[]>> = new Map();
|
|
16
|
+
private customMetrics: CustomMetric[] = [];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 注册自定义指标
|
|
20
|
+
*/
|
|
21
|
+
public registerCustomMetric(metric: CustomMetric): void {
|
|
22
|
+
this.customMetrics.push(metric);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 增加计数器
|
|
27
|
+
*/
|
|
28
|
+
public incrementCounter(name: string, labels?: MetricLabels, value: number = 1): void {
|
|
29
|
+
const key = this.getKey(name, labels);
|
|
30
|
+
const counterMap = this.counters.get(name) || new Map();
|
|
31
|
+
const current = counterMap.get(key) || 0;
|
|
32
|
+
counterMap.set(key, current + value);
|
|
33
|
+
this.counters.set(name, counterMap);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 设置仪表值
|
|
38
|
+
*/
|
|
39
|
+
public setGauge(name: string, labels: MetricLabels | undefined, value: number): void {
|
|
40
|
+
const key = this.getKey(name, labels);
|
|
41
|
+
const gaugeMap = this.gauges.get(name) || new Map();
|
|
42
|
+
gaugeMap.set(key, value);
|
|
43
|
+
this.gauges.set(name, gaugeMap);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 观察直方图值
|
|
48
|
+
*/
|
|
49
|
+
public observeHistogram(name: string, labels: MetricLabels | undefined, value: number): void {
|
|
50
|
+
const key = this.getKey(name, labels);
|
|
51
|
+
const histogramMap = this.histograms.get(name) || new Map();
|
|
52
|
+
const values = histogramMap.get(key) || [];
|
|
53
|
+
values.push(value);
|
|
54
|
+
histogramMap.set(key, values);
|
|
55
|
+
this.histograms.set(name, histogramMap);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 获取所有指标数据点
|
|
60
|
+
*/
|
|
61
|
+
public async getAllDataPoints(): Promise<MetricDataPoint[]> {
|
|
62
|
+
const dataPoints: MetricDataPoint[] = [];
|
|
63
|
+
|
|
64
|
+
// 收集计数器
|
|
65
|
+
for (const [name, counterMap] of this.counters.entries()) {
|
|
66
|
+
for (const [key, value] of counterMap.entries()) {
|
|
67
|
+
const labels = this.parseKey(key);
|
|
68
|
+
dataPoints.push({
|
|
69
|
+
name,
|
|
70
|
+
type: 'counter',
|
|
71
|
+
value,
|
|
72
|
+
labels: labels && Object.keys(labels).length > 0 ? labels : undefined,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 收集仪表
|
|
78
|
+
for (const [name, gaugeMap] of this.gauges.entries()) {
|
|
79
|
+
for (const [key, value] of gaugeMap.entries()) {
|
|
80
|
+
const labels = this.parseKey(key);
|
|
81
|
+
dataPoints.push({
|
|
82
|
+
name,
|
|
83
|
+
type: 'gauge',
|
|
84
|
+
value,
|
|
85
|
+
labels: labels && Object.keys(labels).length > 0 ? labels : undefined,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 收集直方图(计算统计信息)
|
|
91
|
+
for (const [name, histogramMap] of this.histograms.entries()) {
|
|
92
|
+
for (const [key, values] of histogramMap.entries()) {
|
|
93
|
+
const labels = this.parseKey(key);
|
|
94
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
95
|
+
const count = values.length;
|
|
96
|
+
const buckets = this.calculateBuckets(values);
|
|
97
|
+
|
|
98
|
+
// 添加 sum
|
|
99
|
+
dataPoints.push({
|
|
100
|
+
name: `${name}_sum`,
|
|
101
|
+
type: 'histogram',
|
|
102
|
+
value: sum,
|
|
103
|
+
labels: labels && Object.keys(labels).length > 0 ? labels : undefined,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// 添加 count
|
|
107
|
+
dataPoints.push({
|
|
108
|
+
name: `${name}_count`,
|
|
109
|
+
type: 'histogram',
|
|
110
|
+
value: count,
|
|
111
|
+
labels: labels && Object.keys(labels).length > 0 ? labels : undefined,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// 添加 buckets
|
|
115
|
+
for (const [bucket, bucketCount] of Object.entries(buckets)) {
|
|
116
|
+
dataPoints.push({
|
|
117
|
+
name: `${name}_bucket`,
|
|
118
|
+
type: 'histogram',
|
|
119
|
+
value: bucketCount,
|
|
120
|
+
labels: {
|
|
121
|
+
...labels,
|
|
122
|
+
le: bucket,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 收集自定义指标
|
|
130
|
+
for (const metric of this.customMetrics) {
|
|
131
|
+
try {
|
|
132
|
+
const value = await metric.getValue();
|
|
133
|
+
dataPoints.push({
|
|
134
|
+
name: metric.name,
|
|
135
|
+
type: metric.type,
|
|
136
|
+
value,
|
|
137
|
+
help: metric.help,
|
|
138
|
+
});
|
|
139
|
+
} catch (error) {
|
|
140
|
+
// 忽略自定义指标错误
|
|
141
|
+
console.error(`Failed to collect custom metric ${metric.name}:`, error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return dataPoints;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 重置所有指标
|
|
150
|
+
*/
|
|
151
|
+
public reset(): void {
|
|
152
|
+
this.counters.clear();
|
|
153
|
+
this.gauges.clear();
|
|
154
|
+
this.histograms.clear();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 生成标签键
|
|
159
|
+
*/
|
|
160
|
+
private getKey(name: string, labels?: MetricLabels): string {
|
|
161
|
+
if (!labels || Object.keys(labels).length === 0) {
|
|
162
|
+
return '';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const sortedLabels = Object.keys(labels)
|
|
166
|
+
.sort()
|
|
167
|
+
.map((key) => `${key}="${labels[key]}"`)
|
|
168
|
+
.join(',');
|
|
169
|
+
return `{${sortedLabels}}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 解析标签键
|
|
174
|
+
*/
|
|
175
|
+
private parseKey(key: string): MetricLabels | undefined {
|
|
176
|
+
if (!key || key === '') {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const labels: MetricLabels = {};
|
|
181
|
+
const match = key.match(/\{([^}]+)\}/);
|
|
182
|
+
if (match) {
|
|
183
|
+
const labelPairs = match[1].split(',');
|
|
184
|
+
for (const pair of labelPairs) {
|
|
185
|
+
const [k, v] = pair.split('=');
|
|
186
|
+
if (k && v) {
|
|
187
|
+
labels[k.trim()] = v.trim().replace(/^"|"$/g, '');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return Object.keys(labels).length > 0 ? labels : undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 计算直方图桶
|
|
196
|
+
*/
|
|
197
|
+
private calculateBuckets(values: number[]): Record<string, number> {
|
|
198
|
+
// Prometheus 默认桶:.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, +Inf
|
|
199
|
+
const defaultBuckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
|
|
200
|
+
const buckets: Record<string, number> = {};
|
|
201
|
+
|
|
202
|
+
for (const bucket of defaultBuckets) {
|
|
203
|
+
buckets[bucket.toString()] = values.filter((v) => v <= bucket).length;
|
|
204
|
+
}
|
|
205
|
+
buckets['+Inf'] = values.length;
|
|
206
|
+
|
|
207
|
+
return buckets;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { GET } from '../router/decorators';
|
|
2
|
+
import { Inject } from '../di/decorators';
|
|
3
|
+
import { Controller } from '../controller';
|
|
4
|
+
|
|
5
|
+
import { MetricsCollector } from './collector';
|
|
6
|
+
import { PrometheusFormatter } from './prometheus';
|
|
7
|
+
import { METRICS_SERVICE_TOKEN, METRICS_OPTIONS_TOKEN, type MetricsModuleOptions } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Metrics 控制器
|
|
11
|
+
* 提供 `/metrics` 端点用于 Prometheus 指标导出
|
|
12
|
+
*/
|
|
13
|
+
@Controller('/')
|
|
14
|
+
export class MetricsController {
|
|
15
|
+
private readonly formatter: PrometheusFormatter;
|
|
16
|
+
|
|
17
|
+
public constructor(
|
|
18
|
+
@Inject(METRICS_SERVICE_TOKEN)
|
|
19
|
+
private readonly collector: MetricsCollector,
|
|
20
|
+
@Inject(METRICS_OPTIONS_TOKEN)
|
|
21
|
+
private readonly options?: MetricsModuleOptions,
|
|
22
|
+
) {
|
|
23
|
+
this.formatter = new PrometheusFormatter();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 获取 Prometheus 格式的指标
|
|
28
|
+
*/
|
|
29
|
+
@GET('/metrics')
|
|
30
|
+
public async metrics(): Promise<Response> {
|
|
31
|
+
const dataPoints = await this.collector.getAllDataPoints();
|
|
32
|
+
const prometheusText = this.formatter.format(dataPoints);
|
|
33
|
+
|
|
34
|
+
return new Response(prometheusText, {
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { MetricsModule } from './metrics-module';
|
|
2
|
+
export { MetricsCollector } from './collector';
|
|
3
|
+
export { PrometheusFormatter } from './prometheus';
|
|
4
|
+
export { MetricsController } from './controller';
|
|
5
|
+
export { createHttpMetricsMiddleware } from './middleware';
|
|
6
|
+
export {
|
|
7
|
+
METRICS_SERVICE_TOKEN,
|
|
8
|
+
METRICS_OPTIONS_TOKEN,
|
|
9
|
+
type MetricsModuleOptions,
|
|
10
|
+
type MetricType,
|
|
11
|
+
type MetricLabels,
|
|
12
|
+
type MetricValue,
|
|
13
|
+
type MetricDataPoint,
|
|
14
|
+
type CustomMetric,
|
|
15
|
+
} from './types';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Module, MODULE_METADATA_KEY, type ModuleProvider } from '../di/module';
|
|
2
|
+
|
|
3
|
+
import { MetricsController } from './controller';
|
|
4
|
+
import { MetricsCollector } from './collector';
|
|
5
|
+
import type { MetricsModuleOptions } from './types';
|
|
6
|
+
import { METRICS_SERVICE_TOKEN, METRICS_OPTIONS_TOKEN } from './types';
|
|
7
|
+
|
|
8
|
+
@Module({
|
|
9
|
+
controllers: [MetricsController],
|
|
10
|
+
providers: [],
|
|
11
|
+
})
|
|
12
|
+
export class MetricsModule {
|
|
13
|
+
/**
|
|
14
|
+
* 创建指标监控模块
|
|
15
|
+
* @param options - 模块配置
|
|
16
|
+
*/
|
|
17
|
+
public static forRoot(options: MetricsModuleOptions = {}): typeof MetricsModule {
|
|
18
|
+
const providers: ModuleProvider[] = [];
|
|
19
|
+
|
|
20
|
+
const collector = new MetricsCollector();
|
|
21
|
+
|
|
22
|
+
// 注册自定义指标
|
|
23
|
+
if (options.customMetrics) {
|
|
24
|
+
for (const metric of options.customMetrics) {
|
|
25
|
+
collector.registerCustomMetric(metric);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
providers.push(
|
|
30
|
+
{
|
|
31
|
+
provide: METRICS_SERVICE_TOKEN,
|
|
32
|
+
useValue: collector,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
provide: METRICS_OPTIONS_TOKEN,
|
|
36
|
+
useValue: options,
|
|
37
|
+
},
|
|
38
|
+
MetricsCollector,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// 动态更新模块元数据
|
|
42
|
+
const existingMetadata =
|
|
43
|
+
Reflect.getMetadata(MODULE_METADATA_KEY, MetricsModule) || {};
|
|
44
|
+
const metadata = {
|
|
45
|
+
...existingMetadata,
|
|
46
|
+
controllers: [...(existingMetadata.controllers || []), MetricsController],
|
|
47
|
+
providers: [...(existingMetadata.providers || []), ...providers],
|
|
48
|
+
exports: [
|
|
49
|
+
...(existingMetadata.exports || []),
|
|
50
|
+
METRICS_SERVICE_TOKEN,
|
|
51
|
+
MetricsCollector,
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, MetricsModule);
|
|
55
|
+
|
|
56
|
+
return MetricsModule;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Context } from '../core/context';
|
|
2
|
+
import type { Middleware } from '../middleware';
|
|
3
|
+
import { MetricsCollector } from './collector';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 创建 HTTP 请求指标收集中间件
|
|
7
|
+
*/
|
|
8
|
+
export function createHttpMetricsMiddleware(collector: MetricsCollector): Middleware {
|
|
9
|
+
return async (context: Context, next) => {
|
|
10
|
+
const startTime = Date.now();
|
|
11
|
+
|
|
12
|
+
// 执行请求
|
|
13
|
+
const response = await next();
|
|
14
|
+
|
|
15
|
+
// 计算延迟
|
|
16
|
+
const duration = Date.now() - startTime;
|
|
17
|
+
const durationSeconds = duration / 1000;
|
|
18
|
+
|
|
19
|
+
// 收集请求指标
|
|
20
|
+
const method = context.method;
|
|
21
|
+
const path = context.path;
|
|
22
|
+
const statusCode = response.status;
|
|
23
|
+
|
|
24
|
+
// HTTP 请求总数(计数器)
|
|
25
|
+
collector.incrementCounter('http_requests_total', {
|
|
26
|
+
method,
|
|
27
|
+
path,
|
|
28
|
+
status: statusCode.toString(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// HTTP 请求延迟(直方图)
|
|
32
|
+
collector.observeHistogram('http_request_duration_seconds', {
|
|
33
|
+
method,
|
|
34
|
+
path,
|
|
35
|
+
status: statusCode.toString(),
|
|
36
|
+
}, durationSeconds);
|
|
37
|
+
|
|
38
|
+
// HTTP 请求延迟(摘要,用于 p50, p95, p99)
|
|
39
|
+
collector.observeHistogram('http_request_duration_seconds_summary', {
|
|
40
|
+
method,
|
|
41
|
+
path,
|
|
42
|
+
}, durationSeconds);
|
|
43
|
+
|
|
44
|
+
return response;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { MetricDataPoint } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 将指标数据点转换为 Prometheus 格式
|
|
5
|
+
*/
|
|
6
|
+
export class PrometheusFormatter {
|
|
7
|
+
/**
|
|
8
|
+
* 格式化指标为 Prometheus 文本格式
|
|
9
|
+
*/
|
|
10
|
+
public format(dataPoints: MetricDataPoint[]): string {
|
|
11
|
+
const lines: string[] = [];
|
|
12
|
+
const metricGroups = this.groupByMetricName(dataPoints);
|
|
13
|
+
|
|
14
|
+
for (const [metricName, points] of metricGroups.entries()) {
|
|
15
|
+
// 添加帮助文本(如果有)
|
|
16
|
+
const help = points[0]?.help;
|
|
17
|
+
if (help) {
|
|
18
|
+
lines.push(`# HELP ${metricName} ${help}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 添加类型(如果有)
|
|
22
|
+
const type = points[0]?.type;
|
|
23
|
+
if (type) {
|
|
24
|
+
lines.push(`# TYPE ${metricName} ${type}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 添加数据点
|
|
28
|
+
for (const point of points) {
|
|
29
|
+
const labelString = this.formatLabels(point.labels);
|
|
30
|
+
const line = labelString
|
|
31
|
+
? `${point.name}${labelString} ${point.value}`
|
|
32
|
+
: `${point.name} ${point.value}`;
|
|
33
|
+
lines.push(line);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
lines.push(''); // 空行分隔
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return lines.join('\n');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 按指标名称分组
|
|
44
|
+
*/
|
|
45
|
+
private groupByMetricName(dataPoints: MetricDataPoint[]): Map<string, MetricDataPoint[]> {
|
|
46
|
+
const groups = new Map<string, MetricDataPoint[]>();
|
|
47
|
+
|
|
48
|
+
for (const point of dataPoints) {
|
|
49
|
+
const name = point.name;
|
|
50
|
+
const existing = groups.get(name) || [];
|
|
51
|
+
existing.push(point);
|
|
52
|
+
groups.set(name, existing);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return groups;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 格式化标签
|
|
60
|
+
*/
|
|
61
|
+
private formatLabels(labels?: Record<string, string>): string {
|
|
62
|
+
if (!labels || Object.keys(labels).length === 0) {
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const labelPairs = Object.keys(labels)
|
|
67
|
+
.sort()
|
|
68
|
+
.map((key) => `${key}="${this.escapeLabelValue(labels[key])}"`)
|
|
69
|
+
.join(',');
|
|
70
|
+
return `{${labelPairs}}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 转义标签值
|
|
75
|
+
*/
|
|
76
|
+
private escapeLabelValue(value: string): string {
|
|
77
|
+
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
78
|
+
}
|
|
79
|
+
}
|