befly 3.9.38 → 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 +37 -38
  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} +223 -231
  8. package/docs/cipher.md +71 -69
  9. package/docs/database.md +143 -141
  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} +1 -1
  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 +7 -7
  34. package/lib/asyncContext.ts +43 -0
  35. package/lib/cacheHelper.ts +212 -77
  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 +183 -102
  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 +48 -44
  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 -52
  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 -65
  75. package/sync/syncMenu.ts +190 -55
  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
package/lib/validator.ts CHANGED
@@ -1,10 +1,11 @@
1
- /**
1
+ /**
2
2
  * 数据验证器 - Befly 项目专用
3
3
  * 纯静态类设计,简洁易用
4
4
  */
5
5
 
6
- import { RegexAliases, getCompiledRegex } from 'befly-shared/regex';
7
- import type { TableDefinition, FieldDefinition, ValidateResult, SingleResult } from 'befly-shared/types';
6
+ import type { TableDefinition, FieldDefinition, ValidateResult, SingleResult } from "../types/validate.js";
7
+
8
+ import { RegexAliases, getCompiledRegex } from "../utils/regex.js";
8
9
 
9
10
  /**
10
11
  * 验证器类
@@ -30,17 +31,17 @@ export class Validator {
30
31
  const fieldErrors: Record<string, string> = {};
31
32
 
32
33
  // 参数检查
33
- if (!data || typeof data !== 'object' || Array.isArray(data)) {
34
- return this.buildResult({ _error: '数据必须是对象格式' });
34
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
35
+ return this.buildResult({ _error: "数据必须是对象格式" });
35
36
  }
36
- if (!rules || typeof rules !== 'object') {
37
- return this.buildResult({ _error: '验证规则必须是对象格式' });
37
+ if (!rules || typeof rules !== "object") {
38
+ return this.buildResult({ _error: "验证规则必须是对象格式" });
38
39
  }
39
40
 
40
41
  // 检查必填字段
41
42
  for (const field of required) {
42
43
  const value = data[field];
43
- if (value === undefined || value === null || value === '') {
44
+ if (value === undefined || value === null || value === "") {
44
45
  const label = rules[field]?.name || field;
45
46
  fieldErrors[field] = `${label}为必填项`;
46
47
  }
@@ -49,7 +50,8 @@ export class Validator {
49
50
  // 验证有值的字段
50
51
  for (const [field, rule] of Object.entries(rules)) {
51
52
  if (fieldErrors[field]) continue;
52
- if (!(field in data) && !required.includes(field)) continue;
53
+ // 字段值为 undefined 时跳过验证(除非是必填字段,但必填字段已在上面检查过)
54
+ if (data[field] === undefined && !required.includes(field)) continue;
53
55
 
54
56
  const error = this.checkField(data[field], rule, field);
55
57
  if (error) fieldErrors[field] = error;
@@ -65,7 +67,7 @@ export class Validator {
65
67
  const { type, default: defaultValue } = fieldDef;
66
68
 
67
69
  // 处理空值
68
- if (value === undefined || value === null || value === '') {
70
+ if (value === undefined || value === null || value === "") {
69
71
  return { value: this.defaultFor(type, defaultValue), error: null };
70
72
  }
71
73
 
@@ -118,23 +120,36 @@ export class Validator {
118
120
  /** 类型转换 */
