@dangao/bun-server 1.7.1 → 1.8.0
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/README.md +129 -21
- package/dist/di/decorators.d.ts +37 -0
- package/dist/di/decorators.d.ts.map +1 -1
- package/dist/di/index.d.ts +1 -1
- package/dist/di/index.d.ts.map +1 -1
- package/dist/di/module-registry.d.ts +17 -0
- package/dist/di/module-registry.d.ts.map +1 -1
- package/dist/events/decorators.d.ts +52 -0
- package/dist/events/decorators.d.ts.map +1 -0
- package/dist/events/event-module.d.ts +97 -0
- package/dist/events/event-module.d.ts.map +1 -0
- package/dist/events/index.d.ts +5 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/service.d.ts +76 -0
- package/dist/events/service.d.ts.map +1 -0
- package/dist/events/types.d.ts +184 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1511 -11
- package/dist/security/filter.d.ts +23 -0
- package/dist/security/filter.d.ts.map +1 -1
- package/dist/security/guards/builtin/auth-guard.d.ts +44 -0
- package/dist/security/guards/builtin/auth-guard.d.ts.map +1 -0
- package/dist/security/guards/builtin/index.d.ts +3 -0
- package/dist/security/guards/builtin/index.d.ts.map +1 -0
- package/dist/security/guards/builtin/roles-guard.d.ts +66 -0
- package/dist/security/guards/builtin/roles-guard.d.ts.map +1 -0
- package/dist/security/guards/decorators.d.ts +50 -0
- package/dist/security/guards/decorators.d.ts.map +1 -0
- package/dist/security/guards/execution-context.d.ts +56 -0
- package/dist/security/guards/execution-context.d.ts.map +1 -0
- package/dist/security/guards/guard-registry.d.ts +67 -0
- package/dist/security/guards/guard-registry.d.ts.map +1 -0
- package/dist/security/guards/index.d.ts +7 -0
- package/dist/security/guards/index.d.ts.map +1 -0
- package/dist/security/guards/reflector.d.ts +57 -0
- package/dist/security/guards/reflector.d.ts.map +1 -0
- package/dist/security/guards/types.d.ts +126 -0
- package/dist/security/guards/types.d.ts.map +1 -0
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/security-module.d.ts +20 -0
- package/dist/security/security-module.d.ts.map +1 -1
- package/dist/validation/class-validator.d.ts +108 -0
- package/dist/validation/class-validator.d.ts.map +1 -0
- package/dist/validation/custom-validator.d.ts +130 -0
- package/dist/validation/custom-validator.d.ts.map +1 -0
- package/dist/validation/errors.d.ts +22 -2
- package/dist/validation/errors.d.ts.map +1 -1
- package/dist/validation/index.d.ts +7 -1
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/rules/array.d.ts +33 -0
- package/dist/validation/rules/array.d.ts.map +1 -0
- package/dist/validation/rules/common.d.ts +90 -0
- package/dist/validation/rules/common.d.ts.map +1 -0
- package/dist/validation/rules/conditional.d.ts +30 -0
- package/dist/validation/rules/conditional.d.ts.map +1 -0
- package/dist/validation/rules/index.d.ts +5 -0
- package/dist/validation/rules/index.d.ts.map +1 -0
- package/dist/validation/rules/object.d.ts +30 -0
- package/dist/validation/rules/object.d.ts.map +1 -0
- package/dist/validation/types.d.ts +52 -1
- package/dist/validation/types.d.ts.map +1 -1
- package/docs/events.md +494 -0
- package/docs/guards.md +376 -0
- package/docs/guide.md +309 -1
- package/docs/request-lifecycle.md +444 -0
- package/docs/validation.md +407 -0
- package/docs/zh/events.md +494 -0
- package/docs/zh/guards.md +376 -0
- package/docs/zh/guide.md +309 -1
- package/docs/zh/request-lifecycle.md +444 -0
- package/docs/zh/validation.md +407 -0
- package/package.json +1 -1
- package/src/di/decorators.ts +46 -0
- package/src/di/index.ts +10 -1
- package/src/di/module-registry.ts +39 -0
- package/src/events/decorators.ts +103 -0
- package/src/events/event-module.ts +272 -0
- package/src/events/index.ts +32 -0
- package/src/events/service.ts +352 -0
- package/src/events/types.ts +223 -0
- package/src/index.ts +133 -1
- package/src/security/filter.ts +88 -8
- package/src/security/guards/builtin/auth-guard.ts +68 -0
- package/src/security/guards/builtin/index.ts +3 -0
- package/src/security/guards/builtin/roles-guard.ts +165 -0
- package/src/security/guards/decorators.ts +124 -0
- package/src/security/guards/execution-context.ts +152 -0
- package/src/security/guards/guard-registry.ts +164 -0
- package/src/security/guards/index.ts +7 -0
- package/src/security/guards/reflector.ts +99 -0
- package/src/security/guards/types.ts +144 -0
- package/src/security/index.ts +1 -0
- package/src/security/security-module.ts +72 -2
- package/src/validation/class-validator.ts +322 -0
- package/src/validation/custom-validator.ts +289 -0
- package/src/validation/errors.ts +50 -2
- package/src/validation/index.ts +103 -1
- package/src/validation/rules/array.ts +118 -0
- package/src/validation/rules/common.ts +286 -0
- package/src/validation/rules/conditional.ts +52 -0
- package/src/validation/rules/index.ts +51 -0
- package/src/validation/rules/object.ts +86 -0
- package/src/validation/types.ts +61 -1
- package/tests/di/global-module.test.ts +487 -0
- package/tests/events/event-decorators.test.ts +173 -0
- package/tests/events/event-emitter.test.ts +373 -0
- package/tests/events/event-module.test.ts +373 -0
- package/tests/security/guards/guards-integration.test.ts +371 -0
- package/tests/security/guards/guards.test.ts +775 -0
- package/tests/security/security-module.test.ts +2 -2
- package/tests/validation/class-validator.test.ts +349 -0
- package/tests/validation/custom-validator.test.ts +335 -0
- package/tests/validation/rules.test.ts +543 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# 验证系统
|
|
2
|
+
|
|
3
|
+
Bun Server Framework 提供了强大的验证系统,用于验证请求参数、DTO 和复杂数据结构。
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
- [基础验证](#基础验证)
|
|
8
|
+
- [验证规则](#验证规则)
|
|
9
|
+
- [对象规则](#对象规则)
|
|
10
|
+
- [数组规则](#数组规则)
|
|
11
|
+
- [通用规则](#通用规则)
|
|
12
|
+
- [条件规则](#条件规则)
|
|
13
|
+
- [类级别验证](#类级别验证)
|
|
14
|
+
- [嵌套对象验证](#嵌套对象验证)
|
|
15
|
+
- [自定义验证器](#自定义验证器)
|
|
16
|
+
- [内置扩展验证器](#内置扩展验证器)
|
|
17
|
+
- [错误处理](#错误处理)
|
|
18
|
+
|
|
19
|
+
## 基础验证
|
|
20
|
+
|
|
21
|
+
在控制器方法参数上使用 `@Validate()` 装饰器:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { Controller, GET, Query, Validate, IsString, IsEmail, IsOptional, MinLength } from '@dangao/bun-server';
|
|
25
|
+
|
|
26
|
+
@Controller('/api/users')
|
|
27
|
+
class UserController {
|
|
28
|
+
@GET('/search')
|
|
29
|
+
public search(
|
|
30
|
+
@Query('email') @Validate(IsEmail()) email: string,
|
|
31
|
+
@Query('name') @Validate(IsOptional(), IsString(), MinLength(2)) name?: string,
|
|
32
|
+
) {
|
|
33
|
+
return { email, name };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 验证规则
|
|
39
|
+
|
|
40
|
+
### 对象规则
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { IsObject, IsNotEmpty, IsNotEmptyObject, ValidateNested } from '@dangao/bun-server';
|
|
44
|
+
|
|
45
|
+
// 验证值是否为对象
|
|
46
|
+
IsObject()
|
|
47
|
+
|
|
48
|
+
// 验证值是否非空(null、undefined、空字符串、空数组、空对象)
|
|
49
|
+
IsNotEmpty()
|
|
50
|
+
|
|
51
|
+
// 验证值是否为非空对象
|
|
52
|
+
IsNotEmptyObject()
|
|
53
|
+
|
|
54
|
+
// 标记属性需要嵌套验证
|
|
55
|
+
ValidateNested({ each?: boolean }) // each: true 用于数组元素
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 数组规则
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import {
|
|
62
|
+
IsArray, ArrayMinSize, ArrayMaxSize, ArrayUnique,
|
|
63
|
+
ArrayContains, ArrayNotContains, ArrayNotEmpty
|
|
64
|
+
} from '@dangao/bun-server';
|
|
65
|
+
|
|
66
|
+
// 验证值是否为数组
|
|
67
|
+
IsArray()
|
|
68
|
+
|
|
69
|
+
// 验证数组最小长度
|
|
70
|
+
ArrayMinSize(2)
|
|
71
|
+
|
|
72
|
+
// 验证数组最大长度
|
|
73
|
+
ArrayMaxSize(10)
|
|
74
|
+
|
|
75
|
+
// 验证数组元素是否唯一
|
|
76
|
+
ArrayUnique()
|
|
77
|
+
|
|
78
|
+
// 验证数组是否包含指定值
|
|
79
|
+
ArrayContains([1, 2])
|
|
80
|
+
|
|
81
|
+
// 验证数组是否不包含指定值
|
|
82
|
+
ArrayNotContains(['banned'])
|
|
83
|
+
|
|
84
|
+
// 验证数组是否非空
|
|
85
|
+
ArrayNotEmpty()
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 通用规则
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import {
|
|
92
|
+
IsString, IsNumber, IsBoolean, IsInt, IsPositive, IsNegative,
|
|
93
|
+
Min, Max, IsDate, IsUUID, Length, MaxLength, MinLength,
|
|
94
|
+
Matches, IsIn, IsNotIn, IsUrl, IsJSON, IsEmail,
|
|
95
|
+
Equals, NotEquals, IsDefined, IsAlphanumeric, IsAlpha, IsNumberString
|
|
96
|
+
} from '@dangao/bun-server';
|
|
97
|
+
|
|
98
|
+
// 类型验证
|
|
99
|
+
IsString()
|
|
100
|
+
IsNumber()
|
|
101
|
+
IsBoolean()
|
|
102
|
+
IsInt()
|
|
103
|
+
IsDate()
|
|
104
|
+
|
|
105
|
+
// 数字验证
|
|
106
|
+
IsPositive()
|
|
107
|
+
IsNegative()
|
|
108
|
+
Min(0)
|
|
109
|
+
Max(100)
|
|
110
|
+
|
|
111
|
+
// 字符串验证
|
|
112
|
+
IsEmail()
|
|
113
|
+
IsUUID('4') // '3', '4', '5', 或 'all'
|
|
114
|
+
Length(2, 10)
|
|
115
|
+
MinLength(2)
|
|
116
|
+
MaxLength(10)
|
|
117
|
+
Matches(/^[a-z]+$/)
|
|
118
|
+
IsUrl()
|
|
119
|
+
IsJSON()
|
|
120
|
+
IsAlphanumeric()
|
|
121
|
+
IsAlpha()
|
|
122
|
+
IsNumberString()
|
|
123
|
+
|
|
124
|
+
// 值验证
|
|
125
|
+
IsIn(['a', 'b', 'c'])
|
|
126
|
+
IsNotIn(['x', 'y', 'z'])
|
|
127
|
+
Equals('expected')
|
|
128
|
+
NotEquals('forbidden')
|
|
129
|
+
IsDefined()
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 条件规则
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { ValidateIf, Transform } from '@dangao/bun-server';
|
|
136
|
+
|
|
137
|
+
// 条件验证 - 仅当条件为 true 时执行验证
|
|
138
|
+
ValidateIf((value, obj) => obj.type === 'premium')
|
|
139
|
+
|
|
140
|
+
// 验证前转换值
|
|
141
|
+
Transform((value) => String(value).trim())
|
|
142
|
+
Transform((value) => Number(value))
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
使用示例:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
@ValidateClass()
|
|
149
|
+
class UpdateUserDto {
|
|
150
|
+
@Property(ValidateIf((_, obj) => obj.type === 'premium'), IsEmail())
|
|
151
|
+
premiumEmail?: string;
|
|
152
|
+
|
|
153
|
+
@Property(Transform((v) => String(v).trim()), IsString(), MinLength(1))
|
|
154
|
+
name: string;
|
|
155
|
+
|
|
156
|
+
@Property(Transform((v) => Number(v)), IsInt(), Min(0))
|
|
157
|
+
age: number;
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## 类级别验证
|
|
162
|
+
|
|
163
|
+
对于 DTO 类,使用 `@ValidateClass()` 和 `@Property()` 装饰器:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { ValidateClass, Property, IsString, IsEmail, IsOptional, IsInt, Min, Max, MinLength } from '@dangao/bun-server';
|
|
167
|
+
|
|
168
|
+
@ValidateClass()
|
|
169
|
+
class CreateUserDto {
|
|
170
|
+
@Property(IsString(), MinLength(2))
|
|
171
|
+
name: string;
|
|
172
|
+
|
|
173
|
+
@Property(IsEmail())
|
|
174
|
+
email: string;
|
|
175
|
+
|
|
176
|
+
@Property(IsOptional(), IsInt(), Min(0), Max(150))
|
|
177
|
+
age?: number;
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
手动验证对象:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { validateObject, validateObjectSync, ValidationError } from '@dangao/bun-server';
|
|
185
|
+
|
|
186
|
+
// 失败时抛出 ValidationError
|
|
187
|
+
try {
|
|
188
|
+
validateObject(data, CreateUserDto);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (error instanceof ValidationError) {
|
|
191
|
+
console.log(error.issues);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 返回验证结果而不抛出异常
|
|
196
|
+
const result = validateObjectSync(data, CreateUserDto);
|
|
197
|
+
if (!result.valid) {
|
|
198
|
+
console.log(result.issues);
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## 嵌套对象验证
|
|
203
|
+
|
|
204
|
+
用于嵌套对象和数组:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { ValidateClass, Property, NestedProperty, ArrayNestedProperty, IsString, IsNumber, Min, IsArray, ArrayMinSize } from '@dangao/bun-server';
|
|
208
|
+
|
|
209
|
+
@ValidateClass()
|
|
210
|
+
class AddressDto {
|
|
211
|
+
@Property(IsString())
|
|
212
|
+
city: string;
|
|
213
|
+
|
|
214
|
+
@Property(IsString())
|
|
215
|
+
street: string;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@ValidateClass()
|
|
219
|
+
class ItemDto {
|
|
220
|
+
@Property(IsString())
|
|
221
|
+
name: string;
|
|
222
|
+
|
|
223
|
+
@Property(IsNumber(), Min(0))
|
|
224
|
+
price: number;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@ValidateClass()
|
|
228
|
+
class CreateOrderDto {
|
|
229
|
+
@Property(IsString())
|
|
230
|
+
userId: string;
|
|
231
|
+
|
|
232
|
+
// 嵌套对象验证
|
|
233
|
+
@NestedProperty(AddressDto)
|
|
234
|
+
shippingAddress: AddressDto;
|
|
235
|
+
|
|
236
|
+
// 嵌套对象数组验证
|
|
237
|
+
@Property(IsArray(), ArrayMinSize(1))
|
|
238
|
+
@ArrayNestedProperty(ItemDto)
|
|
239
|
+
items: ItemDto[];
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## 自定义验证器
|
|
244
|
+
|
|
245
|
+
### 简单自定义验证器
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { createSimpleValidator } from '@dangao/bun-server';
|
|
249
|
+
|
|
250
|
+
const IsEven = createSimpleValidator(
|
|
251
|
+
'isEven',
|
|
252
|
+
(value) => typeof value === 'number' && value % 2 === 0,
|
|
253
|
+
'必须是偶数'
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// 使用
|
|
257
|
+
@Property(IsEven())
|
|
258
|
+
count: number;
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 带参数的自定义验证器
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { createCustomValidator } from '@dangao/bun-server';
|
|
265
|
+
|
|
266
|
+
const IsDivisibleBy = createCustomValidator(
|
|
267
|
+
'isDivisibleBy',
|
|
268
|
+
(value: unknown, divisor: number) => typeof value === 'number' && value % divisor === 0,
|
|
269
|
+
(divisor: number) => `必须能被 ${divisor} 整除`
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// 使用
|
|
273
|
+
@Property(IsDivisibleBy(5)())
|
|
274
|
+
value: number;
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### 正则表达式自定义验证器
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { createRegexValidator } from '@dangao/bun-server';
|
|
281
|
+
|
|
282
|
+
const IsSlug = createRegexValidator(
|
|
283
|
+
'isSlug',
|
|
284
|
+
/^[a-z0-9]+(?:-[a-z0-9]+)*$/,
|
|
285
|
+
'必须是有效的 slug 格式'
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// 使用
|
|
289
|
+
@Property(IsSlug())
|
|
290
|
+
slug: string;
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## 内置扩展验证器
|
|
294
|
+
|
|
295
|
+
框架提供了多个预置验证器用于常见场景:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import {
|
|
299
|
+
IsPhoneNumber, // 中国手机号
|
|
300
|
+
IsIdCard, // 中国身份证号
|
|
301
|
+
IsIPv4, // IPv4 地址
|
|
302
|
+
IsPort, // 端口号 (0-65535)
|
|
303
|
+
IsPostalCode, // 中国邮政编码
|
|
304
|
+
IsCreditCard, // 信用卡号(Luhn 算法)
|
|
305
|
+
IsHexColor, // 十六进制颜色值(#fff 或 #ffffff)
|
|
306
|
+
IsMacAddress, // MAC 地址
|
|
307
|
+
IsSemVer, // 语义化版本号
|
|
308
|
+
IsDivisibleBy, // 能被指定数字整除
|
|
309
|
+
IsBetween, // 数字在范围内
|
|
310
|
+
Contains, // 字符串包含子串
|
|
311
|
+
NotContains, // 字符串不包含子串
|
|
312
|
+
} from '@dangao/bun-server';
|
|
313
|
+
|
|
314
|
+
// 使用示例
|
|
315
|
+
@Property(IsPhoneNumber())
|
|
316
|
+
phone: string;
|
|
317
|
+
|
|
318
|
+
@Property(IsIPv4())
|
|
319
|
+
ip: string;
|
|
320
|
+
|
|
321
|
+
@Property(IsBetween(1, 100)())
|
|
322
|
+
percentage: number;
|
|
323
|
+
|
|
324
|
+
@Property(Contains('http')())
|
|
325
|
+
url: string;
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## 错误处理
|
|
329
|
+
|
|
330
|
+
### ValidationError
|
|
331
|
+
|
|
332
|
+
验证失败时会抛出 `ValidationError`:
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import { ValidationError, ValidationIssue } from '@dangao/bun-server';
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
validateObject(data, MyDto);
|
|
339
|
+
} catch (error) {
|
|
340
|
+
if (error instanceof ValidationError) {
|
|
341
|
+
// 访问验证问题列表
|
|
342
|
+
console.log(error.issues);
|
|
343
|
+
|
|
344
|
+
// 获取扁平化的错误(对嵌套对象有用)
|
|
345
|
+
console.log(error.getFlattened());
|
|
346
|
+
|
|
347
|
+
// 转换为 JSON
|
|
348
|
+
console.log(error.toJSON());
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### ValidationIssue 结构
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
interface ValidationIssue {
|
|
357
|
+
index?: number; // 参数索引(用于参数验证)
|
|
358
|
+
property?: string; // 属性路径(如 'user.address.city')
|
|
359
|
+
rule: string; // 失败的规则名称
|
|
360
|
+
message: string; // 错误消息
|
|
361
|
+
value?: unknown; // 验证失败的值
|
|
362
|
+
children?: ValidationIssue[]; // 嵌套错误
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### 控制器集成
|
|
367
|
+
|
|
368
|
+
验证错误会被自动捕获并返回 400 Bad Request 响应:
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
@Controller('/api/users')
|
|
372
|
+
class UserController {
|
|
373
|
+
@POST('/')
|
|
374
|
+
public async createUser(@Body() @Validate(IsObject()) body: unknown) {
|
|
375
|
+
const dto = body as CreateUserDto;
|
|
376
|
+
validateObject(dto, CreateUserDto);
|
|
377
|
+
// ... 创建用户
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## 最佳实践
|
|
383
|
+
|
|
384
|
+
1. **复杂验证使用 DTO**:对于具有多个字段的请求体,使用 `@ValidateClass()` 装饰的 DTO。
|
|
385
|
+
|
|
386
|
+
2. **组合规则**:链式组合多个规则以进行全面验证:
|
|
387
|
+
```typescript
|
|
388
|
+
@Property(IsString(), MinLength(2), MaxLength(50), Matches(/^[a-zA-Z\s]+$/))
|
|
389
|
+
name: string;
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
3. **IsOptional() 放在最前**:始终将 `IsOptional()` 放在规则链的开头:
|
|
393
|
+
```typescript
|
|
394
|
+
@Property(IsOptional(), IsString(), MinLength(2))
|
|
395
|
+
nickname?: string;
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
4. **先转换后验证**:使用 `Transform()` 在验证前规范化数据:
|
|
399
|
+
```typescript
|
|
400
|
+
@Property(Transform((v) => String(v).toLowerCase().trim()), IsEmail())
|
|
401
|
+
email: string;
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
5. **自定义错误消息**:提供清晰、用户友好的错误消息:
|
|
405
|
+
```typescript
|
|
406
|
+
IsEmail({ message: '请输入有效的邮箱地址' })
|
|
407
|
+
```
|
package/package.json
CHANGED
package/src/di/decorators.ts
CHANGED
|
@@ -9,6 +9,11 @@ import type { Constructor } from "@/core/types";
|
|
|
9
9
|
const DEPENDENCY_METADATA_KEY = Symbol("dependency:metadata");
|
|
10
10
|
const INJECTABLE_METADATA_KEY = Symbol("injectable");
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* 全局模块元数据键
|
|
14
|
+
*/
|
|
15
|
+
export const GLOBAL_MODULE_METADATA_KEY = Symbol("@dangao/bun-server:global-module");
|
|
16
|
+
|
|
12
17
|
/**
|
|
13
18
|
* 类型引用映射(用于保存构造函数类型,避免 Reflect.defineMetadata 序列化问题)
|
|
14
19
|
*/
|
|
@@ -226,3 +231,44 @@ export function getTypeReference(
|
|
|
226
231
|
}
|
|
227
232
|
return undefined as unknown as Constructor<unknown>;
|
|
228
233
|
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Global 装饰器
|
|
237
|
+
* 标记模块为全局模块,其导出的提供者可在任何模块中使用,无需显式导入
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```typescript
|
|
241
|
+
* @Global()
|
|
242
|
+
* @Module({
|
|
243
|
+
* providers: [ConfigService],
|
|
244
|
+
* exports: [ConfigService],
|
|
245
|
+
* })
|
|
246
|
+
* class GlobalConfigModule {}
|
|
247
|
+
*
|
|
248
|
+
* // 其他模块无需导入 GlobalConfigModule 即可使用 ConfigService
|
|
249
|
+
* @Module({
|
|
250
|
+
* controllers: [UserController],
|
|
251
|
+
* providers: [UserService],
|
|
252
|
+
* })
|
|
253
|
+
* class UserModule {}
|
|
254
|
+
*
|
|
255
|
+
* @Injectable()
|
|
256
|
+
* class UserService {
|
|
257
|
+
* constructor(private readonly config: ConfigService) {}
|
|
258
|
+
* }
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
export function Global(): ClassDecorator {
|
|
262
|
+
return (target) => {
|
|
263
|
+
Reflect.defineMetadata(GLOBAL_MODULE_METADATA_KEY, true, target);
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 检查模块是否为全局模块
|
|
269
|
+
* @param target - 目标模块类
|
|
270
|
+
* @returns 是否为全局模块
|
|
271
|
+
*/
|
|
272
|
+
export function isGlobalModule(target: Constructor<unknown>): boolean {
|
|
273
|
+
return Reflect.getMetadata(GLOBAL_MODULE_METADATA_KEY, target) === true;
|
|
274
|
+
}
|
package/src/di/index.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
export { Container } from './container';
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
Injectable,
|
|
4
|
+
Inject,
|
|
5
|
+
getDependencyMetadata,
|
|
6
|
+
isInjectable,
|
|
7
|
+
getLifecycle,
|
|
8
|
+
Global,
|
|
9
|
+
isGlobalModule,
|
|
10
|
+
GLOBAL_MODULE_METADATA_KEY,
|
|
11
|
+
} from './decorators';
|
|
3
12
|
export {
|
|
4
13
|
Lifecycle,
|
|
5
14
|
INSTANCE_POST_PROCESSOR_TOKEN,
|
|
@@ -2,6 +2,7 @@ import { ControllerRegistry } from '../controller/controller';
|
|
|
2
2
|
import { Container } from './container';
|
|
3
3
|
import { Lifecycle } from './types';
|
|
4
4
|
import { getModuleMetadata, type ModuleClass, type ModuleProvider, type ProviderToken } from './module';
|
|
5
|
+
import { isGlobalModule } from './decorators';
|
|
5
6
|
import type { Constructor } from '@/core/types';
|
|
6
7
|
import type { ApplicationExtension } from '../extensions/types';
|
|
7
8
|
import type { Middleware } from '../middleware';
|
|
@@ -14,6 +15,10 @@ interface ModuleRef {
|
|
|
14
15
|
attachedParents: Set<Container>;
|
|
15
16
|
extensions: ApplicationExtension[];
|
|
16
17
|
middlewares: Middleware[];
|
|
18
|
+
/**
|
|
19
|
+
* 是否为全局模块
|
|
20
|
+
*/
|
|
21
|
+
isGlobal: boolean;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
export class ModuleRegistry {
|
|
@@ -21,6 +26,10 @@ export class ModuleRegistry {
|
|
|
21
26
|
private readonly moduleRefs = new Map<ModuleClass, ModuleRef>();
|
|
22
27
|
private readonly processing = new Set<ModuleClass>();
|
|
23
28
|
private rootContainer?: Container;
|
|
29
|
+
/**
|
|
30
|
+
* 存储全局模块列表,用于在其他模块注册时自动附加全局 exports
|
|
31
|
+
*/
|
|
32
|
+
private readonly globalModules = new Set<ModuleClass>();
|
|
24
33
|
|
|
25
34
|
public static getInstance(): ModuleRegistry {
|
|
26
35
|
if (!ModuleRegistry.instance) {
|
|
@@ -43,9 +52,17 @@ export class ModuleRegistry {
|
|
|
43
52
|
public clear(): void {
|
|
44
53
|
this.moduleRefs.clear();
|
|
45
54
|
this.processing.clear();
|
|
55
|
+
this.globalModules.clear();
|
|
46
56
|
this.rootContainer = undefined;
|
|
47
57
|
}
|
|
48
58
|
|
|
59
|
+
/**
|
|
60
|
+
* 获取所有全局模块
|
|
61
|
+
*/
|
|
62
|
+
public getGlobalModules(): ModuleClass[] {
|
|
63
|
+
return Array.from(this.globalModules);
|
|
64
|
+
}
|
|
65
|
+
|
|
49
66
|
private processModule(moduleClass: ModuleClass, parentContainer: Container): ModuleRef {
|
|
50
67
|
if (this.processing.has(moduleClass)) {
|
|
51
68
|
throw new Error(`Circular module dependency detected for ${moduleClass.name}`);
|
|
@@ -69,6 +86,7 @@ export class ModuleRegistry {
|
|
|
69
86
|
throw new Error('ModuleRegistry is not initialized with a root container');
|
|
70
87
|
}
|
|
71
88
|
const metadata = getModuleMetadata(moduleClass);
|
|
89
|
+
const isGlobal = isGlobalModule(moduleClass);
|
|
72
90
|
const container = new Container({ parent: this.rootContainer });
|
|
73
91
|
this.registerProviders(container, metadata.providers);
|
|
74
92
|
ref = {
|
|
@@ -79,12 +97,33 @@ export class ModuleRegistry {
|
|
|
79
97
|
attachedParents: new Set<Container>(),
|
|
80
98
|
extensions: metadata.extensions ?? [],
|
|
81
99
|
middlewares: metadata.middlewares ?? [],
|
|
100
|
+
isGlobal,
|
|
82
101
|
};
|
|
83
102
|
this.registerControllers(ref);
|
|
84
103
|
this.moduleRefs.set(moduleClass, ref);
|
|
104
|
+
|
|
105
|
+
// 如果是全局模块,注册到根容器并记录
|
|
106
|
+
if (isGlobal) {
|
|
107
|
+
this.globalModules.add(moduleClass);
|
|
108
|
+
this.registerGlobalExports(ref);
|
|
109
|
+
}
|
|
110
|
+
|
|
85
111
|
return ref;
|
|
86
112
|
}
|
|
87
113
|
|
|
114
|
+
/**
|
|
115
|
+
* 将全局模块的 exports 注册到根容器
|
|
116
|
+
* 这样所有模块都可以访问全局模块导出的提供者
|
|
117
|
+
*/
|
|
118
|
+
private registerGlobalExports(moduleRef: ModuleRef): void {
|
|
119
|
+
if (!this.rootContainer) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
for (const exportedToken of moduleRef.metadata.exports) {
|
|
123
|
+
this.registerExport(this.rootContainer, moduleRef, exportedToken);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
88
127
|
private registerProviders(container: Container, providers: ModuleProvider[]): void {
|
|
89
128
|
for (const provider of providers) {
|
|
90
129
|
if (typeof provider === 'function') {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import {
|
|
3
|
+
ON_EVENT_METADATA_KEY,
|
|
4
|
+
EVENT_LISTENER_CLASS_METADATA_KEY,
|
|
5
|
+
type OnEventMethodMetadata,
|
|
6
|
+
} from './types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* OnEvent 装饰器选项
|
|
10
|
+
*/
|
|
11
|
+
export interface OnEventOptions {
|
|
12
|
+
/**
|
|
13
|
+
* 是否异步处理
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
16
|
+
async?: boolean;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 监听器优先级(数值越大优先级越高)
|
|
20
|
+
* @default 0
|
|
21
|
+
*/
|
|
22
|
+
priority?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 事件监听器装饰器
|
|
27
|
+
* 用于标记方法为事件监听器
|
|
28
|
+
*
|
|
29
|
+
* @param event - 事件名称或标识符
|
|
30
|
+
* @param options - 监听选项
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* @Injectable()
|
|
35
|
+
* class NotificationService {
|
|
36
|
+
* @OnEvent('user.created')
|
|
37
|
+
* handleUserCreated(payload: UserCreatedEvent) {
|
|
38
|
+
* console.log('User created:', payload.userId);
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* @OnEvent(USER_DELETED, { async: true, priority: 10 })
|
|
42
|
+
* async handleUserDeleted(payload: UserDeletedEvent) {
|
|
43
|
+
* await this.cleanup(payload.userId);
|
|
44
|
+
* }
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function OnEvent(
|
|
49
|
+
event: string | symbol,
|
|
50
|
+
options: OnEventOptions = {},
|
|
51
|
+
): MethodDecorator {
|
|
52
|
+
return (
|
|
53
|
+
target: object,
|
|
54
|
+
propertyKey: string | symbol,
|
|
55
|
+
descriptor: PropertyDescriptor,
|
|
56
|
+
): PropertyDescriptor => {
|
|
57
|
+
const methodName = String(propertyKey);
|
|
58
|
+
const constructor = target.constructor;
|
|
59
|
+
|
|
60
|
+
// 获取类上已有的事件监听器元数据
|
|
61
|
+
const existingMetadata: OnEventMethodMetadata[] =
|
|
62
|
+
Reflect.getMetadata(ON_EVENT_METADATA_KEY, constructor) || [];
|
|
63
|
+
|
|
64
|
+
// 添加新的监听器元数据
|
|
65
|
+
const metadata: OnEventMethodMetadata = {
|
|
66
|
+
methodName,
|
|
67
|
+
event,
|
|
68
|
+
async: options.async ?? false,
|
|
69
|
+
priority: options.priority ?? 0,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
Reflect.defineMetadata(
|
|
73
|
+
ON_EVENT_METADATA_KEY,
|
|
74
|
+
[...existingMetadata, metadata],
|
|
75
|
+
constructor,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// 标记类为事件监听器类
|
|
79
|
+
Reflect.defineMetadata(EVENT_LISTENER_CLASS_METADATA_KEY, true, constructor);
|
|
80
|
+
|
|
81
|
+
return descriptor;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 获取类的事件监听器元数据
|
|
87
|
+
* @param target - 目标类
|
|
88
|
+
*/
|
|
89
|
+
export function getOnEventMetadata(
|
|
90
|
+
target: Function,
|
|
91
|
+
): OnEventMethodMetadata[] | undefined {
|
|
92
|
+
return Reflect.getMetadata(ON_EVENT_METADATA_KEY, target);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 检查类是否为事件监听器类
|
|
97
|
+
* @param target - 目标类
|
|
98
|
+
*/
|
|
99
|
+
export function isEventListenerClass(target: Function): boolean {
|
|
100
|
+
return (
|
|
101
|
+
Reflect.getMetadata(EVENT_LISTENER_CLASS_METADATA_KEY, target) === true
|
|
102
|
+
);
|
|
103
|
+
}
|