befly 3.9.37 → 3.9.39

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 (155) hide show
  1. package/README.md +38 -39
  2. package/befly.config.ts +62 -40
  3. package/checks/checkApi.ts +16 -16
  4. package/checks/checkApp.ts +19 -25
  5. package/checks/checkTable.ts +42 -42
  6. package/docs/README.md +42 -35
  7. package/docs/{api.md → api/api.md} +225 -235
  8. package/docs/cipher.md +71 -69
  9. package/docs/database.md +155 -153
  10. package/docs/{examples.md → guide/examples.md} +181 -181
  11. package/docs/guide/quickstart.md +331 -0
  12. package/docs/hooks/auth.md +38 -0
  13. package/docs/hooks/cors.md +28 -0
  14. package/docs/{hook.md → hooks/hook.md} +140 -57
  15. package/docs/hooks/parser.md +19 -0
  16. package/docs/hooks/rateLimit.md +47 -0
  17. package/docs/{redis.md → infra/redis.md} +84 -93
  18. package/docs/plugins/cipher.md +61 -0
  19. package/docs/plugins/database.md +128 -0
  20. package/docs/{plugin.md → plugins/plugin.md} +83 -81
  21. package/docs/quickstart.md +26 -26
  22. package/docs/{addon.md → reference/addon.md} +46 -46
  23. package/docs/{config.md → reference/config.md} +32 -80
  24. package/docs/{logger.md → reference/logger.md} +52 -52
  25. package/docs/{sync.md → reference/sync.md} +32 -35
  26. package/docs/{table.md → reference/table.md} +7 -7
  27. package/docs/{validator.md → reference/validator.md} +57 -57
  28. package/hooks/auth.ts +8 -4
  29. package/hooks/cors.ts +13 -13
  30. package/hooks/parser.ts +37 -17
  31. package/hooks/permission.ts +26 -14
  32. package/hooks/rateLimit.ts +276 -0
  33. package/hooks/validator.ts +15 -7
  34. package/lib/asyncContext.ts +43 -0
  35. package/lib/cacheHelper.ts +212 -81
  36. package/lib/cacheKeys.ts +38 -0
  37. package/lib/cipher.ts +30 -30
  38. package/lib/connect.ts +28 -28
  39. package/lib/dbHelper.ts +211 -109
  40. package/lib/jwt.ts +16 -16
  41. package/lib/logger.ts +610 -19
  42. package/lib/redisHelper.ts +185 -44
  43. package/lib/sqlBuilder.ts +90 -91
  44. package/lib/validator.ts +59 -39
  45. package/loader/loadApis.ts +53 -47
  46. package/loader/loadHooks.ts +40 -14
  47. package/loader/loadPlugins.ts +16 -17
  48. package/main.ts +57 -47
  49. package/package.json +47 -45
  50. package/paths.ts +15 -14
  51. package/plugins/cache.ts +5 -4
  52. package/plugins/cipher.ts +3 -3
  53. package/plugins/config.ts +2 -2
  54. package/plugins/db.ts +9 -9
  55. package/plugins/jwt.ts +3 -3
  56. package/plugins/logger.ts +8 -12
  57. package/plugins/redis.ts +8 -8
  58. package/plugins/tool.ts +6 -6
  59. package/router/api.ts +85 -56
  60. package/router/static.ts +12 -12
  61. package/sync/syncAll.ts +12 -12
  62. package/sync/syncApi.ts +55 -54
  63. package/sync/syncDb/apply.ts +20 -19
  64. package/sync/syncDb/constants.ts +25 -23
  65. package/sync/syncDb/ddl.ts +35 -36
  66. package/sync/syncDb/helpers.ts +6 -9
  67. package/sync/syncDb/schema.ts +10 -9
  68. package/sync/syncDb/sqlite.ts +7 -8
  69. package/sync/syncDb/table.ts +37 -35
  70. package/sync/syncDb/tableCreate.ts +21 -20
  71. package/sync/syncDb/types.ts +23 -20
  72. package/sync/syncDb/version.ts +10 -10
  73. package/sync/syncDb.ts +43 -36
  74. package/sync/syncDev.ts +74 -66
  75. package/sync/syncMenu.ts +190 -57
  76. package/tests/api-integration-array-number.test.ts +282 -0
  77. package/tests/befly-config-env.test.ts +78 -0
  78. package/tests/cacheHelper.test.ts +135 -104
  79. package/tests/cacheKeys.test.ts +41 -0
  80. package/tests/cipher.test.ts +90 -89
  81. package/tests/dbHelper-advanced.test.ts +140 -134
  82. package/tests/dbHelper-all-array-types.test.ts +316 -0
  83. package/tests/dbHelper-array-serialization.test.ts +258 -0
  84. package/tests/dbHelper-columns.test.ts +56 -55
  85. package/tests/dbHelper-execute.test.ts +45 -44
  86. package/tests/dbHelper-joins.test.ts +124 -119
  87. package/tests/fields-redis-cache.test.ts +29 -27
  88. package/tests/fields-validate.test.ts +38 -38
  89. package/tests/getClientIp.test.ts +54 -0
  90. package/tests/integration.test.ts +69 -67
  91. package/tests/jwt.test.ts +27 -26
  92. package/tests/logger.test.ts +267 -34
  93. package/tests/rateLimit-hook.test.ts +477 -0
  94. package/tests/redisHelper.test.ts +187 -188
  95. package/tests/redisKeys.test.ts +6 -73
  96. package/tests/scanConfig.test.ts +144 -0
  97. package/tests/sqlBuilder-advanced.test.ts +217 -215
  98. package/tests/sqlBuilder.test.ts +92 -91
  99. package/tests/sync-connection.test.ts +29 -29
  100. package/tests/syncDb-apply.test.ts +97 -96
  101. package/tests/syncDb-array-number.test.ts +160 -0
  102. package/tests/syncDb-constants.test.ts +48 -47
  103. package/tests/syncDb-ddl.test.ts +99 -98
  104. package/tests/syncDb-helpers.test.ts +29 -28
  105. package/tests/syncDb-schema.test.ts +61 -60
  106. package/tests/syncDb-types.test.ts +60 -59
  107. package/tests/syncMenu-paths.test.ts +68 -0
  108. package/tests/util.test.ts +42 -41
  109. package/tests/validator-array-number.test.ts +310 -0
  110. package/tests/validator-default.test.ts +373 -0
  111. package/tests/validator.test.ts +271 -266
  112. package/tsconfig.json +4 -5
  113. package/types/api.d.ts +7 -12
  114. package/types/befly.d.ts +60 -13
  115. package/types/cache.d.ts +8 -4
  116. package/types/common.d.ts +17 -9
  117. package/types/context.d.ts +2 -2
  118. package/types/crypto.d.ts +23 -0
  119. package/types/database.d.ts +19 -19
  120. package/types/hook.d.ts +2 -2
  121. package/types/jwt.d.ts +118 -0
  122. package/types/logger.d.ts +30 -0
  123. package/types/plugin.d.ts +4 -4
  124. package/types/redis.d.ts +7 -3
  125. package/types/roleApisCache.ts +23 -0
  126. package/types/sync.d.ts +10 -10
  127. package/types/table.d.ts +50 -9
  128. package/types/validate.d.ts +69 -0
  129. package/utils/addonHelper.ts +90 -0
  130. package/utils/arrayKeysToCamel.ts +18 -0
  131. package/utils/calcPerfTime.ts +13 -0
  132. package/utils/configTypes.ts +3 -0
  133. package/utils/cors.ts +19 -0
  134. package/utils/fieldClear.ts +75 -0
  135. package/utils/genShortId.ts +12 -0
  136. package/utils/getClientIp.ts +45 -0
  137. package/utils/keysToCamel.ts +22 -0
  138. package/utils/keysToSnake.ts +22 -0
  139. package/utils/modules.ts +98 -0
  140. package/utils/pickFields.ts +19 -0
  141. package/utils/process.ts +56 -0
  142. package/utils/regex.ts +225 -0
  143. package/utils/response.ts +115 -0
  144. package/utils/route.ts +23 -0
  145. package/utils/scanConfig.ts +142 -0
  146. package/utils/scanFiles.ts +48 -0
  147. package/.prettierignore +0 -2
  148. package/.prettierrc +0 -12
  149. package/docs/1-/345/237/272/346/234/254/344/273/213/347/273/215.md +0 -35
  150. package/docs/2-/345/210/235/346/255/245/344/275/223/351/252/214.md +0 -64
  151. package/docs/3-/347/254/254/344/270/200/344/270/252/346/216/245/345/217/243.md +0 -46
  152. package/docs/4-/346/223/215/344/275/234/346/225/260/346/215/256/345/272/223.md +0 -172
  153. package/hooks/requestLogger.ts +0 -84
  154. package/types/index.ts +0 -24
  155. package/util.ts +0 -283
