@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
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
# 错误处理指南
|
|
2
|
+
|
|
3
|
+
本文档介绍 Bun Server Framework
|
|
4
|
+
的错误处理系统,包括错误码规范、国际化支持和最佳实践。
|
|
5
|
+
|
|
6
|
+
## 目录
|
|
7
|
+
|
|
8
|
+
- [错误码系统](#错误码系统)
|
|
9
|
+
- [错误消息国际化](#错误消息国际化)
|
|
10
|
+
- [异常过滤器](#异常过滤器)
|
|
11
|
+
- [最佳实践](#最佳实践)
|
|
12
|
+
- [示例代码](#示例代码)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 错误码系统
|
|
17
|
+
|
|
18
|
+
### 错误码规范
|
|
19
|
+
|
|
20
|
+
错误码采用统一的命名规范:
|
|
21
|
+
|
|
22
|
+
- **格式**:`模块_错误类型_具体错误`
|
|
23
|
+
- **使用大写字母和下划线**
|
|
24
|
+
- **模块前缀**:
|
|
25
|
+
- `AUTH` - 认证和授权
|
|
26
|
+
- `VALIDATION` - 验证
|
|
27
|
+
- `DATABASE` - 数据库
|
|
28
|
+
- `FILE` - 文件操作
|
|
29
|
+
- `MIDDLEWARE` - 中间件
|
|
30
|
+
- `OAUTH2` - OAuth2
|
|
31
|
+
- `CONFIG` - 配置
|
|
32
|
+
|
|
33
|
+
**示例**:
|
|
34
|
+
|
|
35
|
+
- `AUTH_INVALID_TOKEN` - 无效的认证令牌
|
|
36
|
+
- `VALIDATION_REQUIRED_FIELD` - 必填字段缺失
|
|
37
|
+
- `DATABASE_CONNECTION_FAILED` - 数据库连接失败
|
|
38
|
+
|
|
39
|
+
### 错误码分类
|
|
40
|
+
|
|
41
|
+
错误码按功能模块分类,每个分类对应一个数字范围:
|
|
42
|
+
|
|
43
|
+
- **1000-1999**: 通用错误
|
|
44
|
+
- **2000-2999**: 认证和授权错误
|
|
45
|
+
- **3000-3999**: 验证错误
|
|
46
|
+
- **4000-4999**: OAuth2 错误
|
|
47
|
+
- **5000-5999**: 数据库错误
|
|
48
|
+
- **6000-6999**: 文件操作错误
|
|
49
|
+
- **7000-7999**: 中间件错误
|
|
50
|
+
- **8000-8999**: 配置错误
|
|
51
|
+
|
|
52
|
+
### 使用错误码
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { ErrorCode, HttpException } from "@dangao/bun-server";
|
|
56
|
+
|
|
57
|
+
// 方式 1: 使用 HttpException.withCode()
|
|
58
|
+
throw HttpException.withCode(ErrorCode.RESOURCE_NOT_FOUND);
|
|
59
|
+
|
|
60
|
+
// 方式 2: 使用带错误码的异常类
|
|
61
|
+
throw new NotFoundException(
|
|
62
|
+
"User not found",
|
|
63
|
+
undefined,
|
|
64
|
+
ErrorCode.RESOURCE_NOT_FOUND,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// 方式 3: 带自定义消息和详情
|
|
68
|
+
throw HttpException.withCode(
|
|
69
|
+
ErrorCode.VALIDATION_FAILED,
|
|
70
|
+
"Custom validation message",
|
|
71
|
+
{ field: "email", reason: "Invalid format" },
|
|
72
|
+
);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 错误消息国际化
|
|
78
|
+
|
|
79
|
+
### 支持的语言
|
|
80
|
+
|
|
81
|
+
框架支持以下语言:
|
|
82
|
+
|
|
83
|
+
- `en` - 英语(默认)
|
|
84
|
+
- `zh-CN` - 简体中文
|
|
85
|
+
- `ja` - 日语(部分)
|
|
86
|
+
- `ko` - 韩语(部分)
|
|
87
|
+
|
|
88
|
+
### 自动语言检测
|
|
89
|
+
|
|
90
|
+
框架会根据 HTTP 请求头 `Accept-Language` 自动检测用户语言:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// 请求头示例
|
|
94
|
+
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 消息模板系统
|
|
98
|
+
|
|
99
|
+
错误消息支持模板参数,使用 `{key}` 作为占位符:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { ErrorCode, HttpException } from "@dangao/bun-server";
|
|
103
|
+
|
|
104
|
+
// 如果错误消息模板是 'Resource {resource} not found'
|
|
105
|
+
throw HttpException.withCode(
|
|
106
|
+
ErrorCode.RESOURCE_NOT_FOUND,
|
|
107
|
+
undefined,
|
|
108
|
+
undefined,
|
|
109
|
+
{ resource: "User" }, // 消息模板参数
|
|
110
|
+
);
|
|
111
|
+
// 返回: 'Resource User not found' (英文) 或 '资源 User 未找到' (中文)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 手动设置语言
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { ErrorMessageI18n } from "@dangao/bun-server";
|
|
118
|
+
|
|
119
|
+
// 设置全局语言
|
|
120
|
+
ErrorMessageI18n.setLanguage("zh-CN");
|
|
121
|
+
|
|
122
|
+
// 获取指定语言的错误消息
|
|
123
|
+
const message = ErrorMessageI18n.getMessage(
|
|
124
|
+
ErrorCode.RESOURCE_NOT_FOUND,
|
|
125
|
+
"zh-CN",
|
|
126
|
+
{ resource: "User" },
|
|
127
|
+
);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 异常过滤器
|
|
133
|
+
|
|
134
|
+
异常过滤器允许你自定义错误处理逻辑,可以针对特定异常类型或错误码进行处理。
|
|
135
|
+
|
|
136
|
+
### 创建异常过滤器
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import type { ExceptionFilter } from "@dangao/bun-server";
|
|
140
|
+
import type { Context } from "@dangao/bun-server";
|
|
141
|
+
import { ErrorCode, HttpException } from "@dangao/bun-server";
|
|
142
|
+
|
|
143
|
+
class CustomExceptionFilter implements ExceptionFilter {
|
|
144
|
+
public catch(error: unknown, context: Context): Response | undefined {
|
|
145
|
+
if (
|
|
146
|
+
error instanceof HttpException &&
|
|
147
|
+
error.code === ErrorCode.DATABASE_CONNECTION_FAILED
|
|
148
|
+
) {
|
|
149
|
+
// 自定义数据库连接错误的处理
|
|
150
|
+
context.setStatus(503);
|
|
151
|
+
return context.createResponse({
|
|
152
|
+
error: "Database service temporarily unavailable",
|
|
153
|
+
code: error.code,
|
|
154
|
+
retryAfter: 60, // 建议 60 秒后重试
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 返回 undefined 表示不处理,继续交给下一个过滤器
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 注册异常过滤器
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { ExceptionFilterRegistry } from "@dangao/bun-server";
|
|
168
|
+
|
|
169
|
+
const registry = ExceptionFilterRegistry.getInstance();
|
|
170
|
+
registry.register(new CustomExceptionFilter());
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 过滤器执行顺序
|
|
174
|
+
|
|
175
|
+
异常过滤器按照注册顺序执行,第一个返回非 `undefined` 结果的过滤器会被使用。
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 最佳实践
|
|
180
|
+
|
|
181
|
+
### 1. 使用错误码
|
|
182
|
+
|
|
183
|
+
**推荐** ✅:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
throw HttpException.withCode(ErrorCode.RESOURCE_NOT_FOUND);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**不推荐** ❌:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
throw new Error("Resource not found");
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 2. 提供错误详情
|
|
196
|
+
|
|
197
|
+
对于验证错误,提供详细的错误信息:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
throw HttpException.withCode(
|
|
201
|
+
ErrorCode.VALIDATION_FAILED,
|
|
202
|
+
"Validation failed",
|
|
203
|
+
{
|
|
204
|
+
field: "email",
|
|
205
|
+
reason: "Invalid email format",
|
|
206
|
+
value: userInput.email,
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### 3. 使用消息模板参数
|
|
212
|
+
|
|
213
|
+
对于需要动态信息的错误消息,使用消息模板参数:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
throw HttpException.withCode(
|
|
217
|
+
ErrorCode.RESOURCE_NOT_FOUND,
|
|
218
|
+
undefined,
|
|
219
|
+
undefined,
|
|
220
|
+
{ resource: "User", id: userId },
|
|
221
|
+
);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### 4. 自定义异常过滤器
|
|
225
|
+
|
|
226
|
+
对于需要特殊处理的错误,使用异常过滤器:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
class DatabaseExceptionFilter implements ExceptionFilter {
|
|
230
|
+
public catch(error: unknown, context: Context): Response | undefined {
|
|
231
|
+
if (
|
|
232
|
+
error instanceof HttpException &&
|
|
233
|
+
error.code?.startsWith("DATABASE_")
|
|
234
|
+
) {
|
|
235
|
+
// 统一处理所有数据库错误
|
|
236
|
+
return context.createResponse({
|
|
237
|
+
error: "Database error occurred",
|
|
238
|
+
code: error.code,
|
|
239
|
+
timestamp: new Date().toISOString(),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### 5. 生产环境错误处理
|
|
248
|
+
|
|
249
|
+
在生产环境中,避免暴露敏感信息:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
// 错误处理器会自动处理
|
|
253
|
+
// 在生产环境中,非 HttpException 的错误不会暴露详细信息
|
|
254
|
+
if (process.env.NODE_ENV === "production") {
|
|
255
|
+
// 只返回通用错误消息
|
|
256
|
+
return context.createResponse({
|
|
257
|
+
error: "Internal Server Error",
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 6. 错误日志记录
|
|
263
|
+
|
|
264
|
+
在异常过滤器中记录错误日志:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
class LoggingExceptionFilter implements ExceptionFilter {
|
|
268
|
+
public catch(error: unknown, context: Context): Response | undefined {
|
|
269
|
+
// 记录错误日志
|
|
270
|
+
console.error("Error occurred:", {
|
|
271
|
+
error,
|
|
272
|
+
path: context.getPath(),
|
|
273
|
+
method: context.getMethod(),
|
|
274
|
+
timestamp: new Date().toISOString(),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return undefined; // 继续交给下一个过滤器
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 示例代码
|
|
285
|
+
|
|
286
|
+
### 完整的错误处理示例
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import {
|
|
290
|
+
Application,
|
|
291
|
+
type Context,
|
|
292
|
+
Controller,
|
|
293
|
+
ErrorCode,
|
|
294
|
+
ExceptionFilter,
|
|
295
|
+
ExceptionFilterRegistry,
|
|
296
|
+
GET,
|
|
297
|
+
HttpException,
|
|
298
|
+
Param,
|
|
299
|
+
} from "@dangao/bun-server";
|
|
300
|
+
|
|
301
|
+
// 1. 创建自定义异常过滤器
|
|
302
|
+
class ApiExceptionFilter implements ExceptionFilter {
|
|
303
|
+
public catch(error: unknown, context: Context): Response | undefined {
|
|
304
|
+
if (error instanceof HttpException) {
|
|
305
|
+
// 添加请求 ID 和时间戳
|
|
306
|
+
return context.createResponse({
|
|
307
|
+
error: error.message,
|
|
308
|
+
code: error.code,
|
|
309
|
+
details: error.details,
|
|
310
|
+
requestId: context.getHeader("x-request-id"),
|
|
311
|
+
timestamp: new Date().toISOString(),
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 2. 注册异常过滤器
|
|
319
|
+
const registry = ExceptionFilterRegistry.getInstance();
|
|
320
|
+
registry.register(new ApiExceptionFilter());
|
|
321
|
+
|
|
322
|
+
// 3. 在控制器中使用错误码
|
|
323
|
+
@Controller("/api/users")
|
|
324
|
+
class UserController {
|
|
325
|
+
@GET("/:id")
|
|
326
|
+
public async getUser(@Param("id") id: string) {
|
|
327
|
+
const userId = parseInt(id, 10);
|
|
328
|
+
|
|
329
|
+
if (isNaN(userId)) {
|
|
330
|
+
throw HttpException.withCode(
|
|
331
|
+
ErrorCode.VALIDATION_INVALID_FORMAT,
|
|
332
|
+
"Invalid user ID format",
|
|
333
|
+
{ field: "id", value: id },
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const user = await this.userService.findById(userId);
|
|
338
|
+
|
|
339
|
+
if (!user) {
|
|
340
|
+
throw HttpException.withCode(
|
|
341
|
+
ErrorCode.RESOURCE_NOT_FOUND,
|
|
342
|
+
undefined,
|
|
343
|
+
undefined,
|
|
344
|
+
{ resource: "User", id: userId },
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return user;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// 4. 启动应用
|
|
353
|
+
const app = new Application();
|
|
354
|
+
app.registerController(UserController);
|
|
355
|
+
app.listen();
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### 数据库错误处理示例
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
import { ErrorCode, HttpException } from "@dangao/bun-server";
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
await database.query("SELECT * FROM users WHERE id = ?", [userId]);
|
|
365
|
+
} catch (error) {
|
|
366
|
+
if (error.code === "SQLITE_ERROR") {
|
|
367
|
+
throw HttpException.withCode(
|
|
368
|
+
ErrorCode.DATABASE_QUERY_FAILED,
|
|
369
|
+
"Failed to query user",
|
|
370
|
+
{ sql: error.sql, params: [userId] },
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### 验证错误处理示例
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
import { ErrorCode, HttpException } from "@dangao/bun-server";
|
|
381
|
+
|
|
382
|
+
if (!email || !isValidEmail(email)) {
|
|
383
|
+
throw HttpException.withCode(
|
|
384
|
+
ErrorCode.VALIDATION_INVALID_FORMAT,
|
|
385
|
+
"Invalid email format",
|
|
386
|
+
{
|
|
387
|
+
field: "email",
|
|
388
|
+
value: email,
|
|
389
|
+
expected: "valid email address",
|
|
390
|
+
},
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## 错误码参考
|
|
398
|
+
|
|
399
|
+
### 通用错误 (1000-1999)
|
|
400
|
+
|
|
401
|
+
- `INTERNAL_ERROR` - 服务器内部错误
|
|
402
|
+
- `INVALID_REQUEST` - 无效的请求
|
|
403
|
+
- `RESOURCE_NOT_FOUND` - 资源未找到
|
|
404
|
+
- `METHOD_NOT_ALLOWED` - 方法不允许
|
|
405
|
+
- `RATE_LIMIT_EXCEEDED` - 请求频率超限
|
|
406
|
+
- `SERVICE_UNAVAILABLE` - 服务不可用
|
|
407
|
+
- `TIMEOUT` - 请求超时
|
|
408
|
+
|
|
409
|
+
### 认证和授权错误 (2000-2999)
|
|
410
|
+
|
|
411
|
+
- `AUTH_REQUIRED` - 需要认证
|
|
412
|
+
- `AUTH_INVALID_TOKEN` - 无效的认证令牌
|
|
413
|
+
- `AUTH_TOKEN_EXPIRED` - 认证令牌已过期
|
|
414
|
+
- `AUTH_INSUFFICIENT_PERMISSIONS` - 权限不足
|
|
415
|
+
- `AUTH_INVALID_CREDENTIALS` - 无效的凭据
|
|
416
|
+
- `AUTH_ACCOUNT_LOCKED` - 账户已锁定
|
|
417
|
+
- `AUTH_ACCOUNT_DISABLED` - 账户已禁用
|
|
418
|
+
|
|
419
|
+
### 验证错误 (3000-3999)
|
|
420
|
+
|
|
421
|
+
- `VALIDATION_FAILED` - 验证失败
|
|
422
|
+
- `VALIDATION_REQUIRED_FIELD` - 必填字段缺失
|
|
423
|
+
- `VALIDATION_INVALID_FORMAT` - 格式无效
|
|
424
|
+
- `VALIDATION_OUT_OF_RANGE` - 值超出范围
|
|
425
|
+
- `VALIDATION_TYPE_MISMATCH` - 类型不匹配
|
|
426
|
+
- `VALIDATION_CONSTRAINT_VIOLATION` - 约束违反
|
|
427
|
+
|
|
428
|
+
### 数据库错误 (5000-5999)
|
|
429
|
+
|
|
430
|
+
- `DATABASE_CONNECTION_FAILED` - 数据库连接失败
|
|
431
|
+
- `DATABASE_QUERY_FAILED` - 数据库查询失败
|
|
432
|
+
- `DATABASE_TRANSACTION_FAILED` - 数据库事务失败
|
|
433
|
+
- `DATABASE_CONSTRAINT_VIOLATION` - 数据库约束违反
|
|
434
|
+
- `DATABASE_TIMEOUT` - 数据库超时
|
|
435
|
+
- `DATABASE_POOL_EXHAUSTED` - 数据库连接池耗尽
|
|
436
|
+
- `DATABASE_MIGRATION_FAILED` - 数据库迁移失败
|
|
437
|
+
|
|
438
|
+
### 文件操作错误 (6000-6999)
|
|
439
|
+
|
|
440
|
+
- `FILE_NOT_FOUND` - 文件未找到
|
|
441
|
+
- `FILE_UPLOAD_FAILED` - 文件上传失败
|
|
442
|
+
- `FILE_DOWNLOAD_FAILED` - 文件下载失败
|
|
443
|
+
- `FILE_SIZE_EXCEEDED` - 文件大小超限
|
|
444
|
+
- `FILE_TYPE_NOT_ALLOWED` - 不允许的文件类型
|
|
445
|
+
- `FILE_ACCESS_DENIED` - 文件访问被拒绝
|
|
446
|
+
- `FILE_PATH_TRAVERSAL` - 检测到路径遍历攻击
|
|
447
|
+
|
|
448
|
+
完整的错误码列表请参考 `src/error/error-codes.ts`。
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## 相关资源
|
|
453
|
+
|
|
454
|
+
- [API 文档](./api.md)
|
|
455
|
+
- [使用指南](./guide.md)
|
|
456
|
+
- [最佳实践](./best-practices.md)
|