@dangao/bun-server 1.8.1 → 1.8.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/dist/controller/controller.d.ts.map +1 -1
- package/dist/core/application.d.ts.map +1 -1
- package/dist/error/handler.d.ts.map +1 -1
- package/dist/index.js +98 -21
- package/dist/middleware/builtin/error-handler.d.ts.map +1 -1
- package/dist/middleware/builtin/logger.d.ts.map +1 -1
- package/dist/router/decorators.d.ts +10 -10
- package/dist/router/decorators.d.ts.map +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/docs/api.md +194 -81
- package/docs/extensions.md +53 -0
- package/docs/guide.md +243 -1
- package/docs/microservice-config-center.md +73 -74
- package/docs/microservice-nacos.md +89 -90
- package/docs/microservice-service-registry.md +85 -86
- package/docs/microservice.md +142 -137
- package/docs/request-lifecycle.md +45 -4
- package/docs/symbol-interface-pattern.md +106 -106
- package/docs/zh/api.md +458 -18
- package/docs/zh/extensions.md +53 -0
- package/docs/zh/guide.md +251 -4
- package/docs/zh/microservice-config-center.md +258 -0
- package/docs/zh/microservice-nacos.md +346 -0
- package/docs/zh/microservice-service-registry.md +306 -0
- package/docs/zh/microservice.md +680 -0
- package/docs/zh/request-lifecycle.md +43 -5
- package/package.json +1 -1
- package/src/controller/controller.ts +10 -2
- package/src/core/application.ts +11 -0
- package/src/error/handler.ts +17 -0
- package/src/middleware/builtin/error-handler.ts +10 -0
- package/src/middleware/builtin/logger.ts +17 -0
- package/src/router/decorators.ts +15 -15
- package/src/router/router.ts +18 -1
- package/tests/controller/path-combination.test.ts +135 -0
|
@@ -20,6 +20,10 @@ HTTP Request
|
|
|
20
20
|
└─────────────────────────────────────┘
|
|
21
21
|
↓
|
|
22
22
|
┌─────────────────────────────────────┐
|
|
23
|
+
│ 守卫 │
|
|
24
|
+
└─────────────────────────────────────┘
|
|
25
|
+
↓
|
|
26
|
+
┌─────────────────────────────────────┐
|
|
23
27
|
│ 拦截器(前置) │
|
|
24
28
|
└─────────────────────────────────────┘
|
|
25
29
|
↓
|
|
@@ -167,7 +171,39 @@ class UserController {
|
|
|
167
171
|
}
|
|
168
172
|
```
|
|
169
173
|
|
|
170
|
-
## 4.
|
|
174
|
+
## 4. 守卫
|
|
175
|
+
|
|
176
|
+
守卫在路由匹配之后、拦截器之前执行,提供细粒度的访问控制。它们可以访问 `ExecutionContext`,提供关于当前请求的丰富信息。
|
|
177
|
+
|
|
178
|
+
### 执行顺序
|
|
179
|
+
|
|
180
|
+
1. **全局守卫** - 通过 `SecurityModule.forRoot({ globalGuards: [...] })` 注册
|
|
181
|
+
2. **控制器守卫** - 通过 `@UseGuards()` 应用于控制器类
|
|
182
|
+
3. **方法守卫** - 通过 `@UseGuards()` 应用于方法
|
|
183
|
+
|
|
184
|
+
### 内置守卫
|
|
185
|
+
|
|
186
|
+
- `AuthGuard`:要求认证
|
|
187
|
+
- `OptionalAuthGuard`:可选认证
|
|
188
|
+
- `RolesGuard`:基于角色的授权(与 `@Roles()` 装饰器一起使用)
|
|
189
|
+
|
|
190
|
+
### 示例
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
@Controller('/api/admin')
|
|
194
|
+
@UseGuards(AuthGuard, RolesGuard)
|
|
195
|
+
class AdminController {
|
|
196
|
+
@GET('/dashboard')
|
|
197
|
+
@Roles('admin')
|
|
198
|
+
public dashboard() {
|
|
199
|
+
return { message: '管理员仪表板' };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
详细文档请参阅 [守卫](./guards.md)。
|
|
205
|
+
|
|
206
|
+
## 5. 拦截器(前置处理)
|
|
171
207
|
|
|
172
208
|
拦截器在控制器方法之前和之后运行。前置拦截器按顺序执行:
|
|
173
209
|
|
|
@@ -200,7 +236,7 @@ class ApiController {}
|
|
|
200
236
|
- 响应转换
|
|
201
237
|
- 性能监控
|
|
202
238
|
|
|
203
|
-
##
|
|
239
|
+
## 6. 参数绑定和验证
|
|
204
240
|
|
|
205
241
|
### 参数装饰器
|
|
206
242
|
|
|
@@ -208,9 +244,11 @@ class ApiController {}
|
|
|
208
244
|
|--------|------|------|
|
|
209
245
|
| `@Param(name)` | URL 路径参数 | `/users/:id` → `@Param('id')` |
|
|
210
246
|
| `@Query(name)` | 查询字符串 | `?page=1` → `@Query('page')` |
|
|
247
|
+
| `@QueryMap()` | 所有查询参数 | `?page=1&limit=10` → `@QueryMap()` 返回 `{ page: '1', limit: '10' }` |
|
|
211
248
|
| `@Body()` | 请求体 | JSON 请求体 |
|
|
212
249
|
| `@Body(name)` | 请求体属性 | `body.name` → `@Body('name')` |
|
|
213
250
|
| `@Header(name)` | 请求头 | `@Header('Authorization')` |
|
|
251
|
+
| `@HeaderMap()` | 所有请求头 | `@HeaderMap()` 返回所有请求头作为对象 |
|
|
214
252
|
| `@Context()` | 完整上下文 | 请求上下文对象 |
|
|
215
253
|
| `@Session()` | 会话数据 | 会话对象 |
|
|
216
254
|
|
|
@@ -262,7 +300,7 @@ public createUser(
|
|
|
262
300
|
}
|
|
263
301
|
```
|
|
264
302
|
|
|
265
|
-
##
|
|
303
|
+
## 7. 控制器方法执行
|
|
266
304
|
|
|
267
305
|
验证通过后,使用已解析的依赖和绑定的参数调用控制器方法。
|
|
268
306
|
|
|
@@ -294,7 +332,7 @@ class UserController {
|
|
|
294
332
|
- **void** - 空响应(204)
|
|
295
333
|
- **Promise** - 异步操作
|
|
296
334
|
|
|
297
|
-
##
|
|
335
|
+
## 8. 拦截器(后置处理)
|
|
298
336
|
|
|
299
337
|
处理器执行后,后置拦截器按相反顺序运行:
|
|
300
338
|
|
|
@@ -318,7 +356,7 @@ class TransformInterceptor implements Interceptor {
|
|
|
318
356
|
}
|
|
319
357
|
```
|
|
320
358
|
|
|
321
|
-
##
|
|
359
|
+
## 9. 异常过滤器
|
|
322
360
|
|
|
323
361
|
如果在请求生命周期中抛出任何异常,它会被异常过滤器捕获。
|
|
324
362
|
|
package/package.json
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
InterceptorChain,
|
|
16
16
|
scanInterceptorMetadata,
|
|
17
17
|
} from '../interceptor';
|
|
18
|
+
import { LoggerManager } from '@dangao/logsmith';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* 控制器元数据键
|
|
@@ -212,6 +213,13 @@ export class ControllerRegistry {
|
|
|
212
213
|
// 创建响应
|
|
213
214
|
return context.createResponse(responseData);
|
|
214
215
|
} catch (error) {
|
|
216
|
+
LoggerManager.getLogger().debug('Controller handler error', {
|
|
217
|
+
controller: controllerClass.name,
|
|
218
|
+
method: propertyKey,
|
|
219
|
+
path: context.path,
|
|
220
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
221
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
222
|
+
});
|
|
215
223
|
// 使用全局错误处理器,确保错误码和国际化正确应用
|
|
216
224
|
const { handleError } = await import('../error/handler');
|
|
217
225
|
return await handleError(error, context);
|
|
@@ -258,8 +266,8 @@ export class ControllerRegistry {
|
|
|
258
266
|
base = '/';
|
|
259
267
|
}
|
|
260
268
|
|
|
261
|
-
// 规范化 methodPath
|
|
262
|
-
const method = methodPath.replace(/^\/+/, '');
|
|
269
|
+
// 规范化 methodPath:移除前导斜杠(支持 undefined,如 @GET() 无参)
|
|
270
|
+
const method = (methodPath ?? '').replace(/^\/+/, '');
|
|
263
271
|
|
|
264
272
|
if (!method) {
|
|
265
273
|
// 如果方法路径为空,返回基础路径
|
package/src/core/application.ts
CHANGED
|
@@ -242,6 +242,13 @@ export class Application {
|
|
|
242
242
|
* @returns 响应对象
|
|
243
243
|
*/
|
|
244
244
|
private async handleRequest(context: Context): Promise<Response> {
|
|
245
|
+
const logger = LoggerManager.getLogger();
|
|
246
|
+
logger.debug('[Request] Incoming', {
|
|
247
|
+
method: context.method,
|
|
248
|
+
path: context.path,
|
|
249
|
+
url: context.url?.href,
|
|
250
|
+
});
|
|
251
|
+
|
|
245
252
|
// 使用 AsyncLocalStorage 包裹请求处理,确保所有中间件和控制器都在请求上下文中执行
|
|
246
253
|
return await contextStore.run(context, async () => {
|
|
247
254
|
// 对于 POST、PUT、PATCH 请求,提前解析 body 并缓存
|
|
@@ -265,6 +272,10 @@ export class Application {
|
|
|
265
272
|
return response;
|
|
266
273
|
}
|
|
267
274
|
|
|
275
|
+
logger.debug('[Router] No route matched', {
|
|
276
|
+
method: context.method,
|
|
277
|
+
path: context.path,
|
|
278
|
+
});
|
|
268
279
|
context.setStatus(404);
|
|
269
280
|
return context.createResponse({ error: 'Not Found' });
|
|
270
281
|
});
|
package/src/error/handler.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { HttpException } from './http-exception';
|
|
|
3
3
|
import { ExceptionFilterRegistry } from './filter';
|
|
4
4
|
import { ValidationError } from '../validation';
|
|
5
5
|
import { ErrorMessageI18n } from './i18n';
|
|
6
|
+
import { LoggerManager } from '@dangao/logsmith';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 全局错误处理
|
|
@@ -16,6 +17,15 @@ export async function handleError(error: unknown, context: Context): Promise<Res
|
|
|
16
17
|
|
|
17
18
|
if (error instanceof HttpException) {
|
|
18
19
|
context.setStatus(error.status);
|
|
20
|
+
if (error.status >= 400) {
|
|
21
|
+
LoggerManager.getLogger().debug('HttpException', {
|
|
22
|
+
method: context.method,
|
|
23
|
+
path: context.path,
|
|
24
|
+
status: error.status,
|
|
25
|
+
code: error.code,
|
|
26
|
+
message: error.message,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
19
29
|
|
|
20
30
|
// 如果异常有错误码,尝试国际化消息
|
|
21
31
|
let errorMessage = error.message;
|
|
@@ -56,7 +66,14 @@ export async function handleError(error: unknown, context: Context): Promise<Res
|
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
59
70
|
context.setStatus(500);
|
|
71
|
+
LoggerManager.getLogger().debug('Internal error (500)', {
|
|
72
|
+
method: context.method,
|
|
73
|
+
path: context.path,
|
|
74
|
+
message,
|
|
75
|
+
stack,
|
|
76
|
+
});
|
|
60
77
|
return context.createResponse({
|
|
61
78
|
error: 'Internal Server Error',
|
|
62
79
|
details: process.env.NODE_ENV === 'production' ? undefined : message,
|
|
@@ -41,12 +41,22 @@ export function createErrorHandlingMiddleware(
|
|
|
41
41
|
try {
|
|
42
42
|
return await next();
|
|
43
43
|
} catch (error) {
|
|
44
|
+
const routeHandler = (context as { routeHandler?: { controller?: { name?: string }; method?: string } }).routeHandler;
|
|
44
45
|
log(error, { method: context.method, path: context.path });
|
|
45
46
|
logger.error("Unhandled error", {
|
|
46
47
|
method: context.method,
|
|
47
48
|
path: context.path,
|
|
48
49
|
error,
|
|
49
50
|
});
|
|
51
|
+
logger.debug("Unhandled error details", {
|
|
52
|
+
method: context.method,
|
|
53
|
+
path: context.path,
|
|
54
|
+
routeHandler: routeHandler
|
|
55
|
+
? `${routeHandler.controller?.name ?? 'unknown'}.${routeHandler.method ?? 'unknown'}`
|
|
56
|
+
: undefined,
|
|
57
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
58
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
59
|
+
});
|
|
50
60
|
|
|
51
61
|
if (error instanceof Response) {
|
|
52
62
|
return error as Response;
|
|
@@ -38,6 +38,14 @@ export function createLoggerMiddleware(
|
|
|
38
38
|
} finally {
|
|
39
39
|
const status = response?.status ?? context.statusCode ?? 200;
|
|
40
40
|
log(`${prefix} ${context.method} ${context.path} ${status}`);
|
|
41
|
+
if (status >= 400) {
|
|
42
|
+
const logger = LoggerManager.getLogger();
|
|
43
|
+
logger.debug(`${prefix} Error response`, {
|
|
44
|
+
method: context.method,
|
|
45
|
+
path: context.path,
|
|
46
|
+
statusCode: status,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
41
49
|
}
|
|
42
50
|
};
|
|
43
51
|
}
|
|
@@ -85,6 +93,15 @@ export function createRequestLoggingMiddleware(
|
|
|
85
93
|
}ms`,
|
|
86
94
|
error instanceof Error ? { error: error.message } : undefined,
|
|
87
95
|
);
|
|
96
|
+
if (error instanceof Error) {
|
|
97
|
+
LoggerManager.getLogger().debug(`${prefix} Request error details`, {
|
|
98
|
+
method: context.method,
|
|
99
|
+
path: context.path,
|
|
100
|
+
durationMs: duration.toFixed(2),
|
|
101
|
+
message: error.message,
|
|
102
|
+
stack: error.stack,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
88
105
|
throw error;
|
|
89
106
|
}
|
|
90
107
|
};
|
package/src/router/decorators.ts
CHANGED
|
@@ -25,10 +25,10 @@ export interface RouteMetadata {
|
|
|
25
25
|
/**
|
|
26
26
|
* 路由装饰器工厂
|
|
27
27
|
* @param method - HTTP 方法
|
|
28
|
-
* @param path -
|
|
28
|
+
* @param path - 路由路径(可选,默认 '',即 @GET() 等价于根路径)
|
|
29
29
|
* @returns 方法装饰器
|
|
30
30
|
*/
|
|
31
|
-
function createRouteDecorator(method: HttpMethod, path: string) {
|
|
31
|
+
function createRouteDecorator(method: HttpMethod, path: string = '') {
|
|
32
32
|
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
|
33
33
|
// 注意:装饰器应用顺序问题
|
|
34
34
|
// 方法装饰器(@GET)在类装饰器(@Controller)之前应用
|
|
@@ -72,9 +72,9 @@ function createRouteDecorator(method: HttpMethod, path: string) {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
existingRoutes.push({
|
|
76
|
-
method,
|
|
77
|
-
path,
|
|
75
|
+
existingRoutes.push({
|
|
76
|
+
method,
|
|
77
|
+
path: path ?? '',
|
|
78
78
|
handler: descriptor.value as RouteHandler,
|
|
79
79
|
propertyKey: propertyKeyStr || undefined,
|
|
80
80
|
});
|
|
@@ -84,40 +84,40 @@ function createRouteDecorator(method: HttpMethod, path: string) {
|
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* GET 路由装饰器
|
|
87
|
-
* @param path -
|
|
87
|
+
* @param path - 路由路径(可选,默认 '',即 @GET() 映射到控制器基础路径或 /)
|
|
88
88
|
*/
|
|
89
|
-
export function GET(path: string) {
|
|
89
|
+
export function GET(path: string = '') {
|
|
90
90
|
return createRouteDecorator('GET', path);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
94
|
* POST 路由装饰器
|
|
95
|
-
* @param path -
|
|
95
|
+
* @param path - 路由路径(可选,默认 '')
|
|
96
96
|
*/
|
|
97
|
-
export function POST(path: string) {
|
|
97
|
+
export function POST(path: string = '') {
|
|
98
98
|
return createRouteDecorator('POST', path);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
102
|
* PUT 路由装饰器
|
|
103
|
-
* @param path -
|
|
103
|
+
* @param path - 路由路径(可选,默认 '')
|
|
104
104
|
*/
|
|
105
|
-
export function PUT(path: string) {
|
|
105
|
+
export function PUT(path: string = '') {
|
|
106
106
|
return createRouteDecorator('PUT', path);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* DELETE 路由装饰器
|
|
111
|
-
* @param path -
|
|
111
|
+
* @param path - 路由路径(可选,默认 '')
|
|
112
112
|
*/
|
|
113
|
-
export function DELETE(path: string) {
|
|
113
|
+
export function DELETE(path: string = '') {
|
|
114
114
|
return createRouteDecorator('DELETE', path);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
118
|
* PATCH 路由装饰器
|
|
119
|
-
* @param path -
|
|
119
|
+
* @param path - 路由路径(可选,默认 '')
|
|
120
120
|
*/
|
|
121
|
-
export function PATCH(path: string) {
|
|
121
|
+
export function PATCH(path: string = '') {
|
|
122
122
|
return createRouteDecorator('PATCH', path);
|
|
123
123
|
}
|
package/src/router/router.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Constructor } from '../core/types';
|
|
|
3
3
|
import { Route } from './route';
|
|
4
4
|
import type { HttpMethod, RouteHandler, RouteMatch } from './types';
|
|
5
5
|
import type { Middleware } from '../middleware';
|
|
6
|
+
import { LoggerManager } from '@dangao/logsmith';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 路由器类
|
|
@@ -168,14 +169,23 @@ export class Router {
|
|
|
168
169
|
public async preHandle(context: Context): Promise<void> {
|
|
169
170
|
const method = context.method as HttpMethod;
|
|
170
171
|
const path = this.normalizePath(context.path);
|
|
172
|
+
const logger = LoggerManager.getLogger();
|
|
171
173
|
|
|
172
174
|
// 使用 findRouteWithMatch 避免重复匹配
|
|
173
175
|
const result = this.findRouteWithMatch(method, path);
|
|
174
176
|
if (!result) {
|
|
177
|
+
logger.debug('[Router] Route not matched', { method, path });
|
|
175
178
|
return;
|
|
176
179
|
}
|
|
177
180
|
|
|
178
181
|
const { route, match } = result;
|
|
182
|
+
logger.debug('[Router] Route matched', {
|
|
183
|
+
method,
|
|
184
|
+
path,
|
|
185
|
+
routePath: route.path,
|
|
186
|
+
controller: route.controllerClass?.name,
|
|
187
|
+
methodName: route.methodName,
|
|
188
|
+
});
|
|
179
189
|
if (match.matched) {
|
|
180
190
|
context.params = match.params;
|
|
181
191
|
}
|
|
@@ -196,6 +206,7 @@ export class Router {
|
|
|
196
206
|
public async handle(context: Context): Promise<Response | undefined> {
|
|
197
207
|
const method = context.method as HttpMethod;
|
|
198
208
|
const path = this.normalizePath(context.path);
|
|
209
|
+
const logger = LoggerManager.getLogger();
|
|
199
210
|
|
|
200
211
|
// 使用 findRouteWithMatch 获取路由和匹配结果
|
|
201
212
|
const result = this.findRouteWithMatch(method, path);
|
|
@@ -204,7 +215,13 @@ export class Router {
|
|
|
204
215
|
}
|
|
205
216
|
|
|
206
217
|
const { route, match } = result;
|
|
207
|
-
|
|
218
|
+
|
|
219
|
+
logger.debug('[Router] Executing handler', {
|
|
220
|
+
path: route.path,
|
|
221
|
+
controller: route.controllerClass?.name,
|
|
222
|
+
methodName: route.methodName,
|
|
223
|
+
});
|
|
224
|
+
|
|
208
225
|
// 设置路径参数
|
|
209
226
|
if (match.matched) {
|
|
210
227
|
context.params = match.params;
|
|
@@ -483,6 +483,141 @@ describe('Controller Path Normalization', () => {
|
|
|
483
483
|
expect(data.success).toBe(true);
|
|
484
484
|
});
|
|
485
485
|
|
|
486
|
+
// 场景 a: @Controller() + @GET('test') / @POST('test') -> /test
|
|
487
|
+
test('scenario a: @Controller() with @GET("test") and @POST("test") should match /test', async () => {
|
|
488
|
+
@Controller()
|
|
489
|
+
class TestController {
|
|
490
|
+
@GET('test')
|
|
491
|
+
public getTest() {
|
|
492
|
+
return { method: 'GET', path: 'test' };
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
@POST('test')
|
|
496
|
+
public postTest() {
|
|
497
|
+
return { method: 'POST', path: 'test' };
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
app.registerController(TestController);
|
|
502
|
+
await app.listen();
|
|
503
|
+
|
|
504
|
+
const getRes = await fetch(`http://localhost:${port}/test`);
|
|
505
|
+
expect(getRes.status).toBe(200);
|
|
506
|
+
expect((await getRes.json()).method).toBe('GET');
|
|
507
|
+
|
|
508
|
+
const postRes = await fetch(`http://localhost:${port}/test`, { method: 'POST' });
|
|
509
|
+
expect(postRes.status).toBe(200);
|
|
510
|
+
expect((await postRes.json()).method).toBe('POST');
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// 场景 b: @Controller() + @GET('/test') / @POST('/test') -> /test
|
|
514
|
+
test('scenario b: @Controller() with @GET("/test") and @POST("/test") should match /test', async () => {
|
|
515
|
+
@Controller()
|
|
516
|
+
class TestController {
|
|
517
|
+
@GET('/test')
|
|
518
|
+
public getTest() {
|
|
519
|
+
return { method: 'GET', path: 'test' };
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
@POST('/test')
|
|
523
|
+
public postTest() {
|
|
524
|
+
return { method: 'POST', path: 'test' };
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
app.registerController(TestController);
|
|
529
|
+
await app.listen();
|
|
530
|
+
|
|
531
|
+
const getRes = await fetch(`http://localhost:${port}/test`);
|
|
532
|
+
expect(getRes.status).toBe(200);
|
|
533
|
+
expect((await getRes.json()).method).toBe('GET');
|
|
534
|
+
|
|
535
|
+
const postRes = await fetch(`http://localhost:${port}/test`, { method: 'POST' });
|
|
536
|
+
expect(postRes.status).toBe(200);
|
|
537
|
+
expect((await postRes.json()).method).toBe('POST');
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// 场景 c: @Controller('') + @GET() / @POST() -> /
|
|
541
|
+
test('scenario c: @Controller("") with @GET() and @POST() should match /', async () => {
|
|
542
|
+
@Controller('')
|
|
543
|
+
class TestController {
|
|
544
|
+
@GET()
|
|
545
|
+
public getRoot() {
|
|
546
|
+
return { method: 'GET', path: '/' };
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
@POST()
|
|
550
|
+
public postRoot() {
|
|
551
|
+
return { method: 'POST', path: '/' };
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
app.registerController(TestController);
|
|
556
|
+
await app.listen();
|
|
557
|
+
|
|
558
|
+
const getRes = await fetch(`http://localhost:${port}/`);
|
|
559
|
+
expect(getRes.status).toBe(200);
|
|
560
|
+
expect((await getRes.json()).method).toBe('GET');
|
|
561
|
+
|
|
562
|
+
const postRes = await fetch(`http://localhost:${port}/`, { method: 'POST' });
|
|
563
|
+
expect(postRes.status).toBe(200);
|
|
564
|
+
expect((await postRes.json()).method).toBe('POST');
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
// 场景 d: @Controller() + @GET() / @POST() -> /
|
|
568
|
+
test('scenario d: @Controller() with @GET() and @POST() should match /', async () => {
|
|
569
|
+
@Controller()
|
|
570
|
+
class TestController {
|
|
571
|
+
@GET()
|
|
572
|
+
public getRoot() {
|
|
573
|
+
return { method: 'GET', path: '/' };
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
@POST()
|
|
577
|
+
public postRoot() {
|
|
578
|
+
return { method: 'POST', path: '/' };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
app.registerController(TestController);
|
|
583
|
+
await app.listen();
|
|
584
|
+
|
|
585
|
+
const getRes = await fetch(`http://localhost:${port}/`);
|
|
586
|
+
expect(getRes.status).toBe(200);
|
|
587
|
+
expect((await getRes.json()).method).toBe('GET');
|
|
588
|
+
|
|
589
|
+
const postRes = await fetch(`http://localhost:${port}/`, { method: 'POST' });
|
|
590
|
+
expect(postRes.status).toBe(200);
|
|
591
|
+
expect((await postRes.json()).method).toBe('POST');
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// 场景 e: @Controller() + @GET('/') / @POST('/') -> /
|
|
595
|
+
test('scenario e: @Controller() with @GET("/") and @POST("/") should match /', async () => {
|
|
596
|
+
@Controller()
|
|
597
|
+
class TestController {
|
|
598
|
+
@GET('/')
|
|
599
|
+
public getRoot() {
|
|
600
|
+
return { method: 'GET', path: '/' };
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
@POST('/')
|
|
604
|
+
public postRoot() {
|
|
605
|
+
return { method: 'POST', path: '/' };
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
app.registerController(TestController);
|
|
610
|
+
await app.listen();
|
|
611
|
+
|
|
612
|
+
const getRes = await fetch(`http://localhost:${port}/`);
|
|
613
|
+
expect(getRes.status).toBe(200);
|
|
614
|
+
expect((await getRes.json()).method).toBe('GET');
|
|
615
|
+
|
|
616
|
+
const postRes = await fetch(`http://localhost:${port}/`, { method: 'POST' });
|
|
617
|
+
expect(postRes.status).toBe(200);
|
|
618
|
+
expect((await postRes.json()).method).toBe('POST');
|
|
619
|
+
});
|
|
620
|
+
|
|
486
621
|
// 测试所有场景在同一应用中同时工作
|
|
487
622
|
test('should handle all path normalization scenarios simultaneously', async () => {
|
|
488
623
|
@Controller('/')
|