119
121
  private static convert(value: any, type: string): { value: any; error: string | null } {
120
122
  switch (type.toLowerCase()) {
121
- case 'number':
122
- if (typeof value === 'number') {
123
- return Number.isNaN(value) || !isFinite(value) ? { value: null, error: '必须是有效数字' } : { value: value, error: null };
123
+ case "number":
124
+ if (typeof value === "number") {
125
+ return Number.isNaN(value) || !isFinite(value) ? { value: null, error: "必须是有效数字" } : { value: value, error: null };
124
126
  }
125
- if (typeof value === 'string') {
127
+ if (typeof value === "string") {
126
128
  const num = Number(value);
127
- return Number.isNaN(num) || !isFinite(num) ? { value: null, error: '必须是数字' } : { value: num, error: null };
129
+ return Number.isNaN(num) || !isFinite(num) ? { value: null, error: "必须是数字" } : { value: num, error: null };
128
130
  }
129
- return { value: null, error: '必须是数字' };
131
+ return { value: null, error: "必须是数字" };
130
132
 
131
- case 'string':
132
- case 'text':
133
- return typeof value === 'string' ? { value: value, error: null } : { value: null, error: '必须是字符串' };
133
+ case "string":
134
+ case "text":
135
+ return typeof value === "string" ? { value: value, error: null } : { value: null, error: "必须是字符串" };
134
136
 
135
- case 'array_string':
136
- case 'array_text':
137
- return Array.isArray(value) ? { value: value, error: null } : { value: null, error: '必须是数组' };
137
+ case "array_string":
138
+ case "array_text":
139
+ return Array.isArray(value) ? { value: value, error: null } : { value: null, error: "必须是数组" };
140
+
141
+ case "array_number_string":
142
+ case "array_number_text":
143
+ if (!Array.isArray(value)) {
144
+ return { value: null, error: "必须是数组" };
145
+ }
146
+ // 验证数组元素必须是数字
147
+ for (const item of value) {
148
+ if (typeof item !== "number" || !isFinite(item)) {
149
+ return { value: null, error: "数组元素必须是数字" };
150
+ }
151
+ }
152
+ return { value: value, error: null };
138
153
 
139
154
  default:
140
155
  return { value: value, error: null };
@@ -147,26 +162,28 @@ export class Validator {
147
162
  const regex = this.resolveRegex(regexp);
148
163
 
149
164
  switch (type.toLowerCase()) {
150
- case 'number':
165
+ case "number":
151
166
  if (min !== null && value < min) return `不能小于${min}`;
152
167
  if (max !== null && max > 0 && value > max) return `不能大于${max}`;
153
- if (regex && !this.testRegex(regex, String(value))) return '格式不正确';
168
+ if (regex && !this.testRegex(regex, String(value))) return "格式不正确";
154
169
  break;
155
170
 
156
- case 'string':
157
- case 'text':
171
+ case "string":
172
+ case "text":
158
173
  if (min !== null && value.length < min) return `长度不能少于${min}个字符`;
159
174
  if (max !== null && max > 0 && value.length > max) return `长度不能超过${max}个字符`;
160
- if (regex && !this.testRegex(regex, value)) return '格式不正确';
175
+ if (regex && !this.testRegex(regex, value)) return "格式不正确";
161
176
  break;
162
177
 
163
- case 'array_string':
164
- case 'array_text':
178
+ case "array_string":
179
+ case "array_text":
180
+ case "array_number_string":
181
+ case "array_number_text":
165
182
  if (min !== null && value.length < min) return `至少需要${min}个元素`;
166
183
  if (max !== null && max > 0 && value.length > max) return `最多只能有${max}个元素`;
167
184
  if (regex) {
168
185
  for (const item of value) {
169
- if (!this.testRegex(regex, String(item))) return '元素格式不正确';
186
+ if (!this.testRegex(regex, String(item))) return "元素格式不正确";
170
187
  }
171
188
  }
172
189
  break;
@@ -177,7 +194,7 @@ export class Validator {
177
194
  /** 解析正则别名 */
178
195
  private static resolveRegex(regexp: string | null): string | null {
179
196
  if (!regexp) return null;
180
- if (regexp.startsWith('@')) {
197
+ if (regexp.startsWith("@")) {
181
198
  const key = regexp.substring(1) as keyof typeof RegexAliases;
182
199
  return RegexAliases[key] || regexp;
183
200
  }
@@ -195,10 +212,11 @@ export class Validator {
195
212
 
196
213
  /** 获取默认值 */
197
214
  private static defaultFor(type: string, defaultValue: any): any {
215
+ // 如果字段定义了默认值,则使用字段默认值(优先级最高)
198
216
  if (defaultValue !== null && defaultValue !== undefined) {
199
217
  // 数组默认值
200
- if ((type === 'array_string' || type === 'array_text') && typeof defaultValue === 'string') {
201
- if (defaultValue === '[]') return [];
218
+ if ((type === "array_string" || type === "array_text" || type === "array_number_string" || type === "array_number_text") && typeof defaultValue === "string") {
219
+ if (defaultValue === "[]") return [];
202
220
  try {
203
221
  const parsed = JSON.parse(defaultValue);
204
222
  return Array.isArray(parsed) ? parsed : [];
@@ -207,22 +225,24 @@ export class Validator {
207
225
  }
208
226
  }
209
227
  // 数字默认值
210
- if (type === 'number' && typeof defaultValue === 'string') {
228
+ if (type === "number" && typeof defaultValue === "string") {
211
229
  const num = Number(defaultValue);
212
230
  return isNaN(num) ? 0 : num;
213
231
  }
214
232
  return defaultValue;
215
233
  }
216
234
 
217
- // 类型默认值
235
+ // 类型默认值(字段未定义 default 时使用)
218
236
  switch (type.toLowerCase()) {
219
- case 'number':
237
+ case "number":
220
238
  return 0;
221
- case 'array_string':
222
- case 'array_text':
239
+ case "array_string":
240
+ case "array_text":
241
+ case "array_number_string":
242
+ case "array_number_text":
223
243
  return [];
224
244
  default:
225
- return '';
245
+ return "";
226
246
  }
227
247
  }
228
248
  }
@@ -3,55 +3,48 @@
3
3
  * 负责扫描和加载所有 API 路由(组件、项目)
4
4
  */
5
5
 
6
- // 内部依赖
7
- import { existsSync } from 'node:fs';
8
-
9
- // 外部依赖
10
- import { relative, basename, join } from 'pathe';
11
- import { isPlainObject } from 'es-toolkit/compat';
12
- import { scanFiles } from 'befly-shared/scanFiles';
13
- import { scanAddons, getAddonDir, addonDirExists } from 'befly-shared/addonHelper';
6
+ // 类型导入
7
+ import type { ApiRoute } from "../types/api.js";
14
8
 
9
+ import { Logger } from "../lib/logger.js";
10
+ import { projectApiDir } from "../paths.js";
11
+ import { scanAddons, getAddonDir, addonDirExists } from "../utils/addonHelper.js";
12
+ import { makeRouteKey } from "../utils/route.js";
15
13
  // 相对导入
16
- import { Logger } from '../lib/logger.js';
17
- import { projectApiDir } from '../paths.js';
18
-
19
- // 类型导入
20
- import type { ApiRoute } from '../types/api.js';
14
+ import { scanFiles } from "../utils/scanFiles.js";
21
15
 
22
16
  /**
23
17
  * 预定义的默认字段
24
18
  */
25
19
  const PRESET_FIELDS: Record<string, any> = {
26
- '@id': { name: 'ID', type: 'number', min: 1, max: null },
27
- '@page': { name: '页码', type: 'number', min: 1, max: 9999, default: 1 },
28
- '@limit': { name: '每页数量', type: 'number', min: 1, max: 100, default: 30 },
29
- '@keyword': { name: '关键词', type: 'string', min: 0, max: 50 },
30
- '@state': { name: '状态', type: 'number', regex: '^[0-2]$' }
20
+ "@id": { name: "ID", type: "number", min: 1, max: null },
21
+ "@page": { name: "页码", type: "number", min: 1, max: 9999, default: 1 },
22
+ "@limit": { name: "每页数量", type: "number", min: 1, max: 100, default: 30 },
23
+ "@keyword": { name: "关键词", type: "string", min: 0, max: 50 },
24
+ "@state": { name: "状态", type: "number", regex: "^[0-2]$" }
31
25
  };
32
26
 
33
27
  /**
34
- * 处理字段定义,将 @ 符号引用替换为实际字段定义
28
+ * 处理字段定义:将 @ 符号引用替换为实际字段定义
35
29
  */
36
30
  function processFields(fields: Record<string, any>, apiName: string, routePath: string): Record<string, any> {
37
- if (!fields || typeof fields !== 'object') return fields;
31
+ if (!fields || typeof fields !== "object") return fields;
38
32
 
39
33
  const processed: Record<string, any> = {};
40
34
  for (const [key, value] of Object.entries(fields)) {
41
- // 如果值是字符串且以 @ 开头,则查找预定义字段
42
- if (typeof value === 'string' && value.startsWith('@')) {
35
+ if (typeof value === "string" && value.startsWith("@")) {
43
36
  if (PRESET_FIELDS[value]) {
44
37
  processed[key] = PRESET_FIELDS[value];
45
- } else {
46
- // 未找到预定义字段,抛出错误
47
- const validKeys = Object.keys(PRESET_FIELDS).join(', ');
48
- throw new Error(`API [${apiName}] (${routePath}) 字段 [${key}] 引用了未定义的预设字段 "${value}"。可用的预设字段有: ${validKeys}`);
38
+ continue;
49
39
  }
50
- } else {
51
- // 普通字段定义,保持原样
52
- processed[key] = value;
40
+
41
+ const validKeys = Object.keys(PRESET_FIELDS).join(", ");
42
+ throw new Error(`API [${apiName}] (${routePath}) 字段 [${key}] 引用了未定义的预设字段 "${value}"。可用的预设字段有: ${validKeys}`);
53
43
  }
44
+
45
+ processed[key] = value;
54
46
  }
47
+
55
48
  return processed;
56
49
  }
57
50
 
@@ -66,31 +59,31 @@ export async function loadApis(apis: Map<string, ApiRoute>): Promise<void> {
66
59
  const projectApiList = projectApiFiles.map((file) => ({
67
60
  filePath: file.filePath,
68
61
  relativePath: file.relativePath,
69
- type: 'project' as const,
70
- routePrefix: '/',
71
- typeName: '项目'
62
+ type: "project" as const,
63
+ routePrefix: "/",
64
+ typeName: "项目"
72
65
  }));
73
66
 
74
67
  // 2. 扫描组件 API
75
68
  const addonApiList: Array<{
76
69
  filePath: string;
77
70
  relativePath: string;
78
- type: 'addon';
71
+ type: "addon";
79
72
  routePrefix: string;
80
73
  typeName: string;
81
74
  }> = [];
82
75
  const addons = scanAddons();
83
76
  for (const addon of addons) {
84
- if (!addonDirExists(addon, 'apis')) continue;
77
+ if (!addonDirExists(addon, "apis")) continue;
85
78
 
86
- const addonApiDir = getAddonDir(addon, 'apis');
79
+ const addonApiDir = getAddonDir(addon, "apis");
87
80
  const addonApiFiles = await scanFiles(addonApiDir);
88
81
 
89
82
  for (const file of addonApiFiles) {
90
83
  addonApiList.push({
91
84
  filePath: file.filePath,
92
85
  relativePath: file.relativePath,
93
- type: 'addon' as const,
86
+ type: "addon" as const,
94
87
  routePrefix: `/addon/${addon}/`, // 组件 API 默认带斜杠
95
88
  typeName: `组件${addon}`
96
89
  });
@@ -104,39 +97,50 @@ export async function loadApis(apis: Map<string, ApiRoute>): Promise<void> {
104
97
  for (const apiFile of allApiFiles) {
105
98
  try {
106
99
  // Windows 下路径需要转换为正斜杠格式
107
- const normalizedFilePath = apiFile.filePath.replace(/\\/g, '/');
100
+ const normalizedFilePath = apiFile.filePath.replace(/\\/g, "/");
108
101
  const apiImport = await import(normalizedFilePath);
109
102
  const api = apiImport.default;
110
103
 
104
+ api.name = api.name || apiFile.relativePath;
105
+
111
106
  // 设置默认值
112
- const methodStr = (api.method || 'POST').toUpperCase();
107
+ const methodStr = (api.method || "POST").toUpperCase();
113
108
  api.auth = api.auth !== undefined ? api.auth : true;
114
109
 
115
110
  // 构建路由路径(用于错误提示)
116
111
  const routePath = `/api${apiFile.routePrefix}${apiFile.relativePath}`;
117
112
 
118
113
  // 处理字段定义,将 @ 引用替换为实际字段定义
119
- api.fields = processFields(api.fields || {}, api.name || apiFile.relativePath, routePath);
114
+ api.fields = processFields(api.fields || {}, api.name, routePath);
120
115
  api.required = api.required || [];
121
116
 
122
117
  // 支持逗号分隔的多方法,拆分后分别注册
123
118
  const methods = methodStr
124
- .split(',')
119
+ .split(",")
125
120
  .map((m: string) => m.trim())
126
121
  .filter((m: string) => m);
127
122
  for (const method of methods) {
128
- const route = `${method}${routePath}`;
123
+ const route = makeRouteKey(method, routePath);
129
124
  // 为每个方法创建独立的路由对象
130
- const routeApi = { ...api, method: method, route: route };
125
+ const routeApi: ApiRoute = {
126
+ name: api.name,
127
+ handler: api.handler,
128
+ method: method,
129
+ auth: api.auth,
130
+ fields: api.fields,
131
+ required: api.required,
132
+ rawBody: api.rawBody,
133
+ route: route
134
+ };
131
135
  apis.set(route, routeApi);
132
136
  }
133
137
  } catch (error: any) {
134
- Logger.error({ err: error, api: apiFile.relativePath, type: apiFile.typeName }, '接口加载失败');
138
+ Logger.error({ err: error, api: apiFile.relativePath, type: apiFile.typeName }, "接口加载失败");
135
139
  process.exit(1);
136
140
  }
137
141
  }
138
142
  } catch (error: any) {
139
- Logger.error({ err: error }, '加载 API 时发生错误');
143
+ Logger.error({ err: error }, "加载 API 时发生错误");
140
144
  process.exit(1);
141
145
  }
142
146
  }
@@ -1,31 +1,57 @@
1
1
  /**
2
2
  * 钩子加载器
3
- * 只加载核心钩子
3
+ * 默认只加载核心钩子
4
+ * 可选:通过配置开启组件/项目钩子(默认关闭以保持稳定性与可预期性)
4
5
  */
5
6
 
6
- // 相对导入
7
- import { Logger } from '../lib/logger.js';
8
- import { coreHookDir } from '../paths.js';
9
- import { scanModules } from '../util.js';
10
- import { beflyConfig } from '../befly.config.js';
11
-
12
7
  // 类型导入
13
- import type { Hook } from '../types/hook.js';
8
+ import type { Hook } from "../types/hook.js";
9
+
10
+ import { beflyConfig } from "../befly.config.js";
11
+ import { Logger } from "../lib/logger.js";
12
+ import { coreHookDir, projectHookDir } from "../paths.js";
13
+ // 相对导入
14
+ import { scanAddons, getAddonDir, addonDirExists } from "../utils/addonHelper.js";
15
+ import { scanModules } from "../utils/modules.js";
14
16
 
15
17
  export async function loadHooks(hooks: Hook[]): Promise<void> {
16
18
  try {
19
+ const allHooks: Hook[] = [];
20
+
17
21
  // 1. 扫描核心钩子
18
- const coreHooks = await scanModules<Hook>(coreHookDir, 'core', '钩子');
22
+ const coreHooks = await scanModules<Hook>(coreHookDir, "core", "钩子");
23
+ allHooks.push(...coreHooks);
24
+
25
+ // 2. 可选:扫描组件钩子(默认关闭)
26
+ const enableAddonHooks = Boolean((beflyConfig as any).enableAddonHooks);
27
+ if (enableAddonHooks) {
28
+ const addonHooks: Hook[] = [];
29
+ const addons = scanAddons();
30
+ for (const addon of addons) {
31
+ if (!addonDirExists(addon, "hooks")) continue;
32
+ const dir = getAddonDir(addon, "hooks");
33
+ const items = await scanModules<Hook>(dir, "addon", "钩子", addon);
34
+ addonHooks.push(...items);
35
+ }
36
+ allHooks.push(...addonHooks);
37
+ }
38
+
39
+ // 3. 可选:扫描项目钩子(默认关闭)
40
+ const enableAppHooks = Boolean((beflyConfig as any).enableAppHooks);
41
+ if (enableAppHooks) {
42
+ const appHooks = await scanModules<Hook>(projectHookDir, "app", "钩子");
43
+ allHooks.push(...appHooks);
44
+ }
19
45
 
20
- // 2. 过滤禁用的钩子
46
+ // 4. 过滤禁用的钩子
21
47
  const disableHooks = beflyConfig.disableHooks || [];
22
- const enabledHooks = coreHooks.filter((hook) => hook.name && !disableHooks.includes(hook.name));
48
+ const enabledHooks = allHooks.filter((hook) => hook.name && !disableHooks.includes(hook.name));
23
49
 
24
50
  if (disableHooks.length > 0) {
25
- Logger.info({ hooks: disableHooks }, '禁用钩子');
51
+ Logger.info({ hooks: disableHooks }, "禁用钩子");
26
52
  }
27
53
 
28
- // 3. 按 order 排序
54
+ // 5. 按 order 排序
29
55
  const sortedHooks = enabledHooks.sort((a, b) => {
30
56
  const orderA = a.order ?? 999;
31
57
  const orderB = b.order ?? 999;
@@ -34,7 +60,7 @@ export async function loadHooks(hooks: Hook[]): Promise<void> {
34
60
 
35
61
  hooks.push(...sortedHooks);
36
62
  } catch (error: any) {
37
- Logger.error({ err: error }, '加载钩子时发生错误');
63
+ Logger.error({ err: error }, "加载钩子时发生错误");
38
64
  process.exit(1);
39
65
  }
40
66
  }
@@ -3,34 +3,33 @@
3
3
  * 负责扫描和初始化所有插件(核心、组件、项目)
4
4
  */
5
5
 
6
- import { scanAddons, getAddonDir } from 'befly-shared/addonHelper';
6
+ import type { BeflyContext } from "../types/befly.js";
7
+ import type { Plugin } from "../types/plugin.js";
7
8
 
8
- import { Logger } from '../lib/logger.js';
9
- import { corePluginDir, projectPluginDir } from '../paths.js';
10
- import { sortModules, scanModules } from '../util.js';
11
- import { beflyConfig } from '../befly.config.js';
12
-
13
- import type { Plugin } from '../types/plugin.js';
14
- import type { BeflyContext } from '../types/befly.js';
9
+ import { beflyConfig } from "../befly.config.js";
10
+ import { Logger } from "../lib/logger.js";
11
+ import { corePluginDir, projectPluginDir } from "../paths.js";
12
+ import { scanAddons, getAddonDir } from "../utils/addonHelper.js";
13
+ import { sortModules, scanModules } from "../utils/modules.js";
15
14
 
16
15
  export async function loadPlugins(plugins: Plugin[], context: BeflyContext): Promise<void> {
17
16
  try {
18
17
  const allPlugins: Plugin[] = [];
19
18
 
20
19
  // 1. 扫描核心插件
21
- const corePlugins = await scanModules<Plugin>(corePluginDir, 'core', '插件');
20
+ const corePlugins = await scanModules<Plugin>(corePluginDir, "core", "插件");
22
21
 
23
22
  // 2. 扫描组件插件
24
23
  const addonPlugins: Plugin[] = [];
25
24
  const addons = scanAddons();
26
25
  for (const addon of addons) {
27
- const dir = getAddonDir(addon, 'plugins');
28
- const plugins = await scanModules<Plugin>(dir, 'addon', '插件', addon);
26
+ const dir = getAddonDir(addon, "plugins");
27
+ const plugins = await scanModules<Plugin>(dir, "addon", "插件", addon);
29
28
  addonPlugins.push(...plugins);
30
29
  }
31
30
 
32
31
  // 3. 扫描项目插件
33
- const appPlugins = await scanModules<Plugin>(projectPluginDir, 'app', '插件');
32
+ const appPlugins = await scanModules<Plugin>(projectPluginDir, "app", "插件");
34
33
 
35
34
  // 4. 合并所有插件
36
35
  allPlugins.push(...corePlugins);
@@ -42,13 +41,13 @@ export async function loadPlugins(plugins: Plugin[], context: BeflyContext): Pro
42
41
  const enabledPlugins = allPlugins.filter((plugin) => plugin.name && !disablePlugins.includes(plugin.name));
43
42
 
44
43
  if (disablePlugins.length > 0) {
45
- Logger.info({ plugins: disablePlugins }, '禁用插件');
44
+ Logger.info({ plugins: disablePlugins }, "禁用插件");
46
45
  }
47
46
 
48
47
  // 6. 排序与初始化
49
48
  const sortedPlugins = sortModules(enabledPlugins);
50
49
  if (sortedPlugins === false) {
51
- Logger.error('插件依赖关系错误,请检查 after 属性');
50
+ Logger.error("插件依赖关系错误,请检查 after 属性");
52
51
  process.exit(1);
53
52
  }
54
53
 
@@ -56,17 +55,17 @@ export async function loadPlugins(plugins: Plugin[], context: BeflyContext): Pro
56
55
  try {
57
56
  plugins.push(plugin);
58
57
 
59
- const pluginInstance = typeof plugin.handler === 'function' ? await plugin.handler(context) : {};
58
+ const pluginInstance = typeof plugin.handler === "function" ? await plugin.handler(context) : {};
60
59
 
61
60
  // 直接挂载到 befly 下
62
61
  (context as any)[plugin.name!] = pluginInstance;
63
62
  } catch (error: any) {
64
- Logger.error({ err: error, plugin: plugin.name }, '插件初始化失败');
63
+ Logger.error({ err: error, plugin: plugin.name }, "插件初始化失败");
65
64
  process.exit(1);
66
65
  }
67
66
  }
68
67
  } catch (error: any) {
69
- Logger.error({ err: error }, '加载插件时发生错误');
68
+ Logger.error({ err: error }, "加载插件时发生错误");
70
69
  process.exit(1);
71
70
  }
72
71
  }