befly 3.8.30 → 3.8.32

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 (47) hide show
  1. package/README.md +83 -0
  2. package/{config.ts → befly.config.ts} +26 -6
  3. package/checks/checkApp.ts +31 -1
  4. package/hooks/cors.ts +3 -3
  5. package/hooks/parser.ts +3 -3
  6. package/hooks/validator.ts +1 -1
  7. package/lib/cacheHelper.ts +0 -6
  8. package/lib/cipher.ts +2 -1
  9. package/lib/connect.ts +17 -19
  10. package/lib/jwt.ts +1 -1
  11. package/lib/logger.ts +1 -1
  12. package/lib/validator.ts +149 -384
  13. package/loader/loadHooks.ts +4 -3
  14. package/loader/loadPlugins.ts +7 -9
  15. package/main.ts +22 -36
  16. package/package.json +6 -5
  17. package/plugins/cipher.ts +1 -1
  18. package/plugins/config.ts +3 -4
  19. package/plugins/db.ts +4 -5
  20. package/plugins/jwt.ts +3 -2
  21. package/plugins/logger.ts +6 -6
  22. package/plugins/redis.ts +8 -12
  23. package/router/static.ts +3 -6
  24. package/sync/syncAll.ts +7 -12
  25. package/sync/syncApi.ts +4 -3
  26. package/sync/syncDb.ts +6 -5
  27. package/sync/syncDev.ts +9 -8
  28. package/sync/syncMenu.ts +174 -132
  29. package/tests/integration.test.ts +2 -6
  30. package/tests/redisHelper.test.ts +1 -2
  31. package/tests/validator.test.ts +611 -85
  32. package/types/befly.d.ts +7 -0
  33. package/types/cache.d.ts +73 -0
  34. package/types/common.d.ts +1 -37
  35. package/types/database.d.ts +5 -0
  36. package/types/index.ts +5 -5
  37. package/types/plugin.d.ts +1 -4
  38. package/types/redis.d.ts +37 -2
  39. package/types/table.d.ts +6 -44
  40. package/util.ts +283 -0
  41. package/tests/validator-advanced.test.ts +0 -653
  42. package/types/addon.d.ts +0 -50
  43. package/types/crypto.d.ts +0 -23
  44. package/types/jwt.d.ts +0 -99
  45. package/types/logger.d.ts +0 -13
  46. package/types/tool.d.ts +0 -67
  47. package/types/validator.d.ts +0 -43
package/README.md CHANGED
@@ -196,6 +196,89 @@ DB_NAME=/path/to/database.sqlite
196
196
  DB_NAME=:memory:
197
197
  ```
198
198
 
199
+ ## ⚙️ 项目配置文件
200
+
201
+ Befly 使用 `befly.config.ts` 作为统一配置文件:
202
+
203
+ ```typescript
204
+ // befly.config.ts
205
+ export const beflyConfig = {
206
+ appName: '我的应用',
207
+ appPort: 3000,
208
+ appHost: '0.0.0.0',
209
+
210
+ // 数据库配置(优先使用环境变量)
211
+ db: {
212
+ type: 'mysql',
213
+ host: '127.0.0.1',
214
+ port: 3306,
215
+ username: 'root',
216
+ password: 'password',
217
+ database: 'my_database'
218
+ },
219
+
220
+ // Redis 配置
221
+ redis: {
222
+ host: '127.0.0.1',
223
+ port: 6379,
224
+ prefix: 'befly:'
225
+ },
226
+
227
+ // CORS 跨域配置
228
+ cors: {
229
+ origin: ['http://localhost:5173'],
230
+ methods: ['GET', 'POST', 'PUT', 'DELETE']
231
+ },
232
+
233
+ // Addon 插件配置
234
+ addons: {
235
+ admin: {
236
+ email: { host: 'smtp.qq.com' }
237
+ }
238
+ }
239
+ };
240
+ ```
241
+
242
+ ### 数据库连接
243
+
244
+ 框架会自动从 `beflyConfig` 获取配置并建立连接,无需手动传参:
245
+
246
+ ```typescript
247
+ import { Connect } from 'befly/lib/connect';
248
+
249
+ // 连接 SQL 数据库(配置自动从 beflyConfig.db 获取)
250
+ await Connect.connectSql();
251
+
252
+ // 连接 Redis(配置自动从 beflyConfig.redis 获取)
253
+ await Connect.connectRedis();
254
+
255
+ // 同时连接 SQL 和 Redis
256
+ await Connect.connect();
257
+
258
+ // 获取连接状态
259
+ const status = Connect.getStatus();
260
+ console.log(status.sql.connected); // true/false
261
+ console.log(status.redis.connected); // true/false
262
+
263
+ // 断开连接
264
+ await Connect.disconnect();
265
+ ```
266
+
267
+ ### 配置文件迁移指南
268
+
269
+ 如果你的项目之前使用 `app.config.ts`,请按以下步骤迁移:
270
+
271
+ 1. **重命名文件**:`app.config.ts` → `befly.config.ts`
272
+ 2. **更新导出名**:`config` → `beflyConfig`
273
+
274
+ ```typescript
275
+ // 旧写法
276
+ export const config = { ... };
277
+
278
+ // 新写法
279
+ export const beflyConfig = { ... };
280
+ ```
281
+
199
282
  ## 📖 文档
200
283
 
201
284
  完整文档请访问 [`/docs` 目录](./docs/):
@@ -1,10 +1,14 @@
1
1
  /**
2
- * Befly 默认配置
3
- * 包含所有配置项的默认值
2
+ * Befly 配置模块
3
+ * 自动加载 configs 目录下的配置文件并与默认配置合并
4
+ * 支持环境分离:befly.common.json + befly.dev/prod.json + befly.local.json
4
5
  */
6
+ import { scanConfig } from 'befly-shared/scanConfig';
7
+
5
8
  import type { BeflyOptions } from './types/befly.js';
6
9
 
7
- export const defaultOptions: Required<Omit<BeflyOptions, 'devPassword'>> = {
10
+ /** 默认配置 */
11
+ const defaultOptions: BeflyOptions = {
8
12
  // ========== 核心参数 ==========
9
13
  nodeEnv: (process.env.NODE_ENV as any) || 'development',
10
14
  appName: '野蜂飞舞',
@@ -63,8 +67,24 @@ export const defaultOptions: Required<Omit<BeflyOptions, 'devPassword'>> = {
63
67
  },
64
68
 
65
69
  // ========== 禁用配置 ==========
66
- /** 禁用的钩子列表 */
67
70
  disableHooks: [],
68
- /** 禁用的插件列表 */
69
- disablePlugins: []
71
+ disablePlugins: [],
72
+ hiddenMenus: [],
73
+
74
+ // ========== Addon 配置 ==========
75
+ addons: {}
70
76
  };
77
+
78
+ // 确定环境
79
+ const nodeEnv = process.env.NODE_ENV || 'development';
80
+ const envSuffix = nodeEnv === 'production' ? 'prod' : 'dev';
81
+
82
+ // 使用 scanConfig 一次性加载并合并所有配置文件
83
+ // 合并顺序:defaultOptions ← befly.common.json ← befly.dev/prod.json ← befly.local.json
84
+ export const beflyConfig = (await scanConfig({
85
+ dirs: ['configs'],
86
+ files: ['befly.common', `befly.${envSuffix}`, 'befly.local'],
87
+ extensions: ['.json'],
88
+ mode: 'merge',
89
+ defaults: defaultOptions
90
+ })) as BeflyOptions;
@@ -1,6 +1,6 @@
1
1
  // 内部依赖
2
2
  import { join } from 'node:path';
3
- import { existsSync, mkdirSync } from 'node:fs';
3
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
4
4
 
5
5
  // 相对导入
6
6
  import { Logger } from '../lib/logger.js';
@@ -24,6 +24,36 @@ export async function checkApp(): Promise<void> {
24
24
  if (!existsSync(logsDir)) {
25
25
  mkdirSync(logsDir, { recursive: true });
26
26
  }
27
+
28
+ // 检查并创建 configs 目录和配置文件
29
+ const configsDir = join(projectDir, 'configs');
30
+ if (!existsSync(configsDir)) {
31
+ mkdirSync(configsDir, { recursive: true });
32
+ }
33
+
34
+ // 检查并创建 befly.common.json
35
+ const beflyJsonPath = join(configsDir, 'befly.common.json');
36
+ if (!existsSync(beflyJsonPath)) {
37
+ writeFileSync(beflyJsonPath, '{}', 'utf-8');
38
+ }
39
+
40
+ // 检查并创建 befly.dev.json
41
+ const beflyDevJsonPath = join(configsDir, 'befly.dev.json');
42
+ if (!existsSync(beflyDevJsonPath)) {
43
+ writeFileSync(beflyDevJsonPath, '{}', 'utf-8');
44
+ }
45
+
46
+ // 检查并创建 befly.prod.json
47
+ const beflyProdJsonPath = join(configsDir, 'befly.prod.json');
48
+ if (!existsSync(beflyProdJsonPath)) {
49
+ writeFileSync(beflyProdJsonPath, '{}', 'utf-8');
50
+ }
51
+
52
+ // 检查并创建 befly.local.json
53
+ const beflyLocalJsonPath = join(configsDir, 'befly.local.json');
54
+ if (!existsSync(beflyLocalJsonPath)) {
55
+ writeFileSync(beflyLocalJsonPath, '{}', 'utf-8');
56
+ }
27
57
  } catch (error: any) {
28
58
  Logger.error('项目结构检查过程中出错', error);
29
59
  throw error;
package/hooks/cors.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // 相对导入
2
2
  import { setCorsOptions } from '../util.js';
3
+ import { beflyConfig } from '../befly.config.js';
3
4
 
4
5
  // 类型导入
5
6
  import type { Hook } from '../types/hook.js';
@@ -24,11 +25,10 @@ const hook: Hook = {
24
25
  credentials: 'true'
25
26
  };
26
27
 
27
- const userConfig = (hook as any).config || {};
28
- const config = { ...defaultConfig, ...userConfig };
28
+ const corsConfig = { ...defaultConfig, ...(beflyConfig.cors || {}) };
29
29
 
30
30
  // 设置 CORS 响应头
31
- const headers = setCorsOptions(req, config);
31
+ const headers = setCorsOptions(req, corsConfig);
32
32
 
33
33
  ctx.corsHeaders = headers;
34
34
 
package/hooks/parser.ts CHANGED
@@ -53,12 +53,12 @@ const hook: Hook = {
53
53
  }
54
54
  } else {
55
55
  // 不支持的 Content-Type
56
- ctx.response = ErrorResponse(ctx, '无效的请求参数格式');
56
+ ctx.response = ErrorResponse(ctx, '无效的请求参数格式', 1, null, { location: 'content-type', value: contentType });
57
57
  return;
58
58
  }
59
- } catch (e) {
59
+ } catch (e: any) {
60
60
  // 解析失败
61
- ctx.response = ErrorResponse(ctx, '无效的请求参数格式');
61
+ ctx.response = ErrorResponse(ctx, '无效的请求参数格式', 1, null, { location: 'body', error: e.message });
62
62
  return;
63
63
  }
64
64
  }
@@ -23,7 +23,7 @@ const hook: Hook = {
23
23
  const result = Validator.validate(ctx.body, ctx.api.fields, ctx.api.required || []);
24
24
 
25
25
  if (result.code !== 0) {
26
- ctx.response = ErrorResponse(ctx, '无效的请求参数格式', 1, result.fields);
26
+ ctx.response = ErrorResponse(ctx, result.firstError || '参数验证失败', 1, null, result.fieldErrors);
27
27
  return;
28
28
  }
29
29
  }
@@ -46,8 +46,6 @@ export class CacheHelper {
46
46
 
47
47
  if (result === null) {
48
48
  Logger.warn('⚠️ 接口缓存失败');
49
- } else {
50
- Logger.info(`✅ 已缓存 ${apiList.length} 个接口到 Redis (Key: ${RedisKeys.apisAll()})`);
51
49
  }
52
50
  } catch (error: any) {
53
51
  Logger.error({ err: error }, '⚠️ 接口缓存异常');
@@ -78,8 +76,6 @@ export class CacheHelper {
78
76
 
79
77
  if (result === null) {
80
78
  Logger.warn('⚠️ 菜单缓存失败');
81
- } else {
82
- Logger.info(`✅ 已缓存 ${menus.length} 个菜单到 Redis (Key: ${RedisKeys.menusAll()})`);
83
79
  }
84
80
  } catch (error: any) {
85
81
  Logger.warn({ err: error }, '⚠️ 菜单缓存异常');
@@ -159,8 +155,6 @@ export class CacheHelper {
159
155
 
160
156
  // 统计成功缓存的角色数
161
157
  const cachedRoles = results.filter((r) => r > 0).length;
162
-
163
- Logger.info(`✅ 已缓存 ${cachedRoles} 个角色的接口权限(共 ${cacheOperations.reduce((sum, op) => sum + op.apiPaths.length, 0)} 个接口)`);
164
158
  } catch (error: any) {
165
159
  Logger.warn({ err: error }, '⚠️ 角色权限缓存异常');
166
160
  }
package/lib/cipher.ts CHANGED
@@ -4,7 +4,8 @@
4
4
  */
5
5
 
6
6
  import { createSign } from 'node:crypto';
7
- import type { EncodingType, HashAlgorithm, PasswordHashOptions } from '../types/crypto';
7
+
8
+ import type { EncodingType, HashAlgorithm, PasswordHashOptions } from 'befly-shared/types';
8
9
 
9
10
  /**
10
11
  * 加密工具类
package/lib/connect.ts CHANGED
@@ -1,18 +1,18 @@
1
1
  /**
2
2
  * 数据库连接管理器
3
3
  * 统一管理 SQL 和 Redis 连接
4
+ * 配置从 beflyConfig 全局对象获取
4
5
  */
5
6
 
6
7
  import { SQL, RedisClient } from 'bun';
7
8
 
9
+ import { beflyConfig } from '../befly.config.js';
8
10
  import { Logger } from './logger.js';
9
11
 
10
- import type { BeflyOptions, DatabaseConfig, RedisConfig } from '../types/befly.js';
11
- import type { SqlClientOptions } from '../types/database.js';
12
-
13
12
  /**
14
13
  * 数据库连接管理器
15
14
  * 使用静态方法管理全局单例连接
15
+ * 所有配置从 beflyConfig 自动获取
16
16
  */
17
17
  export class Connect {
18
18
  private static sqlClient: SQL | null = null;
@@ -29,10 +29,12 @@ export class Connect {
29
29
 
30
30
  /**
31
31
  * 连接 SQL 数据库
32
- * @param config - 数据库配置
32
+ * 配置从 beflyConfig.db 获取
33
33
  * @returns SQL 客户端实例
34
34
  */
35
- static async connectSql(config: DatabaseConfig): Promise<SQL> {
35
+ static async connectSql(): Promise<SQL> {
36
+ const config = beflyConfig.db || {};
37
+
36
38
  // 构建数据库连接字符串
37
39
  const type = config.type || 'mysql';
38
40
  const host = config.host || '127.0.0.1';
@@ -135,10 +137,12 @@ export class Connect {
135
137
 
136
138
  /**
137
139
  * 连接 Redis
138
- * @param config - Redis 配置
140
+ * 配置从 beflyConfig.redis 获取
139
141
  * @returns Redis 客户端实例
140
142
  */
141
- static async connectRedis(config: RedisConfig = {}): Promise<RedisClient> {
143
+ static async connectRedis(): Promise<RedisClient> {
144
+ const config = beflyConfig.redis || {};
145
+
142
146
  try {
143
147
  // 构建 Redis URL
144
148
  const host = config.host || '127.0.0.1';
@@ -208,21 +212,15 @@ export class Connect {
208
212
 
209
213
  /**
210
214
  * 连接所有数据库(SQL + Redis)
211
- * @param config - Befly 配置对象(可选)
212
- * @param options - 连接选项
215
+ * 配置从 beflyConfig 自动获取
213
216
  */
214
- static async connect(config?: BeflyOptions, options?: { sql?: SqlClientOptions; redis?: boolean }): Promise<void> {
217
+ static async connect(): Promise<void> {
215
218
  try {
216
- // 如果 sql 参数不是 false,则连接 SQL
217
- // 优先级:options.sql > config.db > 跳过
218
- const sqlConfig = options?.db || {};
219
- if (sqlConfig) {
220
- await this.connectSql(sqlConfig);
221
- }
219
+ // 连接 SQL
220
+ await this.connectSql();
222
221
 
223
- // 如果 redis 参数不是 false,则连接 Redis
224
- const redisConfig = config?.redis || {};
225
- await this.connectRedis(redisConfig);
222
+ // 连接 Redis
223
+ await this.connectRedis();
226
224
  } catch (error: any) {
227
225
  Logger.error({ err: error }, '数据库初始化失败');
228
226
  await this.disconnect();
package/lib/jwt.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  import { createSigner, createVerifier, createDecoder } from 'fast-jwt';
6
6
 
7
7
  import type { AuthConfig } from '../types/befly.js';
8
- import type { JwtPayload, JwtSignOptions, JwtVerifyOptions, JwtDecoded, JwtHeader } from '../types/jwt.js';
8
+ import type { JwtPayload, JwtSignOptions, JwtVerifyOptions, JwtDecoded, JwtHeader } from 'befly-shared/types';
9
9
 
10
10
  interface FastJwtComplete {
11
11
  header: JwtHeader;
package/lib/logger.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  import pino from 'pino';
6
6
  import { join } from 'pathe';
7
7
 
8
- import type { LoggerConfig } from '../types/logger.js';
8
+ import type { LoggerConfig } from 'befly-shared/types';
9
9
 
10
10
  let instance: pino.Logger | null = null;
11
11
  let mockInstance: pino.Logger | null = null;