befly 3.7.0 → 3.7.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 CHANGED
@@ -81,16 +81,12 @@ export const checkDefault = async function (): Promise<void> {
81
81
  for (const addonName of addons) {
82
82
  const addonTablesDir = Addon.getDir(addonName, 'tables');
83
83
 
84
- try {
85
- for await (const file of tablesGlob.scan({
86
- cwd: addonTablesDir,
87
- absolute: true,
88
- onlyFiles: true
89
- })) {
90
- allTableFiles.push({ file: file, type: 'addon', addonName: addonName });
91
- }
92
- } catch (error) {
93
- // addon 的 tables 目录可能不存在,跳过
84
+ for await (const file of tablesGlob.scan({
85
+ cwd: addonTablesDir,
86
+ absolute: true,
87
+ onlyFiles: true
88
+ })) {
89
+ allTableFiles.push({ file: file, type: 'addon', addonName: addonName });
94
90
  }
95
91
  }
96
92
 
@@ -124,98 +120,94 @@ export const checkDefault = async function (): Promise<void> {
124
120
  }
125
121
 
126
122
  // 验证规则格式
123
+ fileRules++;
124
+ totalRules++;
125
+
126
+ // 检查是否使用了保留字段
127
+ if (RESERVED_FIELDS.includes(colKey as any)) {
128
+ Logger.warn(`${fileType}表 ${fileName} 文件包含保留字段 ${colKey},` + `不能在表定义中使用以下字段: ${RESERVED_FIELDS.join(', ')}`);
129
+ fileValid = false;
130
+ }
131
+
132
+ // 使用 parseRule 解析字段规则
133
+ let parsed;
127
134
  try {
128
- fileRules++;
129
- totalRules++;
135
+ parsed = parseRule(rule);
136
+ } catch (error: any) {
137
+ Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 字段规则解析失败:${error.message}`);
138
+ fileValid = false;
139
+ continue;
140
+ }
130
141
 
131
- // 检查是否使用了保留字段
132
- if (RESERVED_FIELDS.includes(colKey as any)) {
133
- Logger.warn(`${fileType}表 ${fileName} 文件包含保留字段 ${colKey},` + `不能在表定义中使用以下字段: ${RESERVED_FIELDS.join(', ')}`);
134
- fileValid = false;
135
- }
142
+ const { name: fieldName, type: fieldType, min: fieldMin, max: fieldMax, default: fieldDefault, index: fieldIndex, regex: fieldRegx } = parsed;
136
143
 
137
- // 使用 parseRule 解析字段规则
138
- let parsed;
139
- try {
140
- parsed = parseRule(rule);
141
- } catch (error: any) {
142
- Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 字段规则解析失败:${error.message}`);
143
- fileValid = false;
144
- continue;
145
- }
144
+ // 第1个值:名称必须为中文、数字、字母、下划线、短横线、空格
145
+ if (!FIELD_NAME_REGEX.test(fieldName)) {
146
+ Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 字段名称 "${fieldName}" 格式错误,` + `必须为中文、数字、字母、下划线、短横线、空格`);
147
+ fileValid = false;
148
+ }
149
+
150
+ // 第2个值:字段类型必须为string,number,text,array_string,array_text之一
151
+ if (!FIELD_TYPES.includes(fieldType as any)) {
152
+ Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 字段类型 "${fieldType}" 格式错误,` + `必须为${FIELD_TYPES.join('、')}之一`);
153
+ fileValid = false;
154
+ }
146
155
 
147
- const { name: fieldName, type: fieldType, min: fieldMin, max: fieldMax, default: fieldDefault, index: fieldIndex, regex: fieldRegx } = parsed;
156
+ // 第3/4个值:需要是 null 数字
157
+ if (!(fieldMin === null || typeof fieldMin === 'number')) {
158
+ Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 最小值 "${fieldMin}" 格式错误,必须为null或数字`);
159
+ fileValid = false;
160
+ }
161
+ if (!(fieldMax === null || typeof fieldMax === 'number')) {
162
+ Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 最大值 "${fieldMax}" 格式错误,必须为null或数字`);
163
+ fileValid = false;
164
+ }
148
165
 