@@ -33,7 +33,7 @@ Validator 是 Befly 的参数验证系统,提供:
33
33
  ### 基本结构
34
34
 
35
35
  ```typescript
36
- import { Validator } from '../lib/validator.js';
36
+ import { Validator } from "../lib/validator.js";
37
37
 
38
38
  class Validator {
39
39
  // 验证数据对象
@@ -50,18 +50,18 @@ class Validator {
50
50
 
51
51
  ```typescript
52
52
  const data = {
53
- email: 'test@example.com',
53
+ email: "test@example.com",
54
54
  age: 25,
55
- name: 'John'
55
+ name: "John"
56
56
  };
57
57
 
58
58
  const rules = {
59
- email: { name: '邮箱', type: 'string', min: 5, max: 100, regexp: '@email' },
60
- age: { name: '年龄', type: 'number', min: 0, max: 150 },
61
- name: { name: '姓名', type: 'string', min: 2, max: 50 }
59
+ email: { name: "邮箱", type: "string", min: 5, max: 100, regexp: "@email" },
60
+ age: { name: "年龄", type: "number", min: 0, max: 150 },
61
+ name: { name: "姓名", type: "string", min: 2, max: 50 }
62
62
  };
63
63
 
64
- const result = Validator.validate(data, rules, ['email', 'name']);
64
+ const result = Validator.validate(data, rules, ["email", "name"]);
65
65
 
