befly 3.9.12 → 3.9.14

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/plugin.md ADDED
@@ -0,0 +1,978 @@
1
+ # 插件开发指南
2
+
3
+ > 本文档详细介绍 Befly 框架的插件系统,包括插件结构、生命周期、内置插件及自定义插件开发。
4
+
5
+ ## 目录
6
+
7
+ - [插件开发指南](#插件开发指南)
8
+ - [目录](#目录)
9
+ - [概述](#概述)
10
+ - [核心特性](#核心特性)
11
+ - [插件类型](#插件类型)
12
+ - [插件结构](#插件结构)
13
+ - [基础结构](#基础结构)
14
+ - [完整类型定义](#完整类型定义)
15
+ - [插件存放位置](#插件存放位置)
16
+ - [插件命名规则](#插件命名规则)
17
+ - [插件加载流程](#插件加载流程)
18
+ - [加载顺序](#加载顺序)
19
+ - [依赖排序](#依赖排序)
20
+ - [内置插件](#内置插件)
21
+ - [logger - 日志插件](#logger---日志插件)
22
+ - [config - 配置插件](#config---配置插件)
23
+ - [db - 数据库插件](#db---数据库插件)
24
+ - [redis - Redis 插件](#redis---redis-插件)
25
+ - [jwt - JWT 插件](#jwt---jwt-插件)
26
+ - [cipher - 加密插件](#cipher---加密插件)
27
+ - [cache - 缓存插件](#cache---缓存插件)
28
+ - [tool - 工具插件](#tool---工具插件)
29
+ - [自定义插件开发](#自定义插件开发)
30
+ - [基础插件](#基础插件)
31
+ - [带依赖的插件](#带依赖的插件)
32
+ - [异步初始化插件](#异步初始化插件)
33
+ - [带配置的插件](#带配置的插件)
34
+ - [完整插件示例](#完整插件示例)
35
+ - [插件访问方式](#插件访问方式)
36
+ - [禁用插件](#禁用插件)
37
+ - [Addon 插件](#addon-插件)
38
+ - [Addon 插件示例](#addon-插件示例)
39
+ - [最佳实践](#最佳实践)
40
+ - [常见问题](#常见问题)
41
+
42
+ ---
43
+
44
+ ## 概述
45
+
46
+ Befly 插件系统是框架的核心扩展机制,允许开发者封装和复用功能模块。插件在应用启动时自动加载并初始化,其实例会挂载到 `befly` 全局对象上供 API 和其他插件使用。
47
+
48
+ ### 核心特性
49
+
50
+ - **自动发现**:自动扫描指定目录加载插件
51
+ - **依赖管理**:支持声明插件依赖关系,按依赖顺序初始化
52
+ - **全局访问**:插件实例挂载到 `befly` 对象,API 中通过 `befly.插件名` 访问
53
+ - **生命周期**:支持异步初始化,可访问框架上下文
54
+ - **可禁用**:通过配置禁用指定插件
55
+
56
+ ### 插件类型
57
+
58
+ | 类型 | 目录位置 | 插件名格式 |
59
+ | ---------- | ------------------------------- | ------------------------------ |
60
+ | 核心插件 | `packages/core/plugins/` | `{fileName}` |
61
+ | Addon 插件 | `packages/addon{Name}/plugins/` | `addon_{addonName}_{fileName}` |
62
+ | 项目插件 | `项目根目录/plugins/` | `app_{fileName}` |
63
+
64
+ ---
65
+
66
+ ## 插件结构
67
+
68
+ ### 基础结构
69
+
70
+ ```typescript
71
+ import type { Plugin } from 'befly-core/types/plugin';
72
+
73
+ const plugin: Plugin = {
74
+ // 依赖的插件列表(可选)
75
+ after: ['logger', 'db'],
76
+
77
+ // 初始化函数(必填)
78
+ handler: (befly) => {
79
+ // 返回插件实例
80
+ return {
81
+ someMethod: () => {
82
+ /* ... */
83
+ }
84
+ };
85
+ }
86
+ };
87
+
88
+ export default plugin;
89
+ ```
90
+
91
+ ### 完整类型定义
92
+
93
+ ```typescript
94
+ interface Plugin {
95
+ /** 插件名称(运行时自动生成,无需手动设置) */
96
+ name?: string;
97
+
98
+ /** 依赖的插件列表(在这些插件之后执行) */
99
+ after?: string[];
100
+
101
+ /** 插件初始化函数 */
102
+ handler?: (context: BeflyContext) => any | Promise<any>;
103
+
104
+ /** 插件描述(可选) */
105
+ description?: string;
106
+
107
+ /** 插件版本(可选) */
108
+ version?: string;
109
+
110
+ /** 插件作者(可选) */
111
+ author?: string;
112
+ }
113
+ ```
114
+
115
+ ---
116
+
117
+ ## 插件存放位置
118
+
119
+ | 类型 | 目录 | 说明 |
120
+ | ---------- | ------------------------------- | ------------------------- |
121
+ | 核心插件 | `packages/core/plugins/` | 框架内置插件,随框架发布 |
122
+ | Addon 插件 | `packages/addon{Name}/plugins/` | 组件包插件,随 Addon 发布 |
123
+ | 项目插件 | `项目根目录/plugins/` | 项目自定义插件 |
124
+
125
+ ---
126
+
127
+ ## 插件命名规则
128
+
129
+ 插件名称由 **文件名** 自动生成(小驼峰转换),**禁止在插件对象中定义 `name` 属性**。
130
+
131
+ | 文件名 | 生成的插件名 | 访问方式 |
132
+ | -------------- | ------------ | ---------------- |
133
+ | `db.ts` | `db` | `befly.db` |
134
+ | `redis.ts` | `redis` | `befly.redis` |
135
+ | `myPlugin.ts` | `myPlugin` | `befly.myPlugin` |
136
+ | `my_plugin.ts` | `myPlugin` | `befly.myPlugin` |
137
+
138
+ **Addon 插件命名规则**:
139
+
140
+ | Addon 名称 | 文件名 | 生成的插件名 |
141
+ | ------------ | ----------- | ------------------------ |
142
+ | `addonAdmin` | `email.ts` | `addon_addonAdmin_email` |
143
+ | `addonPay` | `wechat.ts` | `addon_addonPay_wechat` |
144
+
145
+ **项目插件命名规则**:
146
+
147
+ | 文件名 | 生成的插件名 |
148
+ | -------- | ------------ |
149
+ | `sms.ts` | `app_sms` |
150
+ | `oss.ts` | `app_oss` |
151
+
152
+ ---
153
+
154
+ ## 插件加载流程
155
+
156
+ ### 加载顺序
157
+
158
+ ```
159
+ 1. 核心插件(core/plugins/)
160
+
161
+ 2. Addon 插件(addon{Name}/plugins/)
162
+
163
+ 3. 项目插件(项目/plugins/)
164
+
165
+ 4. 过滤禁用的插件
166
+
167
+ 5. 按依赖关系排序
168
+
169
+ 6. 依次初始化并挂载到 befly
170
+ ```
171
+
172
+ ### 依赖排序
173
+
174
+ 使用 `after` 属性声明依赖关系:
175
+
176
+ ```typescript
177
+ // redis.ts - 依赖 logger
178
+ const plugin: Plugin = {
179
+ after: ['logger'], // 在 logger 插件之后初始化
180
+ handler: () => {
181
+ /* ... */
182
+ }
183
+ };
184
+ ```
185
+
186
+ **依赖检查**:
187
+
188
+ - 如果依赖的插件不存在,启动时报错
189
+ - 如果存在循环依赖,启动时报错
190
+
191
+ ---
192
+
193
+ ## 内置插件
194
+
195
+ ### logger - 日志插件
196
+
197
+ 提供全局日志功能。
198
+
199
+ ```typescript
200
+ // 插件源码
201
+ const loggerPlugin: Plugin = {
202
+ after: [],
203
+ async handler(): Promise<typeof Logger> {
204
+ if (beflyConfig.logger) {
205
+ Logger.configure(beflyConfig.logger);
206
+ }
207
+ return Logger;
208
+ }
209
+ };
210
+ ```
211
+
212
+ **使用方式**:
213
+
214
+ ```typescript
215
+ befly.logger.info('信息日志');
216
+ befly.logger.warn('警告日志');
217
+ befly.logger.error({ err: error }, '错误日志');
218
+ befly.logger.debug('调试日志');
219
+ ```
220
+
221
+ ---
222
+
223
+ ### config - 配置插件
224
+
225
+ 提供访问项目配置的能力。
226
+
227
+ ```typescript
228
+ // 插件源码
229
+ const plugin: Plugin = {
230
+ handler: () => {
231
+ return beflyConfig;
232
+ }
233
+ };
234
+ ```
235
+
236
+ **使用方式**:
237
+
238
+ ```typescript
239
+ const port = befly.config.appPort;
240
+ const dbConfig = befly.config.database;
241
+ const redisConfig = befly.config.redis;
242
+ ```
243
+
244
+ ---
245
+
246
+ ### db - 数据库插件
247
+
248
+ 提供数据库操作能力,依赖 `logger` 插件。
249
+
250
+ ```typescript
251
+ // 插件源码
252
+ const dbPlugin: Plugin = {
253
+ after: ['logger'],
254
+ async handler(befly: BeflyContext): Promise<DbHelper> {
255
+ const sql = await Connect.connectSql();
256
+ return new DbHelper(befly, sql);
257
+ }
258
+ };
259
+ ```
260
+
261
+ **使用方式**:
262
+
263
+ ```typescript
264
+ // 查询
265
+ const user = await befly.db.getOne({ table: 'user', where: { id: 1 } });
266
+
267
+ // 插入
268
+ const id = await befly.db.insData({ table: 'user', data: { name: '张三' } });
269
+
270
+ // 更新
271
+ await befly.db.updData({ table: 'user', data: { name: '李四' }, where: { id: 1 } });
272
+
273
+ // 删除
274
+ await befly.db.delData({ table: 'user', where: { id: 1 } });
275
+ ```
276
+
277
+ > 详细用法请参考 [database.md](./database.md)
278
+
279
+ ---
280
+
281
+ ### redis - Redis 插件
282
+
283
+ 提供 Redis 缓存操作能力,依赖 `logger` 插件。
284
+
285
+ ```typescript
286
+ // 插件源码
287
+ const redisPlugin: Plugin = {
288
+ after: ['logger'],
289
+ async handler(): Promise<RedisHelper | Record<string, never>> {
290
+ await Connect.connectRedis();
291
+ return new RedisHelper(redisConfig.prefix);
292
+ }
293
+ };
294
+ ```
295
+
296
+ **使用方式**:
297
+
298
+ ```typescript
299
+ // 字符串操作
300
+ await befly.redis.setString('key', 'value', 3600);
301
+ const value = await befly.redis.getString('key');
302
+
303
+ // 对象操作
304
+ await befly.redis.setObject('user:1', { name: '张三' });
305
+ const user = await befly.redis.getObject('user:1');
306
+
307
+ // 集合操作
308
+ await befly.redis.sadd('set:key', 'member1', 'member2');
309
+ const isMember = await befly.redis.sismember('set:key', 'member1');
310
+ ```
311
+
312
+ > 详细用法请参考 [redis.md](./redis.md)
313
+
314
+ ---
315
+
316
+ ### jwt - JWT 插件
317
+
318
+ 提供 JWT Token 签发和验证功能。
319
+
320
+ ```typescript
321
+ // 插件源码
322
+ const jwtPlugin: Plugin = {
323
+ handler: () => {
324
+ return new Jwt(beflyConfig.auth);
325
+ }
326
+ };
327
+ ```
328
+
329
+ **使用方式**:
330
+
331
+ ```typescript
332
+ // 签发 Token
333
+ const token = await befly.jwt.sign(
334
+ {
335
+ id: user.id,
336
+ roleCode: user.roleCode
337
+ },
338
+ { expiresIn: '7d' }
339
+ );
340
+
341
+ // 验证 Token
342
+ const payload = await befly.jwt.verify(token);
343
+ ```
344
+
345
+ ---
346
+
347
+ ### cipher - 加密插件
348
+
349
+ 提供密码哈希、加密解密等功能。
350
+
351
+ ```typescript
352
+ // 插件源码
353
+ const plugin: Plugin = {
354
+ handler: () => {
355
+ return Cipher;
356
+ }
357
+ };
358
+ ```
359
+
360
+ **使用方式**:
361
+
362
+ ```typescript
363
+ // 密码哈希
364
+ const hashedPassword = await befly.cipher.hashPassword('123456');
365
+
366
+ // 密码验证
367
+ const isValid = await befly.cipher.verifyPassword('123456', hashedPassword);
368
+
369
+ // AES 加密
370
+ const encrypted = befly.cipher.encrypt('敏感数据');
371
+
372
+ // AES 解密
373
+ const decrypted = befly.cipher.decrypt(encrypted);
374
+ ```
375
+
376
+ ---
377
+
378
+ ### cache - 缓存插件
379
+
380
+ 提供应用级缓存管理功能。
381
+
382
+ ```typescript
383
+ // 插件源码
384
+ const cachePlugin: Plugin = {
385
+ after: [],
386
+ async handler(befly: BeflyContext): Promise<CacheHelper> {
387
+ return new CacheHelper(befly);
388
+ }
389
+ };
390
+ ```
391
+
392
+ **使用方式**:
393
+
394
+ ```typescript
395
+ // 刷新角色权限缓存
396
+ await befly.cache.refreshRoleApis('admin');
397
+
398
+ // 获取菜单缓存
399
+ const menus = await befly.cache.getMenus();
400
+
401
+ // 刷新所有缓存
402
+ await befly.cache.refreshAll();
403
+ ```
404
+
405
+ ---
406
+
407
+ ### tool - 工具插件
408
+
409
+ 提供 API 响应辅助函数。
410
+
411
+ ```typescript
412
+ // 插件源码
413
+ const plugin: Plugin = {
414
+ handler: () => {
415
+ return {
416
+ Yes: Yes,
417
+ No: No
418
+ };
419
+ }
420
+ };
421
+ ```
422
+
423
+ **使用方式**:
424
+
425
+ ```typescript
426
+ // 成功响应
427
+ return befly.tool.Yes('操作成功', { id: 1 });
428
+ // 返回: { code: 0, msg: '操作成功', data: { id: 1 } }
429
+
430
+ // 失败响应
431
+ return befly.tool.No('操作失败');
432
+ // 返回: { code: 1, msg: '操作失败', data: null }
433
+ ```
434
+
435
+ ---
436
+
437
+ ## 自定义插件开发
438
+
439
+ ### 基础插件
440
+
441
+ ```typescript
442
+ // plugins/hello.ts
443
+ import type { Plugin } from 'befly-core/types/plugin';
444
+
445
+ const plugin: Plugin = {
446
+ handler: () => {
447
+ return {
448
+ sayHello: (name: string) => `Hello, ${name}!`
449
+ };
450
+ }
451
+ };
452
+
453
+ export default plugin;
454
+ ```
455
+
456
+ **使用**:
457
+
458
+ ```typescript
459
+ // 项目插件名为 app_hello
460
+ const greeting = befly.app_hello.sayHello('World');
461
+ // 返回: "Hello, World!"
462
+ ```
463
+
464
+ ---
465
+
466
+ ### 带依赖的插件
467
+
468
+ ```typescript
469
+ // plugins/userService.ts
470
+ import type { Plugin } from 'befly-core/types/plugin';
471
+ import type { BeflyContext } from 'befly-core/types/befly';
472
+
473
+ const plugin: Plugin = {
474
+ after: ['db', 'redis'], // 依赖数据库和 Redis
475
+ handler: (befly: BeflyContext) => {
476
+ return {
477
+ async getUser(id: number) {
478
+ // 先从缓存获取
479
+ const cacheKey = `user:${id}`;
480
+ let user = await befly.redis.getObject(cacheKey);
481
+
482
+ if (!user) {
483
+ // 缓存不存在,从数据库查询
484
+ user = await befly.db.getOne({
485
+ table: 'user',
486
+ where: { id: id }
487
+ });
488
+
489
+ // 写入缓存
490
+ if (user) {
491
+ await befly.redis.setObject(cacheKey, user, 3600);
492
+ }
493
+ }
494
+
495
+ return user;
496
+ }
497
+ };
498
+ }
499
+ };
500
+
501
+ export default plugin;
502
+ ```
503
+
504
+ ---
505
+
506
+ ### 异步初始化插件
507
+
508
+ ```typescript
509
+ // plugins/elastic.ts
510
+ import type { Plugin } from 'befly-core/types/plugin';
511
+ import { Client } from '@elastic/elasticsearch';
512
+
513
+ const plugin: Plugin = {
514
+ after: ['logger', 'config'],
515
+ async handler(befly) {
516
+ const config = befly.config.elasticsearch || {};
517
+
518
+ const client = new Client({
519
+ node: config.node || 'http://localhost:9200',
520
+ auth: config.auth
521
+ });
522
+
523
+ // 测试连接
524
+ try {
525
+ await client.ping();
526
+ befly.logger.info('Elasticsearch 连接成功');
527
+ } catch (error) {
528
+ befly.logger.error({ err: error }, 'Elasticsearch 连接失败');
529
+ throw error;
530
+ }
531
+
532
+ return {
533
+ client: client,
534
+ async search(index: string, query: object) {
535
+ return client.search({ index: index, body: query });
536
+ },
537
+ async index(index: string, document: object) {
538
+ return client.index({ index: index, body: document });
539
+ }
540
+ };
541
+ }
542
+ };
543
+
544
+ export default plugin;
545
+ ```
546
+
547
+ ---
548
+
549
+ ### 带配置的插件
550
+
551
+ ```typescript
552
+ // plugins/sms.ts
553
+ import type { Plugin } from 'befly-core/types/plugin';
554
+ import type { BeflyContext } from 'befly-core/types/befly';
555
+
556
+ interface SmsConfig {
557
+ accessKeyId: string;
558
+ accessKeySecret: string;
559
+ signName: string;
560
+ templateCode: string;
561
+ }
562
+
563
+ class SmsHelper {
564
+ private config: SmsConfig;
565
+ private befly: BeflyContext;
566
+
567
+ constructor(befly: BeflyContext, config: SmsConfig) {
568
+ this.befly = befly;
569
+ this.config = config;
570
+ }
571
+
572
+ async send(phone: string, params: Record<string, string>) {
573
+ // 实现短信发送逻辑
574
+ this.befly.logger.info({ phone: phone }, '发送短信');
575
+ // ...
576
+ return { success: true };
577
+ }
578
+ }
579
+
580
+ const plugin: Plugin = {
581
+ after: ['logger', 'config'],
582
+ handler: (befly: BeflyContext) => {
583
+ const smsConfig = befly.config.sms || {};
584
+ return new SmsHelper(befly, smsConfig);
585
+ }
586
+ };
587
+
588
+ export default plugin;
589
+ ```
590
+
591
+ ---
592
+
593
+ ### 完整插件示例
594
+
595
+ 以 Addon 邮件插件为例:
596
+
597
+ ```typescript
598
+ // addonAdmin/plugins/email.ts
599
+ import nodemailer from 'nodemailer';
600
+ import type { Transporter } from 'nodemailer';
601
+ import type { Plugin } from 'befly-core/types/plugin';
602
+ import type { BeflyContext } from 'befly-core/types/befly';
603
+
604
+ /** 邮件配置 */
605
+ interface EmailConfig {
606
+ host: string;
607
+ port: number;
608
+ secure: boolean;
609
+ user: string;
610
+ pass: string;
611
+ fromName?: string;
612
+ }
613
+
614
+ /** 发送邮件参数 */
615
+ interface SendEmailOptions {
616
+ to: string;
617
+ subject: string;
618
+ text?: string;
619
+ html?: string;
620
+ }
621
+
622
+ /** 发送结果 */
623
+ interface SendEmailResult {
624
+ success: boolean;
625
+ messageId?: string;
626
+ error?: string;
627
+ }
628
+
629
+ /**
630
+ * 邮件助手类
631
+ */
632
+ class EmailHelper {
633
+ private config: EmailConfig;
634
+ private transporter: Transporter | null = null;
635
+ private befly: BeflyContext;
636
+
637
+ constructor(befly: BeflyContext, config: EmailConfig) {
638
+ this.befly = befly;
639
+ this.config = config;
640
+
641
+ if (this.config.user && this.config.pass) {
642
+ this.transporter = nodemailer.createTransport({
643
+ host: this.config.host,
644
+ port: this.config.port,
645
+ secure: this.config.secure,
646
+ auth: {
647
+ user: this.config.user,
648
+ pass: this.config.pass
649
+ }
650
+ });
651
+ }
652
+ }
653
+
654
+ async send(options: SendEmailOptions): Promise<SendEmailResult> {
655
+ if (!this.transporter) {
656
+ return { success: false, error: '邮件服务未配置' };
657
+ }
658
+
659
+ try {
660
+ const info = await this.transporter.sendMail({
661
+ from: `"${this.config.fromName}" <${this.config.user}>`,
662
+ to: options.to,
663
+ subject: options.subject,
664
+ text: options.text,
665
+ html: options.html
666
+ });
667
+
668
+ return { success: true, messageId: info.messageId };
669
+ } catch (error: any) {
670
+ return { success: false, error: error.message };
671
+ }
672
+ }
673
+
674
+ async verify(): Promise<boolean> {
675
+ if (!this.transporter) return false;
676
+ try {
677
+ await this.transporter.verify();
678
+ return true;
679
+ } catch {
680
+ return false;
681
+ }
682
+ }
683
+ }
684
+
685
+ /**
686
+ * 邮件插件
687
+ */
688
+ const emailPlugin: Plugin = {
689
+ after: ['db', 'logger', 'config'],
690
+ async handler(befly: BeflyContext): Promise<EmailHelper> {
691
+ const emailConfig = befly.config?.addons?.admin?.email || {};
692
+ return new EmailHelper(befly, emailConfig);
693
+ }
694
+ };
695
+
696
+ export default emailPlugin;
697
+ ```
698
+
699
+ **使用方式**:
700
+
701
+ ```typescript
702
+ // Addon 插件名为 addon_addonAdmin_email
703
+ const result = await befly.addon_addonAdmin_email.send({
704
+ to: 'user@example.com',
705
+ subject: '验证码',
706
+ html: '<p>您的验证码是:123456</p>'
707
+ });
708
+
709
+ if (result.success) {
710
+ return befly.tool.Yes('邮件发送成功');
711
+ } else {
712
+ return befly.tool.No(result.error || '邮件发送失败');
713
+ }
714
+ ```
715
+
716
+ ---
717
+
718
+ ## 插件访问方式
719
+
720
+ 插件初始化后会挂载到 `befly` 全局对象:
721
+
722
+ ```typescript
723
+ // 在 API handler 中
724
+ export default {
725
+ name: '示例接口',
726
+ handler: async (befly, ctx) => {
727
+ // 访问内置插件
728
+ const user = await befly.db.getOne({ table: 'user', where: { id: 1 } });
729
+ befly.logger.info({ user: user }, '查询用户');
730
+
731
+ // 访问项目插件
732
+ const result = await befly.app_sms.send('13800138000', { code: '123456' });
733
+
734
+ // 访问 Addon 插件
735
+ await befly.addon_addonAdmin_email.send({
736
+ to: 'admin@example.com',
737
+ subject: '通知'
738
+ });
739
+
740
+ return befly.tool.Yes('成功');
741
+ }
742
+ };
743
+ ```
744
+
745
+ ---
746
+
747
+ ## 禁用插件
748
+
749
+ 在配置文件中设置 `disablePlugins` 数组:
750
+
751
+ ```json
752
+ // befly.local.json
753
+ {
754
+ "disablePlugins": ["redis", "app_sms"]
755
+ }
756
+ ```
757
+
758
+ 被禁用的插件不会被加载和初始化。
759
+
760
+ ---
761
+
762
+ ## Addon 插件
763
+
764
+ Addon 插件是组件包中的扩展功能,用于为 Addon 提供特定的服务能力。
765
+
766
+ ### Addon 插件示例
767
+
768
+ ```typescript
769
+ // packages/addonPay/plugins/wechat.ts
770
+ import type { Plugin } from 'befly-core/types/plugin';
771
+ import type { BeflyContext } from 'befly-core/types/befly';
772
+
773
+ class WechatPayHelper {
774
+ private befly: BeflyContext;
775
+ private config: any;
776
+
777
+ constructor(befly: BeflyContext) {
778
+ this.befly = befly;
779
+ this.config = befly.config?.addons?.pay?.wechat || {};
780
+ }
781
+
782
+ async createOrder(params: { orderId: string; amount: number; description: string }) {
783
+ // 调用微信支付 API 创建订单
784
+ // ...
785
+ return { prepayId: 'xxx', nonceStr: 'xxx' };
786
+ }
787
+
788
+ async queryOrder(orderId: string) {
789
+ // 查询订单状态
790
+ // ...
791
+ return { status: 'SUCCESS' };
792
+ }
793
+
794
+ async refund(orderId: string, amount: number) {
795
+ // 申请退款
796
+ // ...
797
+ return { refundId: 'xxx' };
798
+ }
799
+ }
800
+
801
+ const plugin: Plugin = {
802
+ after: ['logger', 'config'],
803
+ handler: (befly: BeflyContext) => {
804
+ return new WechatPayHelper(befly);
805
+ }
806
+ };
807
+
808
+ export default plugin;
809
+ ```
810
+
811
+ **使用方式**:
812
+
813
+ ```typescript
814
+ // 插件名:addon_addonPay_wechat
815
+ const order = await befly.addon_addonPay_wechat.createOrder({
816
+ orderId: '202312010001',
817
+ amount: 100,
818
+ description: '商品购买'
819
+ });
820
+ ```
821
+
822
+ ---
823
+
824
+ ## 最佳实践
825
+
826
+ ### 1. 使用 TypeScript 类封装
827
+
828
+ ```typescript
829
+ // ✅ 推荐:使用 class 封装
830
+ class MyHelper {
831
+ private befly: BeflyContext;
832
+
833
+ constructor(befly: BeflyContext) {
834
+ this.befly = befly;
835
+ }
836
+
837
+ async doSomething() {
838
+ /* ... */
839
+ }
840
+ }
841
+
842
+ const plugin: Plugin = {
843
+ handler: (befly) => new MyHelper(befly)
844
+ };
845
+ ```
846
+
847
+ ### 2. 正确声明依赖
848
+
849
+ ```typescript
850
+ // ✅ 推荐:明确声明所有依赖
851
+ const plugin: Plugin = {
852
+ after: ['logger', 'db', 'redis'], // 声明所有使用的插件
853
+ handler: (befly) => {
854
+ /* ... */
855
+ }
856
+ };
857
+
858
+ // ❌ 避免:使用未声明的依赖
859
+ const plugin: Plugin = {
860
+ handler: (befly) => {
861
+ befly.db.getOne({
862
+ /* ... */
863
+ }); // 可能 db 还未初始化!
864
+ }
865
+ };
866
+ ```
867
+
868
+ ### 3. 错误处理
869
+
870
+ ```typescript
871
+ // ✅ 推荐:初始化时处理错误
872
+ const plugin: Plugin = {
873
+ async handler(befly) {
874
+ try {
875
+ const client = await connectToService();
876
+ return client;
877
+ } catch (error) {
878
+ befly.logger.error({ err: error }, '服务连接失败');
879
+ throw error; // 抛出错误会终止应用启动
880
+ }
881
+ }
882
+ };
883
+ ```
884
+
885
+ ### 4. 配置验证
886
+
887
+ ```typescript
888
+ // ✅ 推荐:验证必要配置
889
+ const plugin: Plugin = {
890
+ handler: (befly) => {
891
+ const config = befly.config.myService;
892
+
893
+ if (!config?.apiKey) {
894
+ throw new Error('myService.apiKey 配置缺失');
895
+ }
896
+
897
+ return new MyService(config);
898
+ }
899
+ };
900
+ ```
901
+
902
+ ### 5. 资源清理
903
+
904
+ ```typescript
905
+ // ✅ 推荐:提供清理方法
906
+ class DatabasePool {
907
+ private pool: any;
908
+
909
+ async connect() {
910
+ /* ... */
911
+ }
912
+
913
+ async close() {
914
+ if (this.pool) {
915
+ await this.pool.end();
916
+ }
917
+ }
918
+ }
919
+ ```
920
+
921
+ ---
922
+
923
+ ## 常见问题
924
+
925
+ ### Q1: 插件加载顺序如何确定?
926
+
927
+ 插件按以下顺序加载:
928
+
929
+ 1. 核心插件 → Addon 插件 → 项目插件
930
+ 2. 同一类型内根据 `after` 依赖关系排序
931
+ 3. 无依赖的插件按文件名字母顺序
932
+
933
+ ### Q2: 如何在插件中访问其他插件?
934
+
935
+ 通过 `befly` 上下文访问:
936
+
937
+ ```typescript
938
+ const plugin: Plugin = {
939
+ after: ['db'], // 声明依赖
940
+ handler: (befly) => {
941
+ return {
942
+ async getUser(id: number) {
943
+ return befly.db.getOne({ table: 'user', where: { id: id } });
944
+ }
945
+ };
946
+ }
947
+ };
948
+ ```
949
+
950
+ ### Q3: 插件初始化失败会怎样?
951
+
952
+ 插件初始化失败(抛出错误)会导致应用启动终止,并输出错误日志。
953
+
954
+ ### Q4: 可以动态加载插件吗?
955
+
956
+ 目前不支持运行时动态加载插件,所有插件在应用启动时加载。
957
+
958
+ ### Q5: 如何测试插件?
959
+
960
+ ```typescript
961
+ // tests/myPlugin.test.ts
962
+ import { describe, test, expect } from 'bun:test';
963
+
964
+ describe('MyPlugin', () => {
965
+ test('应该正确初始化', async () => {
966
+ const mockBefly = {
967
+ config: { myService: { apiKey: 'test' } },
968
+ logger: { info: () => {}, error: () => {} }
969
+ };
970
+
971
+ const plugin = (await import('../plugins/myPlugin.ts')).default;
972
+ const instance = await plugin.handler(mockBefly);
973
+
974
+ expect(instance).toBeDefined();
975
+ expect(typeof instance.doSomething).toBe('function');
976
+ });
977
+ });
978
+ ```