@dangao/bun-server 1.6.0 → 1.7.1

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.
Files changed (43) hide show
  1. package/README.md +72 -3
  2. package/dist/cache/cache-module.d.ts +18 -0
  3. package/dist/cache/cache-module.d.ts.map +1 -1
  4. package/dist/cache/index.d.ts +3 -1
  5. package/dist/cache/index.d.ts.map +1 -1
  6. package/dist/cache/interceptors.d.ts +41 -0
  7. package/dist/cache/interceptors.d.ts.map +1 -0
  8. package/dist/cache/service-proxy.d.ts +62 -0
  9. package/dist/cache/service-proxy.d.ts.map +1 -0
  10. package/dist/controller/controller.d.ts +8 -0
  11. package/dist/controller/controller.d.ts.map +1 -1
  12. package/dist/core/application.d.ts +5 -0
  13. package/dist/core/application.d.ts.map +1 -1
  14. package/dist/core/server.d.ts.map +1 -1
  15. package/dist/di/container.d.ts +18 -1
  16. package/dist/di/container.d.ts.map +1 -1
  17. package/dist/di/index.d.ts +1 -1
  18. package/dist/di/index.d.ts.map +1 -1
  19. package/dist/di/types.d.ts +22 -0
  20. package/dist/di/types.d.ts.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3235 -2875
  24. package/dist/websocket/registry.d.ts +19 -0
  25. package/dist/websocket/registry.d.ts.map +1 -1
  26. package/docs/symbol-interface-pattern.md +431 -0
  27. package/docs/zh/symbol-interface-pattern.md +431 -0
  28. package/package.json +1 -1
  29. package/src/cache/cache-module.ts +37 -0
  30. package/src/cache/index.ts +16 -1
  31. package/src/cache/interceptors.ts +295 -0
  32. package/src/cache/service-proxy.ts +219 -0
  33. package/src/controller/controller.ts +30 -6
  34. package/src/core/application.ts +25 -1
  35. package/src/core/server.ts +1 -0
  36. package/src/di/container.ts +57 -7
  37. package/src/di/index.ts +7 -1
  38. package/src/di/types.ts +29 -0
  39. package/src/index.ts +7 -0
  40. package/src/websocket/registry.ts +114 -14
  41. package/tests/cache/cache-decorators.test.ts +284 -0
  42. package/tests/controller/path-combination.test.ts +353 -0
  43. package/tests/websocket/gateway.test.ts +207 -1
@@ -1,6 +1,8 @@
1
1
  import "reflect-metadata";
2
2
  import {
3
3
  type DependencyMetadata,
4
+ type InstancePostProcessor,
5
+ INSTANCE_POST_PROCESSOR_TOKEN,
4
6
  Lifecycle,
5
7
  type ProviderConfig,
6
8
  } from "./types";
