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.
- package/config.ts +8 -9
- package/hooks/{rateLimit.ts → _rateLimit.ts} +7 -13
- package/hooks/auth.ts +3 -11
- package/hooks/cors.ts +1 -4
- package/hooks/parser.ts +6 -8
- package/hooks/permission.ts +9 -12
- package/hooks/validator.ts +6 -9
- package/lib/cacheHelper.ts +0 -4
- package/lib/{database.ts → connect.ts} +65 -18
- package/lib/logger.ts +1 -17
- package/lib/redisHelper.ts +6 -5
- package/loader/loadApis.ts +3 -3
- package/loader/loadHooks.ts +15 -41
- package/loader/loadPlugins.ts +10 -16
- package/main.ts +25 -28
- package/package.json +3 -3
- package/plugins/cache.ts +2 -2
- package/plugins/cipher.ts +15 -0
- package/plugins/config.ts +16 -0
- package/plugins/db.ts +7 -17
- package/plugins/jwt.ts +15 -0
- package/plugins/logger.ts +1 -1
- package/plugins/redis.ts +4 -4
- package/plugins/tool.ts +50 -0
- package/router/api.ts +56 -42
- package/router/static.ts +12 -12
- package/sync/syncAll.ts +2 -20
- package/sync/syncApi.ts +7 -7
- package/sync/syncDb/apply.ts +1 -4
- package/sync/syncDb/constants.ts +3 -0
- package/sync/syncDb/ddl.ts +2 -1
- package/sync/syncDb/helpers.ts +5 -117
- package/sync/syncDb/sqlite.ts +1 -3
- package/sync/syncDb/table.ts +8 -142
- package/sync/syncDb/tableCreate.ts +25 -9
- package/sync/syncDb/types.ts +125 -0
- package/sync/syncDb/version.ts +0 -3
- package/sync/syncDb.ts +146 -6
- package/sync/syncDev.ts +19 -15
- package/sync/syncMenu.ts +87 -75
- package/tests/redisHelper.test.ts +15 -16
- package/tests/sync-connection.test.ts +189 -0
- package/tests/syncDb-apply.test.ts +287 -0
- package/tests/syncDb-constants.test.ts +150 -0
- package/tests/syncDb-ddl.test.ts +205 -0
- package/tests/syncDb-helpers.test.ts +112 -0
- package/tests/syncDb-schema.test.ts +178 -0
- package/tests/syncDb-types.test.ts +129 -0
- package/tsconfig.json +2 -2
- package/types/api.d.ts +1 -1
- package/types/befly.d.ts +23 -21
- package/types/common.d.ts +0 -29
- package/types/context.d.ts +8 -6
- package/types/hook.d.ts +3 -4
- package/types/plugin.d.ts +3 -0
- package/hooks/errorHandler.ts +0 -23
- package/hooks/requestId.ts +0 -24
- package/hooks/requestLogger.ts +0 -25
- package/hooks/responseFormatter.ts +0 -64
- package/router/root.ts +0 -56
- package/sync/syncDb/index.ts +0 -164
package/config.ts
CHANGED
|
@@ -6,14 +6,14 @@ import type { BeflyOptions } from './types/befly.js';
|
|
|
6
6
|
|
|
7
7
|
export const defaultOptions: Required<Omit<BeflyOptions, 'devPassword'>> = {
|
|
8
8
|
// ========== 核心参数 ==========
|
|
9
|
-
nodeEnv: 'development',
|
|
10
|
-
appName: '
|
|
9
|
+
nodeEnv: (process.env.NODE_ENV as any) || 'development',
|
|
10
|
+
appName: '野蜂飞舞',
|
|
11
11
|
appPort: 3000,
|
|
12
12
|
appHost: '127.0.0.1',
|
|
13
13
|
devEmail: 'dev@qq.com',
|
|
14
|
+
devPassword: 'beflydev123456',
|
|
14
15
|
bodyLimit: 1048576, // 1MB
|
|
15
16
|
tz: 'Asia/Shanghai',
|
|
16
|
-
dbCache: 0,
|
|
17
17
|
|
|
18
18
|
// ========== 日志配置 ==========
|
|
19
19
|
logger: {
|
|
@@ -26,14 +26,13 @@ export const defaultOptions: Required<Omit<BeflyOptions, 'devPassword'>> = {
|
|
|
26
26
|
|
|
27
27
|
// ========== 数据库配置 ==========
|
|
28
28
|
db: {
|
|
29
|
-
|
|
30
|
-
type: 'sqlite',
|
|
29
|
+
type: 'mysql',
|
|
31
30
|
host: '127.0.0.1',
|
|
32
31
|
port: 3306,
|
|
33
32
|
username: 'root',
|
|
34
|
-
password: '',
|
|
35
|
-
database: '
|
|
36
|
-
poolMax:
|
|
33
|
+
password: 'root',
|
|
34
|
+
database: 'befly_demo',
|
|
35
|
+
poolMax: 10
|
|
37
36
|
},
|
|
38
37
|
|
|
39
38
|
// ========== Redis 配置 ==========
|
|
@@ -43,7 +42,7 @@ export const defaultOptions: Required<Omit<BeflyOptions, 'devPassword'>> = {
|
|
|
43
42
|
username: '',
|
|
44
43
|
password: '',
|
|
45
44
|
db: 0,
|
|
46
|
-
prefix: '
|
|
45
|
+
prefix: 'befly_demo:'
|
|
47
46
|
},
|
|
48
47
|
|
|
49
48
|
// ========== 认证配置 ==========
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 相对导入
|
|
2
2
|
import { Logger } from '../lib/logger.js';
|
|
3
|
-
import {
|
|
3
|
+
import { ErrorResponse } from '../util.js';
|
|
4
4
|
|
|
5
5
|
// 类型导入
|
|
6
6
|
import type { Hook } from '../types/hook.js';
|
|
@@ -14,16 +14,13 @@ import type { Hook } from '../types/hook.js';
|
|
|
14
14
|
* 3. 针对每个用户(userId)或IP进行限制
|
|
15
15
|
*/
|
|
16
16
|
const hook: Hook = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
order: 25,
|
|
20
|
-
|
|
21
|
-
handler: async (befly, ctx, next) => {
|
|
17
|
+
order: 7,
|
|
18
|
+
handler: async (befly, ctx) => {
|
|
22
19
|
const { api } = ctx;
|
|
23
20
|
|
|
24
21
|
// 1. 检查配置
|
|
25
22
|
if (!api || !api.rateLimit || !befly.redis) {
|
|
26
|
-
return
|
|
23
|
+
return;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
// 2. 解析配置 "10/60" -> count=10, seconds=60
|
|
@@ -33,12 +30,12 @@ const hook: Hook = {
|
|
|
33
30
|
|
|
34
31
|
if (isNaN(limitCount) || isNaN(limitSeconds)) {
|
|
35
32
|
Logger.warn(`[RateLimit] Invalid config: ${api.rateLimit}`);
|
|
36
|
-
return
|
|
33
|
+
return;
|
|
37
34
|
}
|
|
38
35
|
|
|
39
36
|
// 3. 生成 Key
|
|
40
37
|
// 优先使用 userId,否则使用 IP
|
|
41
|
-
const identifier = ctx.user?.
|
|
38
|
+
const identifier = ctx.user?.id || ctx.ip || 'unknown';
|
|
42
39
|
const apiPath = ctx.route || `${ctx.req.method}${new URL(ctx.req.url).pathname}`;
|
|
43
40
|
const key = `rate_limit:${apiPath}:${identifier}`;
|
|
44
41
|
|
|
@@ -54,15 +51,12 @@ const hook: Hook = {
|
|
|
54
51
|
|
|
55
52
|
// 6. 判断是否超限
|
|
56
53
|
if (current > limitCount) {
|
|
57
|
-
ctx.response =
|
|
54
|
+
ctx.response = ErrorResponse(ctx, '请求过于频繁,请稍后再试');
|
|
58
55
|
return;
|
|
59
56
|
}
|
|
60
|
-
|
|
61
|
-
return next();
|
|
62
57
|
} catch (err) {
|
|
63
58
|
Logger.error('[RateLimit] Redis error:', err);
|
|
64
59
|
// Redis 故障时,默认放行,避免阻塞业务
|
|
65
|
-
return next();
|
|
66
60
|
}
|
|
67
61
|
}
|
|
68
62
|
};
|
package/hooks/auth.ts
CHANGED
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
import type { Hook } from '../types/hook.js';
|
|
2
|
-
import { Jwt } from '../lib/jwt.js';
|
|
3
2
|
|
|
4
3
|
const hook: Hook = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
handler: async (befly, ctx, next) => {
|
|
8
|
-
// 初始化配置(如果有)
|
|
9
|
-
// 注意:Hook 没有 onInit,如果需要初始化,可以在 handler 首次执行时做,或者保留 Plugin 机制专门做初始化
|
|
10
|
-
// 这里 auth 插件原本有 onInit 来配置 Jwt,现在需要迁移
|
|
11
|
-
// 临时方案:直接在 handler 里判断是否配置过,或者让 Jwt 自身处理配置
|
|
12
|
-
|
|
4
|
+
order: 3,
|
|
5
|
+
handler: async (befly, ctx) => {
|
|
13
6
|
const authHeader = ctx.req.headers.get('authorization');
|
|
14
7
|
|
|
15
8
|
if (authHeader && authHeader.startsWith('Bearer ')) {
|
|
16
9
|
const token = authHeader.substring(7);
|
|
17
10
|
|
|
18
11
|
try {
|
|
19
|
-
const payload = await
|
|
12
|
+
const payload = await befly.jwt.verify(token);
|
|
20
13
|
ctx.user = payload;
|
|
21
14
|
} catch (error: any) {
|
|
22
15
|
ctx.user = {};
|
|
@@ -24,7 +17,6 @@ const hook: Hook = {
|
|
|
24
17
|
} else {
|
|
25
18
|
ctx.user = {};
|
|
26
19
|
}
|
|
27
|
-
await next();
|
|
28
20
|
}
|
|
29
21
|
};
|
|
30
22
|
export default hook;
|
package/hooks/cors.ts
CHANGED
|
@@ -10,9 +10,8 @@ import type { CorsConfig } from '../types/befly.js';
|
|
|
10
10
|
* 设置跨域响应头并处理 OPTIONS 预检请求
|
|
11
11
|
*/
|
|
12
12
|
const hook: Hook = {
|
|
13
|
-
after: ['errorHandler'],
|
|
14
13
|
order: 2,
|
|
15
|
-
handler: async (befly, ctx
|
|
14
|
+
handler: async (befly, ctx) => {
|
|
16
15
|
const req = ctx.req;
|
|
17
16
|
|
|
18
17
|
// 合并默认配置和用户配置
|
|
@@ -41,8 +40,6 @@ const hook: Hook = {
|
|
|
41
40
|
});
|
|
42
41
|
return;
|
|
43
42
|
}
|
|
44
|
-
|
|
45
|
-
await next();
|
|
46
43
|
}
|
|
47
44
|
};
|
|
48
45
|
export default hook;
|
package/hooks/parser.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { pickFields } from 'befly-util';
|
|
|
4
4
|
|
|
5
5
|
// 相对导入
|
|
6
6
|
import { Xml } from '../lib/xml.js';
|
|
7
|
-
import {
|
|
7
|
+
import { ErrorResponse } from '../util.js';
|
|
8
8
|
|
|
9
9
|
// 类型导入
|
|
10
10
|
import type { Hook } from '../types/hook.js';
|
|
@@ -16,10 +16,9 @@ import type { Hook } from '../types/hook.js';
|
|
|
16
16
|
* - 根据 API 定义的 fields 过滤字段
|
|
17
17
|
*/
|
|
18
18
|
const hook: Hook = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (!ctx.api) return next();
|
|
19
|
+
order: 4,
|
|
20
|
+
handler: async (befly, ctx) => {
|
|
21
|
+
if (!ctx.api) return;
|
|
23
22
|
|
|
24
23
|
// GET 请求:解析查询参数
|
|
25
24
|
if (ctx.req.method === 'GET') {
|
|
@@ -52,16 +51,15 @@ const hook: Hook = {
|
|
|
52
51
|
}
|
|
53
52
|
} else {
|
|
54
53
|
// 不支持的 Content-Type
|
|
55
|
-
ctx.response =
|
|
54
|
+
ctx.response = ErrorResponse(ctx, '无效的请求参数格式');
|
|
56
55
|
return;
|
|
57
56
|
}
|
|
58
57
|
} catch (e) {
|
|
59
58
|
// 解析失败
|
|
60
|
-
ctx.response =
|
|
59
|
+
ctx.response = ErrorResponse(ctx, '无效的请求参数格式');
|
|
61
60
|
return;
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
|
-
await next();
|
|
65
63
|
}
|
|
66
64
|
};
|
|
67
65
|
export default hook;
|
package/hooks/permission.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// 相对导入
|
|
2
|
-
import {
|
|
2
|
+
import { ErrorResponse } from '../util.js';
|
|
3
3
|
|
|
4
4
|
// 类型导入
|
|
5
5
|
import type { Hook } from '../types/hook.js';
|
|
@@ -12,25 +12,24 @@ import type { Hook } from '../types/hook.js';
|
|
|
12
12
|
* - 其他角色:检查 Redis 中的角色权限集合
|
|
13
13
|
*/
|
|
14
14
|
const hook: Hook = {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (!ctx.api) return next();
|
|
15
|
+
order: 6,
|
|
16
|
+
handler: async (befly, ctx) => {
|
|
17
|
+
if (!ctx.api) return;
|
|
19
18
|
|
|
20
19
|
// 1. 接口无需权限
|
|
21
20
|
if (ctx.api.auth === false) {
|
|
22
|
-
return
|
|
21
|
+
return;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
// 2. 用户未登录
|
|
26
|
-
if (!ctx.user || !ctx.user.
|
|
27
|
-
ctx.response =
|
|
25
|
+
if (!ctx.user || !ctx.user.id) {
|
|
26
|
+
ctx.response = ErrorResponse(ctx, '未登录');
|
|
28
27
|
return;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
// 3. 开发者权限(最高权限)
|
|
32
31
|
if (ctx.user.roleCode === 'dev') {
|
|
33
|
-
return
|
|
32
|
+
return;
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
// 4. 角色权限检查
|
|
@@ -44,11 +43,9 @@ const hook: Hook = {
|
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
if (!hasPermission) {
|
|
47
|
-
ctx.response =
|
|
46
|
+
ctx.response = ErrorResponse(ctx, '无权访问');
|
|
48
47
|
return;
|
|
49
48
|
}
|
|
50
|
-
|
|
51
|
-
await next();
|
|
52
49
|
}
|
|
53
50
|
};
|
|
54
51
|
export default hook;
|
package/hooks/validator.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 相对导入
|
|
2
2
|
import { Validator } from '../lib/validator.js';
|
|
3
|
-
import {
|
|
3
|
+
import { ErrorResponse } from '../util.js';
|
|
4
4
|
|
|
5
5
|
// 类型导入
|
|
6
6
|
import type { Hook } from '../types/hook.js';
|
|
@@ -10,25 +10,22 @@ import type { Hook } from '../types/hook.js';
|
|
|
10
10
|
* 根据 API 定义的 fields 和 required 验证请求参数
|
|
11
11
|
*/
|
|
12
12
|
const hook: Hook = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (!ctx.api) return next();
|
|
13
|
+
order: 5,
|
|
14
|
+
handler: async (befly, ctx) => {
|
|
15
|
+
if (!ctx.api) return;
|
|
17
16
|
|
|
18
17
|
// 无需验证
|
|
19
18
|
if (!ctx.api.fields) {
|
|
20
|
-
return
|
|
19
|
+
return;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
// 验证参数
|
|
24
23
|
const result = Validator.validate(ctx.body, ctx.api.fields, ctx.api.required || []);
|
|
25
24
|
|
|
26
25
|
if (result.code !== 0) {
|
|
27
|
-
ctx.response =
|
|
26
|
+
ctx.response = ErrorResponse(ctx, '无效的请求参数格式', 1, result.fields);
|
|
28
27
|
return;
|
|
29
28
|
}
|
|
30
|
-
|
|
31
|
-
await next();
|
|
32
29
|
}
|
|
33
30
|
};
|
|
34
31
|
export default hook;
|
package/lib/cacheHelper.ts
CHANGED
|
@@ -153,8 +153,6 @@ export class CacheHelper {
|
|
|
153
153
|
* 缓存所有数据
|
|
154
154
|
*/
|
|
155
155
|
async cacheAll(): Promise<void> {
|
|
156
|
-
Logger.info('========== 开始缓存数据到 Redis ==========');
|
|
157
|
-
|
|
158
156
|
// 1. 缓存接口
|
|
159
157
|
await this.cacheApis();
|
|
160
158
|
|
|
@@ -163,8 +161,6 @@ export class CacheHelper {
|
|
|
163
161
|
|
|
164
162
|
// 3. 缓存角色权限
|
|
165
163
|
await this.cacheRolePermissions();
|
|
166
|
-
|
|
167
|
-
Logger.info('========== 数据缓存完成 ==========\n');
|
|
168
164
|
}
|
|
169
165
|
|
|
170
166
|
/**
|
|
@@ -7,18 +7,23 @@ import { SQL, RedisClient } from 'bun';
|
|
|
7
7
|
import { Logger } from './logger.js';
|
|
8
8
|
import { DbHelper } from './dbHelper.js';
|
|
9
9
|
import { RedisHelper } from './redisHelper.js';
|
|
10
|
-
import type { BeflyContext, DatabaseConfig, RedisConfig } from '../types/befly.js';
|
|
10
|
+
import type { BeflyContext, BeflyOptions, DatabaseConfig, RedisConfig } from '../types/befly.js';
|
|
11
11
|
import type { SqlClientOptions } from '../types/database.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* 数据库连接管理器
|
|
15
15
|
* 使用静态方法管理全局单例连接
|
|
16
16
|
*/
|
|
17
|
-
export class
|
|
17
|
+
export class Connect {
|
|
18
18
|
private static sqlClient: SQL | null = null;
|
|
19
19
|
private static redisClient: RedisClient | null = null;
|
|
20
20
|
private static dbHelper: DbHelper | null = null;
|
|
21
21
|
|
|
22
|
+
// 连接统计信息
|
|
23
|
+
private static sqlConnectedAt: number | null = null;
|
|
24
|
+
private static redisConnectedAt: number | null = null;
|
|
25
|
+
private static sqlPoolMax: number = 1;
|
|
26
|
+
|
|
22
27
|
// ========================================
|
|
23
28
|
// SQL 连接管理
|
|
24
29
|
// ========================================
|
|
@@ -86,11 +91,11 @@ export class Database {
|
|
|
86
91
|
const version = await Promise.race([healthCheckPromise, timeoutPromise]);
|
|
87
92
|
|
|
88
93
|
this.sqlClient = sql;
|
|
94
|
+
this.sqlConnectedAt = Date.now();
|
|
95
|
+
this.sqlPoolMax = config.poolMax ?? 1;
|
|
89
96
|
return sql;
|
|
90
97
|
} catch (error: any) {
|
|
91
|
-
|
|
92
|
-
Logger.error('数据库连接测试失败', error);
|
|
93
|
-
|
|
98
|
+
Logger.error('[Connect] SQL 连接失败', error);
|
|
94
99
|
try {
|
|
95
100
|
await sql?.close();
|
|
96
101
|
} catch (cleanupError) {}
|
|
@@ -107,9 +112,10 @@ export class Database {
|
|
|
107
112
|
try {
|
|
108
113
|
await this.sqlClient.close();
|
|
109
114
|
} catch (error: any) {
|
|
110
|
-
Logger.error('关闭 SQL 连接时出错', error);
|
|
115
|
+
Logger.error('[Connect] 关闭 SQL 连接时出错', error);
|
|
111
116
|
}
|
|
112
117
|
this.sqlClient = null;
|
|
118
|
+
this.sqlConnectedAt = null;
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
if (this.dbHelper) {
|
|
@@ -123,7 +129,7 @@ export class Database {
|
|
|
123
129
|
*/
|
|
124
130
|
static getSql(): SQL {
|
|
125
131
|
if (!this.sqlClient) {
|
|
126
|
-
throw new Error('SQL 客户端未连接,请先调用
|
|
132
|
+
throw new Error('SQL 客户端未连接,请先调用 Connect.connectSql()');
|
|
127
133
|
}
|
|
128
134
|
return this.sqlClient;
|
|
129
135
|
}
|
|
@@ -135,7 +141,7 @@ export class Database {
|
|
|
135
141
|
static getDbHelper(befly?: BeflyContext): DbHelper {
|
|
136
142
|
if (!this.dbHelper) {
|
|
137
143
|
if (!this.sqlClient) {
|
|
138
|
-
throw new Error('SQL 客户端未连接,请先调用
|
|
144
|
+
throw new Error('SQL 客户端未连接,请先调用 Connect.connectSql()');
|
|
139
145
|
}
|
|
140
146
|
// 创建临时 befly 上下文(仅用于 DbHelper)
|
|
141
147
|
const ctx = befly || {
|
|
@@ -188,9 +194,10 @@ export class Database {
|
|
|
188
194
|
await redis.ping();
|
|
189
195
|
|
|
190
196
|
this.redisClient = redis;
|
|
197
|
+
this.redisConnectedAt = Date.now();
|
|
191
198
|
return redis;
|
|
192
199
|
} catch (error: any) {
|
|
193
|
-
Logger.error('Redis 连接失败', error);
|
|
200
|
+
Logger.error('[Connect] Redis 连接失败', error);
|
|
194
201
|
throw new Error(`Redis 连接失败: ${error.message}`);
|
|
195
202
|
}
|
|
196
203
|
}
|
|
@@ -202,8 +209,9 @@ export class Database {
|
|
|
202
209
|
if (this.redisClient) {
|
|
203
210
|
try {
|
|
204
211
|
this.redisClient.close();
|
|
212
|
+
this.redisConnectedAt = null;
|
|
205
213
|
} catch (error: any) {
|
|
206
|
-
Logger.error('关闭 Redis 连接时出错', error);
|
|
214
|
+
Logger.error('[Connect] 关闭 Redis 连接时出错', error);
|
|
207
215
|
}
|
|
208
216
|
this.redisClient = null;
|
|
209
217
|
}
|
|
@@ -215,7 +223,7 @@ export class Database {
|
|
|
215
223
|
*/
|
|
216
224
|
static getRedis(): RedisClient {
|
|
217
225
|
if (!this.redisClient) {
|
|
218
|
-
throw new Error('Redis 客户端未连接,请先调用
|
|
226
|
+
throw new Error('Redis 客户端未连接,请先调用 Connect.connectRedis()');
|
|
219
227
|
}
|
|
220
228
|
return this.redisClient;
|
|
221
229
|
}
|
|
@@ -226,17 +234,21 @@ export class Database {
|
|
|
226
234
|
|
|
227
235
|
/**
|
|
228
236
|
* 连接所有数据库(SQL + Redis)
|
|
229
|
-
* @param
|
|
237
|
+
* @param config - Befly 配置对象(可选)
|
|
238
|
+
* @param options - 连接选项
|
|
230
239
|
*/
|
|
231
|
-
static async connect(options?: { sql?: SqlClientOptions; redis?: boolean }): Promise<void> {
|
|
240
|
+
static async connect(config?: BeflyOptions, options?: { sql?: SqlClientOptions; redis?: boolean }): Promise<void> {
|
|
232
241
|
try {
|
|
233
|
-
|
|
234
|
-
|
|
242
|
+
// 如果 sql 参数不是 false,则连接 SQL
|
|
243
|
+
// 优先级:options.sql > config.db > 跳过
|
|
244
|
+
const sqlConfig = options?.db || {};
|
|
245
|
+
if (sqlConfig) {
|
|
246
|
+
await this.connectSql(sqlConfig);
|
|
235
247
|
}
|
|
236
248
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
249
|
+
// 如果 redis 参数不是 false,则连接 Redis
|
|
250
|
+
const redisConfig = config?.redis || {};
|
|
251
|
+
await this.connectRedis(redisConfig);
|
|
240
252
|
} catch (error: any) {
|
|
241
253
|
Logger.error('数据库初始化失败', error);
|
|
242
254
|
await this.disconnect();
|
|
@@ -262,6 +274,38 @@ export class Database {
|
|
|
262
274
|
};
|
|
263
275
|
}
|
|
264
276
|
|
|
277
|
+
/**
|
|
278
|
+
* 获取连接状态详细信息(用于监控和调试)
|
|
279
|
+
*/
|
|
280
|
+
static getStatus(): {
|
|
281
|
+
sql: {
|
|
282
|
+
connected: boolean;
|
|
283
|
+
connectedAt: number | null;
|
|
284
|
+
uptime: number | null;
|
|
285
|
+
poolMax: number;
|
|
286
|
+
};
|
|
287
|
+
redis: {
|
|
288
|
+
connected: boolean;
|
|
289
|
+
connectedAt: number | null;
|
|
290
|
+
uptime: number | null;
|
|
291
|
+
};
|
|
292
|
+
} {
|
|
293
|
+
const now = Date.now();
|
|
294
|
+
return {
|
|
295
|
+
sql: {
|
|
296
|
+
connected: this.sqlClient !== null,
|
|
297
|
+
connectedAt: this.sqlConnectedAt,
|
|
298
|
+
uptime: this.sqlConnectedAt ? now - this.sqlConnectedAt : null,
|
|
299
|
+
poolMax: this.sqlPoolMax
|
|
300
|
+
},
|
|
301
|
+
redis: {
|
|
302
|
+
connected: this.redisClient !== null,
|
|
303
|
+
connectedAt: this.redisConnectedAt,
|
|
304
|
+
uptime: this.redisConnectedAt ? now - this.redisConnectedAt : null
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
265
309
|
// ========================================
|
|
266
310
|
// 测试辅助方法
|
|
267
311
|
// ========================================
|
|
@@ -287,5 +331,8 @@ export class Database {
|
|
|
287
331
|
this.sqlClient = null;
|
|
288
332
|
this.redisClient = null;
|
|
289
333
|
this.dbHelper = null;
|
|
334
|
+
this.sqlConnectedAt = null;
|
|
335
|
+
this.redisConnectedAt = null;
|
|
336
|
+
this.sqlPoolMax = 1;
|
|
290
337
|
}
|
|
291
338
|
}
|
package/lib/logger.ts
CHANGED
|
@@ -5,20 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
import { join } from 'pathe';
|
|
7
7
|
import { appendFile, stat } from 'node:fs/promises';
|
|
8
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
9
8
|
import type { LogLevel } from '../types/common.js';
|
|
10
9
|
import type { LoggerConfig } from '../types/befly.js';
|
|
11
10
|
|
|
12
|
-
/**
|
|
13
|
-
* 日志上下文存储
|
|
14
|
-
*/
|
|
15
|
-
export interface LogContext {
|
|
16
|
-
requestId?: string;
|
|
17
|
-
[key: string]: any;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const logContextStorage = new AsyncLocalStorage<LogContext>();
|
|
21
|
-
|
|
22
11
|
/**
|
|
23
12
|
* 日志消息类型
|
|
24
13
|
*/
|
|
@@ -84,12 +73,7 @@ export class Logger {
|
|
|
84
73
|
|
|
85
74
|
// 格式化日志消息
|
|
86
75
|
const levelStr = level.toUpperCase().padStart(5);
|
|
87
|
-
|
|
88
|
-
// 获取上下文中的 requestId
|
|
89
|
-
const store = logContextStorage.getStore();
|
|
90
|
-
const requestId = store?.requestId ? ` [${store.requestId}]` : '';
|
|
91
|
-
|
|
92
|
-
const logMessage = `[${timestamp}]${requestId} ${levelStr} - ${content}`;
|
|
76
|
+
const logMessage = `[${timestamp}] ${levelStr} - ${content}`;
|
|
93
77
|
|
|
94
78
|
// 控制台输出
|
|
95
79
|
if (this.config.console === 1) {
|
package/lib/redisHelper.ts
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* 提供 Redis 操作的便捷方法
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { RedisClient } from 'bun';
|
|
7
|
-
import { Logger } from '
|
|
8
|
-
import {
|
|
6
|
+
import { SQL, RedisClient } from 'bun';
|
|
7
|
+
import { Logger } from './logger.js';
|
|
8
|
+
import { Connect } from './connect.js';
|
|
9
|
+
import type { KeyValue } from '../types/common.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Redis 助手类
|
|
@@ -19,9 +20,9 @@ export class RedisHelper {
|
|
|
19
20
|
* @param prefix - Key 前缀
|
|
20
21
|
*/
|
|
21
22
|
constructor(prefix: string = '') {
|
|
22
|
-
const client =
|
|
23
|
+
const client = Connect.getRedis();
|
|
23
24
|
if (!client) {
|
|
24
|
-
throw new Error('Redis 客户端未初始化,请先调用
|
|
25
|
+
throw new Error('Redis 客户端未初始化,请先调用 Connect.connectRedis()');
|
|
25
26
|
}
|
|
26
27
|
this.client = client;
|
|
27
28
|
this.prefix = prefix ? `${prefix}:` : '';
|
package/loader/loadApis.ts
CHANGED
|
@@ -58,9 +58,9 @@ const DEFAULT_API_FIELDS = {
|
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* 加载所有 API 路由
|
|
61
|
-
* @param
|
|
61
|
+
* @param apis - API 路由映射表
|
|
62
62
|
*/
|
|
63
|
-
export async function loadApis(
|
|
63
|
+
export async function loadApis(apis: Map<string, ApiRoute>): Promise<void> {
|
|
64
64
|
try {
|
|
65
65
|
const loadStartTime = Bun.nanoseconds();
|
|
66
66
|
|
|
@@ -120,7 +120,7 @@ export async function loadApis(apiRoutes: Map<string, ApiRoute>): Promise<void>
|
|
|
120
120
|
|
|
121
121
|
// 构建路由
|
|
122
122
|
api.route = `${api.method.toUpperCase()}/api/${apiFile.routePrefix ? apiFile.routePrefix + '/' : ''}${apiFile.relativePath}`;
|
|
123
|
-
|
|
123
|
+
apis.set(api.route, api);
|
|
124
124
|
} catch (error: any) {
|
|
125
125
|
Logger.error(`[${apiFile.typeName}] 接口 ${apiFile.relativePath} 加载失败`, error);
|
|
126
126
|
process.exit(1);
|
package/loader/loadHooks.ts
CHANGED
|
@@ -1,63 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 钩子加载器
|
|
3
|
-
*
|
|
3
|
+
* 只加载核心钩子
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
// 外部依赖
|
|
7
|
-
import { scanAddons, getAddonDir } from 'befly-util';
|
|
8
|
-
|
|
9
6
|
// 相对导入
|
|
10
7
|
import { Logger } from '../lib/logger.js';
|
|
11
|
-
import { coreHookDir
|
|
12
|
-
import {
|
|
8
|
+
import { coreHookDir } from '../paths.js';
|
|
9
|
+
import { scanModules } from '../util.js';
|
|
13
10
|
|
|
14
11
|
// 类型导入
|
|
15
12
|
import type { Hook } from '../types/hook.js';
|
|
16
13
|
|
|
17
|
-
export async function loadHooks(
|
|
18
|
-
//
|
|
19
|
-
hookLists: Hook[];
|
|
20
|
-
pluginsConfig?: Record<string, any>;
|
|
21
|
-
}): Promise<void> {
|
|
14
|
+
export async function loadHooks(pluginsConfig: Record<string, any> | undefined, hooks: Hook[]): Promise<void> {
|
|
22
15
|
try {
|
|
23
|
-
const allHooks: Hook[] = [];
|
|
24
|
-
|
|
25
16
|
// 1. 扫描核心钩子
|
|
26
|
-
const coreHooks = await scanModules<Hook>(coreHookDir, 'core', '钩子',
|
|
27
|
-
|
|
28
|
-
// 2. 扫描组件钩子
|
|
29
|
-
const addonHooks: Hook[] = [];
|
|
30
|
-
const addons = scanAddons();
|
|
31
|
-
for (const addon of addons) {
|
|
32
|
-
const dir = getAddonDir(addon, 'hooks');
|
|
33
|
-
const hooks = await scanModules<Hook>(dir, 'addon', '钩子', befly.pluginsConfig, addon);
|
|
34
|
-
addonHooks.push(...hooks);
|
|
35
|
-
}
|
|
17
|
+
const coreHooks = await scanModules<Hook>(coreHookDir, 'core', '钩子', pluginsConfig);
|
|
36
18
|
|
|
37
|
-
//
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
// 4. 合并所有钩子
|
|
41
|
-
allHooks.push(...coreHooks);
|
|
42
|
-
allHooks.push(...addonHooks);
|
|
43
|
-
allHooks.push(...appHooks);
|
|
44
|
-
|
|
45
|
-
// 5. 过滤禁用的钩子
|
|
46
|
-
const disableHooks = (befly as any).config?.disableHooks || [];
|
|
47
|
-
const enabledHooks = allHooks.filter((hook) => !disableHooks.includes(hook.name));
|
|
19
|
+
// 2. 过滤禁用的钩子
|
|
20
|
+
const disableHooks = (pluginsConfig as any)?.disableHooks || [];
|
|
21
|
+
const enabledHooks = coreHooks.filter((hook) => !disableHooks.includes(hook.name));
|
|
48
22
|
|
|
49
23
|
if (disableHooks.length > 0) {
|
|
50
24
|
Logger.info(`禁用钩子: ${disableHooks.join(', ')}`);
|
|
51
25
|
}
|
|
52
26
|
|
|
53
|
-
//
|
|
54
|
-
const sortedHooks =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
27
|
+
// 3. 按 order 排序
|
|
28
|
+
const sortedHooks = enabledHooks.sort((a, b) => {
|
|
29
|
+
const orderA = a.order ?? 999;
|
|
30
|
+
const orderB = b.order ?? 999;
|
|
31
|
+
return orderA - orderB;
|
|
32
|
+
});
|
|
59
33
|
|
|
60
|
-
|
|
34
|
+
hooks.push(...sortedHooks);
|
|
61
35
|
} catch (error: any) {
|
|
62
36
|
Logger.error('加载钩子时发生错误', error);
|
|
63
37
|
process.exit(1);
|