befly 3.8.1 → 3.8.3

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.
@@ -14,22 +14,22 @@ import { Database } from './database.js';
14
14
  const prefix = Env.REDIS_KEY_PREFIX ? `${Env.REDIS_KEY_PREFIX}:` : '';
15
15
 
16
16
  /**
17
- * 获取 Redis 客户端
18
- * @returns Redis 客户端实例
19
- * @throws 如果客户端未初始化
17
+ * Redis 助手类
20
18
  */
21
- function getClient(): RedisClient {
22
- const client = Database.getRedis();
23
- if (!client) {
24
- throw new Error('Redis 客户端未初始化,请先调用 Database.connectRedis()');
19
+ export class RedisHelper {
20
+ private client: RedisClient;
21
+
22
+ /**
23
+ * 构造函数
24
+ */
25
+ constructor() {
26
+ const client = Database.getRedis();
27
+ if (!client) {
28
+ throw new Error('Redis 客户端未初始化,请先调用 Database.connectRedis()');
29
+ }
30
+ this.client = client;
25
31
  }
26
- return client;
27
- }
28
32
 
29
- /**
30
- * Redis 助手对象
31
- */
32
- export const RedisHelper = {
33
33
  /**
34
34
  * 设置对象到 Redis
35
35
  * @param key - 键名
@@ -39,19 +39,18 @@ export const RedisHelper = {
39
39
  */
40
40
  async setObject<T = any>(key: string, obj: T, ttl: number | null = null): Promise<string | null> {
41
41
  try {
42
- const client = getClient();
43
42
  const data = JSON.stringify(obj);
44
43
  const pkey = `${prefix}${key}`;
45
44
 
46
45
  if (ttl) {
47
- return await client.setex(pkey, ttl, data);
46
+ return await this.client.setex(pkey, ttl, data);
48
47
  }
49
- return await client.set(pkey, data);
48
+ return await this.client.set(pkey, data);
50
49
  } catch (error: any) {
51
50
  Logger.error('Redis setObject 错误', error);
52
51
  return null;
53
52
  }
54
- },
53
+ }
55
54
 
56
55
  /**
57
56
  * 从 Redis 获取对象
@@ -60,15 +59,14 @@ export const RedisHelper = {
60
59
  */
61
60
  async getObject<T = any>(key: string): Promise<T | null> {
62
61
  try {
63
- const client = getClient();
64
62
  const pkey = `${prefix}${key}`;
65
- const data = await client.get(pkey);
63
+ const data = await this.client.get(pkey);
66
64
  return data ? JSON.parse(data) : null;
67
65
  } catch (error: any) {
68
66
  Logger.error('Redis getObject 错误', error);
69
67
  return null;
70
68
  }
71
- },
69
+ }
72
70
 
73
71
  /**
74
72
  * 从 Redis 删除对象
@@ -76,13 +74,12 @@ export const RedisHelper = {
76
74
  */
77
75
  async delObject(key: string): Promise<void> {
78
76
  try {
79
- const client = getClient();
80
77
  const pkey = `${prefix}${key}`;
81
- await client.del(pkey);
78
+ await this.client.del(pkey);
82
79
  } catch (error: any) {
83
80
  Logger.error('Redis delObject 错误', error);
84
81
  }
85
- },
82
+ }
86
83
 
87
84
  /**
88
85
  * 生成基于时间的唯一 ID
@@ -92,17 +89,16 @@ export const RedisHelper = {
92
89
  * @returns 唯一 ID (14位纯数字)
93
90
  */
94
91
  async genTimeID(): Promise<number> {
95
- const client = getClient();
96
92
  const timestamp = Math.floor(Date.now() / 1000); // 秒级时间戳
97
93
  const key = `${prefix}time_id_counter:${timestamp}`;
98
94
 
99
- const counter = await client.incr(key);
100
- await client.expire(key, 1);
95
+ const counter = await this.client.incr(key);
96
+ await this.client.expire(key, 1);
101
97
 
102
98
  const counterSuffix = (counter % 10000).toString().padStart(4, '0');
103
99
 
104
100
  return Number(`${timestamp}${counterSuffix}`);
105
- },
101
+ }
106
102
 