@@ -59,6 +61,11 @@ export class Container {
59
61
  DependencyPlan
60
62
  >();
61
63
 
64
+ /**
65
+ * 实例后处理器列表
66
+ */
67
+ private readonly postProcessors: InstancePostProcessor[] = [];
68
+
62
69
  /**
63
70
  * 注册提供者
64
71
  * @param token - 提供者标识符(类构造函数或 token)
@@ -112,6 +119,45 @@ export class Container {
112
119
  });
113
120
  }
114
121
 
122
+ /**
123
+ * 注册实例后处理器
124
+ * @param processor - 后处理器实例
125
+ */
126
+ public registerPostProcessor(processor: InstancePostProcessor): void {
127
+ this.postProcessors.push(processor);
128
+ // 按优先级排序(数字越小优先级越高)
129
+ this.postProcessors.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
130
+ }
131
+
132
+ /**
133
+ * 应用所有后处理器(包括父容器的后处理器)
134
+ * @param instance - 原始实例
135
+ * @param constructor - 构造函数
136
+ * @param originContainer - 原始调用容器(用于后处理器解析依赖)
137
+ * @returns 处理后的实例
138
+ */
139
+ private applyPostProcessors<T>(
140
+ instance: T,
141
+ constructor: Constructor<T>,
142
+ originContainer?: Container,
143
+ ): T {
144
+ // 使用原始容器或当前容器
145
+ const containerForProcessors = originContainer ?? this;
146
+ let result = instance;
147
+
148
+ // 先应用父容器的后处理器
149
+ if (this.parent) {
150
+ result = (this.parent as Container).applyPostProcessors(result, constructor, containerForProcessors);
151
+ }
152
+
153
+ // 再应用本容器的后处理器
154
+ // 传递原始调用容器,而不是当前容器
155
+ for (const processor of this.postProcessors) {
156
+ result = processor.postProcess(result, constructor, containerForProcessors);
157
+ }
158
+ return result;
159
+ }
160
+
115
161
  /**
116
162
  * 解析依赖
117
163
  * @param token - 提供者标识符
@@ -321,16 +367,19 @@ export class Container {
321
367
  private instantiate<T>(constructor: Constructor<T>): T {
322
368
  const plan = this.getDependencyPlan(constructor);
323
369
 
370
+ let instance: T;
324
371
  if (plan.paramLength === 0) {
325
- return new constructor();
326
- }
327
-
328
- const dependencies = new Array(plan.paramLength);
329
- for (let index = 0; index < plan.paramLength; index++) {
330
- dependencies[index] = this.resolveFromPlan(constructor, plan, index);
372
+ instance = new constructor();
373
+ } else {
374
+ const dependencies = new Array(plan.paramLength);
375
+ for (let index = 0; index < plan.paramLength; index++) {
376
+ dependencies[index] = this.resolveFromPlan(constructor, plan, index);
377
+ }
378
+ instance = new constructor(...dependencies);
331
379
  }
332
380
 
333
- return new constructor(...dependencies);
381
+ // 应用后处理器
382
+ return this.applyPostProcessors(instance, constructor);
334
383
  }
335
384
 
336
385
  /**
@@ -356,6 +405,7 @@ export class Container {
356
405
  this.singletons.clear();
357
406
  this.typeToToken.clear();
358
407
  this.dependencyPlans.clear();
408
+ this.postProcessors.length = 0;
359
409
  // scopedInstances 使用 WeakMap,当 Context 对象被 GC 时会自动清理
360
410
  }
361
411
 
package/src/di/index.ts CHANGED
@@ -1,4 +1,10 @@
1
1
  export { Container } from './container';
2
2
  export { Injectable, Inject, getDependencyMetadata, isInjectable, getLifecycle } from './decorators';
3
- export { Lifecycle, type ProviderConfig, type DependencyMetadata } from './types';
3
+ export {
4
+ Lifecycle,
5
+ INSTANCE_POST_PROCESSOR_TOKEN,
6
+ type ProviderConfig,
7
+ type DependencyMetadata,
8
+ type InstancePostProcessor,
9
+ } from './types';
4
10
 
package/src/di/types.ts CHANGED
@@ -1,5 +1,34 @@
1
1
  import type { Constructor } from '@/core/types'
2
2
 
3
+ /**
4
+ * 实例后处理器接口
5
+ * 用于在 DI 容器创建实例后进行处理(如创建代理、注入额外依赖等)
6
+ */
7
+ export interface InstancePostProcessor {
8
+ /**
9
+ * 处理新创建的实例
10
+ * @param instance - 原始实例
11
+ * @param constructor - 构造函数
12
+ * @param container - DI 容器引用
13
+ * @returns 处理后的实例(可以是代理)
14
+ */
15
+ postProcess<T>(
16
+ instance: T,
17
+ constructor: Constructor<T>,
18
+ container: unknown,
19
+ ): T;
20
+
21
+ /**
22
+ * 优先级(数字越小优先级越高,默认 100)
23
+ */
24
+ priority?: number;
25
+ }
26
+
27
+ /**
28
+ * 实例后处理器注册表 Token
29
+ */
30
+ export const INSTANCE_POST_PROCESSOR_TOKEN = Symbol('@dangao/bun-server:di:post-processor');
31
+
3
32
  /**
4
33
  * 依赖生命周期类型
5
34
  */
package/src/index.ts CHANGED
@@ -254,10 +254,17 @@ export {
254
254
  Cacheable,
255
255
  CacheEvict,
256
256
  CachePut,
257
+ EnableCacheProxy,
258
+ CacheServiceProxy,
259
+ CachePostProcessor,
260
+ CacheableInterceptor,
261
+ CacheEvictInterceptor,
262
+ CachePutInterceptor,
257
263
  MemoryCacheStore,
258
264
  RedisCacheStore,
259
265
  CACHE_SERVICE_TOKEN,
260
266
  CACHE_OPTIONS_TOKEN,
267
+ CACHE_POST_PROCESSOR_TOKEN,
261
268
  } from './cache';
262
269
  export type {
263
270
  CacheModuleOptions,
@@ -16,18 +16,24 @@ interface GatewayDefinition {
16
16
  message?: string;
17
17
  close?: string;
18
18
  };
19
+ pattern: RegExp;
20
+ paramNames: string[];
21
+ isStatic: boolean;
19
22
  }
20
23
 
21
24
  export interface WebSocketConnectionData {
22
25
  path: string;
23
26
  query?: URLSearchParams;
24
27
  context?: Context;
28
+ params?: Record<string, string>;
25
29
  }
26
30
 
27
31
  export class WebSocketGatewayRegistry {
28
32
  private static instance: WebSocketGatewayRegistry;
29
33
  private readonly container: Container;
30
34
  private readonly gateways = new Map<string, GatewayDefinition>();
35
+ private readonly staticGateways = new Map<string, GatewayDefinition>();
36
+ private readonly dynamicGateways: GatewayDefinition[] = [];
31
37
 
32
38
  private constructor() {
33
39
  this.container = ControllerRegistry.getInstance().getContainer();
@@ -40,6 +46,24 @@ export class WebSocketGatewayRegistry {
40
46
  return WebSocketGatewayRegistry.instance;
41
47
  }
42
48
 
49
+ /**
50
+ * 解析路径,生成匹配模式和参数名列表
51
+ * @param path - 路由路径
52
+ * @returns 匹配模式和参数名列表
53
+ */
54
+ private parsePath(path: string): { pattern: RegExp; paramNames: string[] } {
55
+ const paramNames: string[] = [];
56
+ const patternString = path
57
+ .replace(/:([^/]+)/g, (_, paramName) => {
58
+ paramNames.push(paramName);
59
+ return '([^/]+)';
60
+ })
61
+ .replace(/\*/g, '.*');
62
+
63
+ const pattern = new RegExp(`^${patternString}$`);
64
+ return { pattern, paramNames };
65
+ }
66
+
43
67
  public register(gatewayClass: Constructor<unknown>): void {
44
68
  const metadata = getGatewayMetadata(gatewayClass);
45
69
  if (!metadata) {
@@ -55,23 +79,82 @@ export class WebSocketGatewayRegistry {
55
79
  this.container.register(gatewayClass);
56
80
  }
57
81
 
58
- this.gateways.set(metadata.path, {
82
+ // 解析路径
83
+ const { pattern, paramNames } = this.parsePath(metadata.path);
84
+ const isStatic = !metadata.path.includes(':') && !metadata.path.includes('*');
85
+
86
+ const definition: GatewayDefinition = {
59
87
  path: metadata.path,
60
88
  gatewayClass,
61
89
  handlers,
62
- });
90
+ pattern,
91
+ paramNames,
92
+ isStatic,
93
+ };
94
+
95
+ this.gateways.set(metadata.path, definition);
96
+
97
+ // 分别存储静态和动态路由
98
+ if (isStatic) {
99
+ this.staticGateways.set(metadata.path, definition);
100
+ } else {
101
+ this.dynamicGateways.push(definition);
102
+ }
63
103
  }
64
104
 
105
+ /**
106
+ * 检查是否有匹配的网关(支持动态路径匹配)
107
+ * @param path - 请求路径
108
+ * @returns 是否有匹配的网关
109
+ */
65
110
  public hasGateway(path: string): boolean {
66
- return this.gateways.has(path);
111
+ // 先检查静态路由
112
+ if (this.staticGateways.has(path)) {
113
+ return true;
114
+ }
115
+
116
+ // 遍历动态路由
117
+ for (const gateway of this.dynamicGateways) {
118
+ if (gateway.pattern.test(path)) {
119
+ return true;
120
+ }
121
+ }
122
+
123
+ return false;
67
124
  }
68
125
 
69
126
  public clear(): void {
70
127
  this.gateways.clear();
128
+ this.staticGateways.clear();
129
+ this.dynamicGateways.length = 0;
71
130
  }
72
131
 
73
- private getGateway(path: string): GatewayDefinition | undefined {
74
- return this.gateways.get(path);
132
+ /**
133
+ * 获取匹配的网关(支持动态路径匹配)
134
+ * @param path - 请求路径
135
+ * @returns 匹配的网关定义和路径参数
136
+ */
137
+ private getGateway(path: string): { definition: GatewayDefinition; params: Record<string, string> } | undefined {
138
+ // 先检查静态路由
139
+ const staticGateway = this.staticGateways.get(path);
140
+ if (staticGateway) {
141
+ return { definition: staticGateway, params: {} };
142
+ }
143
+
144
+ // 遍历动态路由
145
+ for (const gateway of this.dynamicGateways) {
146
+ const match = path.match(gateway.pattern);
147
+ if (match) {
148
+ // 提取路径参数
149
+ const params: Record<string, string> = {};
150
+ for (let i = 0; i < gateway.paramNames.length; i++) {
151
+ params[gateway.paramNames[i]] = match[i + 1] ?? '';
152
+ }
153
+ return { definition: gateway, params };
154
+ }
155
+ }
156
+
157
+ return undefined;
75
158
  }
76
159
 
77
160
  /**
@@ -129,7 +212,16 @@ export class WebSocketGatewayRegistry {
129
212
  headers: new Headers(),
130
213
  });
131
214
  context = new Context(request);
215
+ // 设置路径参数到 Context
216
+ if (ws.data.params) {
217
+ context.params = ws.data.params;
218
+ }
132
219
  ws.data.context = context;
220
+ } else {
221
+ // 如果 Context 已存在,更新路径参数
222
+ if (ws.data.params) {
223
+ context.params = ws.data.params;
224
+ }
133
225
  }
134
226
 
135
227
  // 使用 ParamBinder 绑定参数
@@ -189,12 +281,16 @@ export class WebSocketGatewayRegistry {
189
281
 
190
282
  public async handleOpen(ws: ServerWebSocket<WebSocketConnectionData>): Promise<void> {
191
283
  const path = ws.data?.path;
192
- const definition = path ? this.getGateway(path) : undefined;
193
- if (!definition) {
284
+ const match = path ? this.getGateway(path) : undefined;
285
+ if (!match) {
194
286
  ws.close(1008, 'Gateway not found');
195
287
  return;
196
288
  }
197
- await this.invokeHandler(ws, definition, definition.handlers.open);
289
+ // 保存路径参数到 WebSocket 连接数据
290
+ if (match.params && Object.keys(match.params).length > 0) {
291
+ ws.data.params = match.params;
292
+ }
293
+ await this.invokeHandler(ws, match.definition, match.definition.handlers.open);
198
294
  }
199
295
 
200
296
  public async handleMessage(
@@ -202,12 +298,16 @@ export class WebSocketGatewayRegistry {
202
298
  message: string | ArrayBuffer | ArrayBufferView,
203
299
  ): Promise<void> {
204
300
  const path = ws.data?.path;
205
- const definition = path ? this.getGateway(path) : undefined;
206
- if (!definition) {
301
+ const match = path ? this.getGateway(path) : undefined;
302
+ if (!match) {
207
303
  ws.close(1008, 'Gateway not found');
208
304
  return;
209
305
  }
210
- await this.invokeHandler(ws, definition, definition.handlers.message, message);
306
+ // 保存路径参数到 WebSocket 连接数据(如果还没有)
307
+ if (match.params && Object.keys(match.params).length > 0 && !ws.data.params) {
308
+ ws.data.params = match.params;
309
+ }
310
+ await this.invokeHandler(ws, match.definition, match.definition.handlers.message, message);
211
311
  }
212
312
 
213
313
  public async handleClose(
@@ -216,11 +316,11 @@ export class WebSocketGatewayRegistry {
216
316
  reason: string,
217
317
  ): Promise<void> {
218
318
  const path = ws.data?.path;
219
- const definition = path ? this.getGateway(path) : undefined;
220
- if (!definition) {
319
+ const match = path ? this.getGateway(path) : undefined;
320
+ if (!match) {
221
321
  return;
222
322
  }
223
- await this.invokeHandler(ws, definition, definition.handlers.close, code, reason);
323
+ await this.invokeHandler(ws, match.definition, match.definition.handlers.close, code, reason);
224
324
  }
225
325
  }
226
326
 
@@ -0,0 +1,284 @@
1
+ import 'reflect-metadata';
2
+ import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
3
+ import { Application } from '../../src/core/application';
4
+ import { Controller } from '../../src/controller/controller';
5
+ import { GET } from '../../src/router/decorators';
6
+ import { Param } from '../../src/controller/decorators';
7
+ import { Module } from '../../src/di/module';
8
+ import { Injectable, Inject } from '../../src/di/decorators';
9
+ import {
10
+ CacheModule,
11
+ CacheService,
12
+ Cacheable,
13
+ CacheEvict,
14
+ CachePut,
15
+ EnableCacheProxy,
16
+ CACHE_SERVICE_TOKEN,
17
+ } from '../../src/cache';
18
+ import { RouteRegistry } from '../../src/router/registry';
19
+ import { ControllerRegistry } from '../../src/controller/controller';
20
+ import { ModuleRegistry } from '../../src/di/module-registry';
21
+ import { getTestPort } from '../utils/test-port';
22
+
23
+ describe('Cache Decorators', () => {
24
+ let app: Application;
25
+ let port: number;
26
+ let callCount: number;
27
+
28
+ beforeEach(() => {
29
+ port = getTestPort();
30
+ callCount = 0;
31
+ CacheModule.reset();
32
+ });
33
+
34
+ afterEach(async () => {
35
+ if (app) {
36
+ await app.stop();
37
+ }
38
+ RouteRegistry.getInstance().clear();
39
+ ControllerRegistry.getInstance().clear();
40
+ ModuleRegistry.getInstance().clear();
41
+ });
42
+
43
+ test('@Cacheable should cache method results in service layer', async () => {
44
+ // 定义服务
45
+ @Injectable()
46
+ @EnableCacheProxy()
47
+ class UserService {
48
+ @Cacheable({ key: 'user:{0}', ttl: 5000 })
49
+ public async findById(id: string): Promise<{ id: string; name: string }> {
50
+ callCount++;
51
+ return { id, name: `User ${id}` };
52
+ }
53
+ }
54
+
55
+ // 定义控制器
56
+ @Controller('/api/users')
57
+ class UserController {
58
+ public constructor(private readonly userService: UserService) {}
59
+
60
+ @GET('/:id')
61
+ public async getUser(@Param('id') id: string): Promise<{ id: string; name: string }> {
62
+ return await this.userService.findById(id);
63
+ }
64
+ }
65
+
66
+ // 定义模块
67
+ @Module({
68
+ imports: [CacheModule.forRoot({ defaultTtl: 5000 })],
69
+ controllers: [UserController],
70
+ providers: [UserService],
71
+ })
72
+ class AppModule {}
73
+
74
+ // 创建应用
75
+ app = new Application({ port });
76
+ app.registerModule(AppModule);
77
+ await app.listen();
78
+
79
+ // 第一次调用 - 应该执行方法
80
+ const response1 = await fetch(`http://localhost:${port}/api/users/1`);
81
+ expect(response1.status).toBe(200);
82
+ const data1 = await response1.json();
83
+ expect(data1.id).toBe('1');
84
+ expect(data1.name).toBe('User 1');
85
+ expect(callCount).toBe(1);
86
+
87
+ // 第二次调用 - 应该使用缓存
88
+ const response2 = await fetch(`http://localhost:${port}/api/users/1`);
89
+ expect(response2.status).toBe(200);
90
+ const data2 = await response2.json();
91
+ expect(data2.id).toBe('1');
92
+ expect(data2.name).toBe('User 1');
93
+ expect(callCount).toBe(1); // 方法没有被再次调用
94
+
95
+ // 调用不同的 ID - 应该执行方法
96
+ const response3 = await fetch(`http://localhost:${port}/api/users/2`);
97
+ expect(response3.status).toBe(200);
98
+ const data3 = await response3.json();
99
+ expect(data3.id).toBe('2');
100
+ expect(data3.name).toBe('User 2');
101
+ expect(callCount).toBe(2);
102
+ });
103
+
104
+ test('@CacheEvict should clear cache', async () => {
105
+ // 定义服务
106
+ @Injectable()
107
+ @EnableCacheProxy()
108
+ class ProductService {
109
+ @Cacheable({ key: 'product:{0}', ttl: 5000 })
110
+ public async findById(id: string): Promise<{ id: string; name: string }> {
111
+ callCount++;
112
+ return { id, name: `Product ${id} v${callCount}` };
113
+ }
114
+
115
+ @CacheEvict({ key: 'product:{0}' })
116
+ public async deleteById(id: string): Promise<void> {
117
+ // 删除产品
118
+ }
119
+ }
120
+
121
+ // 定义控制器
122
+ @Controller('/api/products')
123
+ class ProductController {
124
+ public constructor(private readonly productService: ProductService) {}
125
+
126
+ @GET('/:id')
127
+ public async getProduct(@Param('id') id: string): Promise<{ id: string; name: string }> {
128
+ return await this.productService.findById(id);
129
+ }
130
+
131
+ @GET('/:id/delete')
132
+ public async deleteProduct(@Param('id') id: string): Promise<{ success: boolean }> {
133
+ await this.productService.deleteById(id);
134
+ return { success: true };
135
+ }
136
+ }
137
+
138
+ // 定义模块
139
+ @Module({
140
+ imports: [CacheModule.forRoot({ defaultTtl: 5000 })],
141
+ controllers: [ProductController],
142
+ providers: [ProductService],
143
+ })
144
+ class AppModule {}
145
+
146
+ // 创建应用
147
+ app = new Application({ port });
148
+ app.registerModule(AppModule);
149
+ await app.listen();
150
+
151
+ // 第一次调用 - 缓存产品
152
+ const response1 = await fetch(`http://localhost:${port}/api/products/1`);
153
+ expect(response1.status).toBe(200);
154
+ const data1 = await response1.json();
155
+ expect(data1.name).toBe('Product 1 v1');
156
+ expect(callCount).toBe(1);
157
+
158
+ // 第二次调用 - 使用缓存
159
+ const response2 = await fetch(`http://localhost:${port}/api/products/1`);
160
+ const data2 = await response2.json();
161
+ expect(data2.name).toBe('Product 1 v1');
162
+ expect(callCount).toBe(1);
163
+
164
+ // 删除产品 - 清除缓存
165
+ await fetch(`http://localhost:${port}/api/products/1/delete`);
166
+
167
+ // 再次获取 - 缓存已清除,应重新执行方法
168
+ const response3 = await fetch(`http://localhost:${port}/api/products/1`);
169
+ const data3 = await response3.json();
170
+ expect(data3.name).toBe('Product 1 v2');
171
+ expect(callCount).toBe(2);
172
+ });
173
+
174
+ test('@CachePut should always execute method and update cache', async () => {
175
+ // 定义服务
176
+ @Injectable()
177
+ @EnableCacheProxy()
178
+ class OrderService {
179
+ @Cacheable({ key: 'order:{0}', ttl: 5000 })
180
+ public async findById(id: string): Promise<{ id: string; status: string }> {
181
+ callCount++;
182
+ return { id, status: 'pending' };
183
+ }
184
+
185
+ @CachePut({ key: 'order:{0}', ttl: 5000 })
186
+ public async updateStatus(id: string, status: string): Promise<{ id: string; status: string }> {
187
+ return { id, status };
188
+ }
189
+ }
190
+
191
+ // 定义控制器
192
+ @Controller('/api/orders')
193
+ class OrderController {
194
+ public constructor(private readonly orderService: OrderService) {}
195
+
196
+ @GET('/:id')
197
+ public async getOrder(@Param('id') id: string): Promise<{ id: string; status: string }> {
198
+ return await this.orderService.findById(id);
199
+ }
200
+
201
+ @GET('/:id/update/:status')
202
+ public async updateOrder(
203
+ @Param('id') id: string,
204
+ @Param('status') status: string,
205
+ ): Promise<{ id: string; status: string }> {
206
+ return await this.orderService.updateStatus(id, status);
207
+ }
208
+ }
209
+
210
+ // 定义模块
211
+ @Module({
212
+ imports: [CacheModule.forRoot({ defaultTtl: 5000 })],
213
+ controllers: [OrderController],
214
+ providers: [OrderService],
215
+ })
216
+ class AppModule {}
217
+
218
+ // 创建应用
219
+ app = new Application({ port });
220
+ app.registerModule(AppModule);
221
+ await app.listen();
222
+
223
+ // 第一次调用 - 缓存订单
224
+ const response1 = await fetch(`http://localhost:${port}/api/orders/1`);
225
+ expect(response1.status).toBe(200);
226
+ const data1 = await response1.json();
227
+ expect(data1.status).toBe('pending');
228
+ expect(callCount).toBe(1);
229
+
230
+ // 更新订单状态 - @CachePut 会更新缓存
231
+ const response2 = await fetch(`http://localhost:${port}/api/orders/1/update/completed`);
232
+ const data2 = await response2.json();
233
+ expect(data2.status).toBe('completed');
234
+
235
+ // 再次获取 - 应该返回更新后的缓存
236
+ const response3 = await fetch(`http://localhost:${port}/api/orders/1`);
237
+ const data3 = await response3.json();
238
+ expect(data3.status).toBe('completed');
239
+ expect(callCount).toBe(1); // findById 没有被再次调用
240
+ });
241
+
242
+ test('Service without @EnableCacheProxy should not use cache', async () => {
243
+ // 定义服务(没有 @EnableCacheProxy)
244
+ @Injectable()
245
+ class NoCacheService {
246
+ @Cacheable({ key: 'nocache:{0}', ttl: 5000 })
247
+ public async findById(id: string): Promise<{ id: string }> {
248
+ callCount++;
249
+ return { id };
250
+ }
251
+ }
252
+
253
+ // 定义控制器
254
+ @Controller('/api/nocache')
255
+ class NoCacheController {
256
+ public constructor(private readonly noCacheService: NoCacheService) {}
257
+
258
+ @GET('/:id')
259
+ public async get(@Param('id') id: string): Promise<{ id: string }> {
260
+ return await this.noCacheService.findById(id);
261
+ }
262
+ }
263
+
264
+ // 定义模块
265
+ @Module({
266
+ imports: [CacheModule.forRoot({ defaultTtl: 5000 })],
267
+ controllers: [NoCacheController],
268
+ providers: [NoCacheService],
269
+ })
270
+ class AppModule {}
271
+
272
+ // 创建应用
273
+ app = new Application({ port });
274
+ app.registerModule(AppModule);
275
+ await app.listen();
276
+
277
+ // 两次调用都应该执行方法(没有缓存)
278
+ await fetch(`http://localhost:${port}/api/nocache/1`);
279
+ expect(callCount).toBe(1);
280
+
281
+ await fetch(`http://localhost:${port}/api/nocache/1`);
282
+ expect(callCount).toBe(2); // 方法被再次调用
283
+ });
284
+ });