@dangao/bun-server 1.1.2 → 1.1.4
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/docs/api.md +602 -0
- package/docs/best-practices.md +12 -0
- package/docs/custom-decorators.md +440 -0
- package/docs/deployment.md +447 -0
- package/docs/error-handling.md +462 -0
- package/docs/extensions.md +569 -0
- package/docs/guide.md +634 -0
- package/docs/migration.md +10 -0
- package/docs/performance.md +452 -0
- package/docs/troubleshooting.md +286 -0
- package/docs/zh/api.md +168 -0
- package/docs/zh/best-practices.md +38 -0
- package/docs/zh/custom-decorators.md +466 -0
- package/docs/zh/deployment.md +445 -0
- package/docs/zh/error-handling.md +456 -0
- package/docs/zh/extensions.md +584 -0
- package/docs/zh/guide.md +361 -0
- package/docs/zh/migration.md +86 -0
- package/docs/zh/performance.md +451 -0
- package/docs/zh/troubleshooting.md +279 -0
- package/package.json +4 -3
package/docs/zh/api.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# API 概览
|
|
2
|
+
|
|
3
|
+
本文档概述 Bun Server Framework 目前提供的主要 API,方便快速查阅。
|
|
4
|
+
|
|
5
|
+
## 核心
|
|
6
|
+
|
|
7
|
+
| API | 描述 |
|
|
8
|
+
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| `Application(options?)` | 应用主类,支持 `use` 注册全局中间件、`registerController`/`registerWebSocketGateway` 注册组件以及 `listen/stop` 管理生命周期 |
|
|
10
|
+
| `Context` | 统一的请求上下文,封装 `Request` 并提供 `getQuery/getParam/getBody/setHeader/setStatus/createResponse` 等方法 |
|
|
11
|
+
| `ResponseBuilder` | 提供 `json/text/html/empty/redirect/error/file` 便捷响应构建器 |
|
|
12
|
+
| `RouteRegistry` / `Router` | 可直接注册函数式路由或获取底层 `Router` 进行手动控制 |
|
|
13
|
+
|
|
14
|
+
## 控制器与路由装饰器
|
|
15
|
+
|
|
16
|
+
- `@Controller(path)`:声明控制器前缀。
|
|
17
|
+
- `@GET/@POST/@PUT/@PATCH/@DELETE(path)`:声明 HTTP 方法。
|
|
18
|
+
- 参数装饰器:`@Body() / @Query(key) / @Param(key) / @Header(key)`。
|
|
19
|
+
- `ControllerRegistry` 会自动解析装饰器并注册路由。
|
|
20
|
+
|
|
21
|
+
## 依赖注入
|
|
22
|
+
|
|
23
|
+
- `Container`:`register`、`registerInstance`、`resolve`、`clear`、`isRegistered`。
|
|
24
|
+
- 装饰器:`@Injectable(config?)` 设置生命周期、`@Inject(token?)` 指定依赖。
|
|
25
|
+
- `Lifecycle` 枚举:`Singleton`、`Transient`、`Scoped`(预留)。
|
|
26
|
+
|
|
27
|
+
## 中间件体系
|
|
28
|
+
|
|
29
|
+
- `Middleware` 类型:`(context, next) => Response`。
|
|
30
|
+
- `MiddlewarePipeline`:`use`, `run`, `hasMiddlewares`, `clear`。
|
|
31
|
+
- `@UseMiddleware(...middlewares)`:作用于控制器类或方法。
|
|
32
|
+
- 内置中间件:
|
|
33
|
+
- `createLoggerMiddleware`
|
|
34
|
+
- `createRequestLoggingMiddleware`
|
|
35
|
+
- `createCorsMiddleware`
|
|
36
|
+
- `createErrorHandlingMiddleware`
|
|
37
|
+
- `createFileUploadMiddleware`
|
|
38
|
+
- `createStaticFileMiddleware`
|
|
39
|
+
|
|
40
|
+
## 验证
|
|
41
|
+
|
|
42
|
+
- 装饰器:`@Validate(rule...)`, `IsString`, `IsNumber`, `IsEmail`, `IsOptional`,
|
|
43
|
+
`MinLength`。
|
|
44
|
+
- `ValidationError`:`issues` 数组包含 `index / rule / message`。
|
|
45
|
+
- `validateParameters(params, metadata)` 可在自定义场景复用。
|
|
46
|
+
|
|
47
|
+
## 错误与异常
|
|
48
|
+
|
|
49
|
+
- `HttpException` 及子类:`BadRequestException`, `UnauthorizedException`,
|
|
50
|
+
`ForbiddenException`, `NotFoundException`, `InternalServerErrorException`。
|
|
51
|
+
- `ExceptionFilter` 接口与 `ExceptionFilterRegistry`:可注册自定义过滤器。
|
|
52
|
+
- `handleError(error, context)`:全局错误处理核心逻辑;默认错误中间件已自动调用。
|
|
53
|
+
|
|
54
|
+
## 扩展系统
|
|
55
|
+
|
|
56
|
+
### 中间件
|
|
57
|
+
|
|
58
|
+
- `Middleware` 类型:`(context: Context, next: NextFunction) => Response | Promise<Response>`
|
|
59
|
+
- `app.use(middleware)`:注册全局中间件
|
|
60
|
+
- `@UseMiddleware(...middlewares)`:控制器或方法级中间件
|
|
61
|
+
- 内置中间件工厂函数:`createLoggerMiddleware`, `createCorsMiddleware`, `createErrorHandlingMiddleware`, `createFileUploadMiddleware`, `createStaticFileMiddleware`
|
|
62
|
+
|
|
63
|
+
### 应用扩展
|
|
64
|
+
|
|
65
|
+
- `ApplicationExtension` 接口:`register(container: Container): void`
|
|
66
|
+
- `app.registerExtension(extension)`:注册应用扩展
|
|
67
|
+
- 官方扩展:`LoggerExtension`, `SwaggerExtension`
|
|
68
|
+
|
|
69
|
+
### 模块系统
|
|
70
|
+
|
|
71
|
+
- `@Module(metadata)`:模块装饰器
|
|
72
|
+
- `ModuleMetadata`:支持 `imports`, `controllers`, `providers`, `exports`, `extensions`, `middlewares`
|
|
73
|
+
- `app.registerModule(moduleClass)`:注册模块
|
|
74
|
+
- 官方模块:`LoggerModule.forRoot(options)`, `SwaggerModule.forRoot(options)`
|
|
75
|
+
|
|
76
|
+
### 拦截器系统
|
|
77
|
+
|
|
78
|
+
- `Interceptor` 接口:拦截器核心接口
|
|
79
|
+
- `InterceptorRegistry`:拦截器中央注册表
|
|
80
|
+
- `InterceptorChain`:按优先级顺序执行多个拦截器
|
|
81
|
+
- `BaseInterceptor`:创建自定义拦截器的基类
|
|
82
|
+
- `scanInterceptorMetadata`:扫描方法元数据查找拦截器
|
|
83
|
+
|
|
84
|
+
**内置拦截器**:
|
|
85
|
+
- `@Cache(options)`:缓存方法结果
|
|
86
|
+
- `@Permission(options)`:在执行方法前检查权限
|
|
87
|
+
- `@Log(options)`:记录方法执行日志和耗时
|
|
88
|
+
|
|
89
|
+
**示例**:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { BaseInterceptor, INTERCEPTOR_REGISTRY_TOKEN } from '@dangao/bun-server';
|
|
93
|
+
import type { InterceptorRegistry } from '@dangao/bun-server';
|
|
94
|
+
|
|
95
|
+
// 创建自定义装饰器
|
|
96
|
+
const MY_METADATA_KEY = Symbol('@my-app:my-decorator');
|
|
97
|
+
|
|
98
|
+
function MyDecorator(): MethodDecorator {
|
|
99
|
+
return (target, propertyKey) => {
|
|
100
|
+
Reflect.defineMetadata(MY_METADATA_KEY, true, target, propertyKey);
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 创建拦截器
|
|
105
|
+
class MyInterceptor extends BaseInterceptor {
|
|
106
|
+
public async execute<T>(...): Promise<T> {
|
|
107
|
+
// 前置处理
|
|
108
|
+
await this.before(...);
|
|
109
|
+
|
|
110
|
+
// 执行原方法
|
|
111
|
+
const result = await Promise.resolve(originalMethod.apply(target, args));
|
|
112
|
+
|
|
113
|
+
// 后置处理
|
|
114
|
+
return await this.after(...) as T;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 注册拦截器
|
|
119
|
+
const registry = app.getContainer().resolve<InterceptorRegistry>(INTERCEPTOR_REGISTRY_TOKEN);
|
|
120
|
+
registry.register(MY_METADATA_KEY, new MyInterceptor(), 100);
|
|
121
|
+
|
|
122
|
+
// 使用装饰器
|
|
123
|
+
@Controller('/api/users')
|
|
124
|
+
class UserController {
|
|
125
|
+
@GET('/:id')
|
|
126
|
+
@MyDecorator()
|
|
127
|
+
public getUser(@Param('id') id: string) {
|
|
128
|
+
return { id, name: 'User' };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
详细说明请参考 [自定义注解开发指南](./custom-decorators.md)。
|
|
134
|
+
|
|
135
|
+
详细说明请参考 [扩展系统文档](./extensions.md)。
|
|
136
|
+
|
|
137
|
+
## WebSocket
|
|
138
|
+
|
|
139
|
+
- 装饰器:`@WebSocketGateway(path)` + `@OnOpen`, `@OnMessage`, `@OnClose`。
|
|
140
|
+
- `WebSocketGatewayRegistry`:自动管理依赖注入、在
|
|
141
|
+
`Application.registerWebSocketGateway` 时登记。
|
|
142
|
+
- 服务器会自动处理握手并将事件委托给网关实例。
|
|
143
|
+
|
|
144
|
+
## 请求工具
|
|
145
|
+
|
|
146
|
+
- `BodyParser`:`parse(request)`,自动缓存解析结果。
|
|
147
|
+
- `FileHandler`:解析 `multipart/form-data`,返回结构化文件对象。
|
|
148
|
+
- `RequestWrapper`:用于兼容场景的轻量封装。
|
|
149
|
+
|
|
150
|
+
## 导出入口
|
|
151
|
+
|
|
152
|
+
所有上述 API 均可从 `src/index.ts` 导出,通过
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import {
|
|
156
|
+
Application,
|
|
157
|
+
Controller,
|
|
158
|
+
createLoggerMiddleware,
|
|
159
|
+
GET,
|
|
160
|
+
HttpException,
|
|
161
|
+
Injectable,
|
|
162
|
+
UseMiddleware,
|
|
163
|
+
Validate,
|
|
164
|
+
WebSocketGateway,
|
|
165
|
+
} from "@dangao/bun-server";
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
即可在应用中使用。
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 最佳实践
|
|
2
|
+
|
|
3
|
+
## 架构与模块化
|
|
4
|
+
|
|
5
|
+
- **按领域拆分**:每个业务域单独的 Controller + Service + DTO,更易维护与测试。
|
|
6
|
+
- **依赖显式化**:所有服务都通过构造函数注入,避免在方法内部 `container.resolve`。
|
|
7
|
+
- **清理全局状态**:测试环境中记得清空 `RouteRegistry`、`ControllerRegistry` 与文件级缓存。
|
|
8
|
+
|
|
9
|
+
## 中间件策略
|
|
10
|
+
|
|
11
|
+
- **全局 vs 局部**:跨路由逻辑(日志、错误处理、鉴权)使用 `app.use`;业务相关逻辑在控制器上使用 `@UseMiddleware`。
|
|
12
|
+
- **保持幂等**:中间件应尽量避免修改共享状态,可通过 `context` 新增字段传递数据。
|
|
13
|
+
- **避免重复解析**:`Context.getBody()` 已缓存结果,不要在中间件和控制器中重复 `await request.json()`。
|
|
14
|
+
|
|
15
|
+
## 性能建议
|
|
16
|
+
|
|
17
|
+
- **缓存配置**:频繁读取的配置或数据可使用 Singleton 服务缓存。
|
|
18
|
+
- **路由顺序**:将高频路由优先注册,减少匹配遍历时间。
|
|
19
|
+
- **WebSocket**:处理长连接时避免阻塞操作,可将耗时任务交给队列或其他线程。
|
|
20
|
+
|
|
21
|
+
## 安全
|
|
22
|
+
|
|
23
|
+
- **输入验证**:所有外部输入(Body/Query/Param)都应使用验证装饰器或手动校验。
|
|
24
|
+
- **静态文件**:使用内置 `createStaticFileMiddleware` 可自动阻止路径穿越。
|
|
25
|
+
- **文件上传**:限制文件大小与类型,必要时结合病毒扫描或内容审核。
|
|
26
|
+
|
|
27
|
+
## 日志与监控
|
|
28
|
+
|
|
29
|
+
- **结构化日志**:`createLoggerMiddleware` 支持自定义 logger,可统一输出 JSON 格式,方便集中收集。
|
|
30
|
+
- **请求追踪**:在中间件中注入 trace-id 到 `Context`,贯穿后续日志与下游调用。
|
|
31
|
+
- **指标**:结合 `createRequestLoggingMiddleware` 或自定义中间件统计响应时间,供性能优化参考。
|
|
32
|
+
|
|
33
|
+
## 部署
|
|
34
|
+
|
|
35
|
+
- **多实例**:Bun 进程可结合容器/PM2 等方式多实例部署,并在外层做负载均衡。
|
|
36
|
+
- **可观测**:建议接入健康检查路由(GET `/health`)以及基础指标导出(如 Prometheus)。
|
|
37
|
+
- **灰度发布**:合理配置中间件,使得灰度流量可按 Header/Token 进行分流。
|
|
38
|
+
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
# 自定义注解开发指南
|
|
2
|
+
|
|
3
|
+
本文档介绍如何在 Bun Server Framework 中创建自定义装饰器和拦截器。
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
- [概述](#概述)
|
|
8
|
+
- [创建自定义装饰器](#创建自定义装饰器)
|
|
9
|
+
- [创建拦截器](#创建拦截器)
|
|
10
|
+
- [使用 BaseInterceptor](#使用-baseinterceptor)
|
|
11
|
+
- [访问容器和上下文](#访问容器和上下文)
|
|
12
|
+
- [元数据系统](#元数据系统)
|
|
13
|
+
- [示例](#示例)
|
|
14
|
+
|
|
15
|
+
## 概述
|
|
16
|
+
|
|
17
|
+
Bun Server Framework
|
|
18
|
+
提供了强大的拦截器机制,允许您创建自定义装饰器和拦截器来实现
|
|
19
|
+
AOP(面向切面编程)。这使您能够添加横切关注点,如缓存、日志记录、权限检查等。
|
|
20
|
+
|
|
21
|
+
### 核心概念
|
|
22
|
+
|
|
23
|
+
- **装饰器**:一个 TypeScript 装饰器,用于在方法上添加元数据
|
|
24
|
+
- **拦截器**:一个拦截方法执行并可以修改行为的类
|
|
25
|
+
- **元数据键**:用于将装饰器与拦截器关联的 Symbol
|
|
26
|
+
- **拦截器注册表**:管理所有拦截器的中央注册表
|
|
27
|
+
|
|
28
|
+
## 创建自定义装饰器
|
|
29
|
+
|
|
30
|
+
### 基本装饰器模式
|
|
31
|
+
|
|
32
|
+
自定义装饰器是一个返回 `MethodDecorator` 的函数。它使用 `reflect-metadata`
|
|
33
|
+
在方法上存储元数据。
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import "reflect-metadata";
|
|
37
|
+
|
|
38
|
+
// 1. 定义元数据键(使用 Symbol 确保唯一性)
|
|
39
|
+
export const MY_METADATA_KEY = Symbol("@my-app:my-decorator");
|
|
40
|
+
|
|
41
|
+
// 2. 定义元数据类型
|
|
42
|
+
export interface MyDecoratorOptions {
|
|
43
|
+
option1: string;
|
|
44
|
+
option2?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 3. 创建装饰器函数
|
|
48
|
+
export function MyDecorator(options: MyDecoratorOptions): MethodDecorator {
|
|
49
|
+
return (target, propertyKey, descriptor) => {
|
|
50
|
+
// 在方法上存储元数据
|
|
51
|
+
Reflect.defineMetadata(MY_METADATA_KEY, options, target, propertyKey);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 装饰器命名规范
|
|
57
|
+
|
|
58
|
+
- 使用 PascalCase 命名装饰器:`@Cache()`, `@Permission()`, `@Log()`
|
|
59
|
+
- 使用描述性名称,表明装饰器的用途
|
|
60
|
+
- 元数据键应遵循模式:`Symbol('@namespace:feature')`
|
|
61
|
+
|
|
62
|
+
### 装饰器函数签名
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
function MyDecorator(options?: MyOptions): MethodDecorator {
|
|
66
|
+
return (
|
|
67
|
+
target: any,
|
|
68
|
+
propertyKey: string | symbol,
|
|
69
|
+
descriptor: PropertyDescriptor,
|
|
70
|
+
) => {
|
|
71
|
+
// 实现
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 创建拦截器
|
|
77
|
+
|
|
78
|
+
### 实现 Interceptor 接口
|
|
79
|
+
|
|
80
|
+
拦截器必须实现 `Interceptor` 接口:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import type { Interceptor } from "@dangao/bun-server";
|
|
84
|
+
import type { Container } from "@dangao/bun-server";
|
|
85
|
+
import type { Context } from "@dangao/bun-server";
|
|
86
|
+
|
|
87
|
+
class MyInterceptor implements Interceptor {
|
|
88
|
+
public async execute<T>(
|
|
89
|
+
target: unknown,
|
|
90
|
+
propertyKey: string | symbol,
|
|
91
|
+
originalMethod: (...args: unknown[]) => T | Promise<T>,
|
|
92
|
+
args: unknown[],
|
|
93
|
+
container: Container,
|
|
94
|
+
context?: Context,
|
|
95
|
+
): Promise<T> {
|
|
96
|
+
// 前置处理逻辑
|
|
97
|
+
console.log(`执行 ${String(propertyKey)}`);
|
|
98
|
+
|
|
99
|
+
// 执行原方法
|
|
100
|
+
const result = await Promise.resolve(originalMethod.apply(target, args));
|
|
101
|
+
|
|
102
|
+
// 后置处理逻辑
|
|
103
|
+
console.log(`完成 ${String(propertyKey)}`);
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 注册拦截器
|
|
111
|
+
|
|
112
|
+
拦截器必须注册到 `InterceptorRegistry`:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { Application } from "@dangao/bun-server";
|
|
116
|
+
import { INTERCEPTOR_REGISTRY_TOKEN } from "@dangao/bun-server";
|
|
117
|
+
import type { InterceptorRegistry } from "@dangao/bun-server";
|
|
118
|
+
|
|
119
|
+
const app = new Application({ port: 3000 });
|
|
120
|
+
const registry = app.getContainer().resolve<InterceptorRegistry>(
|
|
121
|
+
INTERCEPTOR_REGISTRY_TOKEN,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// 使用元数据键和优先级注册拦截器
|
|
125
|
+
registry.register(MY_METADATA_KEY, new MyInterceptor(), 100);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 优先级系统
|
|
129
|
+
|
|
130
|
+
拦截器按优先级顺序执行(数字越小越先执行):
|
|
131
|
+
|
|
132
|
+
- 优先级 0-50:系统拦截器(如事务)
|
|
133
|
+
- 优先级 51-100:框架拦截器
|
|
134
|
+
- 优先级 101+:应用拦截器
|
|
135
|
+
|
|
136
|
+
## 使用 BaseInterceptor
|
|
137
|
+
|
|
138
|
+
`BaseInterceptor` 提供了一个便捷的基类,包含常用操作的钩子:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { BaseInterceptor } from "@dangao/bun-server";
|
|
142
|
+
import type { Container } from "@dangao/bun-server";
|
|
143
|
+
import type { Context } from "@dangao/bun-server";
|
|
144
|
+
|
|
145
|
+
class MyInterceptor extends BaseInterceptor {
|
|
146
|
+
public async execute<T>(
|
|
147
|
+
target: unknown,
|
|
148
|
+
propertyKey: string | symbol,
|
|
149
|
+
originalMethod: (...args: unknown[]) => T | Promise<T>,
|
|
150
|
+
args: unknown[],
|
|
151
|
+
container: Container,
|
|
152
|
+
context?: Context,
|
|
153
|
+
): Promise<T> {
|
|
154
|
+
try {
|
|
155
|
+
// 前置处理
|
|
156
|
+
await this.before(target, propertyKey, args, container, context);
|
|
157
|
+
|
|
158
|
+
// 执行原方法
|
|
159
|
+
const result = await Promise.resolve(originalMethod.apply(target, args));
|
|
160
|
+
|
|
161
|
+
// 后置处理
|
|
162
|
+
return await this.after(
|
|
163
|
+
target,
|
|
164
|
+
propertyKey,
|
|
165
|
+
result,
|
|
166
|
+
container,
|
|
167
|
+
context,
|
|
168
|
+
) as T;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
// 错误处理
|
|
171
|
+
return await this.onError(target, propertyKey, error, container, context);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
protected async before(
|
|
176
|
+
target: unknown,
|
|
177
|
+
propertyKey: string | symbol,
|
|
178
|
+
args: unknown[],
|
|
179
|
+
container: Container,
|
|
180
|
+
context?: Context,
|
|
181
|
+
): Promise<void> {
|
|
182
|
+
// 覆盖以添加前置处理逻辑
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
protected async after<T>(
|
|
186
|
+
target: unknown,
|
|
187
|
+
propertyKey: string | symbol,
|
|
188
|
+
result: T,
|
|
189
|
+
container: Container,
|
|
190
|
+
context?: Context,
|
|
191
|
+
): Promise<T> {
|
|
192
|
+
// 覆盖以添加后置处理逻辑
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
protected async onError(
|
|
197
|
+
target: unknown,
|
|
198
|
+
propertyKey: string | symbol,
|
|
199
|
+
error: unknown,
|
|
200
|
+
container: Container,
|
|
201
|
+
context?: Context,
|
|
202
|
+
): Promise<never> {
|
|
203
|
+
// 覆盖以自定义错误处理
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 辅助方法
|
|
210
|
+
|
|
211
|
+
`BaseInterceptor` 提供了几个辅助方法:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// 从方法获取元数据
|
|
215
|
+
// 注意:getMetadata() 的签名是 (target, propertyKey, metadataKey)
|
|
216
|
+
const metadata = this.getMetadata<MyOptions>(
|
|
217
|
+
target,
|
|
218
|
+
propertyKey,
|
|
219
|
+
MY_METADATA_KEY,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// 从容器解析服务
|
|
223
|
+
const service = this.resolveService<MyService>(container, MyService);
|
|
224
|
+
|
|
225
|
+
// 访问上下文
|
|
226
|
+
const header = this.getHeader(context!, "Authorization");
|
|
227
|
+
const query = this.getQuery(context!, "page");
|
|
228
|
+
const param = this.getParam(context!, "id");
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## 访问容器和上下文
|
|
232
|
+
|
|
233
|
+
### 从容器解析服务
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
class MyInterceptor extends BaseInterceptor {
|
|
237
|
+
public async execute<T>(...): Promise<T> {
|
|
238
|
+
// 使用辅助方法解析服务
|
|
239
|
+
const userService = this.resolveService<UserService>(container, UserService);
|
|
240
|
+
|
|
241
|
+
// 或直接解析
|
|
242
|
+
const config = container.resolve<ConfigService>(CONFIG_SERVICE_TOKEN);
|
|
243
|
+
|
|
244
|
+
// 使用服务
|
|
245
|
+
const user = await userService.find(userId);
|
|
246
|
+
// ...
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### 访问请求上下文
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
class MyInterceptor extends BaseInterceptor {
|
|
255
|
+
public async execute<T>(...): Promise<T> {
|
|
256
|
+
if (context) {
|
|
257
|
+
// 获取请求头
|
|
258
|
+
const authHeader = this.getHeader(context, 'Authorization');
|
|
259
|
+
const contentType = this.getHeader(context, 'Content-Type');
|
|
260
|
+
|
|
261
|
+
// 获取查询参数
|
|
262
|
+
const page = this.getQuery(context, 'page');
|
|
263
|
+
const limit = this.getQuery(context, 'limit');
|
|
264
|
+
|
|
265
|
+
// 获取路径参数
|
|
266
|
+
const userId = this.getParam(context, 'id');
|
|
267
|
+
|
|
268
|
+
// 获取请求体
|
|
269
|
+
const body = await context.getBody();
|
|
270
|
+
|
|
271
|
+
// 设置响应头
|
|
272
|
+
context.setHeader('X-Custom-Header', 'value');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## 元数据系统
|
|
279
|
+
|
|
280
|
+
### 存储元数据
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import "reflect-metadata";
|
|
284
|
+
|
|
285
|
+
const METADATA_KEY = Symbol("my-metadata");
|
|
286
|
+
|
|
287
|
+
// 存储元数据
|
|
288
|
+
Reflect.defineMetadata(METADATA_KEY, { value: "data" }, target, propertyKey);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 读取元数据
|
|
292
|
+
|
|
293
|
+
**重要**:装饰器将元数据存储在原型(类)上,而不是实例上。在拦截器中读取元数据时,需要正确处理这一点。
|
|
294
|
+
|
|
295
|
+
**方式 1:使用 `BaseInterceptor.getMetadata()`(推荐)**
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
class MyInterceptor extends BaseInterceptor {
|
|
299
|
+
public async execute<T>(...): Promise<T> {
|
|
300
|
+
// getMetadata() 自动处理原型链查找
|
|
301
|
+
const metadata = this.getMetadata<MyOptions>(target, propertyKey, METADATA_KEY);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**方式 2:手动原型链查找**
|
|
307
|
+
|
|
308
|
+
如果不使用 `BaseInterceptor`,需要手动检查原型链:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
// 读取元数据(处理实例和原型)
|
|
312
|
+
let metadata: MyOptions | undefined;
|
|
313
|
+
if (typeof target === "object" && target !== null) {
|
|
314
|
+
// 首先尝试直接查找(如果 target 是原型)
|
|
315
|
+
metadata = Reflect.getMetadata(METADATA_KEY, target, propertyKey);
|
|
316
|
+
|
|
317
|
+
// 如果未找到且 target 是实例,检查原型
|
|
318
|
+
if (metadata === undefined) {
|
|
319
|
+
const prototype = Object.getPrototypeOf(target);
|
|
320
|
+
if (prototype && prototype !== Object.prototype) {
|
|
321
|
+
metadata = Reflect.getMetadata(METADATA_KEY, prototype, propertyKey);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// 也检查构造函数原型作为后备
|
|
325
|
+
if (metadata === undefined) {
|
|
326
|
+
const constructor = (target as any).constructor;
|
|
327
|
+
if (
|
|
328
|
+
constructor && typeof constructor === "function" &&
|
|
329
|
+
constructor.prototype
|
|
330
|
+
) {
|
|
331
|
+
metadata = Reflect.getMetadata(
|
|
332
|
+
METADATA_KEY,
|
|
333
|
+
constructor.prototype,
|
|
334
|
+
propertyKey,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 检查元数据是否存在
|
|
342
|
+
const exists = metadata !== undefined;
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 拦截器中的元数据
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
class MyInterceptor extends BaseInterceptor {
|
|
349
|
+
public async execute<T>(...): Promise<T> {
|
|
350
|
+
// 使用辅助方法获取元数据
|
|
351
|
+
// 注意:getMetadata() 的签名是 (target, propertyKey, metadataKey)
|
|
352
|
+
const options = this.getMetadata<MyOptions>(target, propertyKey, MY_METADATA_KEY);
|
|
353
|
+
|
|
354
|
+
if (options) {
|
|
355
|
+
// 使用选项
|
|
356
|
+
console.log(`选项值: ${options.value}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## 示例
|
|
363
|
+
|
|
364
|
+
### 示例 1:简单日志拦截器
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
import 'reflect-metadata';
|
|
368
|
+
import { BaseInterceptor } from '@dangao/bun-server';
|
|
369
|
+
import type { Container } from '@dangao/bun-server';
|
|
370
|
+
import type { Context } from '@dangao/bun-server';
|
|
371
|
+
|
|
372
|
+
const LOG_METADATA_KEY = Symbol('@my-app:log');
|
|
373
|
+
|
|
374
|
+
export function Log(message?: string): MethodDecorator {
|
|
375
|
+
return (target, propertyKey) => {
|
|
376
|
+
Reflect.defineMetadata(LOG_METADATA_KEY, { message }, target, propertyKey);
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export class LogInterceptor extends BaseInterceptor {
|
|
381
|
+
public async execute<T>(...): Promise<T> {
|
|
382
|
+
const metadata = this.getMetadata<{ message?: string }>(target, propertyKey, LOG_METADATA_KEY);
|
|
383
|
+
const logMessage = metadata?.message || `执行 ${String(propertyKey)}`;
|
|
384
|
+
|
|
385
|
+
console.log(`[LOG] ${logMessage} - 开始`);
|
|
386
|
+
const start = Date.now();
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
const result = await Promise.resolve(originalMethod.apply(target, args));
|
|
390
|
+
const duration = Date.now() - start;
|
|
391
|
+
console.log(`[LOG] ${logMessage} - 完成,耗时 ${duration}ms`);
|
|
392
|
+
return result;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
const duration = Date.now() - start;
|
|
395
|
+
console.error(`[LOG] ${logMessage} - 失败,耗时 ${duration}ms`, error);
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### 示例 2:限流拦截器
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import 'reflect-metadata';
|
|
406
|
+
import { BaseInterceptor, HttpException } from '@dangao/bun-server';
|
|
407
|
+
import type { Container } from '@dangao/bun-server';
|
|
408
|
+
import type { Context } from '@dangao/bun-server';
|
|
409
|
+
|
|
410
|
+
const RATE_LIMIT_METADATA_KEY = Symbol('@my-app:rate-limit');
|
|
411
|
+
|
|
412
|
+
export interface RateLimitOptions {
|
|
413
|
+
maxRequests: number;
|
|
414
|
+
windowMs: number;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function RateLimit(options: RateLimitOptions): MethodDecorator {
|
|
418
|
+
return (target, propertyKey) => {
|
|
419
|
+
Reflect.defineMetadata(RATE_LIMIT_METADATA_KEY, options, target, propertyKey);
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export class RateLimitInterceptor extends BaseInterceptor {
|
|
424
|
+
private readonly requests = new Map<string, number[]>();
|
|
425
|
+
|
|
426
|
+
public async execute<T>(...): Promise<T> {
|
|
427
|
+
const options = this.getMetadata<RateLimitOptions>(target, propertyKey, RATE_LIMIT_METADATA_KEY);
|
|
428
|
+
if (!options || !context) {
|
|
429
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const clientId = this.getHeader(context, 'X-Client-Id') || context.request.headers.get('X-Forwarded-For') || 'unknown';
|
|
433
|
+
const now = Date.now();
|
|
434
|
+
const windowStart = now - options.windowMs;
|
|
435
|
+
|
|
436
|
+
// 清理旧请求
|
|
437
|
+
const requests = this.requests.get(clientId) || [];
|
|
438
|
+
const recentRequests = requests.filter(time => time > windowStart);
|
|
439
|
+
|
|
440
|
+
if (recentRequests.length >= options.maxRequests) {
|
|
441
|
+
throw new HttpException(429, '请求过于频繁');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
recentRequests.push(now);
|
|
445
|
+
this.requests.set(clientId, recentRequests);
|
|
446
|
+
|
|
447
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## 最佳实践
|
|
453
|
+
|
|
454
|
+
1. **使用 Symbol 作为元数据键**:确保唯一性并避免冲突
|
|
455
|
+
2. **遵循命名规范**:使用一致的命名模式
|
|
456
|
+
3. **文档化您的装饰器**:为用户提供清晰的文档
|
|
457
|
+
4. **优雅处理错误**:始终在拦截器中处理错误
|
|
458
|
+
5. **考虑性能**:最小化拦截器执行开销
|
|
459
|
+
6. **使用 BaseInterceptor**:利用基类实现常见模式
|
|
460
|
+
7. **测试您的拦截器**:编写全面的测试
|
|
461
|
+
|
|
462
|
+
## 相关资源
|
|
463
|
+
|
|
464
|
+
- [API 文档](./api.md)
|
|
465
|
+
- [示例](../examples/)
|
|
466
|
+
- [内置拦截器](../packages/bun-server/src/interceptor/builtin/)
|