66
66
  if (result.failed) {
67
67
  console.log(result.firstError); // 第一条错误信息
@@ -77,14 +77,14 @@ if (result.failed) {
77
77
 
78
78
  ```typescript
79
79
  const fieldDef = {
80
- name: '年龄',
81
- type: 'number',
80
+ name: "年龄",
81
+ type: "number",
82
82
  min: 0,
83
83
  max: 150,
84
84
  default: 0
85
85
  };
86
86
 
87
- const result = Validator.single('25', fieldDef);
87
+ const result = Validator.single("25", fieldDef);
88
88
 
89
89
  if (!result.error) {
90
90
  console.log(result.value); // 25 (已转换为 number)
@@ -199,18 +199,18 @@ if (!result.error) {
199
199
  ```typescript
200
200
  // 使用别名
201
201
  {
202
- regexp: '@email';
202
+ regexp: "@email";
203
203
  } // 邮箱格式
204
204
  {
205
- regexp: '@phone';
205
+ regexp: "@phone";
206
206
  } // 手机号格式
207
207
  {
208
- regexp: '@url';
208
+ regexp: "@url";
209
209
  } // URL 格式
210
210
 
211
211
  // 直接使用正则
212
212
  {
213
- regexp: '^[a-z]+$';
213
+ regexp: "^[a-z]+$";
214
214
  } // 小写字母
215
215
  ```
216
216
 
@@ -319,7 +319,7 @@ const hook: Hook = {
319
319
  const result = Validator.validate(ctx.body, ctx.api.fields, ctx.api.required || []);
320
320
 
321
321
  if (result.code !== 0) {
322
- ctx.response = ErrorResponse(ctx, result.firstError || '参数验证失败', 1, null, result.fieldErrors);
322
+ ctx.response = ErrorResponse(ctx, result.firstError || "参数验证失败", 1, null, result.fieldErrors);
323
323
  }
324
324
  }
325
325
  };
@@ -342,17 +342,17 @@ const hook: Hook = {
342
342
  ```typescript
343
343
  // apis/user/login.ts
344
344
  export default {
345
- name: '用户登录',
346
- method: 'POST',
345
+ name: "用户登录",
346
+ method: "POST",
347
347
  auth: false,
348
348
  fields: {
349
- email: { name: '邮箱', type: 'string', min: 5, max: 100, regexp: '@email' },
350
- password: { name: '密码', type: 'string', min: 6, max: 100 }
349
+ email: { name: "邮箱", type: "string", min: 5, max: 100, regexp: "@email" },
350
+ password: { name: "密码", type: "string", min: 6, max: 100 }
351
351
  },
352
- required: ['email', 'password'],
352
+ required: ["email", "password"],
353
353
  handler: async (befly, ctx) => {
354
354
  // ctx.body.email 和 ctx.body.password 已验证
355
- return Yes('登录成功');
355
+ return Yes("登录成功");
356
356
  }
357
357
  } as ApiRoute;
358
358
  ```
@@ -362,16 +362,16 @@ export default {
362
362
  可以引用表定义中的字段:
363
363
 
364
364
  ```typescript
365
- import { adminTable } from '../../../tables/admin.js';
365
+ import { adminTable } from "../../../tables/admin.js";
366
366
 
367
367
  export default {
368
- name: '创建管理员',
368
+ name: "创建管理员",
369
369
  fields: {
370
370
  email: adminTable.email, // 引用表字段
371
371
  password: adminTable.password,
372
372
  nickname: adminTable.nickname
373
373
  },
374
- required: ['email', 'password'],
374
+ required: ["email", "password"],
375
375
  handler: async (befly, ctx) => {
376
376
  // ...
377
377
  }
@@ -381,14 +381,14 @@ export default {
381
381
  ### 使用公共字段
382
382
 
383
383
  ```typescript
384
- import { Fields } from '../../../config/fields.js';
384
+ import { Fields } from "../../../config/fields.js";
385
385
 
386
386
  export default {
387
- name: '获取列表',
387
+ name: "获取列表",
388
388
  fields: {
389
389
  ...Fields.page, // 分页字段
390
390
  ...Fields.limit,
391
- keyword: { name: '关键词', type: 'string', max: 50 }
391
+ keyword: { name: "关键词", type: "string", max: 50 }
392
392
  },
393
393
  handler: async (befly, ctx) => {
394
394
  // ...
@@ -461,18 +461,18 @@ interface SingleResult {
461
461
 
462
462
  ```typescript
463
463
  const data = {
464
- username: 'john',
464
+ username: "john",
465
465
  age: 25,
466
- email: 'john@example.com'
466
+ email: "john@example.com"
467
467
  };
468
468
 
469
469
  const rules = {
470
- username: { name: '用户名', type: 'string', min: 2, max: 20 },
471
- age: { name: '年龄', type: 'number', min: 0, max: 150 },
472
- email: { name: '邮箱', type: 'string', regexp: '@email' }
470
+ username: { name: "用户名", type: "string", min: 2, max: 20 },
471
+ age: { name: "年龄", type: "number", min: 0, max: 150 },
472
+ email: { name: "邮箱", type: "string", regexp: "@email" }
473
473
  };
474
474
 
475
- const result = Validator.validate(data, rules, ['username', 'email']);
475
+ const result = Validator.validate(data, rules, ["username", "email"]);
476
476
  // result.code === 0
477
477
  ```
478
478
 
@@ -480,13 +480,13 @@ const result = Validator.validate(data, rules, ['username', 'email']);
480
480
 
481
481
  ```typescript
482
482
  const data = {
483
- age: '25', // 字符串
484
- score: '98.5' // 字符串
483
+ age: "25", // 字符串
484
+ score: "98.5" // 字符串
485
485
  };
486
486
 
487
487
  const rules = {
488
- age: { name: '年龄', type: 'number', min: 0 },
489
- score: { name: '分数', type: 'number', min: 0, max: 100 }
488
+ age: { name: "年龄", type: "number", min: 0 },
489
+ score: { name: "分数", type: "number", min: 0, max: 100 }
490
490
  };
491
491
 
492
492
  // 验证通过,'25' 会被转换为 25
@@ -497,31 +497,31 @@ const result = Validator.validate(data, rules);
497
497
 
498
498
  ```typescript
499
499
  const data = {
500
- tags: ['vue', 'react', 'angular'],
500
+ tags: ["vue", "react", "angular"],
501
501
  ids: [1, 2, 3]
502
502
  };
503
503
 
504
504
  const rules = {
505
- tags: { name: '标签', type: 'array_string', min: 1, max: 10 },
506
- ids: { name: 'ID列表', type: 'array_string', min: 1 }
505
+ tags: { name: "标签", type: "array_string", min: 1, max: 10 },
506
+ ids: { name: "ID列表", type: "array_string", min: 1 }
507
507
  };
508
508
 
509
- const result = Validator.validate(data, rules, ['tags']);
509
+ const result = Validator.validate(data, rules, ["tags"]);
510
510
  ```
511
511
 
512
512
  ### 示例 4:正则验证
513
513
 
514
514
  ```typescript
515
515
  const data = {
516
- phone: '13812345678',
517
- email: 'test@example.com',
518
- code: 'ABC123'
516
+ phone: "13812345678",
517
+ email: "test@example.com",
518
+ code: "ABC123"
519
519
  };
520
520
 
521
521
  const rules = {
522
- phone: { name: '手机号', type: 'string', regexp: '@phone' },
523
- email: { name: '邮箱', type: 'string', regexp: '@email' },
524
- code: { name: '验证码', type: 'string', regexp: '^[A-Z0-9]{6}$' }
522
+ phone: { name: "手机号", type: "string", regexp: "@phone" },
523
+ email: { name: "邮箱", type: "string", regexp: "@email" },
524
+ code: { name: "验证码", type: "string", regexp: "^[A-Z0-9]{6}$" }
525
525
  };
526
526
 
527
527
  const result = Validator.validate(data, rules);
@@ -531,9 +531,9 @@ const result = Validator.validate(data, rules);
531
531
 
532
532
  ```typescript
533
533
  // 验证并转换单个值
534
- const ageResult = Validator.single('25', {
535
- name: '年龄',
536
- type: 'number',
534
+ const ageResult = Validator.single("25", {
535
+ name: "年龄",
536
+ type: "number",
537
537
  min: 0,
538
538
  max: 150
539
539
  });
@@ -543,9 +543,9 @@ if (!ageResult.error) {
543
543
  }
544
544
 
545
545
  // 空值使用默认值
546
- const emptyResult = Validator.single('', {
547
- name: '数量',
548
- type: 'number',
546
+ const emptyResult = Validator.single("", {
547
+ name: "数量",
548
+ type: "number",
549
549
  default: 10
550
550
  });
551
551
  console.log(emptyResult.value); // 10
@@ -573,7 +573,7 @@ A: 目前只支持扁平对象验证。嵌套对象需要在 handler 中手动
573
573
 
574
574
  ### Q: 正则别名可以扩展吗?
575
575
 
576
- A: 正则别名定义在 `befly-shared/regex` 中,可以直接使用自定义正则字符串,不需要扩展别名。
576
+ A: 正则别名定义在 `befly/lib/regex` 中,可以直接使用自定义正则字符串,不需要扩展别名。
577
577
 
578
578
  ### Q: 类型转换失败会怎样?
579
579
 
@@ -605,14 +605,14 @@ handler: async (befly, ctx) => {
605
605
 
606
606
  // cleanData 只包含有效值
607
607
  await befly.db.updData({
608
- table: 'user',
608
+ table: "user",
609
609
  data: cleanData,
610
610
  where: { id: ctx.user.userId }
611
611
  });
612
612
 
613
- return Yes('更新成功');
613
+ return Yes("更新成功");
614
614
  };
615
615
  ```
616
616
 
617
617
  > **注意**:数据库操作(insData、updData 等)会自动过滤 null/undefined 值,通常不需要手动调用 cleanFields。
618
- > 详见 [database.md](./database.md#nullundefined-值自动过滤)。
618
+ > 详见 [database.md](../plugins/database.md#nullundefined-值自动过滤)。
package/hooks/auth.ts CHANGED
@@ -1,17 +1,21 @@
1
- import type { Hook } from '../types/hook.js';
1
+ import type { Hook } from "../types/hook.js";
2
+
3
+ import { setCtxUser } from "../lib/asyncContext.js";
2
4
 
3
5
  const hook: Hook = {
4
6
  order: 3,
5
7
  handler: async (befly, ctx) => {
6
- const authHeader = ctx.req.headers.get('authorization');
8
+ const authHeader = ctx.req.headers.get("authorization");
7
9
 
8
- if (authHeader && authHeader.startsWith('Bearer ')) {
10
+ if (authHeader && authHeader.startsWith("Bearer ")) {
9
11
  const token = authHeader.substring(7);
10
12
 
11
13
  try {
12
14
  const payload = await befly.jwt.verify(token);
13
15
  ctx.user = payload;
14
- } catch (error: any) {
16
+
17
+ setCtxUser(payload.id, payload.roleCode, payload.nickname, payload.roleType);
18
+ } catch {
15
19
  ctx.user = {};
16
20
  }
17
21
  } else {
package/hooks/cors.ts CHANGED
@@ -1,10 +1,10 @@
1
- // 相对导入
2
- import { setCorsOptions } from '../util.js';
3
- import { beflyConfig } from '../befly.config.js';
4
-
1
+ import type { CorsConfig } from "../types/befly.js";
5
2
  // 类型导入
6
- import type { Hook } from '../types/hook.js';
7
- import type { CorsConfig } from '../types/befly.js';
3
+ import type { Hook } from "../types/hook.js";
4
+
5
+ import { beflyConfig } from "../befly.config.js";
6
+ // 相对导入
7
+ import { setCorsOptions } from "../utils/cors.js";
8
8
 
9
9
  /**
10
10
  * CORS 跨域处理钩子
@@ -17,15 +17,15 @@ const hook: Hook = {
17
17
 
18
18
  // 合并默认配置和用户配置
19
19
  const defaultConfig: CorsConfig = {
20
- origin: '*',
21
- methods: 'GET, POST, PUT, DELETE, OPTIONS',
22
- allowedHeaders: 'Content-Type, Authorization, authorization, token',
23
- exposedHeaders: 'Content-Range, X-Content-Range, Authorization, authorization, token',
20
+ origin: "*",
21
+ methods: "GET, POST, PUT, DELETE, OPTIONS",
22
+ allowedHeaders: "Content-Type, Authorization, authorization, token",
23
+ exposedHeaders: "Content-Range, X-Content-Range, Authorization, authorization, token",
24
24
  maxAge: 86400,
25
- credentials: 'true'
25
+ credentials: "true"
26
26
  };
27
27
 
28
- const corsConfig = { ...defaultConfig, ...(beflyConfig.cors || {}) };
28
+ const corsConfig = { ...defaultConfig, ...beflyConfig.cors };
29
29
 
30
30
  // 设置 CORS 响应头
31
31
  const headers = setCorsOptions(req, corsConfig);
@@ -33,7 +33,7 @@ const hook: Hook = {
33
33
  ctx.corsHeaders = headers;
34
34
 
35
35
  // 处理 OPTIONS 预检请求
36
- if (req.method === 'OPTIONS') {
36
+ if (req.method === "OPTIONS") {
37
37
  ctx.response = new Response(null, {
38
38
  status: 204,
39
39
  headers: headers
package/hooks/parser.ts CHANGED
@@ -1,13 +1,13 @@
1
+ // 类型导入
2
+ import type { Hook } from "../types/hook.js";
3
+
1
4
  // 外部依赖
2
- import { isPlainObject, isEmpty } from 'es-toolkit/compat';
3
- import { pickFields } from 'befly-shared/pickFields';
4
- import { XMLParser } from 'fast-xml-parser';
5
+ import { isPlainObject, isEmpty } from "es-toolkit/compat";
6
+ import { XMLParser } from "fast-xml-parser";
5
7
 
6
8
  // 相对导入
7
- import { ErrorResponse } from '../util.js';
8
-
9
- // 类型导入
10
- import type { Hook } from '../types/hook.js';
9
+ import { pickFields } from "../utils/pickFields.js";
10
+ import { ErrorResponse } from "../utils/response.js";
11
11
 
12
12
  const xmlParser = new XMLParser();
13
13
 
@@ -31,7 +31,7 @@ const hook: Hook = {
31
31
  }
32
32
 
33
33
  // GET 请求:解析查询参数
34
- if (ctx.req.method === 'GET') {
34
+ if (ctx.req.method === "GET") {
35
35
  const url = new URL(ctx.req.url);
36
36
  const params = Object.fromEntries(url.searchParams);
37
37
  if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
@@ -39,16 +39,16 @@ const hook: Hook = {
39
39
  } else {
40
40
  ctx.body = params;
41
41
  }
42
- } else if (ctx.req.method === 'POST') {
42
+ } else if (ctx.req.method === "POST") {
43
43
  // POST 请求:解析请求体
44
- const contentType = ctx.req.headers.get('content-type') || '';
44
+ const contentType = ctx.req.headers.get("content-type") || "";
45
45
  // 获取 URL 查询参数(POST 请求也可能带参数)
46
46
  const url = new URL(ctx.req.url);
47
47
  const queryParams = Object.fromEntries(url.searchParams);
48
48
 
49
49
  try {
50
50
  // JSON 格式
51
- if (contentType.includes('application/json')) {
51
+ if (contentType.includes("application/json")) {
52
52
  const body = (await ctx.req.json()) as Record<string, any>;
53
53
  // 合并 URL 参数和请求体(请求体优先)
54
54
  const merged = { ...queryParams, ...body };
@@ -57,13 +57,13 @@ const hook: Hook = {
57
57
  } else {
58
58
  ctx.body = merged;
59
59
  }
60
- } else if (contentType.includes('application/xml') || contentType.includes('text/xml')) {
60
+ } else if (contentType.includes("application/xml") || contentType.includes("text/xml")) {
61
61
  // XML 格式
62
62
  const text = await ctx.req.text();
63
63
  const parsed = xmlParser.parse(text) as Record<string, any>;
64
64
  // 提取根节点内容(如 xml),使 body 扁平化
65
65
  const rootKey = Object.keys(parsed)[0];
66
- const body = rootKey && typeof parsed[rootKey] === 'object' ? parsed[rootKey] : parsed;
66
+ const body = rootKey && typeof parsed[rootKey] === "object" ? parsed[rootKey] : parsed;
67
67
  // 合并 URL 参数和请求体(请求体优先)
68
68
  const merged = { ...queryParams, ...body };
69
69
  if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
@@ -73,12 +73,32 @@ const hook: Hook = {
73
73
  }
74
74
  } else {
75
75
  // 不支持的 Content-Type
76
- ctx.response = ErrorResponse(ctx, '无效的请求参数格式', 1, null, { location: 'content-type', value: contentType });
76
+ ctx.response = ErrorResponse(
77
+ ctx,
78
+ "无效的请求参数格式",
79
+ 1,
80
+ null,
81
+ {
82
+ location: "content-type",
83
+ value: contentType
84
+ },
85
+ "parser"
86
+ );
77
87
  return;
78
88
  }
79
- } catch (e: any) {
80
- // 解析失败
81
- ctx.response = ErrorResponse(ctx, '无效的请求参数格式', 1, null, { location: 'body', error: e.message });
89
+ } catch {
90
+ // 解析失败:属于客户端输入错误,返回安全 detail(不回传异常栈/原始 body)
91
+ ctx.response = ErrorResponse(
92
+ ctx,
93
+ "无效的请求参数格式",
94
+ 1,
95
+ null,
96
+ {
97
+ location: "body",
98
+ reason: contentType.includes("application/json") ? "invalid_json" : contentType.includes("xml") ? "invalid_xml" : "invalid_body"
99
+ },
100
+ "parser"
101
+ );
82
102
  return;
83
103
  }
84
104
  }
@@ -1,9 +1,10 @@
1
- // 相对导入
2
- import { ErrorResponse } from '../util.js';
3
- import { RedisKeys } from 'befly-shared/redisKeys';
4
-
5
1
  // 类型导入
6
- import type { Hook } from '../types/hook.js';
2
+ import type { Hook } from "../types/hook.js";
3
+
4
+ import { CacheKeys } from "../lib/cacheKeys.js";
5
+ import { Logger } from "../lib/logger.js";
6
+ // 相对导入
7
+ import { ErrorResponse } from "../utils/response.js";
7
8
 
8
9
  /**
9
10
  * 权限检查钩子
@@ -24,32 +25,43 @@ const hook: Hook = {
24
25
 
25
26
  // 2. 用户未登录
26
27
  if (!ctx.user || !ctx.user.id) {
27
- ctx.response = ErrorResponse(ctx, '未登录');
28
+ ctx.response = ErrorResponse(ctx, "未登录", 1, null, null, "auth");
28
29
  return;
29
30
  }
30
31
 
31
32
  // 3. 开发者权限(最高权限)
32
- if (ctx.user.roleCode === 'dev') {
33
+ if (ctx.user.roleCode === "dev") {
33
34
  return;
34
35
  }
35
36
 
36
37
  // 4. 角色权限检查
37
38
  let hasPermission = false;
38
39
  if (ctx.user.roleCode && befly.redis) {
40
+ // apiPath 在 apiHandler 中已统一生成并写入 ctx.route
41
+ const apiPath = ctx.route;
42
+ const roleCode = ctx.user.roleCode;
43
+
39
44
  try {
40
- // 验证角色权限
41
- const apiPath = `${ctx.req.method}${new URL(ctx.req.url).pathname}`;
42
- const roleApisKey = RedisKeys.roleApis(ctx.user.roleCode);
45
+ // 极简方案:每个角色一个 Set,直接判断成员是否存在
46
+ const roleApisKey = CacheKeys.roleApis(roleCode);
43
47
  hasPermission = await befly.redis.sismember(roleApisKey, apiPath);
44
- } catch (error) {
45
- // Redis 异常时降级为拒绝访问
46
- befly.logger.warn({ err: error, route: ctx.route }, 'Redis 权限检查失败');
48
+ } catch (err: any) {
49
+ // Redis 异常:记录到 error 日志文件(不回传给客户端),并降级为拒绝访问
50
+ Logger.error(
51
+ {
52
+ event: "hook_permission_redis_error",
53
+ apiPath: apiPath,
54
+ roleCode: roleCode,
55
+ err: err
56
+ },
57
+ "hook permission redis error"
58
+ );
47
59
  hasPermission = false;
48
60
  }
49
61
  }
50
62
 
51
63
  if (!hasPermission) {
52
- ctx.response = ErrorResponse(ctx, '无权访问');
64
+ ctx.response = ErrorResponse(ctx, "无权访问", 1, null, null, "permission");
53
65
  return;
54
66
  }
55
67
  }