107
103
  /**
108
104
  * 批量生成基于时间的唯一 ID
@@ -121,13 +117,12 @@ export const RedisHelper = {
121
117
  throw new Error(`批量大小 ${count} 超过最大限制 ${MAX_BATCH_SIZE}`);
122
118
  }
123
119
 
124
- const client = getClient();
125
120
  const timestamp = Math.floor(Date.now() / 1000); // 秒级时间戳
126
121
  const key = `${prefix}time_id_counter:${timestamp}`;
127
122
 
128
123
  // 使用 INCRBY 一次性获取 N 个连续计数
129
- const startCounter = await client.incrby(key, count);
130
- await client.expire(key, 1);
124
+ const startCounter = await this.client.incrby(key, count);
125
+ await this.client.expire(key, 1);
131
126
 
132
127
  // 生成 ID 数组
133
128
  const ids: number[] = [];
@@ -138,7 +133,7 @@ export const RedisHelper = {
138
133
  }
139
134
 
140
135
  return ids;
141
- },
136
+ }
142
137
 
143
138
  /**
144
139
  * 设置字符串值
@@ -148,17 +143,16 @@ export const RedisHelper = {
148
143
  */
149
144
  async setString(key: string, value: string, ttl: number | null = null): Promise<string | null> {
150
145
  try {
151
- const client = getClient();
152
146
  const pkey = `${prefix}${key}`;
153
147
  if (ttl) {
154
- return await client.setex(pkey, ttl, value);
148
+ return await this.client.setex(pkey, ttl, value);
155
149
  }
156
- return await client.set(pkey, value);
150
+ return await this.client.set(pkey, value);
157
151
  } catch (error: any) {
158
152
  Logger.error('Redis setString 错误', error);
159
153
  return null;
160
154
  }
161
- },
155
+ }
162
156
 
163
157
  /**
164
158
  * 获取字符串值
@@ -166,14 +160,13 @@ export const RedisHelper = {
166
160
  */
167
161
  async getString(key: string): Promise<string | null> {
168
162
  try {
169
- const client = getClient();
170
163
  const pkey = `${prefix}${key}`;
171
- return await client.get(pkey);
164
+ return await this.client.get(pkey);
172
165
  } catch (error: any) {
173
166
  Logger.error('Redis getString 错误', error);
174
167
  return null;
175
168
  }
176
- },
169
+ }
177
170
 
178
171
  /**
179
172
  * 检查键是否存在
@@ -181,14 +174,13 @@ export const RedisHelper = {
181
174
  */
182
175
  async exists(key: string): Promise<number> {
183
176
  try {
184
- const client = getClient();
185
177
  const pkey = `${prefix}${key}`;
186
- return await client.exists(pkey);
178
+ return await this.client.exists(pkey);
187
179
  } catch (error: any) {
188
180
  Logger.error('Redis exists 错误', error);
189
181
  return 0;
190
182
  }
191
- },
183
+ }
192
184
 
193
185
  /**
194
186
  * 设置过期时间
@@ -197,14 +189,13 @@ export const RedisHelper = {
197
189
  */
198
190
  async expire(key: string, seconds: number): Promise<number> {
199
191
  try {
200
- const client = getClient();
201
192
  const pkey = `${prefix}${key}`;
202
- return await client.expire(pkey, seconds);
193
+ return await this.client.expire(pkey, seconds);
203
194
  } catch (error: any) {
204
195
  Logger.error('Redis expire 错误', error);
205
196
  return 0;
206
197
  }
207
- },
198
+ }
208
199
 
209
200
  /**
210
201
  * 获取剩余过期时间
@@ -212,14 +203,13 @@ export const RedisHelper = {
212
203
  */
213
204
  async ttl(key: string): Promise<number> {
214
205
  try {
215
- const client = getClient();
216
206
  const pkey = `${prefix}${key}`;
217
- return await client.ttl(pkey);
207
+ return await this.client.ttl(pkey);
218
208
  } catch (error: any) {
219
209
  Logger.error('Redis ttl 错误', error);
220
210
  return -1;
221
211
  }
222
- },
212
+ }
223
213
 
224
214
  /**
225
215
  * 向 Set 中添加一个或多个成员
@@ -231,14 +221,13 @@ export const RedisHelper = {
231
221
  try {
232
222
  if (members.length === 0) return 0;
233
223
 
234
- const client = getClient();
235
224
  const pkey = `${prefix}${key}`;
236
- return await client.sadd(pkey, ...members);
225
+ return await this.client.sadd(pkey, ...members);
237
226
  } catch (error: any) {
238
227
  Logger.error('Redis sadd 错误', error);
239
228
  return 0;
240
229
  }
241
- },
230
+ }
242
231
 
243
232
  /**
244
233
  * 判断成员是否在 Set 中
@@ -248,14 +237,13 @@ export const RedisHelper = {
248
237
  */
