befly 3.8.19 → 3.8.20

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 (49) hide show
  1. package/README.md +7 -6
  2. package/bunfig.toml +1 -1
  3. package/lib/database.ts +28 -25
  4. package/lib/dbHelper.ts +3 -3
  5. package/lib/jwt.ts +90 -99
  6. package/lib/logger.ts +44 -23
  7. package/lib/redisHelper.ts +19 -22
  8. package/loader/loadApis.ts +69 -114
  9. package/loader/loadHooks.ts +65 -0
  10. package/loader/loadPlugins.ts +50 -219
  11. package/main.ts +106 -133
  12. package/package.json +15 -7
  13. package/paths.ts +20 -0
  14. package/plugins/cache.ts +1 -3
  15. package/plugins/db.ts +8 -11
  16. package/plugins/logger.ts +5 -3
  17. package/plugins/redis.ts +10 -14
  18. package/router/api.ts +60 -106
  19. package/router/root.ts +15 -12
  20. package/router/static.ts +54 -58
  21. package/sync/syncAll.ts +58 -0
  22. package/sync/syncApi.ts +264 -0
  23. package/sync/syncDb/apply.ts +194 -0
  24. package/sync/syncDb/constants.ts +76 -0
  25. package/sync/syncDb/ddl.ts +194 -0
  26. package/sync/syncDb/helpers.ts +200 -0
  27. package/sync/syncDb/index.ts +164 -0
  28. package/sync/syncDb/schema.ts +201 -0
  29. package/sync/syncDb/sqlite.ts +50 -0
  30. package/sync/syncDb/table.ts +321 -0
  31. package/sync/syncDb/tableCreate.ts +146 -0
  32. package/sync/syncDb/version.ts +72 -0
  33. package/sync/syncDb.ts +19 -0
  34. package/sync/syncDev.ts +206 -0
  35. package/sync/syncMenu.ts +331 -0
  36. package/tsconfig.json +2 -4
  37. package/types/api.d.ts +6 -0
  38. package/types/befly.d.ts +152 -28
  39. package/types/context.d.ts +29 -3
  40. package/types/hook.d.ts +35 -0
  41. package/types/index.ts +14 -1
  42. package/types/plugin.d.ts +6 -7
  43. package/types/sync.d.ts +403 -0
  44. package/check.ts +0 -378
  45. package/env.ts +0 -106
  46. package/lib/middleware.ts +0 -275
  47. package/types/env.ts +0 -65
  48. package/types/util.d.ts +0 -45
  49. package/util.ts +0 -257
package/plugins/cache.ts CHANGED
@@ -11,9 +11,7 @@ import type { BeflyContext } from '../types/befly.js';
11
11
  * 缓存插件
12
12
  */
