befly 3.8.0 → 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/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
  /**
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { Logger } from '../lib/logger.js';
7
- import { calcPerfTime, No } from '../util.js';
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, appOptions
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(No('内部服务器错误'));
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
  }
@@ -5,7 +5,8 @@
5
5
 
6
6
  import { Logger } from '../lib/logger.js';
7
7
  import { Database } from '../lib/database.js';
8
- import { Loader } from './loader.js';
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, BeflyOptions } from '../types/befly.js';
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, callback?: (server: Server) => void): Promise<Server> {
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 Loader.loadPlugins({ pluginLists: this.pluginLists, appContext });
44
+ await loadPlugins({
45
+ pluginLists: this.pluginLists,
46
+ appContext: appContext
47
+ });
50
48
 
51
- // 3. 加载所有 API(addon + app)
52
- await this.loadAllApis();
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
- apiRoutes: this.apiRoutes,
60
- pluginLists: this.pluginLists,
61
- appContext,
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
+ }