249
238
  async sismember(key: string, member: string): Promise<number> {
250
239
  try {
251
- const client = getClient();
252
240
  const pkey = `${prefix}${key}`;
253
- return await client.sismember(pkey, member);
241
+ return await this.client.sismember(pkey, member);
254
242
  } catch (error: any) {
255
243
  Logger.error('Redis sismember 错误', error);
256
244
  return 0;
257
245
  }
258
- },
246
+ }
259
247
 
260
248
  /**
261
249
  * 获取 Set 的成员数量
@@ -264,14 +252,13 @@ export const RedisHelper = {
264
252
  */
265
253
  async scard(key: string): Promise<number> {
266
254
  try {
267
- const client = getClient();
268
255
  const pkey = `${prefix}${key}`;
269
- return await client.scard(pkey);
256
+ return await this.client.scard(pkey);
270
257
  } catch (error: any) {
271
258
  Logger.error('Redis scard 错误', error);
272
259
  return 0;
273
260
  }
274
- },
261
+ }
275
262
 
276
263
  /**
277
264
  * 获取 Set 的所有成员
@@ -280,14 +267,13 @@ export const RedisHelper = {
280
267
  */
281
268
  async smembers(key: string): Promise<string[]> {
282
269
  try {
283
- const client = getClient();
284
270
  const pkey = `${prefix}${key}`;
285
- return await client.smembers(pkey);
271
+ return await this.client.smembers(pkey);
286
272
  } catch (error: any) {
287
273
  Logger.error('Redis smembers 错误', error);
288
274
  return [];
289
275
  }
290
- },
276
+ }
291
277
 
292
278
  /**
293
279
  * 删除键
@@ -296,14 +282,13 @@ export const RedisHelper = {
296
282
  */
297
283
  async del(key: string): Promise<number> {
298
284
  try {
299
- const client = getClient();
300
285
  const pkey = `${prefix}${key}`;
301
- return await client.del(pkey);
286
+ return await this.client.del(pkey);
302
287
  } catch (error: any) {
303
288
  Logger.error('Redis del 错误', error);
304
289
  return 0;
305
290
  }
306
- },
291
+ }
307
292
 
308
293
  /**
309
294
  * 测试 Redis 连接
@@ -311,11 +296,10 @@ export const RedisHelper = {
311
296
  */
312
297
  async ping(): Promise<string> {
313
298
  try {
314
- const client = getClient();
315
- return await client.ping();
299
+ return await this.client.ping();
316
300
  } catch (error: any) {
317
301
  Logger.error('Redis ping 错误', error);
318
302
  throw error;
319
303
  }
320
304
  }
321
- };
305
+ }
package/lib/validator.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * 数据验证器 - Befly 项目专用
3
- * 内置 RegexAliases,直接使用 util.ts 中的 parseRule
3
+ * 内置 RegexAliases,支持对象格式的字段定义
4
4
  */
5
5
 
6
- import { parseRule } from '../util.js';
7
- import type { TableDefinition, FieldRule } from '../types/common.js';
6
+ import type { TableDefinition, FieldDefinition } from '../types/common.js';
8
7
  import type { ValidationResult, ValidationError } from '../types/validator';
9
8
 
10
9
  /**
@@ -106,7 +105,7 @@ export class Validator {
106
105
  * 检查参数有效性
107
106
  */