13
13
  const cachePlugin: Plugin = {
14
- name: '_cache',
15
- after: ['_db', '_redis'],
16
-
14
+ after: ['db', 'redis'],
17
15
  async onInit(befly: BeflyContext): Promise<CacheHelper> {
18
16
  return new CacheHelper(befly);
19
17
  }
package/plugins/db.ts CHANGED
@@ -3,7 +3,6 @@
3
3
  * 初始化数据库连接和 SQL 管理器
4
4
  */
5
5
 
6
- import { Env } from '../env.js';
7
6
  import { Logger } from '../lib/logger.js';
8
7
  import { Database } from '../lib/database.js';
9
8
  import { DbHelper } from '../lib/dbHelper.js';
@@ -14,21 +13,19 @@ import type { BeflyContext } from '../types/befly.js';
14
13
  * 数据库插件
15
14
  */
16
15
  const dbPlugin: Plugin = {
17
- name: '_db',
18
- after: ['_redis'],
19
-
20
- async onInit(befly: BeflyContext): Promise<DbHelper | Record<string, never>> {
16
+ after: ['redis'],
17
+ async onInit(this: Plugin, befly: BeflyContext): Promise<DbHelper | Record<string, never>> {
21
18
  let sql: any = null;
19
+ const config = this.config || {};
22
20
 
23
21
  try {
24
- if (Env.DATABASE_ENABLE === 1) {
22
+ // 默认启用,除非显式禁用
23
+ if (config.enable !== 0) {
25
24
  // 创建 Bun SQL 客户端(内置连接池),并确保连接验证成功后再继续
26
- // 从环境变量读取连接超时配置
27
- const connectionTimeout = Env.DB_CONNECTION_TIMEOUT ? parseInt(Env.DB_CONNECTION_TIMEOUT) : 30000;
25
+ // 从配置读取连接超时配置
26
+ // const connectionTimeout = config.connectionTimeout ? parseInt(config.connectionTimeout) : 30000;
28
27
 
29
- sql = await Database.connectSql({
30
- connectionTimeout
31
- });
28
+ sql = await Database.connectSql(config);
32
29
 
33
30
  // 创建数据库管理器实例,直接传入 sql 对象
34
31
  const dbManager = new DbHelper(befly, sql);
package/plugins/logger.ts CHANGED
@@ -11,11 +11,13 @@ import type { BeflyContext } from '../types/befly.js';
11
11
  * 日志插件
12
12
  */
13
13
  const loggerPlugin: Plugin = {
14
- name: '_logger',
15
14
  after: [],
16
-
17
- async onInit(befly: BeflyContext): Promise<typeof Logger> {
15
+ async onInit(this: Plugin, befly: BeflyContext): Promise<typeof Logger> {
18
16
  try {
17
+ // 配置 Logger
18
+ if (this.config) {
19
+ Logger.configure(this.config);
20
+ }
19
21
  return Logger;
20
22
  } catch (error: any) {
21
23
  // 插件内禁止直接退出进程,抛出异常交由主流程统一处理
package/plugins/redis.ts CHANGED
@@ -3,7 +3,6 @@
3
3
  * 初始化 Redis 连接和助手工具
4
4
  */
5
5
 
6
- import { Env } from '../env.js';
7
6
  import { Logger } from '../lib/logger.js';
8
7
  import { RedisHelper } from '../lib/redisHelper.js';
9
8
  import { Database } from '../lib/database.js';
@@ -14,21 +13,18 @@ import type { BeflyContext } from '../types/befly.js';
14
13
  * Redis 插件
15
14
  */
16
15
  const redisPlugin: Plugin = {
17
- name: '_redis',
18
- after: ['_logger'],
19
-
20
- async onInit(befly: BeflyContext): Promise<RedisHelper | Record<string, never>> {
16
+ after: ['logger'],
17
+ async onInit(this: Plugin, befly: BeflyContext): Promise<RedisHelper | Record<string, never>> {
18
+ const config = this.config || {};
21
19
  try {
22
- if (Env.DATABASE_ENABLE === 1) {
23
- // 初始化 Redis 客户端(统一使用 database.ts 的连接管理)
24
- await Database.connectRedis();
20
+ // 默认启用,除非显式禁用 (这里假设只要配置了 redis 插件就启用,或者检查 enable 字段)
21
+ // 为了兼容性,如果 config 为空,可能意味着使用默认值连接本地 redis
22
+
23
+ // 初始化 Redis 客户端(统一使用 database.ts 的连接管理)
24
+ await Database.connectRedis(config);
25
25
 
26
- // 返回 RedisHelper 实例
27
- return new RedisHelper();
28
- } else {
29
- Logger.warn('Redis 未启用,跳过初始化');
30
- return {};
31
- }
26
+ // 返回 RedisHelper 实例
27
+ return new RedisHelper(config.prefix);
32
28
  } catch (error: any) {
33
29
  Logger.error('Redis 初始化失败', error);
34
30
 
package/router/api.ts CHANGED
@@ -3,129 +3,83 @@
3
3
  * 处理 /api/* 路径的请求
4
4
  */
5
5
 
6
- import { Logger } from '../lib/logger.js';
7
- import { No } from '../util.js';
8
- import { setCorsOptions, handleOptionsRequest, authenticate, parseGetParams, parsePostParams, checkPermission, validateParams, executePluginHooks, logRequest } from '../lib/middleware.js';
9
- import { Env } from '../env.js';
10
- import type { RequestContext } from '../util.js';
6
+ // 相对导入
7
+ import { compose, JsonResponse } from '../util.js';
8
+
9
+ // 类型导入
10
+ import type { RequestContext } from '../types/context.js';
11
11
  import type { ApiRoute } from '../types/api.js';
12
- import type { Plugin } from '../types/plugin.js';
12
+ import type { Hook } from '../types/hook.js';
13
13
  import type { BeflyContext } from '../types/befly.js';
14
14
 
15
15
  /**
16
16
  * API处理器工厂函数
17
17
  * @param apiRoutes - API路由映射表
18
- * @param pluginLists - 插件列表
18
+ * @param hookLists - 钩子列表
19
19
  * @param appContext - 应用上下文
20
20
  */
21
- export function apiHandler(apiRoutes: Map<string, ApiRoute>, pluginLists: Plugin[], appContext: BeflyContext) {
22
- return async (req: Request): Promise<Response> => {
23
- const corsOptions = setCorsOptions(req);
24
- let ctx: RequestContext | null = null;
25
- let api: ApiRoute | undefined;
26
- let apiPath = '';
27
-
28
- try {
29
- // 1. OPTIONS预检请求
30
- if (req.method === 'OPTIONS') {
31
- return handleOptionsRequest(corsOptions);
32
- }
33
-
34
- // 2. 创建请求上下文
35
- ctx = {
36
- body: {},
37
- user: {},
38
- request: req,
39
- startTime: Date.now()
40
- };
21
+ export function apiHandler(apiRoutes: Map<string, ApiRoute>, hookLists: Hook[], appContext: BeflyContext) {
22
+ // 提取所有钩子的处理函数
23
+ const middleware = hookLists.map((h) => h.handler);
41
24
 
42
- // 3. 获取API路由
43
- const url = new URL(req.url);
44
- apiPath = `${req.method}${url.pathname}`;
45
- api = apiRoutes.get(apiPath);
25
+ // 组合钩子链
26
+ const fn = compose(middleware);
46
27
 
47
- if (!api) {
48
- return Response.json(No('接口不存在'), {
49
- headers: corsOptions.headers
50
- });
51
- }
52
-
53
- // 4. 用户认证
54
- await authenticate(ctx);
28
+ return async (req: Request): Promise<Response> => {
29
+ // 1. 创建请求上下文
30
+ const url = new URL(req.url);
31
+ const apiPath = `${req.method}${url.pathname}`;
32
+
33
+ const ctx: RequestContext = {
34
+ body: {},
35
+ user: {},
36
+ req: req,
37
+ now: Date.now(),
38
+ corsHeaders: {},
39
+ ip: req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || req.headers.get('x-real-ip') || 'unknown',
40
+ route: apiPath
41
+ };
42
+
43
+ // 2. 获取API路由
44
+ const api = apiRoutes.get(apiPath);
45
+
46
+ // 注意:即使 api 不存在,也需要执行插件链(以便处理 CORS OPTIONS 请求或 404 响应)
47
+ // 如果是 OPTIONS 请求,通常不需要 api 对象
48
+ if (api) {
49
+ ctx.api = api;
50
+ }
55
51
 
56
- // 5. 参数解析
57
- if (req.method === 'GET') {
58
- parseGetParams(api, ctx);
59
- } else if (req.method === 'POST') {
60
- const parseSuccess = await parsePostParams(api, ctx);
61
- if (!parseSuccess) {
62
- return Response.json(No('无效的请求参数格式'), {
63
- headers: corsOptions.headers
64
- });
52
+ // 3. 执行插件链(洋葱模型)
53
+ // 错误处理已由 errorHandler 插件接管
54
+ await fn(appContext, ctx, async () => {
55
+ // 核心执行器:执行 API handler
56
+ // 如果没有找到 API 且没有被前面的插件拦截(如 CORS),则返回 404
57
+ if (!ctx.api) {
58
+ // 只有非 OPTIONS 请求才报 404(OPTIONS 请求通常由 cors 插件处理并返回)
59
+ if (req.method !== 'OPTIONS') {
60
+ ctx.response = JsonResponse(ctx, '接口不存在');
65
61
  }
62
+ return;
66
63
  }
67
64
 
68
- // 6. 执行插件钩子
69
- await executePluginHooks(pluginLists, appContext, ctx);
65
+ if (ctx.api.handler) {
66
+ const result = await ctx.api.handler(appContext, ctx, req);
70
67
 
71
- // 7. 记录请求日志
72
- logRequest(apiPath, ctx);
73
-
74
- // 8. 权限验证(使用 Redis Set SISMEMBER 直接判断,提升性能)
75
- let hasPermission = false;
76
- if (api.auth === true && ctx.user?.roleCode && ctx.user.roleCode !== 'dev') {
77
- // 使用 Redis SISMEMBER 直接判断接口是否在角色权限集合中(O(1)复杂度)
78
- const roleApisKey = `role:apis:${ctx.user.roleCode}`;
79
- const isMember = await appContext.redis.sismember(roleApisKey, apiPath);
80
- hasPermission = isMember === 1;
81
- }
82
-
83
- const permissionResult = checkPermission(api, ctx, hasPermission);
84
- if (permissionResult.code !== 0) {
85
- return Response.json(permissionResult, {
86
- headers: corsOptions.headers
87
- });
88
- }
89
-
90
- // 9. 参数验证
91
- const validateResult = validateParams(api, ctx);
92
- if (validateResult.code !== 0) {
93
- return Response.json(No('无效的请求参数格式', validateResult.fields), {
94
- headers: corsOptions.headers
95
- });
96
- }
97
-
98
- // 10. 执行API处理器
99
- const result = await api.handler(appContext, ctx, req);
100
-
101
- // 11. 返回响应
102
- // 🔥 新增:直接返回 Response 对象
103
- if (result instanceof Response) {
104
- return result;
105
- }
106
-
107
- // 12. 返回响应
108
- if (result && typeof result === 'object' && 'code' in result) {
109
- // 处理 BigInt 序列化问题
110
- const jsonString = JSON.stringify(result, (key, value) => (typeof value === 'bigint' ? value.toString() : value));
111
- return new Response(jsonString, {
112
- headers: {
113
- ...corsOptions.headers,
114
- 'Content-Type': 'application/json'
115
- }
116
- });
117
- } else {
118
- return new Response(result, {
119
- headers: corsOptions.headers
120
- });
68
+ if (result instanceof Response) {
69
+ ctx.response = result;
70
+ } else {
71
+ // 将结果存入 ctx.result,由 responseFormatter 插件统一处理
72
+ ctx.result = result;
73
+ }
121
74
  }
122
- } catch (error: any) {
123
- // 记录详细的错误日志
124
- Logger.error(api ? `接口 [${api.name}] 执行失败` : '处理接口请求时发生错误', error);
75
+ });
125
76
 
126
- return Response.json(No('内部服务器错误'), {
127
- headers: corsOptions.headers
128
- });
77
+ // 4. 返回响应
78
+ if (ctx.response) {
79
+ return ctx.response;
129
80
  }
81
+
82
+ // 兜底响应(理论上不应执行到这里,responseFormatter 会处理)
83
+ return JsonResponse(ctx, 'No response generated');
130
84
  };
131
85
  }
package/router/root.ts CHANGED
@@ -3,29 +3,29 @@
3
3
  * 处理 / 路径的请求
4
4
  */
5
5
 
6
- import { Env } from '../env.js';
7
- import { setCorsOptions } from '../lib/middleware.js';
6
+ // 相对导入
8
7
  import { Logger } from '../lib/logger.js';
9
- import { No } from '../util.js';
8
+ import { setCorsOptions } from '../util.js';
10
9
 
11
10
  /**
12
11
  * 根路径处理器
13
12
  */
14
13
  export async function rootHandler(req: Request): Promise<Response> {
15
- const corsOptions = setCorsOptions(req);
14
+ // 设置 CORS 响应头
15
+ const corsHeaders = setCorsOptions(req);
16
16
 
17
17
  try {
18
18
  return Response.json(
19
19
  {
20
20
  code: 0,
21
- msg: `${Env.APP_NAME} 接口服务已启动`,
21
+ msg: `Befly 接口服务已启动`,
22
22
  data: {
23
- mode: Env.NODE_ENV,
23
+ mode: process.env.NODE_ENV || 'development',
24
24
  timestamp: Date.now()
25
25
  }
26
26
  },
27
27
  {
28
- headers: corsOptions.headers
28
+ headers: corsHeaders
29
29
  }
30
30
  );
31
31
  } catch (error: any) {
@@ -37,7 +37,7 @@ export async function rootHandler(req: Request): Promise<Response> {
37
37
  let errorDetail = {};
38
38
 
39
39
  // 开发环境返回详细错误信息
40
- if (Env.NODE_ENV === 'development') {
40
+ if (process.env.NODE_ENV === 'development') {
41
41
  errorDetail = {
42
42
  type: error.constructor?.name || 'Error',
43
43
  message: error.message,
@@ -45,9 +45,12 @@ export async function rootHandler(req: Request): Promise<Response> {
45
45
  };
46
46
  }
47
47
 
48
- return Response.json(No(errorMessage, errorDetail), {
49
- status: 500,
50
- headers: corsOptions.headers
51
- });
48
+ return Response.json(
49
+ { code: 1, msg: errorMessage, data: errorDetail },
50
+ {
51
+ status: 500,
52
+ headers: corsHeaders
53
+ }
54
+ );
52
55
  }
53
56
  }
package/router/static.ts CHANGED
@@ -3,74 +3,70 @@
3
3
  * 处理 /* 路径的静态文件请求
4
4
  */
5
5
 
6
+ // 外部依赖
6
7
  import { join } from 'pathe';
8
+
9
+ // 相对导入
7
10
  import { projectDir } from '../paths.js';
8
- import { No } from '../util.js';
9
- import { setCorsOptions } from '../lib/middleware.js';
10
11
  import { Logger } from '../lib/logger.js';
11
- import { Env } from '../env.js';
12
+ import { setCorsOptions } from '../util.js';
13
+
14
+ // 类型导入
15
+ import type { CorsConfig } from '../types/befly.js';
12
16
 
13
17
  /**
14
- * 静态文件处理器
18
+ * 静态文件处理器工厂
19
+ * @param corsConfig - CORS 配置
15
20
  */
16
- export async function staticHandler(req: Request): Promise<Response> {
17
- const corsOptions = setCorsOptions(req);
18
- const url = new URL(req.url);
19
- const filePath = join(projectDir, 'public', url.pathname);
21
+ export function staticHandler(corsConfig: CorsConfig = {}) {
22
+ return async (req: Request): Promise<Response> => {
23
+ // 设置 CORS 响应头
24
+ const corsHeaders = setCorsOptions(req, corsConfig);
20
25
 
21
- try {
22
- // OPTIONS预检请求
23
- if (req.method === 'OPTIONS') {
24
- return new Response(null, {
25
- status: 204,
26
- headers: corsOptions.headers
27
- });
28
- }
26
+ const url = new URL(req.url);
27
+ const filePath = join(projectDir, 'public', url.pathname);
29
28
 
30
- const file = Bun.file(filePath);
31
- if (await file.exists()) {
32
- return new Response(file, {
33
- headers: {
34
- 'Content-Type': file.type || 'application/octet-stream',
35
- ...corsOptions.headers
36
- }
37
- });
38
- } else {
39
- return Response.json(No('文件未找到'), {
40
- status: 404,
41
- headers: corsOptions.headers
42
- });
43
- }
44
- } catch (error: any) {
45
- // 记录详细的错误日志
46
- Logger.error('静态文件处理失败', error);
29
+ try {
30
+ // OPTIONS预检请求
31
+ if (req.method === 'OPTIONS') {
32
+ return new Response(null, {
33
+ status: 204,
34
+ headers: corsHeaders
35
+ });
36
+ }
47
37
 
48
- // 根据错误类型返回不同的错误信息
49
- let errorMessage = '文件读取失败';
50
- let errorDetail = {};
38
+ const file = Bun.file(filePath);
39
+ if (await file.exists()) {
40
+ return new Response(file, {
41
+ headers: {
42
+ 'Content-Type': file.type || 'application/octet-stream',
43
+ ...corsHeaders
44
+ }
45
+ });
46
+ } else {
47
+ return Response.json(
48
+ { code: 1, msg: '文件未找到' },
49
+ {
50
+ status: 404,
51
+ headers: corsHeaders
52
+ }
53
+ );
54
+ }
55
+ } catch (error: any) {
56
+ // 记录详细的错误日志
57
+ Logger.error('静态文件处理失败', error);
51
58
 
52
- // 权限错误
53
- if (error.message?.includes('EACCES') || error.message?.includes('permission')) {
54
- errorMessage = '文件访问权限不足';
55
- }
56
- // 路径错误
57
- else if (error.message?.includes('ENOENT') || error.message?.includes('not found')) {
58
- errorMessage = '文件路径不存在';
59
- }
59
+ // 根据错误类型返回不同的错误信息
60
+ let errorMessage = '文件读取失败';
61
+ let errorDetail = {};
60
62
 
61
- // 开发环境返回详细错误信息
62
- if (Env.NODE_ENV === 'development') {
63
- errorDetail = {
64
- type: error.constructor?.name || 'Error',
65
- message: error.message,
66
- stack: error.stack,
67
- filePath: filePath
68
- };
63
+ return Response.json(
64
+ { code: 1, msg: errorMessage, data: errorDetail },
65
+ {
66
+ status: 500,
67
+ headers: corsHeaders
68
+ }
69
+ );
69
70
  }
70
-
71
- return Response.json(No(errorMessage, errorDetail), {
72
- status: 500,
73
- headers: corsOptions.headers
74
- });
75
- }
71
+ };
76
72
  }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Sync 命令 - 一次性执行所有同步操作
3
+ * 按顺序执行:syncDb → syncApi → syncMenu → syncDev
4
+ */
5
+
6
+ import { checkApp } from '../checks/checkApp.js';
7
+ import { Logger } from '../lib/logger.js';
8
+ import { syncDbCommand } from './syncDb.js';
9
+ import { syncApiCommand } from './syncApi.js';
10
+ import { syncMenuCommand } from './syncMenu.js';
11
+ import { syncDevCommand } from './syncDev.js';
12
+ import type { SyncOptions, BeflyOptions } from '../types/index.js';
13
+
14
+ export async function syncAllCommand(config: BeflyOptions, options: SyncOptions = {}) {
15
+ try {
16
+ const startTime = Date.now();
17
+
18
+ // 0. 检查项目结构
19
+ Logger.debug('🔍 正在检查项目结构...');
20
+ const checkResult = await checkApp();
21
+ if (!checkResult) {
22
+ Logger.error('项目结构检查失败,程序退出');
23
+ throw new Error('项目结构检查失败');
24
+ }
25
+ Logger.debug(`✓ 项目结构检查完成\n`);
26
+
27
+ Logger.debug('开始执行同步任务...\n');
28
+
29
+ // 1. 同步数据库表结构
30
+ Logger.debug('📦 正在同步数据库...');
31
+ await syncDbCommand(config, { dryRun: false, force: options.force || false });
32
+ Logger.debug(`✓ 数据库同步完成\n`);
33
+
34
+ // 2. 同步接口(并缓存)
35
+ Logger.debug('🔌 正在同步接口...');
36
+ await syncApiCommand(config);
37
+ Logger.debug(`✓ 接口同步完成\n`);
38
+
39
+ // 3. 同步菜单(并缓存)
40
+ Logger.debug('📋 正在同步菜单...');
41
+ await syncMenuCommand(config);
42
+ Logger.debug(`✓ 菜单同步完成\n`);
43
+
44
+ // 4. 同步开发管理员(并缓存角色权限)
45
+ Logger.debug('👤 正在同步开发账号...');
46
+ await syncDevCommand(config);
47
+ Logger.debug(`✓ 开发账号同步完成\n`);
48
+
49
+ // 输出总结
50
+ const totalTimeSeconds = ((Date.now() - startTime) / 1000).toFixed(2);
51
+ Logger.debug('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
52
+ Logger.debug(`✅ 同步完成!总耗时: ${totalTimeSeconds} 秒`);
53
+ Logger.debug('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
54
+ } catch (error: any) {
55
+ Logger.error('同步过程中发生错误', error);
56
+ throw error;
57
+ }
58
+ }