@zhin.js/adapter-lark 1.0.25 → 1.0.27

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 CHANGED
@@ -1,6 +1,15 @@
1
1
  # @zhin.js/adapter-lark
2
2
 
3
- Zhin.js 飞书/Lark 适配器,支持飞书(中国版)和 Lark(国际版)机器人开发。
3
+ Zhin.js 飞书 / Lark 适配器,支持飞书机器人的消息收发和群管理。
4
+
5
+ ## 功能特性
6
+
7
+ - 支持飞书和 Lark 国际版
8
+ - Webhook 事件接收(HTTP 回调)
9
+ - Bearer Token 自动刷新
10
+ - 消息加密和签名验证(可选)
11
+ - URL 验证自动处理
12
+ - 群管理 AI 工具(通过 declareSkill 暴露给 AI)
4
13
 
5
14
  ## 安装
6
15
 
@@ -8,644 +17,99 @@ Zhin.js 飞书/Lark 适配器,支持飞书(中国版)和 Lark(国际版
8
17
  pnpm add @zhin.js/adapter-lark
9
18
  ```
10
19
 
11
- ## 配置
12
-
13
- ### 基础配置
14
-
15
- ```typescript
16
- import { LarkBotConfig } from '@zhin.js/adapter-lark';
17
-
18
- const config: LarkBotConfig = {
19
- context: 'lark',
20
- name: 'my-lark-bot',
21
- appId: 'YOUR_APP_ID', // 飞书应用 ID
22
- appSecret: 'YOUR_APP_SECRET', // 飞书应用密钥
23
- webhookPath: '/lark/webhook', // Webhook 路径
24
- }
25
- ```
26
-
27
- ### 完整配置
28
-
29
- ```typescript
30
- const config: LarkBotConfig = {
31
- context: 'lark',
32
- name: 'my-lark-bot',
33
- appId: 'YOUR_APP_ID',
34
- appSecret: 'YOUR_APP_SECRET',
35
- webhookPath: '/lark/webhook',
36
-
37
- // 安全配置(推荐)
38
- encryptKey: 'YOUR_ENCRYPT_KEY', // 事件推送加密密钥
39
- verificationToken: 'YOUR_VERIFICATION_TOKEN', // 事件推送验证令牌
40
-
41
- // API 配置
42
- isFeishu: true, // true=飞书(中国版), false=Lark(国际版), 默认false
43
- apiBaseUrl: 'https://open.feishu.cn/open-apis' // 自定义API地址(可选)
44
- }
45
- ```
46
-
47
- ### 配置参数说明
20
+ ## 依赖
48
21
 
49
- - `appId` (必需): 飞书应用 ID,在开发者后台获取
50
- - `appSecret` (必需): 飞书应用密钥,在开发者后台获取
51
- - `webhookPath` (必需): Webhook 路径,如 `/lark/webhook`
52
- - `encryptKey` (推荐): 事件推送加密密钥,用于签名验证
53
- - `verificationToken` (推荐): 事件推送验证令牌,额外安全验证
54
- - `isFeishu` (可选): 是否为飞书中国版,默认 `false` (Lark 国际版)
55
- - `apiBaseUrl` (可选): 自定义 API 基础地址
56
-
57
- ## 获取配置信息
58
-
59
- ### 创建飞书/Lark 应用
60
-
61
- #### 飞书(中国版)
62
- 1. 访问 [飞书开放平台](https://open.feishu.cn/app)
63
- 2. 点击「创建应用」,选择「自建应用」
64
- 3. 填写应用信息并创建
65
-
66
- #### Lark(国际版)
67
- 1. 访问 [Lark Developer Console](https://open.larksuite.com/app)
68
- 2. 点击「Create App」,选择「Custom App」
69
- 3. 填写应用信息并创建
70
-
71
- ### 获取应用凭证
72
-
73
- 在应用详情页面的「凭证与基础信息」中获取:
74
- - **App ID**: 应用唯一标识
75
- - **App Secret**: 应用密钥(注意保密)
76
-
77
- ### 配置机器人
78
-
79
- 1. **启用机器人功能**:
80
- - 在应用管理页面,进入「功能配置」→「机器人」
81
- - 启用机器人功能
82
-
83
- 2. **配置事件订阅**:
84
- - 进入「事件订阅」页面
85
- - 设置请求网址:`https://yourdomain.com/lark/webhook`
86
- - 配置加密策略(推荐启用)
87
- - 订阅需要的事件类型:
88
- - `接收消息` - 接收用户发送的消息
89
- - `消息已读` - 消息已读事件
90
- - 其他所需事件
91
-
92
- 3. **配置权限**:
93
- - 在「权限管理」中申请所需权限:
94
- - `以应用的身份发消息` - 发送消息
95
- - `获取与发送单聊、群组消息` - 收发消息
96
- - `读取用户通讯录基本信息` - 获取用户信息
97
- - 其他业务需要的权限
98
-
99
- 4. **发布应用**:
100
- - 完成配置后,提交审核并发布应用
101
-
102
- ## 使用示例
103
-
104
- ### 基础使用
105
-
106
- ```typescript
107
- import { createApp } from 'zhin.js';
108
- import '@zhin.js/adapter-lark';
109
- import '@zhin.js/http'; // 需要 HTTP 插件支持
110
-
111
- const app = createApp();
112
-
113
- // 先加载 HTTP 插件
114
- app.plugin(require('@zhin.js/http'));
115
-
116
- // 配置飞书适配器
117
- app.adapter('lark', {
118
- context: 'lark',
119
- name: 'my-bot',
120
- appId: process.env.LARK_APP_ID!,
121
- appSecret: process.env.LARK_APP_SECRET!,
122
- webhookPath: '/lark/webhook',
123
- encryptKey: process.env.LARK_ENCRYPT_KEY,
124
- verificationToken: process.env.LARK_VERIFICATION_TOKEN,
125
- isFeishu: true // 使用飞书中国版
126
- });
127
-
128
- // 基础命令
129
- app.command('ping').action((session) => {
130
- session.send('pong! 🏓');
131
- });
132
-
133
- // 处理用户消息
134
- app.middleware((session, next) => {
135
- console.log(`收到来自 ${session.$sender.name} 的消息`);
136
- return next();
137
- });
138
-
139
- app.start();
140
- ```
141
-
142
- ### 高级功能使用
143
-
144
- ```typescript
145
- import { createApp } from 'zhin.js';
146
- import '@zhin.js/adapter-lark';
147
-
148
- const app = createApp();
149
-
150
- app.plugin(require('@zhin.js/http'));
151
-
152
- const bot = app.adapter('lark', {
153
- context: 'lark',
154
- name: 'advanced-bot',
155
- appId: process.env.LARK_APP_ID!,
156
- appSecret: process.env.LARK_APP_SECRET!,
157
- webhookPath: '/lark/webhook',
158
- encryptKey: process.env.LARK_ENCRYPT_KEY,
159
- isFeishu: true
160
- });
161
-
162
- // 帮助命令
163
- app.command('help').action(async (session) => {
164
- await session.send([
165
- { type: 'text', data: { content: '🤖 机器人帮助\\n\\n' } },
166
- { type: 'text', data: { content: '📝 /help - 显示此帮助\\n' } },
167
- { type: 'text', data: { content: '🏓 /ping - 测试连通性\\n' } },
168
- { type: 'text', data: { content: '👤 /me - 查看个人信息\\n' } },
169
- { type: 'text', data: { content: '📊 /stats - 查看统计信息' } }
170
- ]);
171
- });
172
-
173
- // 个人信息查询
174
- app.command('me').action(async (session) => {
175
- const userInfo = await bot.getUserInfo(session.$sender.id);
176
- if (userInfo) {
177
- await session.send(`👤 用户信息:\\n姓名: ${userInfo.name}\\n邮箱: ${userInfo.email || '未设置'}`);
178
- } else {
179
- await session.send('❌ 无法获取用户信息');
180
- }
181
- });
182
-
183
- // 处理 @提及
184
- app.middleware((session, next) => {
185
- const mentions = session.content.filter(seg => seg.type === 'at');
186
- if (mentions.length > 0) {
187
- console.log('收到提及:', mentions.map(m => m.data.name));
188
- session.send(`👋 我看到你提及了 ${mentions.length} 个用户`);
189
- }
190
- return next();
191
- });
192
-
193
- // 处理图片消息
194
- app.middleware((session, next) => {
195
- const images = session.content.filter(seg => seg.type === 'image');
196
- if (images.length > 0) {
197
- session.send(`📷 收到了 ${images.length} 张图片!`);
198
- }
199
- return next();
200
- });
201
-
202
- // 处理文件消息
203
- app.middleware((session, next) => {
204
- const files = session.content.filter(seg => seg.type === 'file');
205
- if (files.length > 0) {
206
- const fileNames = files.map(f => f.data.file_name).join(', ');
207
- session.send(`📎 收到文件: ${fileNames}`);
208
- }
209
- return next();
210
- });
22
+ - `@zhin.js/http` HTTP 服务(提供 Webhook 路由)
211
23
 
212
- // 发送卡片消息
213
- app.command('card').action(async (session) => {
214
- await session.send([
215
- {
216
- type: 'card',
217
- data: {
218
- config: {
219
- wide_screen_mode: true
220
- },
221
- elements: [
222
- {
223
- tag: 'div',
224
- text: {
225
- content: '**这是一个交互式卡片**\\n\\n点击下方按钮体验功能',
226
- tag: 'lark_md'
227
- }
228
- },
229
- {
230
- tag: 'action',
231
- actions: [
232
- {
233
- tag: 'button',
234
- text: {
235
- content: '点赞 👍',
236
- tag: 'plain_text'
237
- },
238
- type: 'primary',
239
- value: {
240
- action: 'like'
241
- }
242
- },
243
- {
244
- tag: 'button',
245
- text: {
246
- content: '查看详情',
247
- tag: 'plain_text'
248
- },
249
- type: 'default',
250
- value: {
251
- action: 'detail'
252
- }
253
- }
254
- ]
255
- }
256
- ],
257
- header: {
258
- title: {
259
- content: '🎉 欢迎使用飞书机器人',
260
- tag: 'plain_text'
261
- },
262
- template: 'blue'
263
- }
264
- }
265
- }
266
- ]);
267
- });
24
+ ## 配置
268
25
 
269
- app.start();
26
+ ```yaml
27
+ # zhin.config.yml
28
+ bots:
29
+ - context: lark
30
+ name: my-lark-bot
31
+ appId: cli_xxxxxxxxxxxx
32
+ appSecret: xxxxxxxxxxxxxxxxxxxxxxxx
33
+ webhookPath: /lark/webhook
34
+ # 可选配置
35
+ # encryptKey: your-encrypt-key
36
+ # verificationToken: your-token
37
+ # isFeishu: true # 使用飞书 API(默认)
38
+ # apiBaseUrl: https://open.feishu.cn/open-apis # 自定义 API 地址
39
+
40
+ plugins:
41
+ - adapter-lark
42
+ - http
270
43
  ```
271
44
 
272
- ### 文件上传示例
45
+ 或使用 TypeScript 配置:
273
46
 
274
47
  ```typescript
275
- // 上传并发送图片
276
- app.command('upload-image').action(async (session) => {
277
- try {
278
- const fileKey = await bot.uploadFile('./path/to/image.jpg', 'image');
279
- if (fileKey) {
280
- await session.send([
281
- { type: 'image', data: { file_key: fileKey } }
282
- ]);
283
- }
284
- } catch (error) {
285
- await session.send('❌ 文件上传失败');
286
- }
287
- });
288
-
289
- // 上传并发送文件
290
- app.command('upload-file').action(async (session) => {
291
- try {
292
- const fileKey = await bot.uploadFile('./path/to/document.pdf', 'file');
293
- if (fileKey) {
294
- await session.send([
295
- { type: 'file', data: { file_key: fileKey } }
296
- ]);
297
- }
298
- } catch (error) {
299
- await session.send('❌ 文件上传失败');
300
- }
301
- });
302
- ```
303
-
304
- ## 支持的消息类型
305
-
306
- ### 接收消息类型
307
-
308
- - **文本消息**: 支持纯文本和富文本格式
309
- - **图片消息**: 支持各种格式的图片
310
- - **文件消息**: 支持各种类型的文件附件
311
- - **音频消息**: 支持语音和音频文件
312
- - **视频消息**: 支持视频文件
313
- - **贴纸消息**: 支持飞书表情包
314
- - **卡片消息**: 支持交互式卡片和富文本卡片
315
- - **@提及**: 支持用户提及解析
316
- - **链接消息**: 自动解析文本中的链接
317
- - **富文本**: 支持格式化文本内容
318
-
319
- ### 发送消息类型
48
+ import { defineConfig } from 'zhin.js'
320
49
 
321
- - **文本消息**: 发送纯文本内容
322
- - **图片消息**: 发送图片(需要先上传获取 file_key)
323
- - **文件消息**: 发送文件(需要先上传获取 file_key)
324
- - **卡片消息**: 发送交互式卡片和富文本卡片
325
- - **@提及**: 在消息中提及特定用户
326
- - **链接消息**: 发送包含链接的富文本
327
-
328
- ## 聊天类型支持
329
-
330
- - `private`: 私聊(单聊)
331
- - `group`: 群聊
332
-
333
- ## 飞书特色功能
334
-
335
- ### 交互式卡片
336
-
337
- 飞书支持丰富的交互式卡片,可以创建按钮、表单、图表等:
338
-
339
- ```typescript
340
- app.command('interactive-card').action(async (session) => {
341
- await session.send([
50
+ export default defineConfig({
51
+ bots: [
342
52
  {
343
- type: 'card',
344
- data: {
345
- config: {
346
- wide_screen_mode: true
347
- },
348
- elements: [
349
- // 文本内容
350
- {
351
- tag: 'div',
352
- text: {
353
- content: '请选择您的偏好设置:',
354
- tag: 'plain_text'
355
- }
356
- },
357
- // 分割线
358
- {
359
- tag: 'hr'
360
- },
361
- // 选择器
362
- {
363
- tag: 'div',
364
- fields: [
365
- {
366
- is_short: true,
367
- text: {
368
- content: '**语言偏好**\\nChinese',
369
- tag: 'lark_md'
370
- }
371
- },
372
- {
373
- is_short: true,
374
- text: {
375
- content: '**通知设置**\\n开启',
376
- tag: 'lark_md'
377
- }
378
- }
379
- ]
380
- },
381
- // 按钮组
382
- {
383
- tag: 'action',
384
- actions: [
385
- {
386
- tag: 'button',
387
- text: {
388
- content: '保存设置',
389
- tag: 'plain_text'
390
- },
391
- type: 'primary',
392
- value: {
393
- action: 'save_settings'
394
- }
395
- },
396
- {
397
- tag: 'button',
398
- text: {
399
- content: '重置',
400
- tag: 'plain_text'
401
- },
402
- type: 'danger',
403
- value: {
404
- action: 'reset'
405
- }
406
- }
407
- ]
408
- }
409
- ],
410
- header: {
411
- title: {
412
- content: '⚙️ 设置面板',
413
- tag: 'plain_text'
414
- },
415
- template: 'green'
416
- }
417
- }
53
+ context: 'lark',
54
+ name: 'my-lark-bot',
55
+ appId: process.env.LARK_APP_ID!,
56
+ appSecret: process.env.LARK_APP_SECRET!,
57
+ webhookPath: '/lark/webhook',
418
58
  }
419
- ]);
420
- });
59
+ ],
60
+ plugins: ['adapter-lark', 'http']
61
+ })
421
62
  ```
422
63
 
423
- ### 文件操作
424
-
425
- 飞书支持多种文件操作:
426
-
427
- ```typescript
428
- // 获取文件信息
429
- app.command('file-info <file_key>').action(async (session) => {
430
- const fileKey = session.argv.file_key;
431
-
432
- try {
433
- const response = await bot.axiosInstance.get(\`/im/v1/files/\${fileKey}\`);
434
- const fileInfo = response.data.data;
435
-
436
- await session.send(\`📄 文件信息:\\n文件名: \${fileInfo.file_name}\\n大小: \${fileInfo.file_size} bytes\\n类型: \${fileInfo.file_type}\`);
437
- } catch (error) {
438
- await session.send('❌ 获取文件信息失败');
439
- }
440
- });
441
-
442
- // 下载文件
443
- app.command('download <file_key>').action(async (session) => {
444
- const fileKey = session.argv.file_key;
445
-
446
- try {
447
- const response = await bot.axiosInstance.get(\`/im/v1/files/\${fileKey}/download\`);
448
- // 处理文件下载逻辑
449
- await session.send('✅ 文件下载完成');
450
- } catch (error) {
451
- await session.send('❌ 文件下载失败');
452
- }
453
- });
454
- ```
64
+ ## 使用示例
455
65
 
456
- ### 用户和群组管理
66
+ ### 注册命令
457
67
 
458
68
  ```typescript
459
- // 获取群组成员列表
460
- app.command('members').action(async (session) => {
461
- if (session.$channel.type !== 'group') {
462
- await session.send('❌ 此命令只能在群聊中使用');
463
- return;
464
- }
465
-
466
- try {
467
- const chatInfo = await bot.getChatInfo(session.$channel.id);
468
- const memberCount = chatInfo?.member_count || 0;
469
-
470
- await session.send(\`👥 群组信息:\\n群名称: \${chatInfo?.name || '未知'}\\n成员数量: \${memberCount}\`);
471
- } catch (error) {
472
- await session.send('❌ 获取群组信息失败');
473
- }
474
- });
69
+ import { usePlugin, MessageCommand } from 'zhin.js'
475
70
 
476
- // 获取用户详细信息
477
- app.command('user-info [user_id]').action(async (session) => {
478
- const userId = session.argv.user_id || session.$sender.id;
479
-
480
- try {
481
- const userInfo = await bot.getUserInfo(userId);
482
- if (userInfo) {
483
- await session.send([
484
- { type: 'text', data: { content: \`👤 用户详情:\\n\` } },
485
- { type: 'text', data: { content: \`姓名: \${userInfo.name}\\n\` } },
486
- { type: 'text', data: { content: \`邮箱: \${userInfo.email || '未设置'}\\n\` } },
487
- { type: 'text', data: { content: \`部门: \${userInfo.department_ids?.join(', ') || '未知'}\` } }
488
- ]);
489
- }
490
- } catch (error) {
491
- await session.send('❌ 获取用户信息失败');
492
- }
493
- });
494
- ```
495
-
496
- ## 最佳实践
497
-
498
- ### 1. 安全配置
71
+ const { addCommand } = usePlugin()
499
72
 
500
- ```typescript
501
- // 生产环境建议启用所有安全选项
502
- const config: LarkBotConfig = {
503
- context: 'lark',
504
- name: 'production-bot',
505
- appId: process.env.LARK_APP_ID!,
506
- appSecret: process.env.LARK_APP_SECRET!,
507
- webhookPath: '/lark/webhook',
508
-
509
- // 必须配置的安全选项
510
- encryptKey: process.env.LARK_ENCRYPT_KEY!,
511
- verificationToken: process.env.LARK_VERIFICATION_TOKEN!,
512
-
513
- isFeishu: true
514
- };
73
+ addCommand(
74
+ new MessageCommand('hello')
75
+ .desc('飞书问候')
76
+ .action((message) => {
77
+ return `你好,${message.$sender.name}!`
78
+ })
79
+ )
515
80
  ```
516
81
 
517
- ### 2. 错误处理
82
+ ### 消息处理
518
83
 
519
84
  ```typescript
520
- // 全局错误处理
521
- app.middleware(async (session, next) => {
522
- try {
523
- await next();
524
- } catch (error) {
525
- console.error('Command execution error:', error);
526
- await session.send('❌ 命令执行出错,请稍后重试');
527
- }
528
- });
85
+ import { usePlugin } from 'zhin.js'
529
86
 
530
- // API 调用错误处理
531
- app.command('safe-api').action(async (session) => {
532
- try {
533
- const userInfo = await bot.getUserInfo(session.$sender.id);
534
- // 处理成功逻辑
535
- } catch (error) {
536
- if (error.response?.status === 403) {
537
- await session.send('❌ 权限不足,请联系管理员');
538
- } else if (error.response?.status === 429) {
539
- await session.send('❌ 请求频率过高,请稍后重试');
540
- } else {
541
- await session.send('❌ 服务暂时不可用');
542
- }
543
- }
544
- });
545
- ```
546
-
547
- ### 3. 性能优化
87
+ const { addMiddleware } = usePlugin()
548
88
 
549
- ```typescript
550
- // 缓存 access_token(已在适配器内部实现)
551
- // 批量处理消息
552
- const messageQueue: string[] = [];
553
-
554
- app.middleware((session, next) => {
555
- // 简单的消息队列示例
556
- messageQueue.push(session.$content.map(s => s.data?.content || '').join(''));
557
-
558
- // 每10条消息批量处理一次
559
- if (messageQueue.length >= 10) {
560
- console.log('Processing batch messages:', messageQueue.length);
561
- messageQueue.length = 0; // 清空队列
89
+ addMiddleware(async (message, next) => {
90
+ if (message.$adapter === 'lark') {
91
+ console.log('收到飞书消息:', message.$content)
562
92
  }
563
-
564
- return next();
565
- });
93
+ await next()
94
+ })
566
95
  ```
567
96
 
568
- ## 故障排除
569
-
570
- ### 常见问题
571
-
572
- 1. **Webhook 验证失败**
573
- ```
574
- Invalid verification token in webhook
575
- ```
576
- - 检查 `verificationToken` 配置是否正确
577
- - 确认飞书后台的验证令牌设置
578
-
579
- 2. **签名验证失败**
580
- ```
581
- Invalid signature in webhook
582
- ```
583
- - 检查 `encryptKey` 配置是否正确
584
- - 确认飞书后台的加密设置
585
-
586
- 3. **Token 获取失败**
587
- ```
588
- Failed to get access token
589
- ```
590
- - 检查 `appId` 和 `appSecret` 是否正确
591
- - 确认应用是否已发布且状态正常
592
- - 检查网络连接和 API 地址
593
-
594
- 4. **消息发送失败**
595
- ```
596
- Failed to send message: no permission
597
- ```
598
- - 检查应用权限配置
599
- - 确认机器人是否在目标群组中
600
- - 验证用户是否允许机器人发消息
601
-
602
- 5. **文件上传失败**
603
- ```
604
- Upload failed: file too large
605
- ```
606
- - 检查文件大小是否超出限制
607
- - 确认文件格式是否支持
608
- - 检查应用是否有文件上传权限
609
-
610
- ### 调试技巧
97
+ ## AI 工具(Skill)
611
98
 
612
- 1. **启用详细日志**:
613
- ```typescript
614
- // 在配置中启用调试模式
615
- app.plugin(require('@zhin.js/logger'), {
616
- level: 'debug'
617
- });
618
- ```
99
+ 适配器内置群管理 Skill,自动注册以下工具供 AI 调用:
619
100
 
620
- 2. **查看原始事件数据**:
621
- ```typescript
622
- app.middleware((session, next) => {
623
- console.log('Raw event:', session.$raw);
624
- return next();
625
- });
626
- ```
101
+ - 群聊管理(成员管理、群信息修改等)
627
102
 
628
- 3. **测试 Webhook 连通性**:
629
- 使用工具如 ngrok 在本地测试 Webhook 接收
103
+ > 工具使用飞书的 `open_id` 和 `chat_id` 格式标识用户和群聊。
630
104
 
631
- ## 注意事项
105
+ ## 飞书开发者配置
632
106
 
633
- 1. **应用权限**: 确保在飞书开发者后台配置了正确的权限
634
- 2. **网络环境**: 飞书和 Lark 使用不同的 API 域名,确保网络能正常访问
635
- 3. **消息限制**: 注意飞书的消息发送频率限制
636
- 4. **文件大小**: 文件上传有大小限制,通常为 30MB
637
- 5. **安全配置**: 生产环境强烈建议启用签名验证和令牌验证
638
- 6. **应用审核**: 某些功能可能需要应用通过审核才能使用
107
+ 1. 登录 [飞书开放平台](https://open.feishu.cn/)
108
+ 2. 创建应用并获取 `App ID` `App Secret`
109
+ 3. 配置事件回调 URL:`http://your-server:8086/api/lark/webhook`
110
+ 4. 订阅 `im.message.receive_v1` 事件
111
+ 5. 发布应用
639
112
 
640
- ## 更新日志
113
+ ## 许可证
641
114
 
642
- ### v1.0.0
643
- - 🎉 初始版本发布
644
- - 🔐 完整的安全验证机制
645
- - 📱 支持飞书和 Lark 双平台
646
- - 💬 丰富的消息类型支持
647
- - 🎛️ 交互式卡片支持
648
- - 📁 文件上传下载功能
649
- - 👥 用户和群组管理
650
- - 🚀 使用 `useContext('router')` 集成 HTTP 服务
651
- - ⚡ 自动 token 管理和刷新
115
+ MIT License