befly 3.8.25 → 3.8.27

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 (61) hide show
  1. package/config.ts +8 -9
  2. package/hooks/{rateLimit.ts → _rateLimit.ts} +7 -13
  3. package/hooks/auth.ts +3 -11
  4. package/hooks/cors.ts +1 -4
  5. package/hooks/parser.ts +6 -8
  6. package/hooks/permission.ts +9 -12
  7. package/hooks/validator.ts +6 -9
  8. package/lib/cacheHelper.ts +0 -4
  9. package/lib/{database.ts → connect.ts} +65 -18
  10. package/lib/logger.ts +1 -17
  11. package/lib/redisHelper.ts +6 -5
  12. package/loader/loadApis.ts +3 -3
  13. package/loader/loadHooks.ts +15 -41
  14. package/loader/loadPlugins.ts +10 -16
  15. package/main.ts +25 -28
  16. package/package.json +3 -3
  17. package/plugins/cache.ts +2 -2
  18. package/plugins/cipher.ts +15 -0
  19. package/plugins/config.ts +16 -0
  20. package/plugins/db.ts +7 -17
  21. package/plugins/jwt.ts +15 -0
  22. package/plugins/logger.ts +1 -1
  23. package/plugins/redis.ts +4 -4
  24. package/plugins/tool.ts +50 -0
  25. package/router/api.ts +56 -42
  26. package/router/static.ts +12 -12
  27. package/sync/syncAll.ts +2 -20
  28. package/sync/syncApi.ts +7 -7
  29. package/sync/syncDb/apply.ts +1 -4
  30. package/sync/syncDb/constants.ts +3 -0
  31. package/sync/syncDb/ddl.ts +2 -1
  32. package/sync/syncDb/helpers.ts +5 -117
  33. package/sync/syncDb/sqlite.ts +1 -3
  34. package/sync/syncDb/table.ts +8 -142
  35. package/sync/syncDb/tableCreate.ts +25 -9
  36. package/sync/syncDb/types.ts +125 -0
  37. package/sync/syncDb/version.ts +0 -3
  38. package/sync/syncDb.ts +146 -6
  39. package/sync/syncDev.ts +19 -15
  40. package/sync/syncMenu.ts +87 -75
  41. package/tests/redisHelper.test.ts +15 -16
  42. package/tests/sync-connection.test.ts +189 -0
  43. package/tests/syncDb-apply.test.ts +287 -0
  44. package/tests/syncDb-constants.test.ts +150 -0
  45. package/tests/syncDb-ddl.test.ts +205 -0
  46. package/tests/syncDb-helpers.test.ts +112 -0
  47. package/tests/syncDb-schema.test.ts +178 -0
  48. package/tests/syncDb-types.test.ts +129 -0
  49. package/tsconfig.json +2 -2
  50. package/types/api.d.ts +1 -1
  51. package/types/befly.d.ts +23 -21
  52. package/types/common.d.ts +0 -29
  53. package/types/context.d.ts +8 -6
  54. package/types/hook.d.ts +3 -4
  55. package/types/plugin.d.ts +3 -0
  56. package/hooks/errorHandler.ts +0 -23
  57. package/hooks/requestId.ts +0 -24
  58. package/hooks/requestLogger.ts +0 -25
  59. package/hooks/responseFormatter.ts +0 -64
  60. package/router/root.ts +0 -56
  61. package/sync/syncDb/index.ts +0 -164
