@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,451 @@
|
|
|
1
|
+
# 性能优化指南
|
|
2
|
+
|
|
3
|
+
本文档介绍 Bun Server Framework 应用的性能优化技术。
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
- [基准测试](#基准测试)
|
|
8
|
+
- [缓存策略](#缓存策略)
|
|
9
|
+
- [数据库优化](#数据库优化)
|
|
10
|
+
- [中间件优化](#中间件优化)
|
|
11
|
+
- [响应优化](#响应优化)
|
|
12
|
+
- [内存管理](#内存管理)
|
|
13
|
+
- [并发处理](#并发处理)
|
|
14
|
+
|
|
15
|
+
## 基准测试
|
|
16
|
+
|
|
17
|
+
### 使用 Performance Harness
|
|
18
|
+
|
|
19
|
+
使用内置的 `PerformanceHarness` 进行基准测试:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { PerformanceHarness } from "@dangao/bun-server/testing";
|
|
23
|
+
|
|
24
|
+
const result = await PerformanceHarness.benchmark(
|
|
25
|
+
"my-operation",
|
|
26
|
+
1000,
|
|
27
|
+
async () => {
|
|
28
|
+
// Your code to benchmark
|
|
29
|
+
await doSomething();
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
console.log(`Operations per second: ${result.opsPerSecond}`);
|
|
34
|
+
console.log(`Total duration: ${result.durationMs}ms`);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 压力测试
|
|
38
|
+
|
|
39
|
+
使用 `StressTester` 进行并发负载测试:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { StressTester } from "@dangao/bun-server/testing";
|
|
43
|
+
|
|
44
|
+
const result = await StressTester.run(
|
|
45
|
+
"my-endpoint",
|
|
46
|
+
1000, // iterations
|
|
47
|
+
10, // concurrency
|
|
48
|
+
async (iteration) => {
|
|
49
|
+
const response = await fetch("http://localhost:3000/api/test");
|
|
50
|
+
await response.text();
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
console.log(`Errors: ${result.errors}`);
|
|
55
|
+
console.log(`Duration: ${result.durationMs}ms`);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 缓存策略
|
|
59
|
+
|
|
60
|
+
### 使用 CacheModule
|
|
61
|
+
|
|
62
|
+
为频繁访问的数据启用缓存:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import {
|
|
66
|
+
CACHE_SERVICE_TOKEN,
|
|
67
|
+
CacheModule,
|
|
68
|
+
CacheService,
|
|
69
|
+
} from "@dangao/bun-server";
|
|
70
|
+
|
|
71
|
+
CacheModule.forRoot({
|
|
72
|
+
store: new RedisCacheStore(redisClient), // Use Redis for distributed caching
|
|
73
|
+
defaultTtl: 3600000, // 1 hour
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
@Injectable()
|
|
77
|
+
class ProductService {
|
|
78
|
+
public constructor(
|
|
79
|
+
@Inject(CACHE_SERVICE_TOKEN) private readonly cache: CacheService,
|
|
80
|
+
) {}
|
|
81
|
+
|
|
82
|
+
public async getProduct(id: string) {
|
|
83
|
+
return await this.cache.getOrSet(
|
|
84
|
+
`product:${id}`,
|
|
85
|
+
async () => {
|
|
86
|
+
return await this.db.findProduct(id);
|
|
87
|
+
},
|
|
88
|
+
3600000, // Cache for 1 hour
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 缓存装饰器
|
|
95
|
+
|
|
96
|
+
使用缓存装饰器实现自动缓存:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { Cacheable, CacheEvict, CachePut } from "@dangao/bun-server";
|
|
100
|
+
|
|
101
|
+
@Controller("/api/products")
|
|
102
|
+
class ProductController {
|
|
103
|
+
@GET("/:id")
|
|
104
|
+
@Cacheable({ key: "product", ttl: 3600000 })
|
|
105
|
+
public async getProduct(@Param("id") id: string) {
|
|
106
|
+
return await this.productService.find(id);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@PUT("/:id")
|
|
110
|
+
@CachePut({ key: "product" })
|
|
111
|
+
@CacheEvict({ key: "products:list" })
|
|
112
|
+
public async updateProduct(@Param("id") id: string, @Body() data: any) {
|
|
113
|
+
return await this.productService.update(id, data);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 缓存失效
|
|
119
|
+
|
|
120
|
+
实现缓存失效策略:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// Invalidate on update
|
|
124
|
+
@PUT('/:id')
|
|
125
|
+
@CacheEvict({ key: 'product', pattern: true })
|
|
126
|
+
public async updateProduct(@Param('id') id: string) {
|
|
127
|
+
// Cache for 'product:*' will be invalidated
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Invalidate multiple keys
|
|
131
|
+
@DELETE('/:id')
|
|
132
|
+
@CacheEvict({ keys: ['product', 'products:list'] })
|
|
133
|
+
public async deleteProduct(@Param('id') id: string) {
|
|
134
|
+
// Both caches invalidated
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 数据库优化
|
|
139
|
+
|
|
140
|
+
### 连接池
|
|
141
|
+
|
|
142
|
+
配置适当的连接池大小:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
DatabaseModule.forRoot({
|
|
146
|
+
database: {
|
|
147
|
+
type: "postgres",
|
|
148
|
+
config: {
|
|
149
|
+
connectionString: process.env.DATABASE_URL,
|
|
150
|
+
max: 20, // Maximum connections
|
|
151
|
+
min: 5, // Minimum connections
|
|
152
|
+
idleTimeoutMillis: 30000,
|
|
153
|
+
connectionTimeoutMillis: 2000,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 查询优化
|
|
160
|
+
|
|
161
|
+
使用索引并优化查询:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// Use indexes
|
|
165
|
+
@Table("users")
|
|
166
|
+
class User {
|
|
167
|
+
@Column({ primaryKey: true })
|
|
168
|
+
public id!: number;
|
|
169
|
+
|
|
170
|
+
@Column({ index: true }) // Add index
|
|
171
|
+
public email!: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Use select specific fields
|
|
175
|
+
const users = await db.select("id", "name").from("users").where("active", true);
|
|
176
|
+
|
|
177
|
+
// Use pagination
|
|
178
|
+
const users = await db
|
|
179
|
+
.select()
|
|
180
|
+
.from("users")
|
|
181
|
+
.limit(20)
|
|
182
|
+
.offset((page - 1) * 20);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 批量操作
|
|
186
|
+
|
|
187
|
+
对多个插入/更新使用批量操作:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// Batch insert
|
|
191
|
+
await db.batchInsert("users", [
|
|
192
|
+
{ name: "User 1", email: "user1@example.com" },
|
|
193
|
+
{ name: "User 2", email: "user2@example.com" },
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
// Batch update
|
|
197
|
+
await db.transaction(async (trx) => {
|
|
198
|
+
for (const user of users) {
|
|
199
|
+
await trx("users").where("id", user.id).update(user);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## 中间件优化
|
|
205
|
+
|
|
206
|
+
### 最小化中间件
|
|
207
|
+
|
|
208
|
+
只使用必要的中间件:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// ❌ Too many middleware
|
|
212
|
+
app.use(middleware1);
|
|
213
|
+
app.use(middleware2);
|
|
214
|
+
app.use(middleware3);
|
|
215
|
+
app.use(middleware4);
|
|
216
|
+
app.use(middleware5);
|
|
217
|
+
|
|
218
|
+
// ✅ Combine or remove unnecessary middleware
|
|
219
|
+
app.use(combinedMiddleware);
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### 条件中间件
|
|
223
|
+
|
|
224
|
+
使用条件中间件以获得更好的性能:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
app.use(async (ctx, next) => {
|
|
228
|
+
// Skip middleware for static files
|
|
229
|
+
if (ctx.path.startsWith("/static")) {
|
|
230
|
+
return await next();
|
|
231
|
+
}
|
|
232
|
+
// Apply middleware
|
|
233
|
+
return await middleware(ctx, next);
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 异步中间件
|
|
238
|
+
|
|
239
|
+
确保中间件正确使用异步:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// ✅ Correct: Properly await next()
|
|
243
|
+
app.use(async (ctx, next) => {
|
|
244
|
+
const start = Date.now();
|
|
245
|
+
const response = await next();
|
|
246
|
+
const duration = Date.now() - start;
|
|
247
|
+
console.log(`Request took ${duration}ms`);
|
|
248
|
+
return response;
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// ❌ Wrong: Not awaiting next()
|
|
252
|
+
app.use(async (ctx, next) => {
|
|
253
|
+
next(); // Missing await
|
|
254
|
+
return new Response("ok");
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## 响应优化
|
|
259
|
+
|
|
260
|
+
### 压缩
|
|
261
|
+
|
|
262
|
+
在反向代理(Nginx、Caddy)中启用 gzip 压缩:
|
|
263
|
+
|
|
264
|
+
```nginx
|
|
265
|
+
# Nginx
|
|
266
|
+
gzip on;
|
|
267
|
+
gzip_vary on;
|
|
268
|
+
gzip_min_length 1024;
|
|
269
|
+
gzip_types text/plain text/css application/json application/javascript;
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 响应流
|
|
273
|
+
|
|
274
|
+
对大型响应使用流式传输:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
@GET('/large-data')
|
|
278
|
+
public async getLargeData() {
|
|
279
|
+
const stream = new ReadableStream({
|
|
280
|
+
async start(controller) {
|
|
281
|
+
// Stream data in chunks
|
|
282
|
+
for (const chunk of largeData) {
|
|
283
|
+
controller.enqueue(JSON.stringify(chunk));
|
|
284
|
+
}
|
|
285
|
+
controller.close();
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
return new Response(stream, {
|
|
290
|
+
headers: {
|
|
291
|
+
'Content-Type': 'application/json',
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 分页
|
|
298
|
+
|
|
299
|
+
始终对大型数据集进行分页:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
@GET('/users')
|
|
303
|
+
public async getUsers(
|
|
304
|
+
@Query('page') page: number = 1,
|
|
305
|
+
@Query('limit') limit: number = 20,
|
|
306
|
+
) {
|
|
307
|
+
const offset = (page - 1) * limit;
|
|
308
|
+
const users = await this.userService.findAll({ limit, offset });
|
|
309
|
+
return {
|
|
310
|
+
data: users,
|
|
311
|
+
pagination: {
|
|
312
|
+
page,
|
|
313
|
+
limit,
|
|
314
|
+
total: await this.userService.count(),
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## 内存管理
|
|
321
|
+
|
|
322
|
+
### 避免内存泄漏
|
|
323
|
+
|
|
324
|
+
正确清理资源:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
// Clean up in afterEach/afterAll
|
|
328
|
+
afterEach(async () => {
|
|
329
|
+
await app.stop();
|
|
330
|
+
ModuleRegistry.getInstance().clear();
|
|
331
|
+
RouteRegistry.getInstance().clear();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Clear intervals/timeouts
|
|
335
|
+
const intervalId = setInterval(() => {}, 1000);
|
|
336
|
+
// ... later
|
|
337
|
+
clearInterval(intervalId);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### 使用 TTL 缓存
|
|
341
|
+
|
|
342
|
+
为缓存条目设置适当的 TTL:
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
CacheModule.forRoot({
|
|
346
|
+
defaultTtl: 3600000, // 1 hour
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Override for specific entries
|
|
350
|
+
await cache.set("key", "value", 60000); // 1 minute
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### 监控内存使用
|
|
354
|
+
|
|
355
|
+
在生产环境中监控内存使用:
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
import { performance } from "node:perf_hooks";
|
|
359
|
+
|
|
360
|
+
setInterval(() => {
|
|
361
|
+
const usage = process.memoryUsage();
|
|
362
|
+
console.log({
|
|
363
|
+
heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)}MB`,
|
|
364
|
+
heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)}MB`,
|
|
365
|
+
rss: `${Math.round(usage.rss / 1024 / 1024)}MB`,
|
|
366
|
+
});
|
|
367
|
+
}, 60000); // Every minute
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## 并发处理
|
|
371
|
+
|
|
372
|
+
### 使用 Async/Await
|
|
373
|
+
|
|
374
|
+
对 I/O 操作始终使用 async/await:
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
// ✅ Correct: Async
|
|
378
|
+
@GET('/users')
|
|
379
|
+
public async getUsers() {
|
|
380
|
+
return await this.userService.findAll();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ❌ Wrong: Synchronous
|
|
384
|
+
@GET('/users')
|
|
385
|
+
public getUsers() {
|
|
386
|
+
return this.userService.findAllSync(); // Blocks event loop
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### 并行操作
|
|
391
|
+
|
|
392
|
+
对并行操作使用 `Promise.all()`:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
// ✅ Parallel execution
|
|
396
|
+
const [user, profile, settings] = await Promise.all([
|
|
397
|
+
this.userService.find(id),
|
|
398
|
+
this.profileService.find(id),
|
|
399
|
+
this.settingsService.find(id),
|
|
400
|
+
]);
|
|
401
|
+
|
|
402
|
+
// ❌ Sequential execution
|
|
403
|
+
const user = await this.userService.find(id);
|
|
404
|
+
const profile = await this.profileService.find(id);
|
|
405
|
+
const settings = await this.settingsService.find(id);
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Worker 线程
|
|
409
|
+
|
|
410
|
+
对 CPU 密集型任务使用 worker 线程:
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
import { Worker } from 'worker_threads';
|
|
414
|
+
|
|
415
|
+
@POST('/process')
|
|
416
|
+
public async processData(@Body() data: any) {
|
|
417
|
+
return await new Promise((resolve, reject) => {
|
|
418
|
+
const worker = new Worker('./worker.js', {
|
|
419
|
+
workerData: data,
|
|
420
|
+
});
|
|
421
|
+
worker.on('message', resolve);
|
|
422
|
+
worker.on('error', reject);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## 最佳实践总结
|
|
428
|
+
|
|
429
|
+
1. **缓存频繁访问的数据** - 使用 CacheModule 并设置适当的 TTL
|
|
430
|
+
2. **优化数据库查询** - 使用索引、分页、批量操作
|
|
431
|
+
3. **最小化中间件** - 只使用必要的中间件
|
|
432
|
+
4. **使用 async/await** - 永远不要阻塞事件循环
|
|
433
|
+
5. **启用压缩** - 使用 gzip/brotli 压缩
|
|
434
|
+
6. **监控性能** - 使用 PerformanceHarness 和监控工具
|
|
435
|
+
7. **清理资源** - 在测试和关闭处理程序中正确清理
|
|
436
|
+
8. **使用连接池** - 配置适当的池大小
|
|
437
|
+
9. **流式传输大型响应** - 对大型数据使用流式传输
|
|
438
|
+
10. **并行化操作** - 对独立操作使用 Promise.all()
|
|
439
|
+
|
|
440
|
+
## 性能检查清单
|
|
441
|
+
|
|
442
|
+
- [ ] 为频繁访问的数据启用了缓存
|
|
443
|
+
- [ ] 配置了数据库连接池
|
|
444
|
+
- [ ] 优化了数据库查询(索引、分页)
|
|
445
|
+
- [ ] 最小化并优化了中间件
|
|
446
|
+
- [ ] 启用了压缩(gzip/brotli)
|
|
447
|
+
- [ ] 所有 I/O 操作都使用了 async/await
|
|
448
|
+
- [ ] 防止了内存泄漏(清理处理程序)
|
|
449
|
+
- [ ] 启用了性能监控
|
|
450
|
+
- [ ] 大型响应已分页或流式传输
|
|
451
|
+
- [ ] 在可能的情况下使用了并行操作
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# 故障排查指南
|
|
2
|
+
|
|
3
|
+
本文档帮助你在使用 Bun Server Framework 时诊断和解决常见问题。
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
- [常见问题](#常见问题)
|
|
8
|
+
- [模块注册](#模块注册)
|
|
9
|
+
- [依赖注入](#依赖注入)
|
|
10
|
+
- [路由问题](#路由问题)
|
|
11
|
+
- [性能问题](#性能问题)
|
|
12
|
+
- [调试技巧](#调试技巧)
|
|
13
|
+
|
|
14
|
+
## 常见问题
|
|
15
|
+
|
|
16
|
+
### Provider 未找到
|
|
17
|
+
|
|
18
|
+
**错误**:`Provider not found for token: Symbol(...)`
|
|
19
|
+
|
|
20
|
+
**原因**:模块的 `forRoot()` 方法在 `Application`
|
|
21
|
+
实例创建之前被调用,或者模块未通过 `app.registerModule()` 注册。
|
|
22
|
+
|
|
23
|
+
**解决方案**:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// ❌ Wrong: Module.forRoot() before Application
|
|
27
|
+
ConfigModule.forRoot({ defaultConfig: {} });
|
|
28
|
+
const app = new Application({ port: 3000 });
|
|
29
|
+
|
|
30
|
+
// ✅ Correct: Module.forRoot() before Application, then register
|
|
31
|
+
ConfigModule.forRoot({ defaultConfig: {} });
|
|
32
|
+
const app = new Application({ port: 3000 });
|
|
33
|
+
app.registerModule(ConfigModule);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 模块元数据未清除
|
|
37
|
+
|
|
38
|
+
**错误**:测试失败,提示 "Module already registered" 或测试中出现意外行为。
|
|
39
|
+
|
|
40
|
+
**原因**:模块元数据在测试之间持续存在。
|
|
41
|
+
|
|
42
|
+
**解决方案**:在 `beforeEach` 中清除模块元数据:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { MODULE_METADATA_KEY } from "../../src/di/module";
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
Reflect.deleteMetadata(MODULE_METADATA_KEY, YourModule);
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 端口已被使用
|
|
53
|
+
|
|
54
|
+
**错误**:`Error: listen EADDRINUSE: address already in use`
|
|
55
|
+
|
|
56
|
+
**原因**:另一个进程正在使用该端口,或者之前的测试未清理。
|
|
57
|
+
|
|
58
|
+
**解决方案**:在测试中使用 `getTestPort()` 工具:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { getTestPort } from "../utils/test-port";
|
|
62
|
+
|
|
63
|
+
const port = getTestPort();
|
|
64
|
+
const app = new Application({ port });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 模块注册
|
|
68
|
+
|
|
69
|
+
### 模块顺序很重要
|
|
70
|
+
|
|
71
|
+
模块应按依赖顺序注册。如果 `AppModule` 依赖于 `ConfigModule`,请先注册
|
|
72
|
+
`ConfigModule`:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
ConfigModule.forRoot({ defaultConfig: {} });
|
|
76
|
+
CacheModule.forRoot({ defaultTtl: 60000 });
|
|
77
|
+
const app = new Application({ port: 3000 });
|
|
78
|
+
app.registerModule(ConfigModule);
|
|
79
|
+
app.registerModule(CacheModule);
|
|
80
|
+
app.registerModule(AppModule);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 循环依赖
|
|
84
|
+
|
|
85
|
+
**错误**:检测到循环依赖或模块初始化期间出现无限循环。
|
|
86
|
+
|
|
87
|
+
**解决方案**:重构模块以移除循环依赖,或使用延迟加载:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// Use dynamic import for circular dependencies
|
|
91
|
+
const { SomeService } = await import("./some-service");
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 依赖注入
|
|
95
|
+
|
|
96
|
+
### 服务不可注入
|
|
97
|
+
|
|
98
|
+
**错误**:`Cannot resolve dependency` 或找不到服务。
|
|
99
|
+
|
|
100
|
+
**解决方案**:确保服务使用 `@Injectable()` 装饰器:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
@Injectable()
|
|
104
|
+
class UserService {
|
|
105
|
+
// ...
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Token 未找到
|
|
110
|
+
|
|
111
|
+
**错误**:`Provider not found for token`
|
|
112
|
+
|
|
113
|
+
**解决方案**:确保 token 从模块中导出且模块已注册:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// In module
|
|
117
|
+
@Module({
|
|
118
|
+
providers: [
|
|
119
|
+
{ provide: MY_TOKEN, useValue: myService },
|
|
120
|
+
],
|
|
121
|
+
exports: [MY_TOKEN],
|
|
122
|
+
})
|
|
123
|
+
class MyModule {}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## 路由问题
|
|
127
|
+
|
|
128
|
+
### 路由未找到 (404)
|
|
129
|
+
|
|
130
|
+
**可能原因**:
|
|
131
|
+
|
|
132
|
+
1. 控制器未注册
|
|
133
|
+
2. 路由路径不匹配
|
|
134
|
+
3. HTTP 方法不匹配
|
|
135
|
+
|
|
136
|
+
**解决方案**:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// Ensure controller is registered
|
|
140
|
+
@Module({
|
|
141
|
+
controllers: [UserController],
|
|
142
|
+
})
|
|
143
|
+
class AppModule {}
|
|
144
|
+
|
|
145
|
+
// Check route path
|
|
146
|
+
@Controller("/api/users")
|
|
147
|
+
class UserController {
|
|
148
|
+
@GET("/:id") // Matches /api/users/:id
|
|
149
|
+
public getUser(@Param("id") id: string) {}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 参数未绑定
|
|
154
|
+
|
|
155
|
+
**错误**:`@Param('id')` 返回 `undefined`
|
|
156
|
+
|
|
157
|
+
**解决方案**:确保参数名称与路由参数匹配:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// Route: /api/users/:id
|
|
161
|
+
@GET('/:id')
|
|
162
|
+
public getUser(@Param('id') id: string) { // ✅ Correct
|
|
163
|
+
// ...
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@GET('/:id')
|
|
167
|
+
public getUser(@Param('userId') userId: string) { // ❌ Wrong
|
|
168
|
+
// ...
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## 性能问题
|
|
173
|
+
|
|
174
|
+
### 请求处理缓慢
|
|
175
|
+
|
|
176
|
+
**可能原因**:
|
|
177
|
+
|
|
178
|
+
1. 请求处理程序中的重计算
|
|
179
|
+
2. 同步阻塞操作
|
|
180
|
+
3. 大型响应负载
|
|
181
|
+
|
|
182
|
+
**解决方案**:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// Use async/await for I/O operations
|
|
186
|
+
@GET('/users')
|
|
187
|
+
public async getUsers() {
|
|
188
|
+
return await this.userService.findAll(); // ✅ Async
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Avoid blocking operations
|
|
192
|
+
@GET('/users')
|
|
193
|
+
public getUsers() {
|
|
194
|
+
return this.userService.findAllSync(); // ❌ Blocking
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 内存泄漏
|
|
199
|
+
|
|
200
|
+
**症状**:内存使用量随时间增加。
|
|
201
|
+
|
|
202
|
+
**可能原因**:
|
|
203
|
+
|
|
204
|
+
1. 事件监听器未清理
|
|
205
|
+
2. 定时器/间隔未清除
|
|
206
|
+
3. 缓存未过期
|
|
207
|
+
|
|
208
|
+
**解决方案**:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// Clean up in afterEach/afterAll
|
|
212
|
+
afterEach(async () => {
|
|
213
|
+
await app.stop();
|
|
214
|
+
ModuleRegistry.getInstance().clear();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Use TTL for cache
|
|
218
|
+
CacheModule.forRoot({
|
|
219
|
+
defaultTtl: 3600000, // 1 hour
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## 调试技巧
|
|
224
|
+
|
|
225
|
+
### 启用调试日志
|
|
226
|
+
|
|
227
|
+
将日志级别设置为 `DEBUG`:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
LoggerModule.forRoot({
|
|
231
|
+
logger: {
|
|
232
|
+
level: LogLevel.DEBUG,
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 检查模块元数据
|
|
238
|
+
|
|
239
|
+
检查模块元数据:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
import { MODULE_METADATA_KEY } from "../../src/di/module";
|
|
243
|
+
|
|
244
|
+
const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, YourModule);
|
|
245
|
+
console.log("Module metadata:", metadata);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 验证容器状态
|
|
249
|
+
|
|
250
|
+
检查容器中注册的内容:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
const container = app.getContainer();
|
|
254
|
+
// Inspect container internals (implementation-dependent)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 使用测试工具
|
|
258
|
+
|
|
259
|
+
使用测试工具进行一致的测试设置:
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { getTestPort } from "../utils/test-port";
|
|
263
|
+
|
|
264
|
+
const port = getTestPort();
|
|
265
|
+
const app = new Application({ port });
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## 获取帮助
|
|
269
|
+
|
|
270
|
+
如果仍然遇到问题:
|
|
271
|
+
|
|
272
|
+
1. 查看 [API 文档](./api.md)
|
|
273
|
+
2. 查看 [示例](../examples/)
|
|
274
|
+
3. 搜索现有的 [GitHub Issues](https://github.com/your-repo/issues)
|
|
275
|
+
4. 创建新 issue,包含:
|
|
276
|
+
- Bun 版本
|
|
277
|
+
- 框架版本
|
|
278
|
+
- 最小复现代码
|
|
279
|
+
- 错误消息和堆栈跟踪
|