108
107
  private checkParams(data: any, rules: any, required: any, result: ValidationResult): boolean {
109
- if (!data || typeof data !== 'object') {
108
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
110
109
  result.code = 1;
111
110
  result.fields.error = '数据必须是对象格式';
112
111
  return false;
@@ -135,8 +134,8 @@ export class Validator {
135
134
  const value = data[fieldName];
136
135
  if (!(fieldName in data) || value === undefined || value === null || value === '') {
137
136
  result.code = 1;
138
- const ruleParts = parseRule(rules[fieldName] || '');
139
- const fieldLabel = ruleParts.name || fieldName;
137
+ const fieldDef = rules[fieldName];
138
+ const fieldLabel = fieldDef?.name || fieldName;
140
139
  result.fields[fieldName] = `${fieldLabel}(${fieldName})为必填项`;
141
140
  }
142
141
  }
@@ -158,7 +157,7 @@ export class Validator {
158
157
  }
159
158
 
160
159
  const value = data[fieldName];
161
- const error = this.validateFieldValue(value, rule, fieldName);
160
+ const error = this.validateFieldValue(value, rules[fieldName], fieldName);
162
161
 
163
162
  if (error) {
164
163
  result.code = 1;
@@ -190,21 +189,20 @@ export class Validator {
190
189
  /**
191
190
  * 验证单个字段的值
192
191
  */
193
- private validateFieldValue(value: any, rule: FieldRule, fieldName: string): ValidationError {
194
- const parsed = parseRule(rule);
195
- let { name, type, min, max, regex } = parsed;
192
+ private validateFieldValue(value: any, fieldDef: FieldDefinition, fieldName: string): ValidationError {
193
+ let { name, type, min, max, regexp } = fieldDef;
196
194
 
197
- regex = this.resolveRegexAlias(regex);
195
+ regexp = this.resolveRegexAlias(regexp);
198
196
 
199
197
  switch (type.toLowerCase()) {
200
198
  case 'number':
201
- return this.validateNumber(value, name, min, max, regex, fieldName);
199
+ return this.validateNumber(value, name, min, max, regexp, fieldName);
202
200
  case 'string':
203
201
  case 'text':
204
- return this.validateString(value, name, min, max, regex, fieldName);
202
+ return this.validateString(value, name, min, max, regexp, fieldName);
205
203
  case 'array_string':
206
204
  case 'array_text':
207
- return this.validateArray(value, name, min, max, regex, fieldName);
205
+ return this.validateArray(value, name, min, max, regexp, fieldName);
208
206
  default:
209
207
  return `字段 ${fieldName} 的类型 ${type} 不支持`;
210
208
  }
@@ -215,7 +213,7 @@ export class Validator {
215
213
  */
216
214
  private validateNumber(value: any, name: string, min: number | null, max: number | null, spec: string | null, fieldName: string): ValidationError {
217
215
  try {
218
- if (typeof value !== 'number' || Number.isNaN(value)) {
216
+ if (typeof value !== 'number' || Number.isNaN(value) || !isFinite(value)) {
219
217
  return `${name}(${fieldName})必须是数字`;
220
218
  }
221
219
 
@@ -315,17 +313,16 @@ export class Validator {
315
313
  }
316
314
 
317
315
  /**
318
- * 验证单个值
316
+ * 验证单个值(支持对象格式字段定义)
319
317
  */
320
- validateSingleValue(value: any, rule: string): { valid: boolean; value: any; errors: string[] } {
321
- const parsed = parseRule(rule);
322
- let { name, type, min, max, regex, default: defaultValue } = parsed;
318
+ validateSingleValue(value: any, fieldDef: FieldDefinition): { valid: boolean; value: any; errors: string[] } {
319
+ let { name, type, min, max, regexp, default: defaultValue } = fieldDef;
323
320
 
324
- regex = this.resolveRegexAlias(regex);
321
+ regexp = this.resolveRegexAlias(regexp);
325
322
 
326
323
  // 处理 undefined/null 值,使用默认值
327
324
  if (value === undefined || value === null) {
328
- if (defaultValue !== 'null' && defaultValue !== null) {
325
+ if (defaultValue !== null) {
329
326
  if ((type === 'array_string' || type === 'array_text') && typeof defaultValue === 'string') {
330
327
  if (defaultValue === '[]') {
331
328
  return { valid: true, value: [], errors: [] };
@@ -381,9 +378,9 @@ export class Validator {
381
378
  if (max !== null && max > 0 && convertedValue > max) {
382
379
  errors.push(`${name || '值'}不能大于${max}`);
383
380
  }
384
- if (regex && regex.trim() !== '' && regex !== 'null') {
381
+ if (regexp && regexp.trim() !== '') {
385
382
  try {
386
- const regExp = new RegExp(regex);
383
+ const regExp = new RegExp(regexp);
387
384
  if (!regExp.test(String(convertedValue))) {
388
385
  errors.push(`${name || '值'}格式不正确`);
389
386
  }
@@ -404,9 +401,9 @@ export class Validator {
404
401
  if (max !== null && max > 0 && convertedValue.length > max) {
405
402
  errors.push(`${name || '值'}长度不能超过${max}个字符`);
406
403
  }
407
- if (regex && regex.trim() !== '' && regex !== 'null') {
404
+ if (regexp && regexp.trim() !== '') {
408
405
  try {
409
- const regExp = new RegExp(regex);
406
+ const regExp = new RegExp(regexp);
410
407
  if (!regExp.test(convertedValue)) {
411
408
  errors.push(`${name || '值'}格式不正确`);
412
409
  }
@@ -427,9 +424,9 @@ export class Validator {
427
424
  if (max !== null && max > 0 && convertedValue.length > max) {
428
425
  errors.push(`${name || '值'}元素数量不能超过${max}个`);
429
426
  }
430
- if (regex && regex.trim() !== '' && regex !== 'null') {
427
+ if (regexp && regexp.trim() !== '') {
431
428
  try {
432
- const regExp = new RegExp(regex);
429
+ const regExp = new RegExp(regexp);
433
430
  for (const item of convertedValue) {
434
431
  if (!regExp.test(String(item))) {
435
432
  errors.push(`${name || '值'}的元素格式不正确`);
@@ -454,15 +451,15 @@ export class Validator {
454
451
  * 静态方法:快速验证
455
452
  */
456
453
  static validate(data: Record<string, any>, rules: TableDefinition, required?: string[]): ValidationResult;
457
- static validate(value: any, rule: string): { valid: boolean; value: any; errors: string[] };
458
- static validate(dataOrValue: any, rulesOrRule: any, required?: string[]): any {
454
+ static validate(value: any, fieldDef: FieldDefinition): { valid: boolean; value: any; errors: string[] };
455
+ static validate(dataOrValue: any, rulesOrFieldDef: any, required?: string[]): any {
459
456
  const validator = new Validator();
460
457
 
461
- if (typeof rulesOrRule === 'string') {
462
- return validator.validateSingleValue(dataOrValue, rulesOrRule);
458
+ if (rulesOrFieldDef && 'type' in rulesOrFieldDef) {
459
+ return validator.validateSingleValue(dataOrValue, rulesOrFieldDef);
463
460
  }
464
461
 
465
- return validator.validate(dataOrValue, rulesOrRule, required || []);
462
+ return validator.validate(dataOrValue, rulesOrFieldDef, required || []);
466
463
  }
467
464
 
468
465
  /**
@@ -0,0 +1,172 @@
1
+ /**
2
+ * API 加载器
3
+ * 负责扫描和加载所有 API 路由(组件、用户)
4
+ */
5
+
6
+ import { relative, basename, join } from 'pathe';
7
+ import { existsSync } from 'node:fs';
8
+ import { isPlainObject } from 'es-toolkit/compat';
9
+ import { Logger } from '../lib/logger.js';
10
+ import { calcPerfTime } from '../util.js';
11
+ import { projectApiDir } from '../paths.js';
12
+ import { scanAddons, getAddonDir, addonDirExists } from '../util.js';
13
+ import type { ApiRoute } from '../types/api.js';
14
+
15
+ /**
16
+ * API 默认字段定义
17
+ * 这些字段会自动合并到所有 API 的 fields 中
18
+ * API 自定义的同名字段可以覆盖这些默认值
19
+ */
20
+ const DEFAULT_API_FIELDS = {
21
+ id: {
22
+ name: 'ID',
23
+ type: 'number',
24
+ min: 1,
25
+ max: null
26
+ },
27
+ page: {
28
+ name: '页码',
29
+ type: 'number',
30
+ min: 1,
31
+ max: 9999
32
+ },
33
+ limit: {
34
+ name: '每页数量',
35
+ type: 'number',
36
+ min: 1,
37
+ max: 100
38
+ },
39
+ keyword: {
40
+ name: '关键词',
41
+ type: 'string',
42
+ min: 1,
43
+ max: 50
44
+ },
45
+ state: {
46
+ name: '状态',
47
+ type: 'number',
48
+ min: 0,
49
+ max: 2
50
+ }
51
+ } as const;
52
+
53
+ /**
54
+ * 扫描用户 API 文件
55
+ */
56
+ async function scanUserApis(): Promise<Array<{ file: string; routePrefix: string; displayName: string }>> {
57
+ const apis: Array<{ file: string; routePrefix: string; displayName: string }> = [];
58
+
59
+ if (!existsSync(projectApiDir)) {
60
+ return apis;
61
+ }
62
+
63
+ const glob = new Bun.Glob('**/*.ts');
64
+
65
+ for await (const file of glob.scan({
66
+ cwd: projectApiDir,
67
+ onlyFiles: true,
68
+ absolute: true
69
+ })) {
70
+ const apiPath = relative(projectApiDir, file).replace(/\.ts$/, '');
71
+ if (apiPath.indexOf('_') !== -1) continue;
72
+
73
+ apis.push({
74
+ file: file,
75
+ routePrefix: '',
76
+ displayName: '用户'
77
+ });
78
+ }
79
+
80
+ return apis;
81
+ }
82
+
83
+ /**
84
+ * 扫描组件 API 文件
85
+ */
86
+ async function scanAddonApis(): Promise<Array<{ file: string; routePrefix: string; displayName: string }>> {
87
+ const apis: Array<{ file: string; routePrefix: string; displayName: string }> = [];
88
+ const glob = new Bun.Glob('**/*.ts');
89
+ const addons = scanAddons();
90
+
91
+ for (const addon of addons) {
92
+ if (!addonDirExists(addon, 'apis')) continue;
93
+
94
+ const addonApiDir = getAddonDir(addon, 'apis');
95
+ for await (const file of glob.scan({
96
+ cwd: addonApiDir,
97
+ onlyFiles: true,
98
+ absolute: true
99
+ })) {
100
+ const apiPath = relative(addonApiDir, file).replace(/\.ts$/, '');
101
+ if (apiPath.indexOf('_') !== -1) continue;
102
+
103
+ apis.push({
104
+ file: file,
105
+ routePrefix: `addon/${addon}`,
106
+ displayName: `组件${addon}`
107
+ });
108
+ }
109
+ }
110
+
111
+ return apis;
112
+ }
113
+
114
+ /**
115
+ * 初始化单个 API
116
+ */
117
+ async function initApi(apiRoutes: Map<string, ApiRoute>, apiInfo: { file: string; routePrefix: string; displayName: string }): Promise<void> {
118
+ const { file, routePrefix, displayName } = apiInfo;
119
+ const apiDir = routePrefix === '' ? projectApiDir : getAddonDir(routePrefix.replace('addon/', ''), 'apis');
120
+ const apiPath = relative(apiDir, file).replace(/\.ts$/, '');
121
+
122
+ try {
123
+ // Windows 下路径需要转换为正斜杠格式
124
+ const filePath = file.replace(/\\/g, '/');
125
+ const apiImport = await import(filePath);
126
+ const api = apiImport.default;
127
+
128
+ // 设置默认值
129
+ api.method = api.method || 'POST';
130
+ api.auth = api.auth !== undefined ? api.auth : true;
131
+ // 合并默认字段:先设置自定义字段,再用默认字段覆盖(默认字段优先级更高)
132
+ api.fields = { ...(api.fields || {}), ...DEFAULT_API_FIELDS };
133
+ api.required = api.required || [];
134
+
135
+ // 构建路由
136
+ api.route = `${api.method.toUpperCase()}/api/${routePrefix ? routePrefix + '/' : ''}${apiPath}`;
137
+ apiRoutes.set(api.route, api);
138
+ } catch (error: any) {
139
+ Logger.error(`[${displayName}] 接口 ${apiPath} 加载失败`, error);
140
+ process.exit(1);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * 加载所有 API 路由
146
+ * @param apiRoutes - API 路由映射表
147
+ */
148
+ export async function loadApis(apiRoutes: Map<string, ApiRoute>): Promise<void> {
149
+ try {
150
+ const loadStartTime = Bun.nanoseconds();
151
+
152
+ // 阶段1:扫描所有 API
153
+ const userApis = await scanUserApis();
154
+ const addonApis = await scanAddonApis();
155
+
156
+ // 阶段2:初始化所有 API(用户 → 组件)
157
+ // 2.1 初始化用户 APIs
158
+ for (const apiInfo of userApis) {
159
+ await initApi(apiRoutes, apiInfo);
160
+ }
161
+
162
+ // 2.2 初始化组件 APIs
163
+ for (const apiInfo of addonApis) {
164
+ await initApi(apiRoutes, apiInfo);
165
+ }
166
+
167
+ const totalLoadTime = calcPerfTime(loadStartTime);
168
+ } catch (error: any) {
169
+ Logger.error('加载 API 时发生错误', error);
170
+ process.exit(1);
171
+ }
172
+ }