149
- // 第1个值:名称必须为中文、数字、字母、下划线、短横线、空格
150
- if (!FIELD_NAME_REGEX.test(fieldName)) {
151
- Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 字段名称 "${fieldName}" 格式错误,` + `必须为中文、数字、字母、下划线、短横线、空格`);
166
+ // 约束:当最小值与最大值均为数字时,要求最小值 <= 最大值
167
+ if (fieldMin !== null && fieldMax !== null) {
168
+ if (fieldMin > fieldMax) {
169
+ Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 最小值 "${fieldMin}" 不能大于最大值 "${fieldMax}"`);
152
170
  fileValid = false;
153
171
  }
172
+ }
154
173
 
155
- // 第2个值:字段类型必须为string,number,text,array_string,array_text之一
156
- if (!FIELD_TYPES.includes(fieldType as any)) {
157
- Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 字段类型 "${fieldType}" 格式错误,` + `必须为${FIELD_TYPES.join('、')}之一`);
158
- fileValid = false;
159
- }
174
+ // 第6个值:是否创建索引必须为0或1
175
+ if (fieldIndex !== 0 && fieldIndex !== 1) {
176
+ Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 索引标识 "${fieldIndex}" 格式错误,必须为0或1`);
177
+ fileValid = false;
178
+ }
179
+
180
+ // 第7个值:必须为null或正则表达式(parseRule已经验证过了)
181
+ // parseRule 已经将正则字符串转换为 RegExp 或 null,这里不需要再验证
160
182
 
161
- // 第3/4个值:需要是 null 或 数字
162
- if (!(fieldMin === null || typeof fieldMin === 'number')) {
163
- Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 最小值 "${fieldMin}" 格式错误,必须为null或数字`);
183
+ // 第4个值与类型联动校验 + 默认值规则
184
+ if (fieldType === 'text') {
185
+ // text:min/max 必须为 null,默认值必须为 'null'
186
+ if (fieldMin !== null) {
187
+ Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 的 text 类型最小值必须为 null,当前为 "${fieldMin}"`);
164
188
  fileValid = false;
165
189
  }
166
- if (!(fieldMax === null || typeof fieldMax === 'number')) {
167
- Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 最大值 "${fieldMax}" 格式错误,必须为null或数字`);
190
+ if (fieldMax !== null) {
191
+ Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} text 类型最大长度必须为 null,当前为 "${fieldMax}"`);
168
192
  fileValid = false;
169
193
  }
170
-
171
- // 约束:当最小值与最大值均为数字时,要求最小值 <= 最大值
172
- if (fieldMin !== null && fieldMax !== null) {
173
- if (fieldMin > fieldMax) {
174
- Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 最小值 "${fieldMin}" 不能大于最大值 "${fieldMax}"`);
175
- fileValid = false;
176
- }
194
+ if (fieldDefault !== 'null') {
195
+ Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 为 text 类型,默认值必须为 null,当前为 "${fieldDefault}"`);
196
+ fileValid = false;
177
197
  }
