@dangao/bun-server 1.0.0 → 1.0.3
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/package.json +4 -2
- 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 +251 -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 +233 -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 +62 -0
- package/src/database/database-module.ts +115 -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 +45 -0
- package/src/database/orm/transaction-interceptor.ts +243 -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 +270 -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 +91 -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 +122 -0
- package/src/router/index.ts +6 -0
- package/src/router/registry.ts +98 -0
- package/src/router/route.ts +140 -0
- package/src/router/router.ts +241 -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 +51 -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/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/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 +48 -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,251 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { Container } from '../di/container';
|
|
3
|
+
import { RouteRegistry } from '../router/registry';
|
|
4
|
+
import type { Context } from '../core/context';
|
|
5
|
+
import { ParamBinder } from './param-binder';
|
|
6
|
+
import { getControllerMetadata, getRouteMetadata } from './metadata';
|
|
7
|
+
import { getClassMiddlewares, getMethodMiddlewares } from '../middleware';
|
|
8
|
+
import type { Middleware } from '../middleware';
|
|
9
|
+
import { getValidationMetadata, validateParameters, ValidationError } from '../validation';
|
|
10
|
+
import { HttpException } from '../error';
|
|
11
|
+
import type { Constructor } from '@/core/types'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 控制器元数据键
|
|
15
|
+
*/
|
|
16
|
+
export const CONTROLLER_METADATA_KEY = Symbol('controller');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 控制器元数据
|
|
20
|
+
*/
|
|
21
|
+
export interface ControllerMetadata {
|
|
22
|
+
/**
|
|
23
|
+
* 基础路径
|
|
24
|
+
*/
|
|
25
|
+
path: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 控制器类
|
|
29
|
+
*/
|
|
30
|
+
target: new (...args: unknown[]) => unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Controller 装饰器
|
|
35
|
+
* 标记类为控制器,并指定基础路径
|
|
36
|
+
* @param path - 控制器基础路径
|
|
37
|
+
*/
|
|
38
|
+
export function Controller(path: string = '') {
|
|
39
|
+
return function (target: Constructor<unknown>) {
|
|
40
|
+
// 保存控制器元数据
|
|
41
|
+
Reflect.defineMetadata(CONTROLLER_METADATA_KEY, { path, target }, target);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 控制器注册表
|
|
47
|
+
* 管理所有控制器及其路由
|
|
48
|
+
*/
|
|
49
|
+
export class ControllerRegistry {
|
|
50
|
+
private static instance: ControllerRegistry;
|
|
51
|
+
private readonly container: Container;
|
|
52
|
+
private readonly controllers = new Map<new (...args: unknown[]) => unknown, unknown>();
|
|
53
|
+
private readonly controllerContainers = new Map<Constructor<unknown>, Container>();
|
|
54
|
+
|
|
55
|
+
private constructor() {
|
|
56
|
+
this.container = new Container();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 获取单例实例
|
|
61
|
+
*/
|
|
62
|
+
public static getInstance(): ControllerRegistry {
|
|
63
|
+
if (!ControllerRegistry.instance) {
|
|
64
|
+
ControllerRegistry.instance = new ControllerRegistry();
|
|
65
|
+
}
|
|
66
|
+
return ControllerRegistry.instance;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 注册控制器
|
|
71
|
+
* @param controllerClass - 控制器类
|
|
72
|
+
*/
|
|
73
|
+
public register(controllerClass: Constructor<unknown>, container?: Container): void {
|
|
74
|
+
const targetContainer = container ?? this.container;
|
|
75
|
+
this.controllerContainers.set(controllerClass, targetContainer);
|
|
76
|
+
if (!targetContainer.isRegistered(controllerClass)) {
|
|
77
|
+
targetContainer.register(controllerClass);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 获取控制器元数据
|
|
81
|
+
const metadata = getControllerMetadata(controllerClass);
|
|
82
|
+
if (!metadata) {
|
|
83
|
+
throw new Error(`Controller ${controllerClass.name} must be decorated with @Controller()`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 获取路由元数据(从原型获取)
|
|
87
|
+
const prototype = controllerClass.prototype;
|
|
88
|
+
const routes = getRouteMetadata(prototype);
|
|
89
|
+
|
|
90
|
+
if (!routes || routes.length === 0) {
|
|
91
|
+
// 没有路由,跳过(可能是装饰器还没有执行)
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const classMiddlewares = getClassMiddlewares(controllerClass);
|
|
96
|
+
|
|
97
|
+
// 注册所有路由
|
|
98
|
+
const basePath = metadata.path;
|
|
99
|
+
const registry = RouteRegistry.getInstance();
|
|
100
|
+
|
|
101
|
+
for (const route of routes) {
|
|
102
|
+
// 组合基础路径和方法路径
|
|
103
|
+
const fullPath = this.combinePaths(basePath, route.path);
|
|
104
|
+
|
|
105
|
+
// 获取方法名:优先使用 propertyKey,否则通过函数引用查找
|
|
106
|
+
let propertyKey = route.propertyKey;
|
|
107
|
+
if (!propertyKey) {
|
|
108
|
+
// 通过函数引用查找方法名
|
|
109
|
+
const prototype = controllerClass.prototype;
|
|
110
|
+
const propertyNames = Object.getOwnPropertyNames(prototype);
|
|
111
|
+
for (const key of propertyNames) {
|
|
112
|
+
if (key === 'constructor') continue;
|
|
113
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
|
|
114
|
+
if (descriptor && descriptor.value === route.handler) {
|
|
115
|
+
propertyKey = key;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!propertyKey) {
|
|
122
|
+
// 如果仍然找不到方法名,跳过这个路由
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 创建路由处理器,支持控制器方法调用
|
|
127
|
+
const handler = async (context: Context): Promise<Response> => {
|
|
128
|
+
try {
|
|
129
|
+
// 从容器解析控制器实例
|
|
130
|
+
const controllerContainer = this.controllerContainers.get(controllerClass) ?? this.container;
|
|
131
|
+
const controllerInstance = controllerContainer.resolve(controllerClass);
|
|
132
|
+
|
|
133
|
+
// 绑定参数(从原型获取元数据,因为装饰器元数据保存在原型上)
|
|
134
|
+
const prototype = controllerClass.prototype;
|
|
135
|
+
const params = await ParamBinder.bind(prototype, propertyKey!, context, controllerContainer);
|
|
136
|
+
|
|
137
|
+
// 执行参数验证
|
|
138
|
+
const validationMetadata = getValidationMetadata(prototype, propertyKey!);
|
|
139
|
+
if (validationMetadata.length > 0) {
|
|
140
|
+
validateParameters(params, validationMetadata);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 调用控制器方法
|
|
144
|
+
// 优先从实例获取,如果不存在则从原型获取
|
|
145
|
+
let method = (controllerInstance as Record<string, (...args: unknown[]) => unknown>)[propertyKey!];
|
|
146
|
+
if (!method || typeof method !== 'function') {
|
|
147
|
+
// 从构造函数原型获取方法
|
|
148
|
+
const prototype = controllerClass.prototype;
|
|
149
|
+
method = prototype[propertyKey!];
|
|
150
|
+
}
|
|
151
|
+
if (!method || typeof method !== 'function') {
|
|
152
|
+
throw new Error(`Method ${propertyKey} not found on controller ${controllerClass.name}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 检查是否有事务装饰器
|
|
156
|
+
let result: unknown;
|
|
157
|
+
try {
|
|
158
|
+
const { TransactionInterceptor } = await import('../database/orm/transaction-interceptor');
|
|
159
|
+
const { getTransactionMetadata } = await import('../database/orm/transaction-decorator');
|
|
160
|
+
const hasTransaction = getTransactionMetadata(prototype, propertyKey!);
|
|
161
|
+
if (hasTransaction) {
|
|
162
|
+
result = TransactionInterceptor.executeWithTransaction(
|
|
163
|
+
prototype,
|
|
164
|
+
propertyKey!,
|
|
165
|
+
method,
|
|
166
|
+
params,
|
|
167
|
+
controllerContainer,
|
|
168
|
+
);
|
|
169
|
+
} else {
|
|
170
|
+
result = method.apply(controllerInstance, params);
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
// 如果导入失败或执行失败,回退到直接调用
|
|
174
|
+
result = method.apply(controllerInstance, params);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 处理异步结果
|
|
178
|
+
const responseData = await Promise.resolve(result);
|
|
179
|
+
|
|
180
|
+
// 如果已经是 Response 对象,直接返回
|
|
181
|
+
if (responseData instanceof Response) {
|
|
182
|
+
return responseData;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 创建响应
|
|
186
|
+
return context.createResponse(responseData);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
// 使用全局错误处理器,确保错误码和国际化正确应用
|
|
189
|
+
const { handleError } = await import('../error/handler');
|
|
190
|
+
return await handleError(error, context);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// 组合中间件:类级 + 方法级
|
|
195
|
+
const middlewares: Middleware[] = [...classMiddlewares];
|
|
196
|
+
if (propertyKey) {
|
|
197
|
+
middlewares.push(...getMethodMiddlewares(prototype, propertyKey));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 注册路由,传递控制器和方法信息
|
|
201
|
+
registry.register(route.method, fullPath, handler, middlewares, controllerClass, propertyKey);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 组合路径
|
|
207
|
+
* @param basePath - 基础路径
|
|
208
|
+
* @param methodPath - 方法路径
|
|
209
|
+
* @returns 组合后的路径
|
|
210
|
+
*/
|
|
211
|
+
private combinePaths(basePath: string, methodPath: string): string {
|
|
212
|
+
// 规范化路径:确保 basePath 以 / 开头,不以 / 结尾
|
|
213
|
+
const base = basePath.replace(/\/$/, '').replace(/^\/?/, '/');
|
|
214
|
+
// methodPath 移除前导斜杠
|
|
215
|
+
const method = methodPath.replace(/^\//, '');
|
|
216
|
+
|
|
217
|
+
if (!method) {
|
|
218
|
+
// 如果方法路径为空,返回基础路径
|
|
219
|
+
return base === '/' ? '/' : base;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 组合路径
|
|
223
|
+
return base + '/' + method;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 获取 DI 容器
|
|
228
|
+
* @returns DI 容器
|
|
229
|
+
*/
|
|
230
|
+
public getContainer(): Container {
|
|
231
|
+
return this.container;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 获取所有已注册的控制器类
|
|
236
|
+
* @returns 控制器类数组
|
|
237
|
+
*/
|
|
238
|
+
public getRegisteredControllers(): Constructor<unknown>[] {
|
|
239
|
+
return Array.from(this.controllerContainers.keys());
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 清除所有控制器注册和容器状态(主要用于测试)
|
|
244
|
+
*/
|
|
245
|
+
public clear(): void {
|
|
246
|
+
this.controllers.clear();
|
|
247
|
+
this.container.clear();
|
|
248
|
+
this.controllerContainers.clear();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 参数元数据键
|
|
5
|
+
*/
|
|
6
|
+
const PARAM_METADATA_KEY = Symbol('param');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 参数类型枚举
|
|
10
|
+
*/
|
|
11
|
+
export enum ParamType {
|
|
12
|
+
BODY = 'body',
|
|
13
|
+
QUERY = 'query',
|
|
14
|
+
PARAM = 'param',
|
|
15
|
+
HEADER = 'header',
|
|
16
|
+
SESSION = 'session',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 参数元数据
|
|
21
|
+
*/
|
|
22
|
+
export interface ParamMetadata {
|
|
23
|
+
type: ParamType;
|
|
24
|
+
key?: string;
|
|
25
|
+
index: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 参数装饰器工厂
|
|
30
|
+
* @param type - 参数类型
|
|
31
|
+
* @param key - 参数键(可选)
|
|
32
|
+
* @returns 参数装饰器
|
|
33
|
+
*/
|
|
34
|
+
export function createParamDecorator(type: ParamType, key?: string) {
|
|
35
|
+
return function (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
|
|
36
|
+
const existingParams: ParamMetadata[] =
|
|
37
|
+
Reflect.getMetadata(PARAM_METADATA_KEY, target, propertyKey as string) || [];
|
|
38
|
+
existingParams.push({ type, key, index: parameterIndex });
|
|
39
|
+
Reflect.defineMetadata(PARAM_METADATA_KEY, existingParams, target, propertyKey as string);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Body 参数装饰器
|
|
45
|
+
* @param key - 参数键(可选,用于提取对象中的特定字段)
|
|
46
|
+
*/
|
|
47
|
+
export function Body(key?: string) {
|
|
48
|
+
return createParamDecorator(ParamType.BODY, key);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Query 参数装饰器
|
|
53
|
+
* @param key - 查询参数键
|
|
54
|
+
*/
|
|
55
|
+
export function Query(key: string) {
|
|
56
|
+
return createParamDecorator(ParamType.QUERY, key);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Param 参数装饰器(路径参数)
|
|
61
|
+
* @param key - 路径参数键
|
|
62
|
+
*/
|
|
63
|
+
export function Param(key: string) {
|
|
64
|
+
return createParamDecorator(ParamType.PARAM, key);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Header 参数装饰器
|
|
69
|
+
* @param key - 请求头键
|
|
70
|
+
*/
|
|
71
|
+
export function Header(key: string) {
|
|
72
|
+
return createParamDecorator(ParamType.HEADER, key);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 获取参数元数据
|
|
77
|
+
* @param target - 目标对象
|
|
78
|
+
* @param propertyKey - 属性键
|
|
79
|
+
* @returns 参数元数据列表
|
|
80
|
+
*/
|
|
81
|
+
export function getParamMetadata(target: any, propertyKey: string): ParamMetadata[] {
|
|
82
|
+
return Reflect.getMetadata(PARAM_METADATA_KEY, target, propertyKey) || [];
|
|
83
|
+
}
|
|
84
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Body, Query, Param, Header, getParamMetadata, ParamType } from './decorators';
|
|
2
|
+
export type { ParamMetadata } from './decorators';
|
|
3
|
+
export { ParamBinder } from './param-binder';
|
|
4
|
+
export { Controller, ControllerRegistry } from './controller';
|
|
5
|
+
export type { ControllerMetadata } from './controller';
|
|
6
|
+
export { getControllerMetadata, getRouteMetadata } from './metadata';
|
|
7
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import type { ControllerMetadata } from './controller';
|
|
3
|
+
import { CONTROLLER_METADATA_KEY } from './controller';
|
|
4
|
+
import type { RouteMetadata } from '../router/decorators';
|
|
5
|
+
import { ROUTE_METADATA_KEY } from '../router/decorators';
|
|
6
|
+
import type { Constructor } from '@/core/types'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 获取控制器元数据
|
|
10
|
+
* @param target - 控制器类
|
|
11
|
+
* @returns 控制器元数据
|
|
12
|
+
*/
|
|
13
|
+
export function getControllerMetadata(
|
|
14
|
+
target: Constructor<unknown>,
|
|
15
|
+
): ControllerMetadata | undefined {
|
|
16
|
+
return Reflect.getMetadata(CONTROLLER_METADATA_KEY, target);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 获取路由元数据
|
|
21
|
+
* @param target - 控制器原型
|
|
22
|
+
* @returns 路由元数据列表
|
|
23
|
+
*/
|
|
24
|
+
export function getRouteMetadata(target: unknown): RouteMetadata[] {
|
|
25
|
+
return Reflect.getMetadata(ROUTE_METADATA_KEY, target as Object) || [];
|
|
26
|
+
}
|
|
27
|
+
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { Context } from '../core/context';
|
|
2
|
+
import { getParamMetadata, ParamType, type ParamMetadata } from './decorators';
|
|
3
|
+
import { Container } from '../di/container';
|
|
4
|
+
import { SessionService } from '../session/service';
|
|
5
|
+
import { SESSION_SERVICE_TOKEN } from '../session/types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 参数绑定器
|
|
9
|
+
* 根据装饰器元数据绑定参数
|
|
10
|
+
*/
|
|
11
|
+
export class ParamBinder {
|
|
12
|
+
/**
|
|
13
|
+
* 绑定参数
|
|
14
|
+
* @param target - 目标对象
|
|
15
|
+
* @param propertyKey - 属性键
|
|
16
|
+
* @param context - 请求上下文
|
|
17
|
+
* @param container - DI 容器(用于解析 Session)
|
|
18
|
+
* @returns 参数数组
|
|
19
|
+
*/
|
|
20
|
+
public static async bind(
|
|
21
|
+
target: any,
|
|
22
|
+
propertyKey: string,
|
|
23
|
+
context: Context,
|
|
24
|
+
container?: Container,
|
|
25
|
+
): Promise<unknown[]> {
|
|
26
|
+
const metadata = getParamMetadata(target, propertyKey);
|
|
27
|
+
const params: unknown[] = [];
|
|
28
|
+
|
|
29
|
+
// 按索引排序
|
|
30
|
+
metadata.sort((a, b) => a.index - b.index);
|
|
31
|
+
|
|
32
|
+
// 绑定参数
|
|
33
|
+
for (const meta of metadata) {
|
|
34
|
+
const value = await this.getValue(meta, context, container);
|
|
35
|
+
params[meta.index] = value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 确保参数数组是连续的(填充 undefined)
|
|
39
|
+
// 例如:如果只有 index 1 的参数,params 应该是 [undefined, value]
|
|
40
|
+
const maxIndex = metadata.length > 0 ? Math.max(...metadata.map((m) => m.index)) : -1;
|
|
41
|
+
for (let i = 0; i <= maxIndex; i++) {
|
|
42
|
+
if (!(i in params)) {
|
|
43
|
+
params[i] = undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return params;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 获取参数值
|
|
52
|
+
* @param meta - 参数元数据
|
|
53
|
+
* @param context - 请求上下文
|
|
54
|
+
* @param container - DI 容器(用于解析 Session)
|
|
55
|
+
* @returns 参数值
|
|
56
|
+
*/
|
|
57
|
+
private static async getValue(meta: ParamMetadata, context: Context, container?: Container): Promise<unknown> {
|
|
58
|
+
switch (meta.type) {
|
|
59
|
+
case ParamType.BODY:
|
|
60
|
+
return await this.getBodyValue(meta.key, context);
|
|
61
|
+
case ParamType.QUERY:
|
|
62
|
+
// Query 装饰器要求提供 key
|
|
63
|
+
if (!meta.key) {
|
|
64
|
+
throw new Error('@Query() decorator requires a key parameter');
|
|
65
|
+
}
|
|
66
|
+
return this.getQueryValue(meta.key, context);
|
|
67
|
+
case ParamType.PARAM:
|
|
68
|
+
// Param 装饰器要求提供 key
|
|
69
|
+
if (!meta.key) {
|
|
70
|
+
throw new Error('@Param() decorator requires a key parameter');
|
|
71
|
+
}
|
|
72
|
+
return this.getParamValue(meta.key, context);
|
|
73
|
+
case ParamType.HEADER:
|
|
74
|
+
// Header 装饰器要求提供 key
|
|
75
|
+
if (!meta.key) {
|
|
76
|
+
throw new Error('@Header() decorator requires a key parameter');
|
|
77
|
+
}
|
|
78
|
+
return this.getHeaderValue(meta.key, context);
|
|
79
|
+
case ParamType.SESSION:
|
|
80
|
+
// Session 装饰器需要 container
|
|
81
|
+
if (!container) {
|
|
82
|
+
throw new Error('@Session() decorator requires a Container instance');
|
|
83
|
+
}
|
|
84
|
+
// 从 Context 中获取 Session(由中间件设置)
|
|
85
|
+
const session = (context as unknown as { session?: unknown }).session;
|
|
86
|
+
if (session) {
|
|
87
|
+
return session;
|
|
88
|
+
}
|
|
89
|
+
// 如果没有 Session,尝试创建新 Session
|
|
90
|
+
try {
|
|
91
|
+
const sessionService = container.resolve<SessionService>(
|
|
92
|
+
SESSION_SERVICE_TOKEN,
|
|
93
|
+
);
|
|
94
|
+
if (sessionService) {
|
|
95
|
+
const newSession = await sessionService.create();
|
|
96
|
+
(context as unknown as { session: typeof newSession }).session =
|
|
97
|
+
newSession;
|
|
98
|
+
(context as unknown as { sessionId: string }).sessionId = newSession.id;
|
|
99
|
+
return newSession;
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// SessionService 未注册,返回 undefined
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
default:
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 获取 Body 值
|
|
112
|
+
* @param key - 键(可选)
|
|
113
|
+
* @param context - 请求上下文
|
|
114
|
+
* @returns Body 值
|
|
115
|
+
*/
|
|
116
|
+
private static async getBodyValue(key: string | undefined, context: Context): Promise<unknown> {
|
|
117
|
+
const body = await context.getBody();
|
|
118
|
+
if (!key) {
|
|
119
|
+
return body;
|
|
120
|
+
}
|
|
121
|
+
if (typeof body === 'object' && body !== null) {
|
|
122
|
+
return (body as Record<string, unknown>)[key];
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 获取 Query 值
|
|
129
|
+
* @param key - 键
|
|
130
|
+
* @param context - 请求上下文
|
|
131
|
+
* @returns Query 值
|
|
132
|
+
*/
|
|
133
|
+
private static getQueryValue(key: string, context: Context): string | null {
|
|
134
|
+
return context.getQuery(key);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 获取 Param 值
|
|
139
|
+
* @param key - 键
|
|
140
|
+
* @param context - 请求上下文
|
|
141
|
+
* @returns Param 值
|
|
142
|
+
*/
|
|
143
|
+
private static getParamValue(key: string, context: Context): string | undefined {
|
|
144
|
+
return context.getParam(key);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 获取 Header 值
|
|
149
|
+
* @param key - 键
|
|
150
|
+
* @param context - 请求上下文
|
|
151
|
+
* @returns Header 值
|
|
152
|
+
*/
|
|
153
|
+
private static getHeaderValue(key: string, context: Context): string | null {
|
|
154
|
+
return context.getHeader(key);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|