@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,140 @@
|
|
|
1
|
+
import type { Context } from '../core/context';
|
|
2
|
+
import type { HttpMethod, RouteHandler, RouteMatch } from './types';
|
|
3
|
+
import { MiddlewarePipeline } from '../middleware/pipeline';
|
|
4
|
+
import type { Middleware } from '../middleware';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 路由类
|
|
8
|
+
* 表示一个路由定义
|
|
9
|
+
*/
|
|
10
|
+
export class Route {
|
|
11
|
+
/**
|
|
12
|
+
* HTTP 方法
|
|
13
|
+
*/
|
|
14
|
+
public readonly method: HttpMethod;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 路由路径(支持参数,如 /users/:id)
|
|
18
|
+
*/
|
|
19
|
+
public readonly path: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 路由处理器
|
|
23
|
+
*/
|
|
24
|
+
public readonly handler: RouteHandler;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 控制器类(可选,用于控制器路由)
|
|
28
|
+
*/
|
|
29
|
+
public readonly controllerClass?: new (...args: unknown[]) => unknown;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 方法名(可选,用于控制器路由)
|
|
33
|
+
*/
|
|
34
|
+
public readonly methodName?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 路径模式(用于匹配)
|
|
38
|
+
*/
|
|
39
|
+
private readonly pattern: RegExp;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 路径参数名列表
|
|
43
|
+
*/
|
|
44
|
+
private readonly paramNames: string[];
|
|
45
|
+
|
|
46
|
+
private readonly middlewarePipeline: MiddlewarePipeline | null;
|
|
47
|
+
private readonly staticKey?: string;
|
|
48
|
+
public readonly isStatic: boolean;
|
|
49
|
+
|
|
50
|
+
public constructor(
|
|
51
|
+
method: HttpMethod,
|
|
52
|
+
path: string,
|
|
53
|
+
handler: RouteHandler,
|
|
54
|
+
middlewares: Middleware[] = [],
|
|
55
|
+
controllerClass?: new (...args: unknown[]) => unknown,
|
|
56
|
+
methodName?: string,
|
|
57
|
+
) {
|
|
58
|
+
this.method = method;
|
|
59
|
+
this.path = path;
|
|
60
|
+
this.handler = handler;
|
|
61
|
+
this.controllerClass = controllerClass;
|
|
62
|
+
this.methodName = methodName;
|
|
63
|
+
|
|
64
|
+
// 解析路径参数
|
|
65
|
+
const { pattern, paramNames } = this.parsePath(path);
|
|
66
|
+
this.pattern = pattern;
|
|
67
|
+
this.paramNames = paramNames;
|
|
68
|
+
this.middlewarePipeline = middlewares.length > 0 ? new MiddlewarePipeline(middlewares) : null;
|
|
69
|
+
this.isStatic = !path.includes(':') && !path.includes('*');
|
|
70
|
+
if (this.isStatic) {
|
|
71
|
+
this.staticKey = `${method}:${path}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 解析路径,生成匹配模式和参数名列表
|
|
77
|
+
* @param path - 路由路径
|
|
78
|
+
* @returns 匹配模式和参数名列表
|
|
79
|
+
*/
|
|
80
|
+
private parsePath(path: string): { pattern: RegExp; paramNames: string[] } {
|
|
81
|
+
const paramNames: string[] = [];
|
|
82
|
+
const patternString = path
|
|
83
|
+
.replace(/:([^/]+)/g, (_, paramName) => {
|
|
84
|
+
paramNames.push(paramName);
|
|
85
|
+
return '([^/]+)';
|
|
86
|
+
})
|
|
87
|
+
.replace(/\*/g, '.*');
|
|
88
|
+
|
|
89
|
+
const pattern = new RegExp(`^${patternString}$`);
|
|
90
|
+
return { pattern, paramNames };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 匹配路由
|
|
95
|
+
* @param method - HTTP 方法
|
|
96
|
+
* @param path - 请求路径
|
|
97
|
+
* @returns 匹配结果
|
|
98
|
+
*/
|
|
99
|
+
public match(method: HttpMethod, path: string): RouteMatch {
|
|
100
|
+
// 方法不匹配
|
|
101
|
+
if (this.method !== method) {
|
|
102
|
+
return { matched: false, params: {} };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 路径不匹配
|
|
106
|
+
const match = path.match(this.pattern);
|
|
107
|
+
if (!match) {
|
|
108
|
+
return { matched: false, params: {} };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 提取路径参数
|
|
112
|
+
const params: Record<string, string> = {};
|
|
113
|
+
for (let i = 0; i < this.paramNames.length; i++) {
|
|
114
|
+
params[this.paramNames[i]] = match[i + 1] ?? '';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { matched: true, params };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 执行路由处理器
|
|
122
|
+
* @param context - 请求上下文
|
|
123
|
+
* @returns 响应对象
|
|
124
|
+
*/
|
|
125
|
+
public async execute(context: Context): Promise<Response> {
|
|
126
|
+
if (!this.middlewarePipeline || !this.middlewarePipeline.hasMiddlewares()) {
|
|
127
|
+
return await this.handler(context);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return await this.middlewarePipeline.run(context, async () => this.handler(context));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 获取静态路由缓存 key
|
|
135
|
+
*/
|
|
136
|
+
public getStaticKey(): string | undefined {
|
|
137
|
+
return this.staticKey;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import type { Context } from '../core/context';
|
|
2
|
+
import { Route } from './route';
|
|
3
|
+
import type { HttpMethod, RouteHandler, RouteMatch } from './types';
|
|
4
|
+
import type { Middleware } from '../middleware';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 路由器类
|
|
8
|
+
* 管理所有路由,提供路由注册和匹配功能
|
|
9
|
+
*/
|
|
10
|
+
export class Router {
|
|
11
|
+
/**
|
|
12
|
+
* 路由列表
|
|
13
|
+
*/
|
|
14
|
+
private readonly routes: Route[] = [];
|
|
15
|
+
private readonly staticRoutes = new Map<string, Route>();
|
|
16
|
+
private readonly dynamicRoutes: Route[] = [];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 路由匹配结果缓存(method:path -> { route, match })
|
|
20
|
+
* 用于避免重复匹配,提升性能
|
|
21
|
+
*/
|
|
22
|
+
private readonly matchCache = new Map<string, { route: Route; match: RouteMatch }>();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 规范化路径(移除尾部斜杠,除非是根路径)
|
|
26
|
+
*/
|
|
27
|
+
private normalizePath(path: string): string {
|
|
28
|
+
if (path.length > 1 && path.endsWith('/')) {
|
|
29
|
+
return path.slice(0, -1);
|
|
30
|
+
}
|
|
31
|
+
return path;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 注册路由
|
|
36
|
+
* @param method - HTTP 方法
|
|
37
|
+
* @param path - 路由路径
|
|
38
|
+
* @param handler - 路由处理器
|
|
39
|
+
* @param middlewares - 中间件列表
|
|
40
|
+
* @param controllerClass - 控制器类(可选)
|
|
41
|
+
* @param methodName - 方法名(可选)
|
|
42
|
+
*/
|
|
43
|
+
public register(
|
|
44
|
+
method: HttpMethod,
|
|
45
|
+
path: string,
|
|
46
|
+
handler: RouteHandler,
|
|
47
|
+
middlewares: Middleware[] = [],
|
|
48
|
+
controllerClass?: new (...args: unknown[]) => unknown,
|
|
49
|
+
methodName?: string,
|
|
50
|
+
): void {
|
|
51
|
+
// 规范化路径
|
|
52
|
+
const normalizedPath = this.normalizePath(path);
|
|
53
|
+
const route = new Route(method, normalizedPath, handler, middlewares, controllerClass, methodName);
|
|
54
|
+
this.routes.push(route);
|
|
55
|
+
const staticKey = route.getStaticKey();
|
|
56
|
+
if (staticKey) {
|
|
57
|
+
this.staticRoutes.set(staticKey, route);
|
|
58
|
+
} else {
|
|
59
|
+
this.dynamicRoutes.push(route);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 清除匹配缓存,因为路由已更新
|
|
63
|
+
this.matchCache.clear();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 注册 GET 路由
|
|
68
|
+
* @param path - 路由路径
|
|
69
|
+
* @param handler - 路由处理器
|
|
70
|
+
*/
|
|
71
|
+
public get(path: string, handler: RouteHandler, middlewares: Middleware[] = []): void {
|
|
72
|
+
this.register('GET', path, handler, middlewares);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 注册 POST 路由
|
|
77
|
+
* @param path - 路由路径
|
|
78
|
+
* @param handler - 路由处理器
|
|
79
|
+
*/
|
|
80
|
+
public post(path: string, handler: RouteHandler, middlewares: Middleware[] = []): void {
|
|
81
|
+
this.register('POST', path, handler, middlewares);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 注册 PUT 路由
|
|
86
|
+
* @param path - 路由路径
|
|
87
|
+
* @param handler - 路由处理器
|
|
88
|
+
*/
|
|
89
|
+
public put(path: string, handler: RouteHandler, middlewares: Middleware[] = []): void {
|
|
90
|
+
this.register('PUT', path, handler, middlewares);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 注册 DELETE 路由
|
|
95
|
+
* @param path - 路由路径
|
|
96
|
+
* @param handler - 路由处理器
|
|
97
|
+
*/
|
|
98
|
+
public delete(path: string, handler: RouteHandler, middlewares: Middleware[] = []): void {
|
|
99
|
+
this.register('DELETE', path, handler, middlewares);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 注册 PATCH 路由
|
|
104
|
+
* @param path - 路由路径
|
|
105
|
+
* @param handler - 路由处理器
|
|
106
|
+
*/
|
|
107
|
+
public patch(path: string, handler: RouteHandler, middlewares: Middleware[] = []): void {
|
|
108
|
+
this.register('PATCH', path, handler, middlewares);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 查找匹配的路由
|
|
113
|
+
* @param method - HTTP 方法
|
|
114
|
+
* @param path - 请求路径
|
|
115
|
+
* @returns 匹配的路由,如果没有找到则返回 undefined
|
|
116
|
+
*/
|
|
117
|
+
public findRoute(method: HttpMethod, path: string): Route | undefined {
|
|
118
|
+
const result = this.findRouteWithMatch(method, path);
|
|
119
|
+
return result?.route;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 查找匹配的路由并返回匹配结果(包含参数)
|
|
124
|
+
* @param method - HTTP 方法
|
|
125
|
+
* @param path - 请求路径
|
|
126
|
+
* @returns 匹配结果,包含路由和参数,如果没有找到则返回 undefined
|
|
127
|
+
*/
|
|
128
|
+
public findRouteWithMatch(method: HttpMethod, path: string): { route: Route; match: RouteMatch } | undefined {
|
|
129
|
+
// 规范化路径,确保与注册时的路径格式一致
|
|
130
|
+
const normalizedPath = this.normalizePath(path);
|
|
131
|
+
const cacheKey = `${method}:${normalizedPath}`;
|
|
132
|
+
|
|
133
|
+
// 检查缓存(只缓存匹配成功的结果)
|
|
134
|
+
const cached = this.matchCache.get(cacheKey);
|
|
135
|
+
if (cached && cached.match.matched) {
|
|
136
|
+
return cached;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 先检查静态路由
|
|
140
|
+
const staticRoute = this.staticRoutes.get(cacheKey);
|
|
141
|
+
if (staticRoute) {
|
|
142
|
+
const match = { matched: true, params: {} };
|
|
143
|
+
const result = { route: staticRoute, match };
|
|
144
|
+
this.matchCache.set(cacheKey, result);
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 遍历动态路由(使用规范化后的路径进行匹配)
|
|
149
|
+
for (const route of this.dynamicRoutes) {
|
|
150
|
+
const match = route.match(method, normalizedPath);
|
|
151
|
+
if (match.matched) {
|
|
152
|
+
const result = { route, match };
|
|
153
|
+
// 缓存匹配结果
|
|
154
|
+
this.matchCache.set(cacheKey, result);
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 不缓存未匹配结果,因为路由可能会动态添加
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 预处理请求:仅匹配路由并设置路径参数 / routeHandler,但不执行处理器
|
|
165
|
+
* 供安全过滤器等中间件在真正执行前基于路由元数据做鉴权
|
|
166
|
+
*/
|
|
167
|
+
public async preHandle(context: Context): Promise<void> {
|
|
168
|
+
const method = context.method as HttpMethod;
|
|
169
|
+
const path = this.normalizePath(context.path);
|
|
170
|
+
|
|
171
|
+
// 使用 findRouteWithMatch 避免重复匹配
|
|
172
|
+
const result = this.findRouteWithMatch(method, path);
|
|
173
|
+
if (!result) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const { route, match } = result;
|
|
178
|
+
if (match.matched) {
|
|
179
|
+
context.params = match.params;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (route.controllerClass && route.methodName) {
|
|
183
|
+
(context as any).routeHandler = {
|
|
184
|
+
controller: route.controllerClass,
|
|
185
|
+
method: route.methodName,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 处理请求(包含路由匹配 + 执行)
|
|
192
|
+
* @param context - 请求上下文
|
|
193
|
+
* @returns 响应对象,如果没有匹配的路由则返回 undefined
|
|
194
|
+
*/
|
|
195
|
+
public async handle(context: Context): Promise<Response | undefined> {
|
|
196
|
+
const method = context.method as HttpMethod;
|
|
197
|
+
const path = this.normalizePath(context.path);
|
|
198
|
+
|
|
199
|
+
// 使用 findRouteWithMatch 获取路由和匹配结果
|
|
200
|
+
const result = this.findRouteWithMatch(method, path);
|
|
201
|
+
if (!result) {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const { route, match } = result;
|
|
206
|
+
|
|
207
|
+
// 设置路径参数
|
|
208
|
+
if (match.matched) {
|
|
209
|
+
context.params = match.params;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 设置 routeHandler
|
|
213
|
+
if (route.controllerClass && route.methodName) {
|
|
214
|
+
(context as any).routeHandler = {
|
|
215
|
+
controller: route.controllerClass,
|
|
216
|
+
method: route.methodName,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return await route.execute(context);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 获取所有路由
|
|
225
|
+
* @returns 路由列表
|
|
226
|
+
*/
|
|
227
|
+
public getRoutes(): readonly Route[] {
|
|
228
|
+
return this.routes;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* 清除所有已注册路由(主要用于测试环境)
|
|
233
|
+
*/
|
|
234
|
+
public clear(): void {
|
|
235
|
+
this.routes.length = 0;
|
|
236
|
+
this.dynamicRoutes.length = 0;
|
|
237
|
+
this.staticRoutes.clear();
|
|
238
|
+
this.matchCache.clear();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Context } from '../core/context';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HTTP 方法类型
|
|
5
|
+
*/
|
|
6
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 路由处理器函数
|
|
10
|
+
*/
|
|
11
|
+
export type RouteHandler = (context: Context) => Response | Promise<Response>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 路由匹配结果
|
|
15
|
+
*/
|
|
16
|
+
export interface RouteMatch {
|
|
17
|
+
/**
|
|
18
|
+
* 是否匹配
|
|
19
|
+
*/
|
|
20
|
+
matched: boolean;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 路径参数
|
|
24
|
+
*/
|
|
25
|
+
params: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Authentication, AccessDecisionManager } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 基于角色的访问决策器
|
|
5
|
+
*/
|
|
6
|
+
export class RoleBasedAccessDecisionManager implements AccessDecisionManager {
|
|
7
|
+
/**
|
|
8
|
+
* 决定是否授权
|
|
9
|
+
* @param authentication - 认证信息
|
|
10
|
+
* @param requiredAuthorities - 需要的权限(角色)
|
|
11
|
+
* @returns 是否授权
|
|
12
|
+
*/
|
|
13
|
+
public decide(
|
|
14
|
+
authentication: Authentication,
|
|
15
|
+
requiredAuthorities: string[],
|
|
16
|
+
): boolean {
|
|
17
|
+
// 如果没有要求权限,则允许访问
|
|
18
|
+
if (requiredAuthorities.length === 0) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 如果未认证,拒绝访问
|
|
23
|
+
if (!authentication.authenticated) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 检查是否有足够的权限
|
|
28
|
+
const userAuthorities = authentication.authorities || [];
|
|
29
|
+
return requiredAuthorities.some((required) =>
|
|
30
|
+
userAuthorities.includes(required),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Authentication,
|
|
3
|
+
AuthenticationProvider,
|
|
4
|
+
AuthenticationRequest,
|
|
5
|
+
} from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 认证管理器
|
|
9
|
+
*/
|
|
10
|
+
export class AuthenticationManager {
|
|
11
|
+
private readonly providers: AuthenticationProvider[] = [];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 注册认证提供者
|
|
15
|
+
*/
|
|
16
|
+
public registerProvider(provider: AuthenticationProvider): void {
|
|
17
|
+
this.providers.push(provider);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 认证
|
|
22
|
+
* @param request - 认证请求
|
|
23
|
+
* @returns 认证信息
|
|
24
|
+
*/
|
|
25
|
+
public async authenticate(
|
|
26
|
+
request: AuthenticationRequest,
|
|
27
|
+
): Promise<Authentication | null> {
|
|
28
|
+
const type = request.type || 'default';
|
|
29
|
+
|
|
30
|
+
// 查找支持的提供者
|
|
31
|
+
const provider = this.providers.find((p) => p.supports(type));
|
|
32
|
+
|
|
33
|
+
if (!provider) {
|
|
34
|
+
throw new Error(`No authentication provider found for type: ${type}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return await provider.authenticate(request);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 获取所有提供者
|
|
42
|
+
*/
|
|
43
|
+
public getProviders(): AuthenticationProvider[] {
|
|
44
|
+
return [...this.providers];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
+
|
|
3
|
+
import type { Authentication, Principal, SecurityContext } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 安全上下文实现
|
|
7
|
+
*/
|
|
8
|
+
export class SecurityContextImpl implements SecurityContext {
|
|
9
|
+
private _authentication: Authentication | null = null;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 当前认证信息
|
|
13
|
+
*/
|
|
14
|
+
public get authentication(): Authentication | null {
|
|
15
|
+
return this._authentication;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 设置认证信息
|
|
20
|
+
*/
|
|
21
|
+
public setAuthentication(authentication: Authentication | null): void {
|
|
22
|
+
this._authentication = authentication;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 是否已认证
|
|
27
|
+
*/
|
|
28
|
+
public isAuthenticated(): boolean {
|
|
29
|
+
return this._authentication?.authenticated ?? false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 获取主体
|
|
34
|
+
*/
|
|
35
|
+
public getPrincipal(): Principal | null {
|
|
36
|
+
return this._authentication?.principal ?? null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 获取权限
|
|
41
|
+
*/
|
|
42
|
+
public getAuthorities(): string[] {
|
|
43
|
+
return this._authentication?.authorities ?? [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 清除认证信息
|
|
48
|
+
*/
|
|
49
|
+
public clear(): void {
|
|
50
|
+
this._authentication = null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 安全上下文持有者(ThreadLocal 模式)
|
|
56
|
+
*/
|
|
57
|
+
export class SecurityContextHolder {
|
|
58
|
+
private static readonly storage = new AsyncLocalStorage<SecurityContextImpl>();
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 获取当前上下文
|
|
62
|
+
*/
|
|
63
|
+
public static getContext(): SecurityContextImpl {
|
|
64
|
+
let context = this.storage.getStore();
|
|
65
|
+
if (!context) {
|
|
66
|
+
context = new SecurityContextImpl();
|
|
67
|
+
this.storage.enterWith(context);
|
|
68
|
+
}
|
|
69
|
+
return context;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 在给定回调中运行,绑定独立的安全上下文(每个请求一个)
|
|
74
|
+
* @param callback - 要在安全上下文中执行的回调
|
|
75
|
+
*/
|
|
76
|
+
public static runWithContext<T>(callback: () => T): T {
|
|
77
|
+
// 总是创建新的上下文,确保每个请求都有独立的上下文
|
|
78
|
+
const context = new SecurityContextImpl();
|
|
79
|
+
return this.storage.run(context, callback);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 清除当前上下文中的认证信息
|
|
84
|
+
*/
|
|
85
|
+
public static clearContext(): void {
|
|
86
|
+
const context = this.storage.getStore();
|
|
87
|
+
if (context) {
|
|
88
|
+
context.clear();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|