befly 3.8.1 → 3.8.2
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/check.ts +57 -24
- package/lib/cacheHelper.ts +169 -0
- package/lib/redisHelper.ts +49 -65
- package/lib/validator.ts +29 -32
- package/lifecycle/bootstrap.ts +5 -19
- package/lifecycle/lifecycle.ts +16 -59
- package/lifecycle/loadApis.ts +164 -0
- package/lifecycle/loadPlugins.ts +244 -0
- package/main.ts +12 -15
- package/menu.json +13 -13
- package/package.json +2 -2
- package/plugins/cache.ts +3 -167
- package/types/common.d.ts +28 -7
- package/util.ts +4 -48
- package/lifecycle/loader.ts +0 -427
package/lib/validator.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 数据验证器 - Befly 项目专用
|
|
3
|
-
* 内置 RegexAliases
|
|
3
|
+
* 内置 RegexAliases,支持对象格式的字段定义
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
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
|
|
139
|
-
const fieldLabel =
|
|
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,
|
|
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,
|
|
194
|
-
|
|
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
|
-
|
|
195
|
+
regexp = this.resolveRegexAlias(regexp);
|
|
198
196
|
|
|
199
197
|
switch (type.toLowerCase()) {
|
|
200
198
|
case 'number':
|
|
201
|
-
return this.validateNumber(value, name, min, max,
|
|
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,
|
|
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,
|
|
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,
|
|
321
|
-
|
|
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
|
-
|
|
321
|
+
regexp = this.resolveRegexAlias(regexp);
|
|
325
322
|
|
|
326
323
|
// 处理 undefined/null 值,使用默认值
|
|
327
324
|
if (value === undefined || value === null) {
|
|
328
|
-
if (defaultValue !==
|
|
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 (
|
|
381
|
+
if (regexp && regexp.trim() !== '') {
|
|
385
382
|
try {
|
|
386
|
-
const regExp = new RegExp(
|
|
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 (
|
|
404
|
+
if (regexp && regexp.trim() !== '') {
|
|
408
405
|
try {
|
|
409
|
-
const regExp = new RegExp(
|
|
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 (
|
|
427
|
+
if (regexp && regexp.trim() !== '') {
|
|
431
428
|
try {
|
|
432
|
-
const regExp = new RegExp(
|
|
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,
|
|
458
|
-
static validate(dataOrValue: 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 (
|
|
462
|
-
return validator.validateSingleValue(dataOrValue,
|
|
458
|
+
if (rulesOrFieldDef && 'type' in rulesOrFieldDef) {
|
|
459
|
+
return validator.validateSingleValue(dataOrValue, rulesOrFieldDef);
|
|
463
460
|
}
|
|
464
461
|
|
|
465
|
-
return validator.validate(dataOrValue,
|
|
462
|
+
return validator.validate(dataOrValue, rulesOrFieldDef, required || []);
|
|
466
463
|
}
|
|
467
464
|
|
|
468
465
|
/**
|
package/lifecycle/bootstrap.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Logger } from '../lib/logger.js';
|
|
7
|
-
import { calcPerfTime
|
|
7
|
+
import { calcPerfTime } from '../util.js';
|
|
8
8
|
import { Env } from '../env.js';
|
|
9
9
|
import { rootHandler } from '../router/root.js';
|
|
10
10
|
import { apiHandler } from '../router/api.js';
|
|
@@ -21,18 +21,9 @@ import type { Plugin } from '../types/plugin.js';
|
|
|
21
21
|
export class Bootstrap {
|
|
22
22
|
/**
|
|
23
23
|
* 启动HTTP服务器
|
|
24
|
-
* @param befly - Befly实例(需要访问 apiRoutes, pluginLists, appContext
|
|
25
|
-
* @param callback - 启动后的回调函数
|
|
24
|
+
* @param befly - Befly实例(需要访问 apiRoutes, pluginLists, appContext)
|
|
26
25
|
*/
|
|
27
|
-
static async start(
|
|
28
|
-
befly: {
|
|
29
|
-
apiRoutes: Map<string, ApiRoute>;
|
|
30
|
-
pluginLists: Plugin[];
|
|
31
|
-
appContext: BeflyContext;
|
|
32
|
-
appOptions: any;
|
|
33
|
-
},
|
|
34
|
-
callback?: (server: Server) => void
|
|
35
|
-
): Promise<Server> {
|
|
26
|
+
static async start(befly: { apiRoutes: Map<string, ApiRoute>; pluginLists: Plugin[]; appContext: BeflyContext }): Promise<Server> {
|
|
36
27
|
const startTime = Bun.nanoseconds();
|
|
37
28
|
|
|
38
29
|
const server = Bun.serve({
|
|
@@ -41,12 +32,11 @@ export class Bootstrap {
|
|
|
41
32
|
routes: {
|
|
42
33
|
'/': rootHandler,
|
|
43
34
|
'/api/*': apiHandler(befly.apiRoutes, befly.pluginLists, befly.appContext),
|
|
44
|
-
'/*': staticHandler
|
|
45
|
-
...(befly.appOptions.routes || {})
|
|
35
|
+
'/*': staticHandler
|
|
46
36
|
},
|
|
47
37
|
error: (error: Error) => {
|
|
48
38
|
Logger.error('服务启动时发生错误', error);
|
|
49
|
-
return Response.json(
|
|
39
|
+
return Response.json({ code: 1, msg: '内部服务器错误' });
|
|
50
40
|
}
|
|
51
41
|
});
|
|
52
42
|
|
|
@@ -54,10 +44,6 @@ export class Bootstrap {
|
|
|
54
44
|
Logger.info(`${Env.APP_NAME} 服务器启动成功! 服务器启动耗时: ${finalStartupTime}`);
|
|
55
45
|
Logger.info(`服务器监听地址: http://${Env.APP_HOST}:${Env.APP_PORT}`);
|
|
56
46
|
|
|
57
|
-
if (callback && typeof callback === 'function') {
|
|
58
|
-
callback(server);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
47
|
return server;
|
|
62
48
|
}
|
|
63
49
|
}
|
package/lifecycle/lifecycle.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import { Logger } from '../lib/logger.js';
|
|
7
7
|
import { Database } from '../lib/database.js';
|
|
8
|
-
import {
|
|
8
|
+
import { loadPlugins } from './loadPlugins.js';
|
|
9
|
+
import { loadApis } from './loadApis.js';
|
|
9
10
|
import { Checker } from './checker.js';
|
|
10
11
|
import { Env } from '../env.js';
|
|
11
12
|
import { calcPerfTime } from '../util.js';
|
|
@@ -15,7 +16,7 @@ import { Bootstrap } from './bootstrap.js';
|
|
|
15
16
|
import type { Server } from 'bun';
|
|
16
17
|
import type { Plugin } from '../types/plugin.js';
|
|
17
18
|
import type { ApiRoute } from '../types/api.js';
|
|
18
|
-
import type { BeflyContext
|
|
19
|
+
import type { BeflyContext } from '../types/befly.js';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* 生命周期管理类
|
|
@@ -27,78 +28,34 @@ export class Lifecycle {
|
|
|
27
28
|
/** 插件列表 */
|
|
28
29
|
private pluginLists: Plugin[] = [];
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
private options: BeflyOptions;
|
|
32
|
-
|
|
33
|
-
constructor(options: BeflyOptions = {}) {
|
|
34
|
-
this.options = options;
|
|
35
|
-
}
|
|
31
|
+
constructor() {}
|
|
36
32
|
|
|
37
33
|
/**
|
|
38
34
|
* 启动完整的生命周期流程
|
|
39
35
|
* @param appContext - 应用上下文
|
|
40
|
-
* @param callback - 启动完成后的回调函数
|
|
41
36
|
*/
|
|
42
|
-
async start(appContext: BeflyContext
|
|
37
|
+
async start(appContext: BeflyContext): Promise<Server> {
|
|
43
38
|
const serverStartTime = Bun.nanoseconds();
|
|
44
39
|
|
|
45
40
|
// 1. 执行系统检查
|
|
46
41
|
await Checker.run();
|
|
47
42
|
|
|
48
43
|
// 2. 加载插件
|
|
49
|
-
await
|
|
44
|
+
await loadPlugins({
|
|
45
|
+
pluginLists: this.pluginLists,
|
|
46
|
+
appContext: appContext
|
|
47
|
+
});
|
|
50
48
|
|
|
51
|
-
// 3. 加载所有 API
|
|
52
|
-
await this.
|
|
49
|
+
// // 3. 加载所有 API
|
|
50
|
+
await loadApis(this.apiRoutes);
|
|
53
51
|
|
|
54
52
|
// 4. 启动 HTTP 服务器
|
|
55
53
|
const totalStartupTime = calcPerfTime(serverStartTime);
|
|
56
54
|
|
|
57
|
-
return await Bootstrap.start(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
appOptions: this.options
|
|
63
|
-
},
|
|
64
|
-
callback
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* 加载所有 API 路由
|
|
70
|
-
* 包括 core APIs、addon APIs 和 app APIs
|
|
71
|
-
*/
|
|
72
|
-
private async loadAllApis(): Promise<void> {
|
|
73
|
-
// 1. 加载 Core APIs
|
|
74
|
-
try {
|
|
75
|
-
await Loader.loadApis('core', this.apiRoutes, { where: 'core' });
|
|
76
|
-
} catch (error: any) {
|
|
77
|
-
Logger.error(`核心 APIs 加载失败`, error);
|
|
78
|
-
throw error;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// 2. 加载 addon APIs
|
|
82
|
-
const addons = Addon.scan();
|
|
83
|
-
|
|
84
|
-
for (const addon of addons) {
|
|
85
|
-
const hasApis = Addon.dirExists(addon, 'apis');
|
|
86
|
-
if (hasApis) {
|
|
87
|
-
try {
|
|
88
|
-
await Loader.loadApis(addon, this.apiRoutes, { where: 'addon', addonName: addon });
|
|
89
|
-
} catch (error: any) {
|
|
90
|
-
Logger.error(`[组件 ${addon}] APIs 加载失败`, error);
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// 3. 加载用户 APIs
|
|
97
|
-
try {
|
|
98
|
-
await Loader.loadApis('app', this.apiRoutes, { where: 'app' });
|
|
99
|
-
} catch (error: any) {
|
|
100
|
-
Logger.error(`用户 APIs 加载失败`, error);
|
|
101
|
-
throw error;
|
|
102
|
-
}
|
|
55
|
+
return await Bootstrap.start({
|
|
56
|
+
apiRoutes: this.apiRoutes,
|
|
57
|
+
pluginLists: this.pluginLists,
|
|
58
|
+
appContext: appContext
|
|
59
|
+
});
|
|
103
60
|
}
|
|
104
61
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API 加载器
|
|
3
|
+
* 负责扫描和加载所有 API 路由(核心、组件、用户)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { relative, basename } 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 { coreApiDir, projectApiDir } from '../paths.js';
|
|
12
|
+
import { Addon } from '../lib/addon.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
|
+
* @param apiDir - API 目录路径
|
|
56
|
+
* @param apiRoutes - API 路由映射表
|
|
57
|
+
* @param routePrefix - 路由前缀(如 'core', 'addon/admin', '')
|
|
58
|
+
* @param displayName - 显示名称(用于日志)
|
|
59
|
+
*/
|
|
60
|
+
async function scanApisFromDir(apiDir: string, apiRoutes: Map<string, ApiRoute>, routePrefix: string, displayName: string): Promise<void> {
|
|
61
|
+
if (!existsSync(apiDir)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const glob = new Bun.Glob('**/*.ts');
|
|
66
|
+
|
|
67
|
+
for await (const file of glob.scan({
|
|
68
|
+
cwd: apiDir,
|
|
69
|
+
onlyFiles: true,
|
|
70
|
+
absolute: true
|
|
71
|
+
})) {
|
|
72
|
+
const fileName = basename(file).replace(/\.ts$/, '');
|
|
73
|
+
const apiPath = relative(apiDir, file).replace(/\.ts$/, '');
|
|
74
|
+
if (apiPath.indexOf('_') !== -1) continue;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const apiImport = await import(file);
|
|
78
|
+
const api = apiImport.default;
|
|
79
|
+
// 验证必填属性:name 和 handler
|
|
80
|
+
if (typeof api.name !== 'string' || api.name.trim() === '') {
|
|
81
|
+
throw new Error(`接口 ${apiPath} 的 name 属性必须是非空字符串`);
|
|
82
|
+
}
|
|
83
|
+
if (typeof api.handler !== 'function') {
|
|
84
|
+
throw new Error(`接口 ${apiPath} 的 handler 属性必须是函数`);
|
|
85
|
+
}
|
|
86
|
+
// 设置默认值
|
|
87
|
+
api.method = api.method || 'POST';
|
|
88
|
+
api.auth = api.auth !== undefined ? api.auth : true;
|
|
89
|
+
// 合并默认字段:先设置自定义字段,再用默认字段覆盖(默认字段优先级更高)
|
|
90
|
+
api.fields = { ...(api.fields || {}), ...DEFAULT_API_FIELDS };
|
|
91
|
+
api.required = api.required || [];
|
|
92
|
+
// 验证可选属性的类型(如果提供了)
|
|
93
|
+
if (api.method && !['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'].includes(api.method.toUpperCase())) {
|
|
94
|
+
throw new Error(`接口 ${apiPath} 的 method 属性必须是有效的 HTTP 方法`);
|
|
95
|
+
}
|
|
96
|
+
if (api.auth !== undefined && typeof api.auth !== 'boolean') {
|
|
97
|
+
throw new Error(`接口 ${apiPath} 的 auth 属性必须是布尔值 (true=需登录, false=公开)`);
|
|
98
|
+
}
|
|
99
|
+
if (api.fields && !isPlainObject(api.fields)) {
|
|
100
|
+
throw new Error(`接口 ${apiPath} 的 fields 属性必须是对象`);
|
|
101
|
+
}
|
|
102
|
+
if (api.required && !Array.isArray(api.required)) {
|
|
103
|
+
throw new Error(`接口 ${apiPath} 的 required 属性必须是数组`);
|
|
104
|
+
}
|
|
105
|
+
if (api.required && api.required.some((item: any) => typeof item !== 'string')) {
|
|
106
|
+
throw new Error(`接口 ${apiPath} 的 required 属性必须是字符串数组`);
|
|
107
|
+
}
|
|
108
|
+
// 构建路由
|
|
109
|
+
api.route = `${api.method.toUpperCase()}/api/${routePrefix ? routePrefix + '/' : ''}${apiPath}`;
|
|
110
|
+
apiRoutes.set(api.route, api);
|
|
111
|
+
} catch (error: any) {
|
|
112
|
+
Logger.error(`[${displayName}] 接口 ${apiPath} 加载失败`, error);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 扫描核心 APIs
|
|
120
|
+
*/
|
|
121
|
+
async function scanCoreApis(apiRoutes: Map<string, ApiRoute>): Promise<void> {
|
|
122
|
+
await scanApisFromDir(coreApiDir, apiRoutes, 'core', '核心');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 扫描组件 APIs
|
|
127
|
+
*/
|
|
128
|
+
async function scanAddonApis(apiRoutes: Map<string, ApiRoute>): Promise<void> {
|
|
129
|
+
const addons = Addon.scan();
|
|
130
|
+
|
|
131
|
+
for (const addon of addons) {
|
|
132
|
+
if (!Addon.dirExists(addon, 'apis')) continue;
|
|
133
|
+
|
|
134
|
+
const addonApiDir = Addon.getDir(addon, 'apis');
|
|
135
|
+
await scanApisFromDir(addonApiDir, apiRoutes, `addon/${addon}`, `组件${addon}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 扫描用户 APIs
|
|
141
|
+
*/
|
|
142
|
+
async function scanUserApis(apiRoutes: Map<string, ApiRoute>): Promise<void> {
|
|
143
|
+
await scanApisFromDir(projectApiDir, apiRoutes, '', '用户');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 加载所有 API 路由
|
|
148
|
+
* @param apiRoutes - API 路由映射表
|
|
149
|
+
*/
|
|
150
|
+
export async function loadApis(apiRoutes: Map<string, ApiRoute>): Promise<void> {
|
|
151
|
+
try {
|
|
152
|
+
const loadStartTime = Bun.nanoseconds();
|
|
153
|
+
|
|
154
|
+
// 扫描所有 APIs
|
|
155
|
+
await scanCoreApis(apiRoutes);
|
|
156
|
+
await scanAddonApis(apiRoutes);
|
|
157
|
+
await scanUserApis(apiRoutes);
|
|
158
|
+
|
|
159
|
+
const totalLoadTime = calcPerfTime(loadStartTime);
|
|
160
|
+
} catch (error: any) {
|
|
161
|
+
Logger.error('加载 API 时发生错误', error);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
}
|