@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
@@ -0,0 +1,295 @@
1
+ import 'reflect-metadata';
2
+ import { BaseInterceptor } from '../interceptor/base-interceptor';
3
+ import type { Container } from '../di/container';
4
+ import type { Context } from '../core/context';
5
+ import type { CacheService } from './service';
6
+ import { CACHE_SERVICE_TOKEN } from './types';
7
+ import {
8
+ getCacheableMetadata,
9
+ getCacheEvictMetadata,
10
+ getCachePutMetadata,
11
+ type CacheableMetadata,
12
+ type CacheEvictMetadata,
13
+ type CachePutMetadata,
14
+ } from './decorators';
15
+
16
+ /**
17
+ * 缓存装饰器元数据键(用于拦截器注册)
18
+ */
19
+ export const CACHEABLE_INTERCEPTOR_KEY = Symbol('@dangao/bun-server:cache:cacheable');
20
+ export const CACHE_EVICT_INTERCEPTOR_KEY = Symbol('@dangao/bun-server:cache:cache-evict');
21
+ export const CACHE_PUT_INTERCEPTOR_KEY = Symbol('@dangao/bun-server:cache:cache-put');
22
+
23
+ /**
24
+ * 解析缓存键中的参数占位符
25
+ * 支持 SpEL 风格的表达式,如 'user:{id}' 或 'user:{0}'
26
+ * @param keyTemplate - 键模板
27
+ * @param args - 方法参数
28
+ * @param paramNames - 参数名称(如果可用)
29
+ * @returns 解析后的缓存键
30
+ */
31
+ function resolveKeyTemplate(
32
+ keyTemplate: string,
33
+ args: unknown[],
34
+ paramNames?: string[],
35
+ ): string {
36
+ let result = keyTemplate;
37
+
38
+ // 替换数字索引占位符,如 {0}, {1}
39
+ result = result.replace(/\{(\d+)\}/g, (_, index) => {
40
+ const i = parseInt(index, 10);
41
+ if (i < args.length) {
42
+ return String(args[i]);
43
+ }
44
+ return `{${index}}`;
45
+ });
46
+
47
+ // 替换命名参数占位符,如 {id}, {name}
48
+ if (paramNames) {
49
+ for (let i = 0; i < paramNames.length; i++) {
50
+ const name = paramNames[i];
51
+ const regex = new RegExp(`\\{${name}\\}`, 'g');
52
+ result = result.replace(regex, String(args[i]));
53
+ }
54
+ }
55
+
56
+ return result;
57
+ }
58
+
59
+ /**
60
+ * 生成默认缓存键
61
+ * @param target - 目标对象
62
+ * @param propertyKey - 方法名
63
+ * @param args - 方法参数
64
+ * @param keyPrefix - 键前缀
65
+ * @returns 缓存键
66
+ */
67
+ function generateDefaultCacheKey(
68
+ target: unknown,
69
+ propertyKey: string | symbol,
70
+ args: unknown[],
71
+ keyPrefix?: string,
72
+ ): string {
73
+ const className = typeof target === 'object' && target !== null
74
+ ? (target as { constructor?: { name?: string } }).constructor?.name || 'Unknown'
75
+ : 'Unknown';
76
+ const methodName = String(propertyKey);
77
+ const argsKey = args.length > 0 ? ':' + JSON.stringify(args) : '';
78
+ const prefix = keyPrefix ? `${keyPrefix}:` : '';
79
+
80
+ return `${prefix}${className}:${methodName}${argsKey}`;
81
+ }
82
+
83
+ /**
84
+ * @Cacheable 拦截器
85
+ * 实现方法结果缓存功能
86
+ */
87
+ export class CacheableInterceptor extends BaseInterceptor {
88
+ /**
89
+ * 执行拦截器逻辑
90
+ */
91
+ public async execute<T>(
92
+ target: unknown,
93
+ propertyKey: string | symbol,
94
+ originalMethod: (...args: unknown[]) => T | Promise<T>,
95
+ args: unknown[],
96
+ container: Container,
97
+ context?: Context,
98
+ ): Promise<T> {
99
+ // 从原型方法获取元数据(元数据存储在原型上,而不是绑定后的函数上)
100
+ // 当通过代理调用时,originalMethod 可能是 .bind() 后的函数,没有元数据
101
+ // 所以我们需要从 target 的原型上获取原始方法来读取元数据
102
+ let metadata = getCacheableMetadata(originalMethod);
103
+
104
+ if (!metadata && target && typeof target === 'object') {
105
+ const prototype = Object.getPrototypeOf(target);
106
+ if (prototype && typeof propertyKey === 'string') {
107
+ const protoMethod = prototype[propertyKey];
108
+ if (protoMethod) {
109
+ metadata = getCacheableMetadata(protoMethod);
110
+ }
111
+ }
112
+ }
113
+
114
+ if (!metadata) {
115
+ // 没有缓存元数据,直接执行原方法
116
+ return await Promise.resolve(originalMethod.apply(target, args));
117
+ }
118
+
119
+ // 获取缓存服务
120
+ let cacheService: CacheService;
121
+ try {
122
+ cacheService = container.resolve<CacheService>(CACHE_SERVICE_TOKEN);
123
+ } catch {
124
+ // 缓存服务未注册,直接执行原方法
125
+ console.warn('[CacheableInterceptor] CacheService not registered, skipping cache');
126
+ return await Promise.resolve(originalMethod.apply(target, args));
127
+ }
128
+
129
+ // 生成缓存键
130
+ const cacheKey = metadata.key
131
+ ? resolveKeyTemplate(metadata.key, args)
132
+ : generateDefaultCacheKey(target, propertyKey, args, metadata.keyPrefix);
133
+
134
+ // 检查条件表达式(如果有)
135
+ if (metadata.condition) {
136
+ // 简单的条件评估:目前只支持 'true'/'false' 字符串
137
+ // 未来可以扩展为完整的表达式求值
138
+ if (metadata.condition === 'false') {
139
+ return await Promise.resolve(originalMethod.apply(target, args));
140
+ }
141
+ }
142
+
143
+ // 使用 getOrSet 实现缓存逻辑
144
+ const result = await cacheService.getOrSet<T>(
145
+ cacheKey,
146
+ async () => {
147
+ return await Promise.resolve(originalMethod.apply(target, args));
148
+ },
149
+ metadata.ttl,
150
+ );
151
+
152
+ return result;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * @CacheEvict 拦截器
158
+ * 实现缓存清除功能
159
+ */
160
+ export class CacheEvictInterceptor extends BaseInterceptor {
161
+ /**
162
+ * 执行拦截器逻辑
163
+ */
164
+ public async execute<T>(
165
+ target: unknown,
166
+ propertyKey: string | symbol,
167
+ originalMethod: (...args: unknown[]) => T | Promise<T>,
168
+ args: unknown[],
169
+ container: Container,
170
+ context?: Context,
171
+ ): Promise<T> {
172
+ // 从原型方法获取元数据
173
+ let metadata = getCacheEvictMetadata(originalMethod);
174
+
175
+ if (!metadata && target && typeof target === 'object') {
176
+ const prototype = Object.getPrototypeOf(target);
177
+ if (prototype && typeof propertyKey === 'string') {
178
+ const protoMethod = prototype[propertyKey];
179
+ if (protoMethod) {
180
+ metadata = getCacheEvictMetadata(protoMethod);
181
+ }
182
+ }
183
+ }
184
+
185
+ if (!metadata) {
186
+ // 没有缓存清除元数据,直接执行原方法
187
+ return await Promise.resolve(originalMethod.apply(target, args));
188
+ }
189
+
190
+ // 获取缓存服务
191
+ let cacheService: CacheService;
192
+ try {
193
+ cacheService = container.resolve<CacheService>(CACHE_SERVICE_TOKEN);
194
+ } catch {
195
+ // 缓存服务未注册,直接执行原方法
196
+ console.warn('[CacheEvictInterceptor] CacheService not registered, skipping cache eviction');
197
+ return await Promise.resolve(originalMethod.apply(target, args));
198
+ }
199
+
200
+ // 生成缓存键
201
+ const cacheKey = metadata.key
202
+ ? resolveKeyTemplate(metadata.key, args)
203
+ : generateDefaultCacheKey(target, propertyKey, args, metadata.keyPrefix);
204
+
205
+ // 如果配置了在方法执行前清除缓存
206
+ if (metadata.beforeInvocation) {
207
+ if (metadata.allEntries) {
208
+ await cacheService.clear();
209
+ } else {
210
+ await cacheService.delete(cacheKey);
211
+ }
212
+ }
213
+
214
+ // 执行原方法
215
+ const result = await Promise.resolve(originalMethod.apply(target, args));
216
+
217
+ // 如果配置了在方法执行后清除缓存(默认行为)
218
+ if (!metadata.beforeInvocation) {
219
+ if (metadata.allEntries) {
220
+ await cacheService.clear();
221
+ } else {
222
+ await cacheService.delete(cacheKey);
223
+ }
224
+ }
225
+
226
+ return result;
227
+ }
228
+ }
229
+
230
+ /**
231
+ * @CachePut 拦截器
232
+ * 实现缓存更新功能(总是执行方法并更新缓存)
233
+ */
234
+ export class CachePutInterceptor extends BaseInterceptor {
235
+ /**
236
+ * 执行拦截器逻辑
237
+ */
238
+ public async execute<T>(
239
+ target: unknown,
240
+ propertyKey: string | symbol,
241
+ originalMethod: (...args: unknown[]) => T | Promise<T>,
242
+ args: unknown[],
243
+ container: Container,
244
+ context?: Context,
245
+ ): Promise<T> {
246
+ // 从原型方法获取元数据
247
+ let metadata = getCachePutMetadata(originalMethod);
248
+
249
+ if (!metadata && target && typeof target === 'object') {
250
+ const prototype = Object.getPrototypeOf(target);
251
+ if (prototype && typeof propertyKey === 'string') {
252
+ const protoMethod = prototype[propertyKey];
253
+ if (protoMethod) {
254
+ metadata = getCachePutMetadata(protoMethod);
255
+ }
256
+ }
257
+ }
258
+
259
+ if (!metadata) {
260
+ // 没有缓存更新元数据,直接执行原方法
261
+ return await Promise.resolve(originalMethod.apply(target, args));
262
+ }
263
+
264
+ // 获取缓存服务
265
+ let cacheService: CacheService;
266
+ try {
267
+ cacheService = container.resolve<CacheService>(CACHE_SERVICE_TOKEN);
268
+ } catch {
269
+ // 缓存服务未注册,直接执行原方法
270
+ console.warn('[CachePutInterceptor] CacheService not registered, skipping cache update');
271
+ return await Promise.resolve(originalMethod.apply(target, args));
272
+ }
273
+
274
+ // 执行原方法
275
+ const result = await Promise.resolve(originalMethod.apply(target, args));
276
+
277
+ // 检查条件表达式(如果有)
278
+ if (metadata.condition) {
279
+ // 简单的条件评估
280
+ if (metadata.condition === 'false') {
281
+ return result;
282
+ }
283
+ }
284
+
285
+ // 生成缓存键
286
+ const cacheKey = metadata.key
287
+ ? resolveKeyTemplate(metadata.key, args)
288
+ : generateDefaultCacheKey(target, propertyKey, args, metadata.keyPrefix);
289
+
290
+ // 更新缓存
291
+ await cacheService.set(cacheKey, result, metadata.ttl);
292
+
293
+ return result;
294
+ }
295
+ }
@@ -0,0 +1,219 @@
1
+ import 'reflect-metadata';
2
+ import type { Container } from '../di/container';
3
+ import type { InstancePostProcessor } from '../di/types';
4
+ import type { Constructor } from '../core/types';
5
+ import {
6
+ getCacheableMetadata,
7
+ getCacheEvictMetadata,
8
+ getCachePutMetadata,
9
+ } from './decorators';
10
+ import {
11
+ CacheableInterceptor,
12
+ CacheEvictInterceptor,
13
+ CachePutInterceptor,
14
+ } from './interceptors';
15
+
16
+ /**
17
+ * 缓存服务代理工厂
18
+ * 为服务实例创建代理,拦截带有缓存装饰器的方法
19
+ */
20
+ export class CacheServiceProxy {
21
+ private static cacheableInterceptor = new CacheableInterceptor();
22
+ private static cacheEvictInterceptor = new CacheEvictInterceptor();
23
+ private static cachePutInterceptor = new CachePutInterceptor();
24
+
25
+ /**
26
+ * 为服务实例创建缓存代理
27
+ * @param instance - 原始服务实例
28
+ * @param container - DI 容器
29
+ * @returns 代理实例
30
+ */
31
+ public static createProxy<T extends object>(
32
+ instance: T,
33
+ container: Container,
34
+ ): T {
35
+ const prototype = Object.getPrototypeOf(instance);
36
+ const methodNames = Object.getOwnPropertyNames(prototype).filter(
37
+ (name) => name !== 'constructor' && typeof prototype[name] === 'function',
38
+ );
39
+
40
+ // 检查是否有任何需要缓存处理的方法
41
+ // 从原型获取方法(元数据存储在原型的方法上)
42
+ let hasAnyMetadata = false;
43
+ for (const methodName of methodNames) {
44
+ const prototypeMethod = prototype[methodName];
45
+ const cacheable = getCacheableMetadata(prototypeMethod);
46
+ const cacheEvict = getCacheEvictMetadata(prototypeMethod);
47
+ const cachePut = getCachePutMetadata(prototypeMethod);
48
+
49
+ if (cacheable || cacheEvict || cachePut) {
50
+ hasAnyMetadata = true;
51
+ break;
52
+ }
53
+ }
54
+
55
+ // 如果没有缓存装饰器,直接返回原实例
56
+ if (!hasAnyMetadata) {
57
+ return instance;
58
+ }
59
+
60
+ // 创建代理
61
+ return new Proxy(instance, {
62
+ get(target, prop, receiver) {
63
+ const value = Reflect.get(target, prop, receiver);
64
+
65
+ // 只处理函数
66
+ if (typeof value !== 'function' || typeof prop === 'symbol') {
67
+ return value;
68
+ }
69
+
70
+ const methodName = prop as string;
71
+
72
+ // 从原型获取方法以检查元数据(元数据存储在原型的方法上)
73
+ const prototypeMethod = prototype[methodName];
74
+ if (!prototypeMethod) {
75
+ return value;
76
+ }
77
+
78
+ // 检查缓存装饰器(从原型方法获取元数据)
79
+ const cacheableMetadata = getCacheableMetadata(prototypeMethod);
80
+ const cacheEvictMetadata = getCacheEvictMetadata(prototypeMethod);
81
+ const cachePutMetadata = getCachePutMetadata(prototypeMethod);
82
+
83
+ // 如果没有缓存装饰器,返回原方法
84
+ if (!cacheableMetadata && !cacheEvictMetadata && !cachePutMetadata) {
85
+ return value;
86
+ }
87
+
88
+ // 返回包装后的方法
89
+ // 使用原型方法以确保拦截器能获取到正确的元数据
90
+ const originalMethod = prototypeMethod as (...args: unknown[]) => unknown;
91
+
92
+ return async function (this: T, ...args: unknown[]): Promise<unknown> {
93
+ // 按优先级执行拦截器:CacheEvict (beforeInvocation) -> Cacheable/CachePut -> CacheEvict (afterInvocation)
94
+
95
+ // 如果有 @CacheEvict 且配置了 beforeInvocation
96
+ if (cacheEvictMetadata?.beforeInvocation) {
97
+ await CacheServiceProxy.cacheEvictInterceptor.execute(
98
+ target,
99
+ methodName,
100
+ originalMethod.bind(target),
101
+ args,
102
+ container,
103
+ );
104
+ // beforeInvocation 只清除缓存,继续执行
105
+ }
106
+
107
+ let result: unknown;
108
+
109
+ // 如果有 @Cacheable,使用缓存逻辑
110
+ if (cacheableMetadata) {
111
+ result = await CacheServiceProxy.cacheableInterceptor.execute(
112
+ target,
113
+ methodName,
114
+ originalMethod.bind(target),
115
+ args,
116
+ container,
117
+ );
118
+ } else if (cachePutMetadata) {
119
+ // 如果有 @CachePut,执行并更新缓存
120
+ result = await CacheServiceProxy.cachePutInterceptor.execute(
121
+ target,
122
+ methodName,
123
+ originalMethod.bind(target),
124
+ args,
125
+ container,
126
+ );
127
+ } else {
128
+ // 只有 @CacheEvict,正常执行方法
129
+ result = await originalMethod.apply(target, args);
130
+ }
131
+
132
+ // 如果有 @CacheEvict 且未配置 beforeInvocation(默认行为)
133
+ if (cacheEvictMetadata && !cacheEvictMetadata.beforeInvocation) {
134
+ // 执行缓存清除逻辑(方法已执行,只需清除缓存)
135
+ await CacheServiceProxy.cacheEvictInterceptor.execute(
136
+ target,
137
+ methodName,
138
+ async () => result, // 返回已有结果,不再执行方法
139
+ args,
140
+ container,
141
+ );
142
+ }
143
+
144
+ return result;
145
+ };
146
+ },
147
+ });
148
+ }
149
+ }
150
+
151
+ /**
152
+ * 元数据键:标记服务需要缓存代理
153
+ */
154
+ export const CACHE_PROXY_ENABLED_KEY = Symbol('@dangao/bun-server:cache:proxy-enabled');
155
+
156
+ /**
157
+ * 启用服务缓存代理的装饰器
158
+ * 用于标记服务类需要缓存代理支持
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * @Injectable()
163
+ * @EnableCacheProxy()
164
+ * class UserService {
165
+ * @Cacheable({ key: 'user:{0}', ttl: 60000 })
166
+ * async findById(id: string) {
167
+ * return await this.db.findUser(id);
168
+ * }
169
+ * }
170
+ * ```
171
+ */
172
+ export function EnableCacheProxy(): ClassDecorator {
173
+ return function (target: Function): void {
174
+ Reflect.defineMetadata(CACHE_PROXY_ENABLED_KEY, true, target);
175
+ };
176
+ }
177
+
178
+ /**
179
+ * 检查类是否启用了缓存代理
180
+ * @param target - 目标类
181
+ * @returns 是否启用
182
+ */
183
+ export function isCacheProxyEnabled(target: Function): boolean {
184
+ return Reflect.getMetadata(CACHE_PROXY_ENABLED_KEY, target) === true;
185
+ }
186
+
187
+ /**
188
+ * 缓存实例后处理器
189
+ * 自动为带有 @EnableCacheProxy() 装饰器的服务创建缓存代理
190
+ */
191
+ export class CachePostProcessor implements InstancePostProcessor {
192
+ /**
193
+ * 优先级(较低以确保在其他处理器之后运行)
194
+ */
195
+ public priority = 50;
196
+
197
+ /**
198
+ * 处理新创建的实例
199
+ */
200
+ public postProcess<T>(
201
+ instance: T,
202
+ constructor: Constructor<T>,
203
+ container: unknown,
204
+ ): T {
205
+ // 检查是否启用了缓存代理
206
+ const proxyEnabled = isCacheProxyEnabled(constructor);
207
+ if (!proxyEnabled) {
208
+ return instance;
209
+ }
210
+
211
+ // 创建缓存代理
212
+ const proxied = CacheServiceProxy.createProxy(
213
+ instance as object,
214
+ container as Container,
215
+ ) as T;
216
+
217
+ return proxied;
218
+ }
219
+ }
@@ -234,19 +234,43 @@ export class ControllerRegistry {
234
234
  * @param basePath - 基础路径
235
235
  * @param methodPath - 方法路径
236
236
  * @returns 组合后的路径
237
+ *
238
+ * 路径规范化规则:
239
+ * - [/ + /api/base] -> /api/base
240
+ * - [// + /api/base] -> /api/base
241
+ * - [/api + /base] -> /api/base
242
+ * - [/api/ + base] -> /api/base
243
+ * - [/api/base + ""] -> /api/base
244
+ * - [/api/base + /] -> /api/base
237
245
  */
238
246
  private combinePaths(basePath: string, methodPath: string): string {
239
- // 规范化路径:确保 basePath 以 / 开头,不以 / 结尾
240
- const base = basePath.replace(/\/$/, '').replace(/^\/?/, '/');
241
- // methodPath 移除前导斜杠
242
- const method = methodPath.replace(/^\//, '');
247
+ // 规范化 basePath
248
+ // 1. 移除所有末尾斜杠
249
+ // 2. 确保以单个 / 开头
250
+ // 3. 合并多个连续斜杠为单个斜杠
251
+ let base = basePath
252
+ .replace(/\/+$/, '') // 移除末尾所有斜杠
253
+ .replace(/^\/+/, '/') // 确保开头只有一个斜杠
254
+ .replace(/\/+/g, '/'); // 合并多个连续斜杠
255
+
256
+ // 如果 base 为空,设为 /
257
+ if (!base) {
258
+ base = '/';
259
+ }
260
+
261
+ // 规范化 methodPath:移除前导斜杠
262
+ const method = methodPath.replace(/^\/+/, '');
243
263
 
244
264
  if (!method) {
245
265
  // 如果方法路径为空,返回基础路径
246
266
  return base === '/' ? '/' : base;
247
267
  }
248
-
249
- // 组合路径
268
+
269
+ // 组合路径:如果 base 是 '/',直接返回 '/' + method,避免 '//method'
270
+ if (base === '/') {
271
+ return '/' + method;
272
+ }
273
+
250
274
  return base + '/' + method;
251
275
  }
252
276
 
@@ -16,6 +16,7 @@ import { InterceptorRegistry, INTERCEPTOR_REGISTRY_TOKEN } from '../interceptor'
16
16
  import { CONFIG_SERVICE_TOKEN } from '../config/types';
17
17
  import { ConfigService } from '../config/service';
18
18
  import { ConfigModule } from '../config/config-module';
19
+ import { CacheModule, CACHE_POST_PROCESSOR_TOKEN } from '../cache';
19
20
  import { LoggerManager } from '@dangao/logsmith';
20
21
 
21
22
  /**
@@ -285,7 +286,8 @@ export class Application {
285
286
  */
286
287
  public registerModule(moduleClass: ModuleClass): void {
287
288
  const registry = ModuleRegistry.getInstance();
288
- registry.register(moduleClass, this.getContainer());
289
+ const container = this.getContainer();
290
+ registry.register(moduleClass, container);
289
291
 
290
292
  // 注册模块的扩展和中间件
291
293
  const extensions = registry.getModuleExtensions(moduleClass);
@@ -297,6 +299,28 @@ export class Application {
297
299
  for (const middleware of middlewares) {
298
300
  this.use(middleware);
299
301
  }
302
+
303
+ // 检测并注册缓存后处理器
304
+ // CacheModule.getPostProcessor() 会在 forRoot() 被调用后返回后处理器
305
+ this.registerCachePostProcessorIfNeeded(container);
306
+ }
307
+
308
+ /**
309
+ * 检测并注册缓存后处理器
310
+ * @param container - DI 容器
311
+ */
312
+ private registerCachePostProcessorIfNeeded(container: ReturnType<typeof this.getContainer>): void {
313
+ // 直接从 CacheModule 获取后处理器
314
+ // 如果 CacheModule.forRoot() 被调用过,后处理器就会存在
315
+ const postProcessor = CacheModule.getPostProcessor();
316
+ if (postProcessor) {
317
+ // 检查是否已经注册过(避免重复注册)
318
+ // @ts-expect-error - 访问私有属性
319
+ const existingProcessors = container.postProcessors as unknown[];
320
+ if (!existingProcessors.includes(postProcessor)) {
321
+ container.registerPostProcessor(postProcessor);
322
+ }
323
+ }
300
324
  }
301
325
 
302
326
  /**
@@ -86,6 +86,7 @@ export class BunServer {
86
86
  upgradeHeader.toLowerCase() === "websocket"
87
87
  ) {
88
88
  const url = new URL(request.url);
89
+ // 检查是否有匹配的网关(支持动态路径匹配)
89
90
  if (!this.options.websocketRegistry.hasGateway(url.pathname)) {
90
91
  return new Response("WebSocket gateway not found", { status: 404 });
91
92
  }