package/types/befly.d.ts CHANGED
@@ -146,32 +146,34 @@ export interface BeflyOptions {
146
146
  * 包含所有插件挂载的实例
147
147
  */
148
148
  export interface BeflyContext extends KeyValue {
149
- // ========== 核心插件(带下划线前缀) ==========
150
- /** 数据库助手 (db 插件) */
151
- _db?: DbHelper;
152
-
153
- /** Redis 助手 (redis 插件) */
154
- _redis?: RedisHelper;
155
-
156
- /** 日志器 (logger 插件) */
157
- _logger?: typeof Logger;
158
-
159
- /** 缓存助手 (cache 插件) */
160
- _cache?: CacheHelper;
161
-
162
- // ========== 核心插件便捷访问(无前缀) ==========
163
- /** 数据库助手便捷访问 */
149
+ // ========== 核心插件 ==========
150
+ /** 数据库助手 */
164
151
  db?: DbHelper;
165
152
 
166
- /** Redis 助手便捷访问 */
153
+ /** Redis 助手 */
167
154
  redis?: RedisHelper;
168
155
 
169
- /** 日志器便捷访问 */
156
+ /** 日志器 */
170
157
  logger?: typeof Logger;
171
158
 
172
- /** 缓存助手便捷访问 */
159
+ /** 缓存助手 */
173
160
  cache?: CacheHelper;
174
161
 
162
+ /** 工具函数 */
163
+ tool?: {
164
+ Yes: (msg: string, data?: any, other?: Record<string, any>) => { code: 0; msg: string; data: any };
165
+ No: (msg: string, data?: any, other?: Record<string, any>) => { code: 1; msg: string; data: any };
166
+ };
167
+
168
+ /** 加密解密 */
169
+ cipher?: typeof Cipher;
170
+
171
+ /** JWT 令牌 */
172
+ jwt?: typeof Jwt;
173
+
174
+ /** 项目配置 */
175
+ config?: BeflyOptions;
176
+
175
177
  // ========== 动态插件 ==========
176
178
  /** 组件插件:addon_{addonName}_{pluginName} */
177
179
  /** 项目插件:app_{pluginName} */
@@ -183,13 +185,13 @@ export interface BeflyContext extends KeyValue {
183
185
  */
184
186
  export interface Befly {
185
187
  /** API 路由映射 */
186
- apiRoutes: Map<string, ApiRoute>;
188
+ apis: Map<string, ApiRoute>;
187
189
 
188
190
  /** 插件列表 */
189
- pluginLists: Plugin[];
191
+ plugins: Plugin[];
190
192
 
191
193
  /** 应用上下文 */
192
- appContext: KeyValue;
194
+ context: KeyValue;
193
195
 
194
196
  /** 应用选项 */
195
197
  appOptions: BeflyOptions;
package/types/common.d.ts CHANGED
@@ -128,35 +128,6 @@ export interface LoggerConfig {
128
128
  };
129
129
  }
130
130
 
131
- /**
132
- * 环境变量类型
133
- */
134
- export interface EnvConfig {
135
- // 服务配置
136
- appName: string;
137
- appHost: string;
138
- appPort: number;
139
-
140
- // 数据库配置
141
- mysqlHost: string;
142
- mysqlPort: number;
143
- mysqlUsername: string;
144
- mysqlPassword: string;
145
- mysqlDatabase: string;
146
-
147
- // Redis 配置
148
- redisHost?: string;
149
- redisPort?: number;
150
- redisPassword?: string;
151
-
152
- // JWT 配置
153
- jwtSecret: string;
154
- jwtExpires?: string;
155
-
156
- // 其他配置
157
- [key: string]: any;
158
- }
159
-
160
131
  /**
161
132
  * 工具函数返回类型
162
133
  */
@@ -17,17 +17,19 @@ export interface RequestContext {
17
17
  /** 请求开始时间(毫秒) */
18
18
  now: number;
19
19
  /** 客户端 IP 地址 */
20
- ip?: string;
20
+ ip: string;
21
+ /** 请求头 */
22
+ headers: Headers;
21
23
  /** API 路由路径(如 POST/api/user/login) */
22
- route?: string;
24
+ route: string;
25
+ /** 请求唯一 ID */
26
+ requestId: string;
27
+ /** CORS 响应头 */
28
+ corsHeaders: Record<string, string>;
23
29
  /** 当前请求的 API 路由对象 */
24
30
  api?: ApiRoute;
25
31
  /** 响应对象(如果设置了此属性,将直接返回该响应) */
26
32
  response?: Response;
27
33
  /** 原始处理结果(未转换为 Response 对象前) */
28
34
  result?: any;
29
- /** 请求唯一 ID */
30
- requestId?: string;
31
- /** CORS 响应头 */
32
- corsHeaders?: Record<string, string>;
33
35
  }
package/types/hook.d.ts CHANGED
@@ -6,10 +6,9 @@ import type { BeflyContext } from './befly.js';
6
6
  import type { RequestContext } from './context.js';
7
7
 
8
8
  /**
9
- * 钩子处理函数类型
9
+ * 钩子处理函数类型(串联模式,无 next 参数)
10
10
  */
11
- export type Next = () => Promise<void>;
12
- export type HookHandler = (befly: BeflyContext, ctx: RequestContext, next: Next) => Promise<void> | void;
11
+ export type HookHandler = (befly: BeflyContext, ctx: RequestContext) => Promise<void> | void;
13
12
 
14
13
  /**
15
14
  * 钩子配置类型
@@ -21,7 +20,7 @@ export interface Hook {
21
20
  /** 依赖的钩子列表(在这些钩子之后执行) */
22
21
  after?: string[];
23
22
 
24
- /** 执行顺序(数字越小越先执行,同级别时使用) */
23
+ /** 执行顺序(数字越小越先执行) */
25
24
  order?: number;
26
25
 
27
26
  /** 钩子处理函数 */
package/types/plugin.d.ts CHANGED
@@ -28,6 +28,9 @@ export interface Plugin {
28
28
  after?: string[];
29
29
 
30
30
  /** 插件初始化函数 */
31
+ handler?: (context: BeflyContext, config?: Record<string, any>) => any | Promise<any>;
32
+
33
+ /** @deprecated use handler instead */
31
34
  onInit?: PluginInitFunction;
32
35
 
33
36
  /** 插件配置 */
@@ -1,23 +0,0 @@
1
- // 相对导入
2
- import { Logger } from '../lib/logger.js';
3
- import { JsonResponse } from '../util.js';
4
-
5
- // 类型导入
6
- import type { Hook } from '../types/hook.js';
7
-
8
- const hook: Hook = {
9
- order: 1,
10
- handler: async (befly, ctx, next) => {
11
- try {
12
- await next();
13
- } catch (err: any) {
14
- // 记录错误信息
15
- const apiPath = ctx.api ? `${ctx.req.method}${new URL(ctx.req.url).pathname}` : ctx.req.url;
16
- Logger.error(`Request Error: ${apiPath}`, err);
17
-
18
- // 设置错误响应
19
- ctx.response = JsonResponse(ctx, '内部服务错误');
20
- }
21
- }
22
- };
23
- export default hook;
@@ -1,24 +0,0 @@
1
- import type { Hook } from '../types/hook.js';
2
- import { logContextStorage } from '../lib/logger.js';
3
-
4
- const hook: Hook = {
5
- after: ['errorHandler'],
6
- order: 3,
7
- handler: async (befly, ctx, next) => {
8
- // 生成唯一请求 ID
9
- const requestId = crypto.randomUUID();
10
- ctx.requestId = requestId;
11
-
12
- // 添加到 CORS 响应头
13
- if (!ctx.corsHeaders) {
14
- ctx.corsHeaders = {};
15
- }
16
- ctx.corsHeaders['X-Request-ID'] = requestId;
17
-
18
- // 在 AsyncLocalStorage 上下文中执行后续钩子
19
- await logContextStorage.run({ requestId }, async () => {
20
- await next();
21
- });
22
- }
23
- };
24
- export default hook;
@@ -1,25 +0,0 @@
1
- // 相对导入
2
- import { Logger } from '../lib/logger.js';
3
-
4
- // 类型导入
5
- import type { Hook } from '../types/hook.js';
6
-
7
- /**
8
- * 请求日志记录钩子
9
- * 记录请求方法、路径、用户信息和响应时间
10
- */
11
- const hook: Hook = {
12
- after: ['parser'],
13
- order: 30,
14
- handler: async (befly, ctx, next) => {
15
- await next();
16
-
17
- if (ctx.api) {
18
- const apiPath = `${ctx.req.method}${new URL(ctx.req.url).pathname}`;
19
- const duration = Date.now() - ctx.now;
20
- const user = ctx.user?.userId ? `[User:${ctx.user.userId}]` : '[Guest]';
21
- Logger.info(`[${ctx.req.method}] ${apiPath} ${user} ${duration}ms`);
22
- }
23
- }
24
- };
25
- export default hook;
@@ -1,64 +0,0 @@
1
- // 相对导入
2
- import { JsonResponse } from '../util.js';
3
-
4
- // 类型导入
5
- import type { Hook } from '../types/hook.js';
6
-
7
- const hook: Hook = {
8
- after: ['requestId'],
9
- order: 100,
10
- handler: async (befly, ctx, next) => {
11
- await next();
12
-
13
- // 如果已经有 response,直接返回
14
- if (ctx.response) {
15
- return;
16
- }
17
-
18
- // 如果有 result,格式化为 JSON 响应
19
- if (ctx.result !== undefined) {
20
- let result = ctx.result;
21
-
22
- // 1. 如果是字符串,自动包裹为成功响应
23
- if (typeof result === 'string') {
24
- result = {
25
- code: 0,
26
- msg: result,
27
- data: {}
28
- };
29
- }
30
- // 2. 如果是对象,自动补充 code: 0
31
- else if (result && typeof result === 'object') {
32
- if (!('code' in result)) {
33
- result = {
34
- code: 0,
35
- ...result
36
- };
37
- }
38
- }
39
-
40
- // 处理 BigInt 序列化问题
41
- if (result && typeof result === 'object') {
42
- const jsonString = JSON.stringify(result, (key, value) => (typeof value === 'bigint' ? value.toString() : value));
43
- ctx.response = new Response(jsonString, {
44
- headers: {
45
- ...ctx.corsHeaders,
46
- 'Content-Type': 'application/json'
47
- }
48
- });
49
- } else {
50
- // 简单类型直接返回
51
- ctx.response = Response.json(result, {
52
- headers: ctx.corsHeaders
53
- });
54
- }
55
- return;
56
- }
57
-
58
- // 如果还没有响应,且不是 OPTIONS 请求,则设置默认 JSON 响应
59
- if (ctx.req.method !== 'OPTIONS' && !ctx.response) {
60
- ctx.response = JsonResponse(ctx, 'No response generated');
61
- }
62
- }
63
- };
64
- export default hook;
package/router/root.ts DELETED
@@ -1,56 +0,0 @@
1
- /**
2
- * 根路径路由处理器
3
- * 处理 / 路径的请求
4
- */
5
-
6
- // 相对导入
7
- import { Logger } from '../lib/logger.js';
8
- import { setCorsOptions } from '../util.js';
9
-
10
- /**
11
- * 根路径处理器
12
- */
13
- export async function rootHandler(req: Request): Promise<Response> {
14
- // 设置 CORS 响应头
15
- const corsHeaders = setCorsOptions(req);
16
-
17
- try {
18
- return Response.json(
19
- {
20
- code: 0,
21
- msg: `Befly 接口服务已启动`,
22
- data: {
23
- mode: process.env.NODE_ENV || 'development',
24
- timestamp: Date.now()
25
- }
26
- },
27
- {
28
- headers: corsHeaders
29
- }
30
- );
31
- } catch (error: any) {
32
- // 记录详细的错误日志
33
- Logger.error('根路径处理失败', error);
34
-
35
- // 根据错误类型返回不同的错误信息
36
- let errorMessage = '服务异常';
37
- let errorDetail = {};
38
-
39
- // 开发环境返回详细错误信息
40
- if (process.env.NODE_ENV === 'development') {
41
- errorDetail = {
42
- type: error.constructor?.name || 'Error',
43
- message: error.message,
44
- stack: error.stack
45
- };
46
- }
47
-
48
- return Response.json(
49
- { code: 1, msg: errorMessage, data: errorDetail },
50
- {
51
- status: 500,
52
- headers: corsHeaders
53
- }
54
- );
55
- }
56
- }
@@ -1,164 +0,0 @@
1
- /**
2
- * syncDb 主入口文件
3
- *
4
- * 功能:
5
- * - 协调所有模块,执行数据库表结构同步
6
- * - 处理核心表、项目表、addon 表
7
- * - 提供统计信息和错误处理
8
- */
9
-
10
- import { basename, resolve } from 'pathe';
11
- import { existsSync } from 'node:fs';
12
- import { snakeCase } from 'es-toolkit/string';
13
- import { Database } from '../../lib/database.js';
14
- import { RedisHelper } from '../../lib/redisHelper.js';
15
- import { checkTable } from '../../checks/checkTable.js';
16
- import { scanFiles, scanAddons, addonDirExists, getAddonDir } from 'befly-util';
17
- import { Logger } from '../../lib/logger.js';
18
- import { projectDir } from '../../paths.js';
19
-
20
- // 导入模块化的功能
21
- import { ensureDbVersion } from './version.js';
22
- import { tableExists } from './schema.js';
23
- import { modifyTable } from './table.js';
24
- import { createTable } from './tableCreate.js';
25
- import { applyFieldDefaults } from './helpers.js';
26
- import type { SQL } from 'bun';
27
- import type { BeflyOptions, SyncDbOptions } from '../../types/index.js';
28
-
29
- // 全局 SQL 客户端实例
30
- let sql: SQL | null = null;
31
-
32
- // 记录处理过的表名(用于清理缓存)
33
- const processedTables: string[] = [];
34
-
35
- /**
36
- * 主同步函数
37
- *
38
- * 流程:
39
- * 1. 验证表定义文件
40
- * 2. 建立数据库连接并检查版本
41
- * 3. 扫描表定义文件(核心表、项目表、addon表)
42
- * 4. 对比并应用表结构变更
43
- */
44
- export const SyncDb = async (config: BeflyOptions, options: SyncDbOptions = {}): Promise<void> => {
45
- try {
46
- // 清空处理记录
47
- processedTables.length = 0;
48
-
49
- // 验证表定义文件
50
- await checkTable();
51
-
52
- // 建立数据库连接并检查版本
53
- sql = await Database.connectSql({ max: 1 });
54
- await ensureDbVersion(sql);
55
-
56
- // 初始化 Redis 连接(用于清理缓存)
57
- await Database.connectRedis();
58
-
59
- // 扫描表定义文件
60
- const directories: Array<{ path: string; type: 'app' | 'addon'; addonName?: string; addonNameSnake?: string }> = [];
61
-
62
- // 1. 项目表(无前缀)- 如果 tables 目录存在
63
- const projectTablesDir = resolve(projectDir, 'tables');
64
- if (existsSync(projectTablesDir)) {
65
- directories.push({ path: projectTablesDir, type: 'app' });
66
- }
67
-
68
- // 添加所有 addon 的 tables 目录(addon_{name}_ 前缀)
69
- const addons = scanAddons();
70
- for (const addon of addons) {
71
- if (addonDirExists(addon, 'tables')) {
72
- directories.push({
73
- path: getAddonDir(addon, 'tables'),
74
- type: 'addon',
75
- addonName: addon,
76
- addonNameSnake: snakeCase(addon) // 提前转换,避免每个文件都转换
77
- });
78
- }
79
- }
80
-
81
- // 处理表文件
82
- for (const dirConfig of directories) {
83
- const { path: dir, type, addonName } = dirConfig;
84
- const dirType = type === 'addon' ? `组件${addonName}` : '项目';
85
-
86
- const files = await scanFiles(dir, '*.json');
87
-
88
- for (const { filePath: file, fileName } of files) {
89
- // 确定表名:
90
- // - addon 表:{addonName}_{表名}
91
- // 例如:admin addon 的 user.json → admin_user
92
- // - 项目表:{表名}
93
- // 例如:user.json → user
94
- let tableName = snakeCase(fileName);
95
- if (type === 'addon' && dirConfig.addonNameSnake) {
96
- // addon 表,使用提前转换好的名称
97
- tableName = `addon_${dirConfig.addonNameSnake}_${tableName}`;
98
- }
99
-
100
- // 如果指定了表名,则只同步该表
101
- if (options.table && options.table !== tableName) {
102
- continue;
103
- }
104
-
105
- const tableDefinitionModule = await import(file, { with: { type: 'json' } });
106
- const tableDefinition = tableDefinitionModule.default;
107
-
108
- // 为字段属性设置默认值
109
- for (const fieldDef of Object.values(tableDefinition)) {
110
- applyFieldDefaults(fieldDef);
111
- }
112
-
113
- const dbName = config.plugins?.db?.database;
114
- const existsTable = await tableExists(sql!, tableName, dbName);
115
-
116
- // 读取 force 参数
117
- const force = options.force || false;
118
-
119
- if (existsTable) {
120
- await modifyTable(sql!, tableName, tableDefinition, force, dbName);
121
- } else {
122
- await createTable(sql!, tableName, tableDefinition);
123
- }
124
-
125
- // 记录处理过的表名(用于清理缓存)
126
- processedTables.push(tableName);
127
- }
128
- }
129
-
130
- // 清理 Redis 缓存(如果有表被处理)
131
- if (processedTables.length > 0) {
132
- Logger.debug(`🧹 清理 ${processedTables.length} 个表的字段缓存...`);
133
-
134
- const redisHelper = new RedisHelper();
135
- for (const tableName of processedTables) {
136
- const cacheKey = `table:columns:${tableName}`;
137
- try {
138
- await redisHelper.del(cacheKey);
139
- } catch (error: any) {
140
- Logger.warn(`清理表 ${tableName} 的缓存失败: ${error.message}`);
141
- }
142
- }
143
-
144
- Logger.debug(`✓ 已清理表字段缓存`);
145
- }
146
- } catch (error: any) {
147
- Logger.error(`数据库同步失败`, error);
148
- throw error;
149
- } finally {
150
- if (sql) {
151
- try {
152
- await Database.disconnectSql();
153
- } catch (error: any) {
154
- Logger.warn(`关闭数据库连接时出错: ${error.message}`);
155
- }
156
- }
157
-
158
- try {
159
- await Database.disconnectRedis();
160
- } catch (error: any) {
161
- Logger.warn(`关闭 Redis 连接时出错: ${error.message}`);
162
- }
163
- }
164
- };