nebula-engine 1.0.0-beta3
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 +1255 -0
- package/dist/index.d.mts +1984 -0
- package/dist/index.d.ts +1984 -0
- package/dist/index.js +3533 -0
- package/dist/index.mjs +3457 -0
- package/docs/plugin-system.md +733 -0
- package/package.json +54 -0
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
# 插件系统完整指南
|
|
2
|
+
|
|
3
|
+
## 目录
|
|
4
|
+
|
|
5
|
+
1. [概述](#概述)
|
|
6
|
+
2. [核心概念](#核心概念)
|
|
7
|
+
3. [插件优先级系统](#插件优先级系统)
|
|
8
|
+
4. [洋葱圈模型](#洋葱圈模型)
|
|
9
|
+
5. [插件开发指南](#插件开发指南)
|
|
10
|
+
6. [默认插件机制](#默认插件机制)
|
|
11
|
+
7. [最佳实践](#最佳实践)
|
|
12
|
+
|
|
13
|
+
## 概述
|
|
14
|
+
|
|
15
|
+
本框架的插件系统采用**优先级驱动的洋葱圈模型**,让用户无需关心插件注册顺序,引擎会自动按优先级排序并构建执行链。
|
|
16
|
+
|
|
17
|
+
### 核心特性
|
|
18
|
+
|
|
19
|
+
- ✅ **自动优先级排序**:插件声明优先级,引擎自动排序
|
|
20
|
+
- ✅ **洋葱圈执行模型**:支持前置和后置处理逻辑
|
|
21
|
+
- ✅ **用户友好**:用户按任意顺序注册插件即可
|
|
22
|
+
- ✅ **灵活扩展**:插件开发者可以声明自定义优先级
|
|
23
|
+
|
|
24
|
+
## 核心概念
|
|
25
|
+
|
|
26
|
+
### 插件接口
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
export interface Plugin<TModuleOptions = Record<string, any>> {
|
|
30
|
+
// 插件唯一名称
|
|
31
|
+
name: string;
|
|
32
|
+
|
|
33
|
+
// 插件优先级(可选,默认 BUSINESS = 300)
|
|
34
|
+
priority?: PluginPriority | number;
|
|
35
|
+
|
|
36
|
+
// 声明插件的Module配置Schema
|
|
37
|
+
getModuleOptionsSchema?: () => PluginModuleOptionsSchema<TModuleOptions>;
|
|
38
|
+
|
|
39
|
+
// 生命周期钩子
|
|
40
|
+
onInit?: (engine: BaseMicroservice) => void;
|
|
41
|
+
onModuleLoad?: (modules: ModuleMetadata[]) => void;
|
|
42
|
+
onHandlerLoad?: (handlers: HandlerMetadata[]) => void;
|
|
43
|
+
onBeforeStart?: (engine: BaseMicroservice) => void;
|
|
44
|
+
onAfterStart?: (engine: BaseMicroservice) => void;
|
|
45
|
+
onDestroy?: () => void;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### handler.wrap() API
|
|
50
|
+
|
|
51
|
+
插件通过 `handler.wrap()` 包装方法,引擎自动管理包装链:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
55
|
+
// 前置逻辑
|
|
56
|
+
const result = await next(); // 调用下一个包装层或原始方法
|
|
57
|
+
// 后置逻辑
|
|
58
|
+
return result;
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 插件优先级系统
|
|
63
|
+
|
|
64
|
+
### 设计理念
|
|
65
|
+
|
|
66
|
+
**将用户当成"傻子"**:用户只需要注册插件,不需要关心顺序。引擎会自动按优先级排序,确保插件按正确的顺序执行。
|
|
67
|
+
|
|
68
|
+
### 优先级枚举
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
export enum PluginPriority {
|
|
72
|
+
/**
|
|
73
|
+
* 最高优先级:安全相关插件(限流、认证等)
|
|
74
|
+
* 应该最先执行,快速拒绝无效请求
|
|
75
|
+
*/
|
|
76
|
+
SECURITY = 100,
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 高优先级:日志、监控等插件
|
|
80
|
+
* 记录所有请求,包括被安全插件拒绝的
|
|
81
|
+
*/
|
|
82
|
+
LOGGING = 200,
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 中优先级:业务逻辑插件(数据转换等)
|
|
86
|
+
* 在安全和日志之后执行
|
|
87
|
+
*/
|
|
88
|
+
BUSINESS = 300,
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 低优先级:性能优化插件(缓存等)
|
|
92
|
+
* 在业务逻辑之后执行,避免重复计算
|
|
93
|
+
*/
|
|
94
|
+
PERFORMANCE = 400,
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 最低优先级:路由插件
|
|
98
|
+
* 必须最后执行,注册HTTP路由
|
|
99
|
+
*/
|
|
100
|
+
ROUTE = 1000,
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 排序规则
|
|
105
|
+
|
|
106
|
+
1. **按优先级排序**:数值越小,优先级越高(越先执行)
|
|
107
|
+
2. **相同优先级**:按注册顺序执行(保持稳定排序)
|
|
108
|
+
3. **默认优先级**:如果不指定,默认为 `PluginPriority.BUSINESS` (300)
|
|
109
|
+
|
|
110
|
+
### 使用示例
|
|
111
|
+
|
|
112
|
+
#### 用户视角(无需关心顺序)
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// 用户可以按任意顺序注册插件
|
|
116
|
+
const engine = new Microservice({
|
|
117
|
+
name: "my-service",
|
|
118
|
+
version: "1.0.0",
|
|
119
|
+
})
|
|
120
|
+
.plugin(new CachePlugin()) // PERFORMANCE = 400(可以放在前面)
|
|
121
|
+
.plugin(new RateLimitPlugin()) // SECURITY = 100(可以放在后面)
|
|
122
|
+
.plugin(new LogPlugin()) // LOGGING = 200(可以放在中间)
|
|
123
|
+
.plugin(new AuthPlugin()); // SECURITY = 100
|
|
124
|
+
|
|
125
|
+
// 引擎自动按优先级排序:
|
|
126
|
+
// 1. RateLimitPlugin (SECURITY = 100,先注册)
|
|
127
|
+
// 2. AuthPlugin (SECURITY = 100,后注册)
|
|
128
|
+
// 3. LogPlugin (LOGGING = 200)
|
|
129
|
+
// 4. CachePlugin (PERFORMANCE = 400)
|
|
130
|
+
// 5. RoutePlugin (ROUTE = 1000,自动注册)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### 插件开发者视角(声明优先级)
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
export class RateLimitPlugin implements Plugin {
|
|
137
|
+
public readonly name = "rate-limit-plugin";
|
|
138
|
+
public readonly priority = PluginPriority.SECURITY; // 声明为安全插件
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export class CachePlugin implements Plugin {
|
|
142
|
+
public readonly name = "cache-plugin";
|
|
143
|
+
public readonly priority = PluginPriority.PERFORMANCE; // 声明为性能优化插件
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 优先级选择指南
|
|
148
|
+
|
|
149
|
+
| 插件类型 | 优先级 | 说明 | 示例 |
|
|
150
|
+
|---------|--------|------|------|
|
|
151
|
+
| 安全插件 | `SECURITY` (100) | 限流、认证等,快速拒绝无效请求 | RateLimitPlugin, AuthPlugin |
|
|
152
|
+
| 日志插件 | `LOGGING` (200) | 记录所有请求,包括被拒绝的 | LogPlugin, MonitorPlugin |
|
|
153
|
+
| 业务逻辑插件 | `BUSINESS` (300) | 数据转换、业务处理等 | DataTransformPlugin |
|
|
154
|
+
| 性能优化插件 | `PERFORMANCE` (400) | 缓存、压缩等 | CachePlugin |
|
|
155
|
+
| 路由插件 | `ROUTE` (1000) | 注册HTTP路由,必须最后 | RoutePlugin |
|
|
156
|
+
|
|
157
|
+
## 洋葱圈模型
|
|
158
|
+
|
|
159
|
+
### 工作原理
|
|
160
|
+
|
|
161
|
+
插件包装机制采用**洋葱圈模型**(Onion Model),类似于 Koa.js 的中间件机制。每个插件可以包装方法,形成多层嵌套的执行链。
|
|
162
|
+
|
|
163
|
+
### 执行流程图示
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
请求进入
|
|
167
|
+
↓
|
|
168
|
+
┌─────────────────────────────────────┐
|
|
169
|
+
│ RateLimitPlugin(最外层,SECURITY) │ ← 前置:限流检查
|
|
170
|
+
│ ↓ 调用 next() │
|
|
171
|
+
│ ┌─────────────────────────────────┐ │
|
|
172
|
+
│ │ AuthPlugin(中间层,SECURITY) │ │ ← 前置:认证检查
|
|
173
|
+
│ │ ↓ 调用 next() │ │
|
|
174
|
+
│ │ ┌─────────────────────────────┐ │ │
|
|
175
|
+
│ │ │ CachePlugin(内层,PERFORMANCE)│ │ │ ← 前置:缓存检查
|
|
176
|
+
│ │ │ ↓ 调用 next() │ │ │
|
|
177
|
+
│ │ │ ┌─────────────────────────┐ │ │ │
|
|
178
|
+
│ │ │ │ 原始业务方法(核心) │ │ │ │ ← 执行业务逻辑
|
|
179
|
+
│ │ │ └─────────────────────────┘ │ │ │
|
|
180
|
+
│ │ │ ← 后置:存储缓存 │ │ │
|
|
181
|
+
│ │ └─────────────────────────────┘ │ │
|
|
182
|
+
│ │ ← 后置:认证后处理 │ │
|
|
183
|
+
│ └─────────────────────────────────┘ │
|
|
184
|
+
│ ← 后置:限流后处理 │
|
|
185
|
+
└─────────────────────────────────────┘
|
|
186
|
+
↓
|
|
187
|
+
响应返回
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 包装链构建
|
|
191
|
+
|
|
192
|
+
引擎按照**插件优先级**自动排序,然后**从后往前**应用包装,形成洋葱圈结构:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
// 插件注册顺序(用户可以按任意顺序)
|
|
196
|
+
engine
|
|
197
|
+
.plugin(new CachePlugin()) // PERFORMANCE = 400
|
|
198
|
+
.plugin(new RateLimitPlugin()) // SECURITY = 100
|
|
199
|
+
.plugin(new AuthPlugin()); // SECURITY = 100
|
|
200
|
+
|
|
201
|
+
// 引擎自动按优先级排序:
|
|
202
|
+
// 1. RateLimitPlugin (SECURITY = 100)
|
|
203
|
+
// 2. AuthPlugin (SECURITY = 100)
|
|
204
|
+
// 3. CachePlugin (PERFORMANCE = 400)
|
|
205
|
+
|
|
206
|
+
// 包装链构建顺序(从后往前应用):
|
|
207
|
+
// CachePlugin -> AuthPlugin -> RateLimitPlugin -> 原始方法
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### 代码示例
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// RateLimitPlugin(最外层)
|
|
214
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
215
|
+
console.log("1. RateLimit: 前置检查");
|
|
216
|
+
if (!checkRateLimit()) {
|
|
217
|
+
throw new Error("Rate limit exceeded");
|
|
218
|
+
}
|
|
219
|
+
const result = await next(); // 调用下一个包装层
|
|
220
|
+
console.log("1. RateLimit: 后置处理");
|
|
221
|
+
return result;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// AuthPlugin(中间层)
|
|
225
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
226
|
+
console.log("2. Auth: 前置检查");
|
|
227
|
+
if (!isAuthenticated()) {
|
|
228
|
+
throw new Error("Unauthorized");
|
|
229
|
+
}
|
|
230
|
+
const result = await next(); // 调用下一个包装层
|
|
231
|
+
console.log("2. Auth: 后置处理");
|
|
232
|
+
return result;
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// CachePlugin(内层)
|
|
236
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
237
|
+
console.log("3. Cache: 前置检查");
|
|
238
|
+
const cached = getCache(...args);
|
|
239
|
+
if (cached) return cached;
|
|
240
|
+
|
|
241
|
+
const result = await next(); // 调用原始方法
|
|
242
|
+
console.log("3. Cache: 后置处理");
|
|
243
|
+
setCache(...args, result);
|
|
244
|
+
return result;
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**执行输出**:
|
|
249
|
+
```
|
|
250
|
+
1. RateLimit: 前置检查
|
|
251
|
+
2. Auth: 前置检查
|
|
252
|
+
3. Cache: 前置检查
|
|
253
|
+
[原始方法执行]
|
|
254
|
+
3. Cache: 后置处理
|
|
255
|
+
2. Auth: 后置处理
|
|
256
|
+
1. RateLimit: 后置处理
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 关键特性
|
|
260
|
+
|
|
261
|
+
1. **双向执行**:每个包装器可以在 `next()` 之前和之后执行代码
|
|
262
|
+
2. **优先级排序**:包装器按照插件优先级自动排序,用户无需关心注册顺序
|
|
263
|
+
3. **自动管理**:引擎自动构建和执行包装链,插件无需关心实现细节
|
|
264
|
+
|
|
265
|
+
## 插件开发指南
|
|
266
|
+
|
|
267
|
+
### 基本结构
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import {
|
|
271
|
+
BaseMicroservice,
|
|
272
|
+
HandlerMetadata,
|
|
273
|
+
Plugin,
|
|
274
|
+
PluginModuleOptionsSchema,
|
|
275
|
+
PluginPriority,
|
|
276
|
+
} from "../../core/types";
|
|
277
|
+
|
|
278
|
+
export interface MyModuleOptions {
|
|
279
|
+
myOption?: string;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export class MyPlugin implements Plugin<MyModuleOptions> {
|
|
283
|
+
public readonly name = "my-plugin";
|
|
284
|
+
public readonly priority = PluginPriority.BUSINESS; // 声明优先级
|
|
285
|
+
|
|
286
|
+
getModuleOptionsSchema(): PluginModuleOptionsSchema<MyModuleOptions> {
|
|
287
|
+
return {
|
|
288
|
+
_type: {} as MyModuleOptions,
|
|
289
|
+
validate: (options) => {
|
|
290
|
+
if (options.myOption && typeof options.myOption !== "string") {
|
|
291
|
+
return "myOption must be a string";
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
onHandlerLoad(handlers: HandlerMetadata[]): void {
|
|
299
|
+
const myHandlers = handlers.filter((h) => h.type === "my-type");
|
|
300
|
+
|
|
301
|
+
for (const handler of myHandlers) {
|
|
302
|
+
// 使用 handler.wrap() API
|
|
303
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
304
|
+
// 前置逻辑
|
|
305
|
+
console.log(`[MyPlugin] Before ${handler.methodName}`);
|
|
306
|
+
|
|
307
|
+
// 调用下一个包装层或原始方法
|
|
308
|
+
const result = await next();
|
|
309
|
+
|
|
310
|
+
// 后置逻辑
|
|
311
|
+
console.log(`[MyPlugin] After ${handler.methodName}`);
|
|
312
|
+
|
|
313
|
+
return result;
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### handler.wrap() API 详解
|
|
321
|
+
|
|
322
|
+
#### 签名
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
handler.wrap(wrapper: HandlerWrapper): void;
|
|
326
|
+
|
|
327
|
+
type HandlerWrapper = (
|
|
328
|
+
next: () => Promise<any> | any,
|
|
329
|
+
instance: any,
|
|
330
|
+
...args: any[]
|
|
331
|
+
) => Promise<any> | any;
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### 参数说明
|
|
335
|
+
|
|
336
|
+
- `next`: 调用下一个包装层或原始方法的函数
|
|
337
|
+
- `instance`: 模块实例(`this`)
|
|
338
|
+
- `args`: 方法参数
|
|
339
|
+
|
|
340
|
+
#### 使用模式
|
|
341
|
+
|
|
342
|
+
**模式1:简单包装**
|
|
343
|
+
```typescript
|
|
344
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
345
|
+
const result = await next();
|
|
346
|
+
return result;
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**模式2:前置和后置逻辑**
|
|
351
|
+
```typescript
|
|
352
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
353
|
+
// 前置逻辑
|
|
354
|
+
console.log("Before:", args);
|
|
355
|
+
|
|
356
|
+
// 调用下一个包装层
|
|
357
|
+
const result = await next();
|
|
358
|
+
|
|
359
|
+
// 后置逻辑
|
|
360
|
+
console.log("After:", result);
|
|
361
|
+
|
|
362
|
+
return result;
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**模式3:条件执行**
|
|
367
|
+
```typescript
|
|
368
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
369
|
+
if (shouldSkip(...args)) {
|
|
370
|
+
return defaultValue;
|
|
371
|
+
}
|
|
372
|
+
return await next();
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**模式4:错误处理**
|
|
377
|
+
```typescript
|
|
378
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
379
|
+
try {
|
|
380
|
+
return await next();
|
|
381
|
+
} catch (error) {
|
|
382
|
+
// 错误处理逻辑
|
|
383
|
+
console.error("Error:", error);
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### 常见错误
|
|
390
|
+
|
|
391
|
+
#### ❌ 错误1:直接修改原型
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
// ❌ 错误
|
|
395
|
+
const prototype = handler.module.prototype;
|
|
396
|
+
prototype[handler.methodName] = wrappedMethod;
|
|
397
|
+
|
|
398
|
+
// ✅ 正确
|
|
399
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
400
|
+
return await next();
|
|
401
|
+
});
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
#### ❌ 错误2:使用 handler.method
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
// ❌ 错误(handler.method 是原始方法,不是当前方法)
|
|
408
|
+
const result = await handler.method.apply(instance, args);
|
|
409
|
+
|
|
410
|
+
// ✅ 正确(使用 next() 调用当前方法)
|
|
411
|
+
const result = await next();
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
#### ❌ 错误3:忘记声明优先级
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
// ❌ 错误(不声明优先级,默认使用 BUSINESS,可能不是最佳选择)
|
|
418
|
+
export class RateLimitPlugin implements Plugin {
|
|
419
|
+
public readonly name = "rate-limit-plugin";
|
|
420
|
+
// 缺少 priority
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ✅ 正确(声明合理的优先级)
|
|
424
|
+
export class RateLimitPlugin implements Plugin {
|
|
425
|
+
public readonly name = "rate-limit-plugin";
|
|
426
|
+
public readonly priority = PluginPriority.SECURITY; // 安全插件,高优先级
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## 默认插件机制
|
|
431
|
+
|
|
432
|
+
### 概述
|
|
433
|
+
|
|
434
|
+
框架支持将常用插件注册为**默认内置插件**,无需在每次创建引擎时手动注册。`RoutePlugin` 已经作为默认内置插件自动注册。
|
|
435
|
+
|
|
436
|
+
### 使用方式
|
|
437
|
+
|
|
438
|
+
#### 基本使用(RoutePlugin 自动注册)
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
import { Microservice } from "nebula-engine";
|
|
442
|
+
|
|
443
|
+
// RoutePlugin 已经自动注册,无需手动添加
|
|
444
|
+
const engine = new Microservice({
|
|
445
|
+
name: "my-service",
|
|
446
|
+
version: "1.0.0",
|
|
447
|
+
}).plugin(new CachePlugin()); // 只需要注册其他插件
|
|
448
|
+
|
|
449
|
+
// 直接使用 @Route 装饰器
|
|
450
|
+
@Module("UserModule", {
|
|
451
|
+
routePrefix: "/api/users",
|
|
452
|
+
})
|
|
453
|
+
class UserService {
|
|
454
|
+
@Route({ method: "GET", path: "/list" })
|
|
455
|
+
async getUsers(ctx: Context) {
|
|
456
|
+
return ctx.json({ users: [] });
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
#### 禁用默认插件
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
const engine = new Microservice({
|
|
465
|
+
name: "my-service",
|
|
466
|
+
version: "1.0.0",
|
|
467
|
+
disableDefaultPlugins: true, // 禁用默认插件
|
|
468
|
+
}).plugin(new CustomRoutePlugin()); // 使用自定义路由插件
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
#### 覆盖默认插件
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
const engine = new Microservice({
|
|
475
|
+
name: "my-service",
|
|
476
|
+
version: "1.0.0",
|
|
477
|
+
}).plugin(new CustomRoutePlugin()); // 覆盖默认的 RoutePlugin
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### 执行顺序保证
|
|
481
|
+
|
|
482
|
+
即使 `RoutePlugin` 作为默认插件在构造函数中自动注册,引擎仍然会确保:
|
|
483
|
+
|
|
484
|
+
1. **按优先级排序**:所有插件(包括默认插件)按优先级自动排序
|
|
485
|
+
2. **包装插件先执行**:其他插件(如 `CachePlugin`、`AuthPlugin`)的 `onHandlerLoad` 先执行,调用 `handler.wrap()` 构建包装链
|
|
486
|
+
3. **应用包装链**:引擎将所有包装链应用到原型上
|
|
487
|
+
4. **RoutePlugin 最后执行**:`RoutePlugin` 的 `onHandlerLoad` 最后执行(优先级 `ROUTE = 1000`),注册路由时调用的是已被包装后的方法
|
|
488
|
+
|
|
489
|
+
**执行流程**:
|
|
490
|
+
```
|
|
491
|
+
1. 构造函数自动注册 RoutePlugin(作为默认插件,priority = ROUTE = 1000)
|
|
492
|
+
2. 手动注册 CachePlugin(priority = PERFORMANCE = 400)
|
|
493
|
+
3. 引擎启动:
|
|
494
|
+
a. 按优先级排序:CachePlugin (400) → RoutePlugin (1000)
|
|
495
|
+
b. CachePlugin.onHandlerLoad() → 调用 handler.wrap()
|
|
496
|
+
c. 应用包装链到原型
|
|
497
|
+
d. RoutePlugin.onHandlerLoad() → 注册路由(调用已被包装的方法)
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### 注册新的默认插件
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
import { Microservice } from "nebula-engine";
|
|
504
|
+
import { MyPlugin } from "./my-plugin";
|
|
505
|
+
|
|
506
|
+
// 注册为默认插件
|
|
507
|
+
Microservice.registerDefaultPlugin(new MyPlugin());
|
|
508
|
+
|
|
509
|
+
// 之后创建的引擎实例都会自动包含 MyPlugin
|
|
510
|
+
const engine = new Microservice({
|
|
511
|
+
name: "my-service",
|
|
512
|
+
version: "1.0.0",
|
|
513
|
+
});
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## 最佳实践
|
|
517
|
+
|
|
518
|
+
### 1. 优先级选择
|
|
519
|
+
|
|
520
|
+
#### 安全插件 → `PluginPriority.SECURITY` (100)
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
export class RateLimitPlugin implements Plugin {
|
|
524
|
+
public readonly priority = PluginPriority.SECURITY;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export class AuthPlugin implements Plugin {
|
|
528
|
+
public readonly priority = PluginPriority.SECURITY;
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
**原因**:安全插件应该最先执行,快速拒绝无效请求,避免消耗后续资源。
|
|
533
|
+
|
|
534
|
+
#### 日志插件 → `PluginPriority.LOGGING` (200)
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
export class LogPlugin implements Plugin {
|
|
538
|
+
public readonly priority = PluginPriority.LOGGING;
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
**原因**:日志插件应该记录所有请求,包括被安全插件拒绝的请求。
|
|
543
|
+
|
|
544
|
+
#### 业务逻辑插件 → `PluginPriority.BUSINESS` (300,默认)
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
export class DataTransformPlugin implements Plugin {
|
|
548
|
+
// 不指定 priority,默认使用 BUSINESS (300)
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
**原因**:业务逻辑插件在安全和日志之后执行。
|
|
553
|
+
|
|
554
|
+
#### 性能优化插件 → `PluginPriority.PERFORMANCE` (400)
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
export class CachePlugin implements Plugin {
|
|
558
|
+
public readonly priority = PluginPriority.PERFORMANCE;
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
**原因**:性能优化插件应该在内层执行,避免外层插件重复执行。
|
|
563
|
+
|
|
564
|
+
#### 路由插件 → `PluginPriority.ROUTE` (1000)
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
export class RoutePlugin implements Plugin {
|
|
568
|
+
public readonly priority = PluginPriority.ROUTE;
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
**原因**:路由插件必须最后执行,注册HTTP路由。
|
|
573
|
+
|
|
574
|
+
### 2. 插件实现原则
|
|
575
|
+
|
|
576
|
+
#### ✅ 总是使用 `handler.wrap()`
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
// ✅ 正确
|
|
580
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
581
|
+
return await next();
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// ❌ 错误
|
|
585
|
+
const prototype = handler.module.prototype;
|
|
586
|
+
prototype[handler.methodName] = wrappedMethod;
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
#### ✅ 使用 `next()` 调用
|
|
590
|
+
|
|
591
|
+
```typescript
|
|
592
|
+
// ✅ 正确
|
|
593
|
+
const result = await next();
|
|
594
|
+
|
|
595
|
+
// ❌ 错误
|
|
596
|
+
const result = await handler.method.apply(instance, args);
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
#### ✅ 声明合理的优先级
|
|
600
|
+
|
|
601
|
+
```typescript
|
|
602
|
+
// ✅ 正确
|
|
603
|
+
export class RateLimitPlugin implements Plugin {
|
|
604
|
+
public readonly priority = PluginPriority.SECURITY;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// ❌ 错误(不声明优先级)
|
|
608
|
+
export class RateLimitPlugin implements Plugin {
|
|
609
|
+
// 缺少 priority
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### 3. 典型场景示例
|
|
614
|
+
|
|
615
|
+
#### 场景1:限流 + 缓存
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
// 限流插件(外层,SECURITY = 100)
|
|
619
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
620
|
+
if (!checkRateLimit()) {
|
|
621
|
+
throw new Error("Rate limit exceeded");
|
|
622
|
+
}
|
|
623
|
+
return await next();
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// 缓存插件(内层,PERFORMANCE = 400)
|
|
627
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
628
|
+
const cached = getCache(...args);
|
|
629
|
+
if (cached) return cached;
|
|
630
|
+
|
|
631
|
+
const result = await next();
|
|
632
|
+
setCache(...args, result);
|
|
633
|
+
return result;
|
|
634
|
+
});
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
**执行顺序**:
|
|
638
|
+
1. 限流检查(外层)
|
|
639
|
+
2. 缓存检查(内层)
|
|
640
|
+
3. 原始方法执行
|
|
641
|
+
4. 缓存存储(内层)
|
|
642
|
+
5. 限流后置处理(外层)
|
|
643
|
+
|
|
644
|
+
**优势**:缓存命中时不会进行限流检查,提高性能。
|
|
645
|
+
|
|
646
|
+
#### 场景2:认证 + 日志
|
|
647
|
+
|
|
648
|
+
```typescript
|
|
649
|
+
// 日志插件(外层,LOGGING = 200)
|
|
650
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
651
|
+
const start = Date.now();
|
|
652
|
+
console.log("Request started");
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
const result = await next();
|
|
656
|
+
console.log(`Request completed in ${Date.now() - start}ms`);
|
|
657
|
+
return result;
|
|
658
|
+
} catch (error) {
|
|
659
|
+
console.error(`Request failed: ${error}`);
|
|
660
|
+
throw error;
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// 认证插件(内层,SECURITY = 100)
|
|
665
|
+
handler.wrap(async (next, instance, ...args) => {
|
|
666
|
+
if (!isAuthenticated()) {
|
|
667
|
+
throw new Error("Unauthorized");
|
|
668
|
+
}
|
|
669
|
+
return await next();
|
|
670
|
+
});
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
**执行顺序**:
|
|
674
|
+
1. 日志记录开始(外层)
|
|
675
|
+
2. 认证检查(内层)
|
|
676
|
+
3. 如果未认证,返回 401,日志记录失败 ✅
|
|
677
|
+
4. 如果已认证,执行原始方法,日志记录成功
|
|
678
|
+
|
|
679
|
+
**优势**:日志插件在外层,可以记录所有请求(包括认证失败的)。
|
|
680
|
+
|
|
681
|
+
### 4. 测试建议
|
|
682
|
+
|
|
683
|
+
#### 测试插件配合
|
|
684
|
+
|
|
685
|
+
```typescript
|
|
686
|
+
describe("插件配合", () => {
|
|
687
|
+
it("应该支持多个插件链式包装", async () => {
|
|
688
|
+
const engine = new Microservice({
|
|
689
|
+
name: "test-service",
|
|
690
|
+
version: "1.0.0",
|
|
691
|
+
})
|
|
692
|
+
.plugin(new RateLimitPlugin())
|
|
693
|
+
.plugin(new CachePlugin());
|
|
694
|
+
|
|
695
|
+
// 测试逻辑...
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
#### 测试优先级排序
|
|
701
|
+
|
|
702
|
+
```typescript
|
|
703
|
+
it("应该按优先级自动排序", () => {
|
|
704
|
+
const engine = new Microservice({
|
|
705
|
+
name: "test-service",
|
|
706
|
+
version: "1.0.0",
|
|
707
|
+
})
|
|
708
|
+
.plugin(new CachePlugin()) // PERFORMANCE = 400
|
|
709
|
+
.plugin(new RateLimitPlugin()); // SECURITY = 100
|
|
710
|
+
|
|
711
|
+
// 验证执行顺序:RateLimitPlugin → CachePlugin
|
|
712
|
+
});
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
## 总结
|
|
716
|
+
|
|
717
|
+
### 核心要点
|
|
718
|
+
|
|
719
|
+
1. **优先级系统**:插件声明优先级,引擎自动排序,用户无需关心注册顺序
|
|
720
|
+
2. **洋葱圈模型**:支持前置和后置处理逻辑,执行顺序清晰可预测
|
|
721
|
+
3. **handler.wrap() API**:简单的包装API,引擎自动管理包装链
|
|
722
|
+
4. **默认插件**:常用插件自动注册,简化使用
|
|
723
|
+
|
|
724
|
+
### 关键原则
|
|
725
|
+
|
|
726
|
+
- ✅ 使用 `handler.wrap()` API 包装方法
|
|
727
|
+
- ✅ 声明合理的优先级
|
|
728
|
+
- ✅ 使用 `next()` 调用下一个包装层
|
|
729
|
+
- ✅ 不要直接修改原型
|
|
730
|
+
- ✅ 不要使用 `handler.method`
|
|
731
|
+
|
|
732
|
+
遵循这些原则,你的插件就能与其他插件正确配合,并且用户无需关心注册顺序。
|
|
733
|
+
|