178
-
179
- // 第6个值:是否创建索引必须为0或1
180
- if (fieldIndex !== 0 && fieldIndex !== 1) {
181
- Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 索引标识 "${fieldIndex}" 格式错误,必须为0或1`);
198
+ } else if (fieldType === 'string' || fieldType === 'array') {
199
+ if (fieldMax === null || typeof fieldMax !== 'number') {
200
+ Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} ${fieldType} 类型,` + `最大长度必须为数字,当前为 "${fieldMax}"`);
201
+ fileValid = false;
202
+ } else if (fieldMax > MAX_VARCHAR_LENGTH) {
203
+ Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 最大长度 ${fieldMax} 越界,` + `${fieldType} 类型长度必须在 1..${MAX_VARCHAR_LENGTH} 范围内`);
182
204
  fileValid = false;
183
205
  }
184
-
185
- // 第7个值:必须为null或正则表达式(parseRule已经验证过了)
186
- // parseRule 已经将正则字符串转换为 RegExp null,这里不需要再验证
187
-
188
- // 第4个值与类型联动校验 + 默认值规则
189
- if (fieldType === 'text') {
190
- // text:min/max 必须为 null,默认值必须为 'null'
191
- if (fieldMin !== null) {
192
- Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 的 text 类型最小值必须为 null,当前为 "${fieldMin}"`);
193
- fileValid = false;
194
- }
195
- if (fieldMax !== null) {
196
- Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 的 text 类型最大长度必须为 null,当前为 "${fieldMax}"`);
197
- fileValid = false;
198
- }
199
- if (fieldDefault !== 'null') {
200
- Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 为 text 类型,默认值必须为 null,当前为 "${fieldDefault}"`);
201
- fileValid = false;
202
- }
203
- } else if (fieldType === 'string' || fieldType === 'array') {
204
- if (fieldMax === null || typeof fieldMax !== 'number') {
205
- Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 为 ${fieldType} 类型,` + `最大长度必须为数字,当前为 "${fieldMax}"`);
206
- fileValid = false;
207
- } else if (fieldMax > MAX_VARCHAR_LENGTH) {
208
- Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 最大长度 ${fieldMax} 越界,` + `${fieldType} 类型长度必须在 1..${MAX_VARCHAR_LENGTH} 范围内`);
209
- fileValid = false;
210
- }
211
- } else if (fieldType === 'number') {
212
- if (fieldDefault !== 'null' && typeof fieldDefault !== 'number') {
213
- Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} 为 number 类型,` + `默认值必须为数字或null,当前为 "${fieldDefault}"`);
214
- fileValid = false;
215
- }
206
+ } else if (fieldType === 'number') {
207
+ if (fieldDefault !== 'null' && typeof fieldDefault !== 'number') {
208
+ Logger.warn(`${fileType}表 ${fileName} 文件 ${colKey} number 类型,` + `默认值必须为数字或null,当前为 "${fieldDefault}"`);
209
+ fileValid = false;
216
210
  }
217
- } catch (error: any) {
218
- // 单个字段规则解析失败已在上面处理
219
211
  }
220
212
  }
221
213
 
@@ -226,7 +218,7 @@ export const checkDefault = async function (): Promise<void> {
226
218
  invalidFiles++;
227
219
  }
228
220
  } catch (error: any) {
229
- Logger.error(`${fileType}表 ${fileName} 解析失败: ${error.message}`);
221
+ Logger.error(`${fileType}表 ${fileName} 解析失败`, error);
230
222
  invalidFiles++;
231
223
  }
232
224
  }
@@ -241,7 +233,7 @@ export const checkDefault = async function (): Promise<void> {
241
233
  throw new Error('表定义检查失败,请修复上述错误后重试');
242
234
  }
243
235
  } catch (error: any) {
244
- Logger.error('数据表定义检查过程中出错:', error);
236
+ Logger.error('数据表定义检查过程中出错', error);
245
237
  throw error;
246
238
  }
247
239
  };
package/lib/database.ts CHANGED
@@ -94,7 +94,7 @@ export class Database {
94
94
  Logger.error('数据库连接测试失败', error);
95
95
 
96
96
  try {
97
- await sql.close();
97
+ await sql?.close();
98
98
  } catch (cleanupError) {}
99
99
 
100
100
  throw error;
@@ -109,7 +109,7 @@ export class Database {
109
109
  try {
110
110
  await this.sqlClient.close();
111
111
  } catch (error: any) {
112
- Logger.warn('关闭 SQL 连接时出错:', error.message);
112
+ Logger.error('关闭 SQL 连接时出错', error);
113
113
  }
114
114
  this.sqlClient = null;
115
115
  }
@@ -200,7 +200,7 @@ export class Database {
200
200
  try {
201
201
  this.redisClient.close();
202
202
  } catch (error: any) {
203
- Logger.warn('关闭 Redis 连接时出错:', error);
203
+ Logger.error('关闭 Redis 连接时出错', error);
204
204
  }
205
205
  this.redisClient = null;
206
206
  }
package/lib/dbHelper.ts CHANGED
@@ -410,7 +410,7 @@ export class DbHelper {
410
410
  try {
411
411
  processed.id = await this.befly.redis.genTimeID();
412
412
  } catch (error: any) {
413
- throw new Error(`生成 ID 失败,Redis 可能不可用 (table: ${table}): ${error.message}`);
413
+ throw new Error(`生成 ID 失败,Redis 可能不可用 (table: ${table})`, error);
414
414
  }
415
415
 
416
416
  // 强制生成时间戳(不可被用户覆盖)
@@ -634,7 +634,7 @@ export class DbHelper {
634
634
  await conn.query('ROLLBACK');
635
635
  Logger.warn('事务已回滚');
636
636
  } catch (rollbackError: any) {
637
- Logger.error('事务回滚失败:', rollbackError);
637
+ Logger.error('事务回滚失败', rollbackError);
638
638
  }
639
639
  }
640
640
  throw error;
package/lib/middleware.ts CHANGED
@@ -132,8 +132,8 @@ export async function parsePostParams(api: ApiRoute, ctx: RequestContext): Promi
132
132
  }
133
133
 
134
134
  return true;
135
- } catch (err: any) {
136
- Logger.error('处理请求参数时发生错误', err);
135
+ } catch (error: any) {
136
+ Logger.error('处理请求参数时发生错误', error);
137
137
  return false;
138
138
  }
139
139
  }
@@ -53,7 +53,6 @@ export class Lifecycle {
53
53
 
54
54
  // 4. 启动 HTTP 服务器
55
55
  const totalStartupTime = calcPerfTime(serverStartTime);
56
- Logger.info(`✓ 服务器启动准备完成,总耗时: ${totalStartupTime}`);
57
56
 
58
57
  return await Bootstrap.start(
59
58
  {
@@ -212,7 +212,6 @@ export class Loader {
212
212
  }
213
213
  }
214
214
  const addonPluginsInitTime = calcPerfTime(addonPluginsInitStart);
215
- Logger.info(`✓ 组件插件加载完成: ${addonPlugins.length} 个,耗时: ${addonPluginsScanTime}`);
216
215
  }
217
216
  }
218
217
 
@@ -275,12 +274,10 @@ export class Loader {
275
274
  }
276
275
  }
277
276
  const userPluginsInitTime = calcPerfTime(userPluginsInitStart);
278
- Logger.info(`✓ 用户插件加载完成: ${sortedUserPlugins.length} 个,耗时: ${userPluginsInitTime}`);
279
277
  }
280
278
 
281
279
  const totalLoadTime = calcPerfTime(loadStartTime);
282
280
  const totalPluginCount = sortedCorePlugins.length + addonPlugins.length + sortedUserPlugins.length;
283
- Logger.info(`✓ 所有插件加载完成: ${totalPluginCount} 个,总耗时: ${totalLoadTime}`);
284
281
 
285
282
  // 核心插件失败 → 关键错误,必须退出
286
283
  if (hadCorePluginError) {
@@ -412,7 +409,6 @@ export class Loader {
412
409
  }
413
410
 
414
411
  const totalLoadTime = calcPerfTime(loadStartTime);
415
- Logger.info(`✓ ${dirDisplayName}接口加载完成: ${loadedApis}/${totalApis},耗时: ${totalLoadTime}`);
416
412
 
417
413
  // 检查是否有加载失败的 API(理论上不会到达这里,因为上面已经 critical 退出)
418
414
  if (failedApis > 0) {
package/main.ts CHANGED
@@ -41,23 +41,20 @@ export class Befly {
41
41
  async listen(callback?: (server: Server) => void): Promise<Server> {
42
42
  const server = await this.lifecycle.start(this.appContext, callback);
43
43
 
44
- // 注册优雅关闭信号处理器
45
44
  const gracefulShutdown = async (signal: string) => {
46
- Logger.info(`\n收到 ${signal} 信号,开始优雅关闭...`);
47
-
48
45
  // 1. 停止接收新请求
49
46
  server.stop(true);
50
- Logger.info('HTTP 服务器已停止');
47
+ Logger.info('HTTP 服务器已停止');
51
48
 
52
49
  // 2. 关闭数据库连接
53
50
  try {
54
51
  await Database.disconnect();
55
- Logger.info('数据库连接已关闭');
52
+ Logger.info('数据库连接已关闭');
56
53
  } catch (error: any) {
57
- Logger.warn('⚠️ 关闭数据库连接时出错:', error.message);
54
+ Logger.err('关闭数据库连接时出错:', error);
58
55
  }
59
56
 
60
- Logger.info('服务器已优雅关闭');
57
+ Logger.info('服务器已优雅关闭');
61
58
  process.exit(0);
62
59
  };
63
60
 
package/menu.json CHANGED
@@ -7,61 +7,49 @@
7
7
  "type": 1
8
8
  },
9
9
  {
10
- "name": "管理员管理",
11
- "path": "/admin",
12
- "icon": "Users",
10
+ "name": "人员管理",
11
+ "path": "/people",
12
+ "icon": "UserCircle",
13
13
  "sort": 2,
14
- "type": 1
14
+ "type": 1,
15
+ "children": [
16
+ {
17
+ "name": "管理员管理",
18
+ "path": "/admin",
19
+ "icon": "Users",
20
+ "sort": 2,
21
+ "type": 1
22
+ }
23
+ ]
15
24
  },
16
25
  {
17
- "name": "新闻管理",
18
- "path": "/news",
19
- "icon": "Newspaper",
26
+ "name": "权限设置",
27
+ "path": "/settings",
28
+ "icon": "Settings",
20
29
  "sort": 3,
21
- "type": 1
22
- },
23
- {
24
- "name": "菜单管理",
25
- "path": "/menu",
26
- "icon": "Menu",
27
- "sort": 4,
28
- "type": 1
29
- },
30
- {
31
- "name": "角色管理",
32
- "path": "/role",
33
- "icon": "Users",
34
- "sort": 5,
35
- "type": 1
36
- },
37
- {
38
- "name": "字典管理",
39
- "path": "/dict",
40
- "icon": "BookOpen",
41
- "sort": 6,
42
- "type": 1
43
- },
44
- {
45
- "name": "用户管理",
46
- "path": "/user",
47
- "icon": "UserCog",
48
- "sort": 7,
49
30
  "type": 1,
50
31
  "children": [
51
32
  {
52
- "name": "用户列表",
53
- "path": "/list",
54
- "icon": "Minus",
55
- "sort": 1,
33
+ "name": "菜单管理",
34
+ "path": "/menu",
35
+ "icon": "Menu",
36
+ "sort": 4,
56
37
  "type": 1
57
38
  },
58
39
  {
59
- "name": "用户权限",
60
- "path": "/permission",
61
- "icon": "Minus",
62
- "sort": 2,
40
+ "name": "角色管理",
41
+ "path": "/role",
42
+ "icon": "Users",
43
+ "sort": 5,
63
44
  "type": 1
64
45
  }
65
46
  ]
47
+ },
48
+ {
49
+ "name": "字典管理",
50
+ "path": "/dict",
51
+ "icon": "BookOpen",
52
+ "sort": 6,
53
+ "type": 1
66
54
  }
67
55
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.7.0",
3
+ "version": "3.7.2",
4
4
  "description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
5
5
  "type": "module",
6
6
  "private": false,
@@ -69,5 +69,5 @@
69
69
  "es-toolkit": "^1.41.0",
70
70
  "pathe": "^2.0.3"
71
71
  },
72
- "gitHead": "cdd2d42fdd5e0f887b434fa70dff72028d9e31ed"
72
+ "gitHead": "4e67637bf6481e2455901b0dba798bfa52236898"
73
73
  }
package/router/api.ts CHANGED
@@ -115,7 +115,7 @@ export function apiHandler(apiRoutes: Map<string, ApiRoute>, pluginLists: Plugin
115
115
  }
116
116
  } catch (error: any) {
117
117
  // 记录详细的错误日志
118
- Logger.warn(api ? `接口 [${api.name}] 执行失败` : '处理接口请求时发生错误', error);
118
+ Logger.error(api ? `接口 [${api.name}] 执行失败` : '处理接口请求时发生错误', error);
119
119
 
120
120
  return Response.json(No('内部服务器错误'), {
121
121
  headers: corsOptions.headers