befly 3.10.18 → 3.11.1
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/README.md +83 -307
- package/dist/befly.config.d.ts +7 -0
- package/{befly.config.ts → dist/befly.config.js} +11 -36
- package/dist/befly.js +15621 -0
- package/dist/befly.min.js +21 -0
- package/dist/checks/checkApi.d.ts +1 -0
- package/{checks/checkApi.ts → dist/checks/checkApi.js} +12 -30
- package/dist/checks/checkHook.d.ts +1 -0
- package/dist/checks/checkHook.js +86 -0
- package/dist/checks/checkMenu.d.ts +7 -0
- package/{checks/checkMenu.ts → dist/checks/checkMenu.js} +18 -53
- package/dist/checks/checkPlugin.d.ts +1 -0
- package/dist/checks/checkPlugin.js +86 -0
- package/dist/checks/checkTable.d.ts +6 -0
- package/{checks/checkTable.ts → dist/checks/checkTable.js} +17 -41
- package/dist/configs/presetFields.d.ts +4 -0
- package/{configs/presetFields.ts → dist/configs/presetFields.js} +1 -1
- package/dist/configs/presetRegexp.d.ts +145 -0
- package/{utils/regex.ts → dist/configs/presetRegexp.js} +8 -31
- package/dist/hooks/auth.d.ts +7 -0
- package/{hooks/auth.ts → dist/hooks/auth.js} +8 -10
- package/dist/hooks/cors.d.ts +11 -0
- package/{hooks/cors.ts → dist/hooks/cors.js} +5 -13
- package/dist/hooks/parser.d.ts +14 -0
- package/{hooks/parser.ts → dist/hooks/parser.js} +31 -45
- package/dist/hooks/permission.d.ts +14 -0
- package/{hooks/permission.ts → dist/hooks/permission.js} +16 -25
- package/dist/hooks/validator.d.ts +11 -0
- package/{hooks/validator.ts → dist/hooks/validator.js} +9 -14
- package/dist/index.d.ts +26 -0
- package/{main.ts → dist/index.js} +61 -100
- package/dist/lib/asyncContext.d.ts +21 -0
- package/dist/lib/asyncContext.js +27 -0
- package/dist/lib/cacheHelper.d.ts +95 -0
- package/{lib/cacheHelper.ts → dist/lib/cacheHelper.js} +45 -105
- package/dist/lib/cacheKeys.d.ts +23 -0
- package/{lib/cacheKeys.ts → dist/lib/cacheKeys.js} +5 -10
- package/dist/lib/cipher.d.ts +153 -0
- package/{lib/cipher.ts → dist/lib/cipher.js} +23 -44
- package/dist/lib/connect.d.ts +91 -0
- package/{lib/connect.ts → dist/lib/connect.js} +47 -88
- package/dist/lib/dbDialect.d.ts +87 -0
- package/{lib/dbDialect.ts → dist/lib/dbDialect.js} +32 -112
- package/dist/lib/dbHelper.d.ts +204 -0
- package/{lib/dbHelper.ts → dist/lib/dbHelper.js} +82 -241
- package/dist/lib/dbUtils.d.ts +68 -0
- package/{lib/dbUtils.ts → dist/lib/dbUtils.js} +51 -126
- package/dist/lib/jwt.d.ts +13 -0
- package/{lib/jwt.ts → dist/lib/jwt.js} +11 -32
- package/dist/lib/logger.d.ts +42 -0
- package/dist/lib/logger.js +1144 -0
- package/dist/lib/redisHelper.d.ts +185 -0
- package/{lib/redisHelper.ts → dist/lib/redisHelper.js} +97 -141
- package/dist/lib/sqlBuilder.d.ts +160 -0
- package/{lib/sqlBuilder.ts → dist/lib/sqlBuilder.js} +132 -278
- package/dist/lib/sqlCheck.d.ts +23 -0
- package/{lib/sqlCheck.ts → dist/lib/sqlCheck.js} +24 -41
- package/dist/lib/validator.d.ts +45 -0
- package/{lib/validator.ts → dist/lib/validator.js} +44 -61
- package/dist/loader/loadApis.d.ts +12 -0
- package/{loader/loadApis.ts → dist/loader/loadApis.js} +10 -20
- package/dist/loader/loadHooks.d.ts +7 -0
- package/dist/loader/loadHooks.js +35 -0
- package/dist/loader/loadPlugins.d.ts +8 -0
- package/{loader/loadPlugins.ts → dist/loader/loadPlugins.js} +14 -26
- package/dist/paths.d.ts +93 -0
- package/{paths.ts → dist/paths.js} +6 -19
- package/dist/plugins/cache.d.ts +16 -0
- package/{plugins/cache.ts → dist/plugins/cache.js} +7 -12
- package/dist/plugins/cipher.d.ts +12 -0
- package/{plugins/cipher.ts → dist/plugins/cipher.js} +4 -6
- package/dist/plugins/config.d.ts +12 -0
- package/dist/plugins/config.js +8 -0
- package/dist/plugins/db.d.ts +16 -0
- package/{plugins/db.ts → dist/plugins/db.js} +11 -17
- package/dist/plugins/jwt.d.ts +12 -0
- package/dist/plugins/jwt.js +12 -0
- package/dist/plugins/logger.d.ts +32 -0
- package/{plugins/logger.ts → dist/plugins/logger.js} +5 -8
- package/dist/plugins/redis.d.ts +16 -0
- package/{plugins/redis.ts → dist/plugins/redis.js} +9 -12
- package/dist/plugins/tool.d.ts +81 -0
- package/{plugins/tool.ts → dist/plugins/tool.js} +9 -30
- package/dist/router/api.d.ts +14 -0
- package/dist/router/api.js +107 -0
- package/dist/router/static.d.ts +9 -0
- package/{router/static.ts → dist/router/static.js} +20 -34
- package/dist/scripts/ensureDist.d.ts +1 -0
- package/dist/scripts/ensureDist.js +296 -0
- package/dist/sync/syncApi.d.ts +3 -0
- package/{sync/syncApi.ts → dist/sync/syncApi.js} +35 -55
- package/dist/sync/syncCache.d.ts +2 -0
- package/{sync/syncCache.ts → dist/sync/syncCache.js} +1 -6
- package/dist/sync/syncDev.d.ts +6 -0
- package/{sync/syncDev.ts → dist/sync/syncDev.js} +29 -62
- package/dist/sync/syncMenu.d.ts +14 -0
- package/{sync/syncMenu.ts → dist/sync/syncMenu.js} +65 -125
- package/dist/sync/syncTable.d.ts +151 -0
- package/{sync/syncTable.ts → dist/sync/syncTable.js} +172 -379
- package/{types → dist/types}/api.d.ts +12 -51
- package/dist/types/api.js +4 -0
- package/{types → dist/types}/befly.d.ts +32 -227
- package/dist/types/befly.js +4 -0
- package/{types → dist/types}/cache.d.ts +7 -15
- package/dist/types/cache.js +4 -0
- package/dist/types/cipher.d.ts +27 -0
- package/dist/types/cipher.js +7 -0
- package/{types → dist/types}/common.d.ts +8 -33
- package/dist/types/common.js +5 -0
- package/{types → dist/types}/context.d.ts +3 -5
- package/dist/types/context.js +4 -0
- package/{types → dist/types}/crypto.d.ts +0 -3
- package/dist/types/crypto.js +4 -0
- package/dist/types/database.d.ts +138 -0
- package/dist/types/database.js +4 -0
- package/dist/types/hook.d.ts +17 -0
- package/dist/types/hook.js +6 -0
- package/dist/types/jwt.d.ts +75 -0
- package/dist/types/jwt.js +4 -0
- package/dist/types/logger.d.ts +59 -0
- package/dist/types/logger.js +6 -0
- package/dist/types/plugin.d.ts +16 -0
- package/dist/types/plugin.js +6 -0
- package/dist/types/redis.d.ts +71 -0
- package/dist/types/redis.js +4 -0
- package/{types/roleApisCache.ts → dist/types/roleApisCache.d.ts} +0 -2
- package/dist/types/roleApisCache.js +8 -0
- package/dist/types/sync.d.ts +92 -0
- package/dist/types/sync.js +4 -0
- package/dist/types/table.d.ts +34 -0
- package/dist/types/table.js +4 -0
- package/dist/types/validate.d.ts +67 -0
- package/dist/types/validate.js +4 -0
- package/dist/utils/calcPerfTime.d.ts +4 -0
- package/{utils/calcPerfTime.ts → dist/utils/calcPerfTime.js} +3 -3
- package/dist/utils/convertBigIntFields.d.ts +11 -0
- package/{utils/convertBigIntFields.ts → dist/utils/convertBigIntFields.js} +5 -9
- package/dist/utils/cors.d.ts +8 -0
- package/{utils/cors.ts → dist/utils/cors.js} +1 -3
- package/dist/utils/disableMenusGlob.d.ts +13 -0
- package/{utils/disableMenusGlob.ts → dist/utils/disableMenusGlob.js} +9 -29
- package/dist/utils/fieldClear.d.ts +11 -0
- package/{utils/fieldClear.ts → dist/utils/fieldClear.js} +15 -33
- package/dist/utils/getClientIp.d.ts +6 -0
- package/{utils/getClientIp.ts → dist/utils/getClientIp.js} +1 -7
- package/dist/utils/importDefault.d.ts +1 -0
- package/dist/utils/importDefault.js +29 -0
- package/dist/utils/isDirentDirectory.d.ts +2 -0
- package/{utils/isDirentDirectory.ts → dist/utils/isDirentDirectory.js} +3 -8
- package/dist/utils/loadMenuConfigs.d.ts +29 -0
- package/{utils/loadMenuConfigs.ts → dist/utils/loadMenuConfigs.js} +66 -52
- package/dist/utils/mergeAndConcat.d.ts +7 -0
- package/dist/utils/mergeAndConcat.js +72 -0
- package/dist/utils/processAtSymbol.d.ts +4 -0
- package/{utils/processFields.ts → dist/utils/processAtSymbol.js} +5 -9
- package/dist/utils/processInfo.d.ts +24 -0
- package/{utils/process.ts → dist/utils/processInfo.js} +2 -18
- package/dist/utils/response.d.ts +20 -0
- package/{utils/response.ts → dist/utils/response.js} +28 -49
- package/dist/utils/scanAddons.d.ts +17 -0
- package/{utils/scanAddons.ts → dist/utils/scanAddons.js} +7 -41
- package/dist/utils/scanConfig.d.ts +26 -0
- package/{utils/scanConfig.ts → dist/utils/scanConfig.js} +28 -66
- package/dist/utils/scanCoreBuiltins.d.ts +3 -0
- package/dist/utils/scanCoreBuiltins.js +65 -0
- package/dist/utils/scanFiles.d.ts +30 -0
- package/{utils/scanFiles.ts → dist/utils/scanFiles.js} +44 -71
- package/dist/utils/scanSources.d.ts +10 -0
- package/dist/utils/scanSources.js +46 -0
- package/dist/utils/sortModules.d.ts +28 -0
- package/{utils/sortModules.ts → dist/utils/sortModules.js} +26 -66
- package/dist/utils/util.d.ts +84 -0
- package/dist/utils/util.js +262 -0
- package/package.json +26 -34
- package/.gitignore +0 -0
- package/bunfig.toml +0 -3
- package/checks/checkHook.ts +0 -48
- package/checks/checkPlugin.ts +0 -48
- package/configs/presetRegexp.ts +0 -225
- package/docs/README.md +0 -98
- package/docs/api/api.md +0 -1921
- package/docs/guide/examples.md +0 -926
- package/docs/guide/quickstart.md +0 -354
- package/docs/hooks/auth.md +0 -38
- package/docs/hooks/cors.md +0 -28
- package/docs/hooks/hook.md +0 -838
- package/docs/hooks/parser.md +0 -19
- package/docs/hooks/rateLimit.md +0 -47
- package/docs/infra/redis.md +0 -628
- package/docs/plugins/cipher.md +0 -61
- package/docs/plugins/database.md +0 -189
- package/docs/plugins/plugin.md +0 -986
- package/docs/reference/addon.md +0 -510
- package/docs/reference/config.md +0 -573
- package/docs/reference/logger.md +0 -495
- package/docs/reference/sync.md +0 -478
- package/docs/reference/table.md +0 -763
- package/docs/reference/validator.md +0 -620
- package/lib/asyncContext.ts +0 -43
- package/lib/logger.ts +0 -811
- package/loader/loadHooks.ts +0 -51
- package/plugins/config.ts +0 -13
- package/plugins/jwt.ts +0 -15
- package/router/api.ts +0 -130
- package/tsconfig.json +0 -8
- package/types/database.d.ts +0 -541
- package/types/hook.d.ts +0 -25
- package/types/jwt.d.ts +0 -118
- package/types/logger.d.ts +0 -65
- package/types/plugin.d.ts +0 -19
- package/types/redis.d.ts +0 -83
- package/types/sync.d.ts +0 -398
- package/types/table.d.ts +0 -216
- package/types/validate.d.ts +0 -69
- package/utils/arrayKeysToCamel.ts +0 -18
- package/utils/configTypes.ts +0 -3
- package/utils/genShortId.ts +0 -12
- package/utils/importDefault.ts +0 -21
- package/utils/keysToCamel.ts +0 -22
- package/utils/keysToSnake.ts +0 -22
- package/utils/pickFields.ts +0 -19
- package/utils/scanSources.ts +0 -64
- package/utils/sqlLog.ts +0 -37
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
// 类型导入
|
|
2
|
-
import type { Hook } from "../types/hook.ts";
|
|
3
|
-
|
|
4
1
|
// 外部依赖
|
|
5
|
-
import { isPlainObject, isEmpty } from "es-toolkit/compat";
|
|
6
2
|
import { XMLParser } from "fast-xml-parser";
|
|
7
|
-
|
|
3
|
+
import { ErrorResponse } from "../utils/response";
|
|
8
4
|
// 相对导入
|
|
9
|
-
import { pickFields } from "../utils/
|
|
10
|
-
import { ErrorResponse } from "../utils/response.ts";
|
|
11
|
-
|
|
5
|
+
import { isEmpty, isPlainObject, pickFields } from "../utils/util";
|
|
12
6
|
const xmlParser = new XMLParser();
|
|
13
|
-
|
|
14
7
|
/**
|
|
15
8
|
* 请求参数解析钩子
|
|
16
9
|
* - GET 请求:解析 URL 查询参数
|
|
@@ -19,48 +12,52 @@ const xmlParser = new XMLParser();
|
|
|
19
12
|
* - rawBody: true 时跳过解析,由 handler 自行处理原始请求
|
|
20
13
|
*/
|
|
21
14
|
export default {
|
|
15
|
+
name: "parser",
|
|
16
|
+
enable: true,
|
|
22
17
|
deps: ["auth"],
|
|
23
18
|
handler: async (befly, ctx) => {
|
|
24
|
-
if (!ctx.api)
|
|
25
|
-
|
|
19
|
+
if (!ctx.api)
|
|
20
|
+
return;
|
|
26
21
|
// rawBody 模式:跳过解析,保留原始请求供 handler 自行处理
|
|
27
22
|
// 适用于:微信回调、支付回调、webhook 等需要手动解密/验签的场景
|
|
28
23
|
if (ctx.api.rawBody) {
|
|
29
24
|
ctx.body = {};
|
|
30
25
|
return;
|
|
31
26
|
}
|
|
32
|
-
|
|
33
27
|
// GET 请求:解析查询参数
|
|
34
28
|
if (ctx.req.method === "GET") {
|
|
35
29
|
const url = new URL(ctx.req.url);
|
|
36
30
|
const params = Object.fromEntries(url.searchParams);
|
|
37
31
|
if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
|
|
38
32
|
ctx.body = pickFields(params, Object.keys(ctx.api.fields));
|
|
39
|
-
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
40
35
|
ctx.body = params;
|
|
41
36
|
}
|
|
42
|
-
}
|
|
37
|
+
}
|
|
38
|
+
else if (ctx.req.method === "POST") {
|
|
43
39
|
// POST 请求:解析请求体
|
|
44
40
|
const contentType = ctx.req.headers.get("content-type") || "";
|
|
45
41
|
// 获取 URL 查询参数(POST 请求也可能带参数)
|
|
46
42
|
const url = new URL(ctx.req.url);
|
|
47
43
|
const queryParams = Object.fromEntries(url.searchParams);
|
|
48
|
-
|
|
49
44
|
try {
|
|
50
45
|
// JSON 格式
|
|
51
46
|
if (contentType.includes("application/json")) {
|
|
52
|
-
const body = (await ctx.req.json())
|
|
47
|
+
const body = (await ctx.req.json());
|
|
53
48
|
// 合并 URL 参数和请求体(请求体优先)
|
|
54
49
|
const merged = { ...queryParams, ...body };
|
|
55
50
|
if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
|
|
56
51
|
ctx.body = pickFields(merged, Object.keys(ctx.api.fields));
|
|
57
|
-
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
58
54
|
ctx.body = merged;
|
|
59
55
|
}
|
|
60
|
-
}
|
|
56
|
+
}
|
|
57
|
+
else if (contentType.includes("application/xml") || contentType.includes("text/xml")) {
|
|
61
58
|
// XML 格式
|
|
62
59
|
const text = await ctx.req.text();
|
|
63
|
-
const parsed = xmlParser.parse(text)
|
|
60
|
+
const parsed = xmlParser.parse(text);
|
|
64
61
|
// 提取根节点内容(如 xml),使 body 扁平化
|
|
65
62
|
const rootKey = Object.keys(parsed)[0];
|
|
66
63
|
const body = rootKey && typeof parsed[rootKey] === "object" ? parsed[rootKey] : parsed;
|
|
@@ -68,39 +65,28 @@ export default {
|
|
|
68
65
|
const merged = { ...queryParams, ...body };
|
|
69
66
|
if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
|
|
70
67
|
ctx.body = pickFields(merged, Object.keys(ctx.api.fields));
|
|
71
|
-
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
72
70
|
ctx.body = merged;
|
|
73
71
|
}
|
|
74
|
-
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
75
74
|
// 不支持的 Content-Type
|
|
76
|
-
ctx.response = ErrorResponse(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
null,
|
|
81
|
-
{
|
|
82
|
-
location: "content-type",
|
|
83
|
-
value: contentType
|
|
84
|
-
},
|
|
85
|
-
"parser"
|
|
86
|
-
);
|
|
75
|
+
ctx.response = ErrorResponse(ctx, "无效的请求参数格式", 1, null, {
|
|
76
|
+
location: "content-type",
|
|
77
|
+
value: contentType
|
|
78
|
+
}, "parser");
|
|
87
79
|
return;
|
|
88
80
|
}
|
|
89
|
-
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
90
83
|
// 解析失败:属于客户端输入错误,返回安全 detail(不回传异常栈/原始 body)
|
|
91
|
-
ctx.response = ErrorResponse(
|
|
92
|
-
|
|
93
|
-
"
|
|
94
|
-
|
|
95
|
-
null,
|
|
96
|
-
{
|
|
97
|
-
location: "body",
|
|
98
|
-
reason: contentType.includes("application/json") ? "invalid_json" : contentType.includes("xml") ? "invalid_xml" : "invalid_body"
|
|
99
|
-
},
|
|
100
|
-
"parser"
|
|
101
|
-
);
|
|
84
|
+
ctx.response = ErrorResponse(ctx, "无效的请求参数格式", 1, null, {
|
|
85
|
+
location: "body",
|
|
86
|
+
reason: contentType.includes("application/json") ? "invalid_json" : contentType.includes("xml") ? "invalid_xml" : "invalid_body"
|
|
87
|
+
}, "parser");
|
|
102
88
|
return;
|
|
103
89
|
}
|
|
104
90
|
}
|
|
105
91
|
}
|
|
106
|
-
}
|
|
92
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 权限检查钩子
|
|
3
|
+
* - 接口无需权限(auth=false):直接通过
|
|
4
|
+
* - 用户未登录:返回 401
|
|
5
|
+
* - 开发者角色(dev):最高权限,直接通过
|
|
6
|
+
* - 其他角色:检查 Redis 中的角色权限集合
|
|
7
|
+
*/
|
|
8
|
+
declare const _default: {
|
|
9
|
+
name: string;
|
|
10
|
+
enable: true;
|
|
11
|
+
deps: string[];
|
|
12
|
+
handler: (befly: import("../types/befly").BeflyContext, ctx: import("../types/context").RequestContext<Record<string, any>>) => Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
export default _default;
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import { CacheKeys } from "../lib/cacheKeys.ts";
|
|
5
|
-
import { Logger } from "../lib/logger.ts";
|
|
1
|
+
import { CacheKeys } from "../lib/cacheKeys";
|
|
2
|
+
import { Logger } from "../lib/logger";
|
|
6
3
|
// 相对导入
|
|
7
|
-
import { ErrorResponse } from "../utils/response
|
|
8
|
-
|
|
4
|
+
import { ErrorResponse } from "../utils/response";
|
|
9
5
|
/**
|
|
10
6
|
* 权限检查钩子
|
|
11
7
|
* - 接口无需权限(auth=false):直接通过
|
|
@@ -14,52 +10,47 @@ import { ErrorResponse } from "../utils/response.ts";
|
|
|
14
10
|
* - 其他角色:检查 Redis 中的角色权限集合
|
|
15
11
|
*/
|
|
16
12
|
export default {
|
|
13
|
+
name: "permission",
|
|
14
|
+
enable: true,
|
|
17
15
|
deps: ["validator"],
|
|
18
16
|
handler: async (befly, ctx) => {
|
|
19
|
-
if (!ctx.api)
|
|
20
|
-
|
|
17
|
+
if (!ctx.api)
|
|
18
|
+
return;
|
|
21
19
|
// 1. 接口无需权限
|
|
22
20
|
if (ctx.api.auth === false) {
|
|
23
21
|
return;
|
|
24
22
|
}
|
|
25
|
-
|
|
26
23
|
// 2. 用户未登录
|
|
27
24
|
if (!ctx.user || !ctx.user.id) {
|
|
28
25
|
ctx.response = ErrorResponse(ctx, "未登录", 1, null, null, "auth");
|
|
29
26
|
return;
|
|
30
27
|
}
|
|
31
|
-
|
|
32
28
|
// 3. 开发者权限(最高权限)
|
|
33
29
|
if (ctx.user.roleCode === "dev") {
|
|
34
30
|
return;
|
|
35
31
|
}
|
|
36
|
-
|
|
37
32
|
// 4. 角色权限检查
|
|
38
33
|
// apiPath 在 apiHandler 中已统一生成并写入 ctx.route
|
|
39
34
|
const apiPath = ctx.route;
|
|
40
35
|
const roleCode = ctx.user.roleCode;
|
|
41
|
-
|
|
42
36
|
let hasPermission = false;
|
|
43
37
|
if (roleCode && befly.redis) {
|
|
44
38
|
try {
|
|
45
39
|
// 极简方案:每个角色一个 Set,直接判断成员是否存在
|
|
46
40
|
const roleApisKey = CacheKeys.roleApis(roleCode);
|
|
47
41
|
hasPermission = await befly.redis.sismember(roleApisKey, apiPath);
|
|
48
|
-
}
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
49
44
|
// Redis 异常:记录到 error 日志文件(不回传给客户端),并降级为拒绝访问
|
|
50
|
-
Logger.error(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
},
|
|
57
|
-
"hook permission redis error"
|
|
58
|
-
);
|
|
45
|
+
Logger.error({
|
|
46
|
+
event: "hook_permission_redis_error",
|
|
47
|
+
apiPath: apiPath,
|
|
48
|
+
roleCode: roleCode,
|
|
49
|
+
err: err
|
|
50
|
+
}, "hook permission redis error");
|
|
59
51
|
hasPermission = false;
|
|
60
52
|
}
|
|
61
53
|
}
|
|
62
|
-
|
|
63
54
|
if (!hasPermission) {
|
|
64
55
|
const apiNameLabel = typeof ctx.api.name === "string" && ctx.api.name.length > 0 ? ctx.api.name : null;
|
|
65
56
|
const apiPathLabel = typeof apiPath === "string" && apiPath.length > 0 ? apiPath : null;
|
|
@@ -68,4 +59,4 @@ export default {
|
|
|
68
59
|
return;
|
|
69
60
|
}
|
|
70
61
|
}
|
|
71
|
-
}
|
|
62
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 参数验证钩子
|
|
3
|
+
* 根据 API 定义的 fields 和 required 验证请求参数
|
|
4
|
+
*/
|
|
5
|
+
declare const _default: {
|
|
6
|
+
name: string;
|
|
7
|
+
enable: true;
|
|
8
|
+
deps: string[];
|
|
9
|
+
handler: (befly: import("../types/befly").BeflyContext, ctx: import("../types/context").RequestContext<Record<string, any>>) => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
export default _default;
|
|
@@ -1,38 +1,33 @@
|
|
|
1
|
-
// 类型导入
|
|
2
|
-
import type { Hook } from "../types/hook.ts";
|
|
3
|
-
|
|
4
1
|
// 相对导入
|
|
5
|
-
import { Validator } from "../lib/validator
|
|
6
|
-
import { ErrorResponse } from "../utils/response
|
|
7
|
-
|
|
2
|
+
import { Validator } from "../lib/validator";
|
|
3
|
+
import { ErrorResponse } from "../utils/response";
|
|
8
4
|
/**
|
|
9
5
|
* 参数验证钩子
|
|
10
6
|
* 根据 API 定义的 fields 和 required 验证请求参数
|
|
11
7
|
*/
|
|
12
8
|
export default {
|
|
9
|
+
name: "validator",
|
|
10
|
+
enable: true,
|
|
13
11
|
deps: ["parser"],
|
|
14
12
|
handler: async (befly, ctx) => {
|
|
15
|
-
if (!ctx.api)
|
|
16
|
-
|
|
13
|
+
if (!ctx.api)
|
|
14
|
+
return;
|
|
17
15
|
// 无需验证
|
|
18
16
|
if (!ctx.api.fields) {
|
|
19
17
|
return;
|
|
20
18
|
}
|
|
21
|
-
|
|
22
19
|
// 应用字段默认值
|
|
23
20
|
for (const [field, fieldDef] of Object.entries(ctx.api.fields)) {
|
|
24
21
|
// 字段未传值且定义了默认值时,应用默认值
|
|
25
|
-
if (ctx.body[field] === undefined &&
|
|
26
|
-
ctx.body[field] =
|
|
22
|
+
if (ctx.body[field] === undefined && fieldDef?.default !== undefined && fieldDef?.default !== null) {
|
|
23
|
+
ctx.body[field] = fieldDef.default;
|
|
27
24
|
}
|
|
28
25
|
}
|
|
29
|
-
|
|
30
26
|
// 验证参数
|
|
31
27
|
const result = Validator.validate(ctx.body, ctx.api.fields, ctx.api.required || []);
|
|
32
|
-
|
|
33
28
|
if (result.code !== 0) {
|
|
34
29
|
ctx.response = ErrorResponse(ctx, result.firstError || "参数验证失败", 1, null, result.fieldErrors, "validator");
|
|
35
30
|
return;
|
|
36
31
|
}
|
|
37
32
|
}
|
|
38
|
-
}
|
|
33
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Befly 框架主入口文件
|
|
3
|
+
* 提供简洁的框架接口,核心逻辑已提取到 loader 层
|
|
4
|
+
*/
|
|
5
|
+
import type { BeflyContext } from "./types/befly";
|
|
6
|
+
/**
|
|
7
|
+
* Befly 框架核心类
|
|
8
|
+
* 职责:管理应用上下文和生命周期
|
|
9
|
+
*/
|
|
10
|
+
export declare class Befly {
|
|
11
|
+
/** API 路由映射表 */
|
|
12
|
+
private apis;
|
|
13
|
+
/** 插件列表 */
|
|
14
|
+
private plugins;
|
|
15
|
+
/** 钩子列表 */
|
|
16
|
+
private hooks;
|
|
17
|
+
/** 应用上下文 */
|
|
18
|
+
context: Partial<BeflyContext>;
|
|
19
|
+
/** 配置引用(延迟加载) */
|
|
20
|
+
private config;
|
|
21
|
+
/**
|
|
22
|
+
* 启动完整的生命周期流程
|
|
23
|
+
* @returns HTTP 服务器实例
|
|
24
|
+
*/
|
|
25
|
+
start(): Promise<ReturnType<typeof Bun.serve>>;
|
|
26
|
+
}
|
|
@@ -2,204 +2,165 @@
|
|
|
2
2
|
* Befly 框架主入口文件
|
|
3
3
|
* 提供简洁的框架接口,核心逻辑已提取到 loader 层
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
import { checkApi } from "./checks/checkApi.ts";
|
|
13
|
-
import { checkHook } from "./checks/checkHook.ts";
|
|
14
|
-
import { checkMenu } from "./checks/checkMenu.ts";
|
|
15
|
-
import { checkPlugin } from "./checks/checkPlugin.ts";
|
|
16
|
-
import { checkTable } from "./checks/checkTable.ts";
|
|
5
|
+
import { loadBeflyConfig } from "./befly.config";
|
|
6
|
+
import { checkApi } from "./checks/checkApi";
|
|
7
|
+
import { checkHook } from "./checks/checkHook";
|
|
8
|
+
import { checkMenu } from "./checks/checkMenu";
|
|
9
|
+
import { checkPlugin } from "./checks/checkPlugin";
|
|
10
|
+
import { checkTable } from "./checks/checkTable";
|
|
17
11
|
// ========== 相对导入(项目内部文件) ==========
|
|
18
12
|
// 基础设施
|
|
19
|
-
import { Connect } from "./lib/connect
|
|
20
|
-
import { Logger } from "./lib/logger
|
|
13
|
+
import { Connect } from "./lib/connect";
|
|
14
|
+
import { Logger } from "./lib/logger";
|
|
21
15
|
// 加载器
|
|
22
|
-
import { loadApis } from "./loader/loadApis
|
|
23
|
-
import { loadHooks } from "./loader/loadHooks
|
|
24
|
-
import { loadPlugins } from "./loader/loadPlugins
|
|
16
|
+
import { loadApis } from "./loader/loadApis";
|
|
17
|
+
import { loadHooks } from "./loader/loadHooks";
|
|
18
|
+
import { loadPlugins } from "./loader/loadPlugins";
|
|
25
19
|
// 路由处理
|
|
26
|
-
import { apiHandler } from "./router/api
|
|
27
|
-
import { staticHandler } from "./router/static
|
|
20
|
+
import { apiHandler } from "./router/api";
|
|
21
|
+
import { staticHandler } from "./router/static";
|
|
28
22
|
// 同步
|
|
29
|
-
import { syncApi } from "./sync/syncApi
|
|
30
|
-
import { syncCache } from "./sync/syncCache
|
|
31
|
-
import { syncDev } from "./sync/syncDev
|
|
32
|
-
import { syncMenu } from "./sync/syncMenu
|
|
33
|
-
import { syncTable } from "./sync/syncTable
|
|
23
|
+
import { syncApi } from "./sync/syncApi";
|
|
24
|
+
import { syncCache } from "./sync/syncCache";
|
|
25
|
+
import { syncDev } from "./sync/syncDev";
|
|
26
|
+
import { syncMenu } from "./sync/syncMenu";
|
|
27
|
+
import { syncTable } from "./sync/syncTable";
|
|
34
28
|
// 工具
|
|
35
|
-
import { calcPerfTime } from "./utils/calcPerfTime
|
|
36
|
-
import { getProcessRole } from "./utils/
|
|
37
|
-
import { scanSources } from "./utils/scanSources
|
|
38
|
-
|
|
29
|
+
import { calcPerfTime } from "./utils/calcPerfTime";
|
|
30
|
+
import { getProcessRole } from "./utils/processInfo";
|
|
31
|
+
import { scanSources } from "./utils/scanSources";
|
|
39
32
|
/**
|
|
40
33
|
* Befly 框架核心类
|
|
41
34
|
* 职责:管理应用上下文和生命周期
|
|
42
35
|
*/
|
|
43
36
|
export class Befly {
|
|
44
37
|
/** API 路由映射表 */
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
apis = new Map();
|
|
47
39
|
/** 插件列表 */
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
plugins = [];
|
|
50
41
|
/** 钩子列表 */
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
hooks = [];
|
|
53
43
|
/** 应用上下文 */
|
|
54
|
-
|
|
55
|
-
|
|
44
|
+
context = {};
|
|
56
45
|
/** 配置引用(延迟加载) */
|
|
57
|
-
|
|
58
|
-
|
|
46
|
+
config = null;
|
|
59
47
|
/**
|
|
60
48
|
* 启动完整的生命周期流程
|
|
61
49
|
* @returns HTTP 服务器实例
|
|
62
50
|
*/
|
|
63
|
-
|
|
51
|
+
async start() {
|
|
64
52
|
try {
|
|
65
53
|
const serverStartTime = Bun.nanoseconds();
|
|
66
|
-
|
|
67
|
-
// 0. 延迟加载配置(避免循环依赖)
|
|
68
|
-
const { loadBeflyConfig } = await import("./befly.config.ts");
|
|
54
|
+
// 0. 加载配置
|
|
69
55
|
this.config = await loadBeflyConfig();
|
|
70
|
-
|
|
71
56
|
// 将配置注入到 ctx,供插件/Hook/sync 等按需读取
|
|
72
57
|
this.context.config = this.config;
|
|
73
|
-
|
|
74
58
|
const { apis, tables, plugins, hooks, addons } = await scanSources();
|
|
75
|
-
|
|
76
59
|
// 让后续 syncMenu 能拿到 addon 的 views 路径等信息
|
|
77
60
|
this.context.addons = addons;
|
|
78
|
-
|
|
79
61
|
await checkApi(apis);
|
|
80
62
|
await checkTable(tables);
|
|
81
63
|
await checkPlugin(plugins);
|
|
82
64
|
await checkHook(hooks);
|
|
83
65
|
const checkedMenus = await checkMenu(addons, { disableMenus: this.config.disableMenus || [] });
|
|
84
|
-
|
|
85
66
|
// 1. 启动期建立基础连接(SQL + Redis)
|
|
86
67
|
// 说明:连接职责收敛到启动期单点;插件只消费已连接实例(Connect.getSql/getRedis)。
|
|
87
68
|
await Connect.connect({
|
|
88
69
|
db: this.config.db || {},
|
|
89
70
|
redis: this.config.redis || {}
|
|
90
71
|
});
|
|
91
|
-
|
|
92
72
|
// 2. 加载插件
|
|
93
|
-
this.plugins = await loadPlugins(plugins
|
|
94
|
-
|
|
73
|
+
this.plugins = await loadPlugins(plugins, this.context);
|
|
95
74
|
// 启动期依赖完整性检查:避免 sync 阶段出现 undefined 调用
|
|
96
75
|
// 注意:这里不做兼容别名(例如 dbHelper=db),要求上下文必须注入标准字段。
|
|
97
|
-
if (!
|
|
76
|
+
if (!this.context.redis) {
|
|
98
77
|
throw new Error("启动失败:ctx.redis 未初始化(Redis 插件未加载或注入失败)");
|
|
99
78
|
}
|
|
100
|
-
if (!
|
|
79
|
+
if (!this.context.db) {
|
|
101
80
|
throw new Error("启动失败:ctx.db 未初始化(Db 插件未加载或注入失败)");
|
|
102
81
|
}
|
|
103
|
-
if (!
|
|
82
|
+
if (!this.context.cache) {
|
|
104
83
|
throw new Error("启动失败:ctx.cache 未初始化(cache 插件未加载或注入失败)");
|
|
105
84
|
}
|
|
106
|
-
|
|
107
85
|
// 5. 自动同步 (仅主进程执行,避免集群模式下重复执行)
|
|
108
|
-
await syncTable(this.context
|
|
109
|
-
await syncApi(this.context
|
|
110
|
-
|
|
111
|
-
await
|
|
112
|
-
await syncDev(this.context as BeflyContext, { devEmail: this.config.devEmail, devPassword: this.config.devPassword });
|
|
113
|
-
|
|
86
|
+
await syncTable(this.context, tables);
|
|
87
|
+
await syncApi(this.context, apis);
|
|
88
|
+
await syncMenu(this.context, checkedMenus);
|
|
89
|
+
await syncDev(this.context, { devEmail: this.config.devEmail, devPassword: this.config.devPassword });
|
|
114
90
|
// 缓存同步:统一在所有同步完成后执行(cacheApis + cacheMenus + rebuildRoleApiPermissions)
|
|
115
|
-
await syncCache(this.context
|
|
116
|
-
|
|
91
|
+
await syncCache(this.context);
|
|
117
92
|
// 3. 加载钩子
|
|
118
|
-
this.hooks = await loadHooks(hooks
|
|
119
|
-
|
|
93
|
+
this.hooks = await loadHooks(hooks);
|
|
120
94
|
// 4. 加载所有 API
|
|
121
|
-
this.apis = await loadApis(apis
|
|
122
|
-
|
|
95
|
+
this.apis = await loadApis(apis);
|
|
123
96
|
// 6. 启动 HTTP服务器
|
|
124
|
-
const apiFetch = apiHandler(this.apis, this.hooks, this.context
|
|
125
|
-
const staticFetch = staticHandler(this.config
|
|
126
|
-
|
|
97
|
+
const apiFetch = apiHandler(this.apis, this.hooks, this.context);
|
|
98
|
+
const staticFetch = staticHandler(this.config.cors);
|
|
127
99
|
const server = Bun.serve({
|
|
128
|
-
port: this.config
|
|
129
|
-
hostname: this.config
|
|
100
|
+
port: this.config.appPort,
|
|
101
|
+
hostname: this.config.appHost,
|
|
130
102
|
// 开发模式下启用详细错误信息
|
|
131
|
-
development: this.config
|
|
103
|
+
development: this.config.nodeEnv === "development",
|
|
132
104
|
// 空闲连接超时时间(秒),防止恶意连接占用资源
|
|
133
105
|
idleTimeout: 30,
|
|
134
106
|
fetch: async (req, bunServer) => {
|
|
135
107
|
const url = new URL(req.url);
|
|
136
|
-
|
|
137
108
|
if (url.pathname === "/") {
|
|
138
|
-
return Response.json({ code: 0, msg: `${this.config
|
|
109
|
+
return Response.json({ code: 0, msg: `${this.config.appName} 接口服务已启动` });
|
|
139
110
|
}
|
|
140
|
-
|
|
141
111
|
if (url.pathname.startsWith("/api/")) {
|
|
142
112
|
return apiFetch(req, bunServer);
|
|
143
113
|
}
|
|
144
|
-
|
|
145
114
|
return staticFetch(req);
|
|
146
115
|
},
|
|
147
|
-
error: (error
|
|
116
|
+
error: (error) => {
|
|
148
117
|
Logger.error({ err: error }, "服务启动时发生错误");
|
|
149
118
|
// 开发模式下返回详细错误信息
|
|
150
|
-
if (this.config
|
|
151
|
-
return Response.json(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
},
|
|
158
|
-
{ status: 200 }
|
|
159
|
-
);
|
|
119
|
+
if (this.config.nodeEnv === "development") {
|
|
120
|
+
return Response.json({
|
|
121
|
+
code: 1,
|
|
122
|
+
msg: "内部服务器错误",
|
|
123
|
+
error: error.message,
|
|
124
|
+
stack: error.stack
|
|
125
|
+
}, { status: 200 });
|
|
160
126
|
}
|
|
161
127
|
return Response.json({ code: 1, msg: "内部服务器错误" }, { status: 200 });
|
|
162
128
|
}
|
|
163
129
|
});
|
|
164
|
-
|
|
165
130
|
const finalStartupTime = calcPerfTime(serverStartTime);
|
|
166
131
|
const processRole = getProcessRole();
|
|
167
132
|
const roleLabel = processRole.role === "primary" ? "主进程" : `工作进程 #${processRole.instanceId}`;
|
|
168
133
|
const envLabel = processRole.env === "standalone" ? "" : ` [${processRole.env}]`;
|
|
169
|
-
|
|
170
|
-
Logger.info(`${this.config!.appName} 启动成功! (${roleLabel}${envLabel})`);
|
|
134
|
+
Logger.info(`${this.config.appName} 启动成功! (${roleLabel}${envLabel})`);
|
|
171
135
|
Logger.info(`服务器启动耗时: ${finalStartupTime}`);
|
|
172
136
|
Logger.info(`服务器监听地址: ${server.url}`);
|
|
173
|
-
|
|
174
137
|
// 7. 注册优雅关闭处理
|
|
175
|
-
const gracefulShutdown = async (signal
|
|
138
|
+
const gracefulShutdown = async (signal) => {
|
|
176
139
|
Logger.info(`收到 ${signal} 信号,开始优雅关闭...`);
|
|
177
|
-
|
|
178
140
|
// 优雅停止(等待进行中的请求完成)
|
|
179
141
|
try {
|
|
180
142
|
await server.stop();
|
|
181
143
|
Logger.info("HTTP 服务器已停止");
|
|
182
|
-
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
183
146
|
Logger.error({ err: error }, "停止 HTTP 服务器时出错");
|
|
184
147
|
}
|
|
185
|
-
|
|
186
148
|
// 关闭数据库连接
|
|
187
149
|
try {
|
|
188
150
|
await Connect.disconnect();
|
|
189
151
|
Logger.info("数据库连接已关闭");
|
|
190
|
-
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
191
154
|
Logger.error({ err: error }, "关闭数据库连接时出错");
|
|
192
155
|
}
|
|
193
|
-
|
|
194
156
|
Logger.info("服务器已优雅关闭");
|
|
195
157
|
process.exit(0);
|
|
196
158
|
};
|
|
197
|
-
|
|
198
159
|
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
199
160
|
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
200
|
-
|
|
201
161
|
return server;
|
|
202
|
-
}
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
203
164
|
Logger.error({ err: error }, "项目启动失败");
|
|
204
165
|
process.exit(1);
|
|
205
166
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AsyncLocalStorage 请求级上下文
|
|
3
|
+
*
|
|
4
|
+
* 目标:
|
|
5
|
+
* - 给 Logger/慢查询日志提供 requestId/route/userId 等元信息
|
|
6
|
+
* - 避免在深层工具函数中层层传参
|
|
7
|
+
*/
|
|
8
|
+
export type RequestMeta = {
|
|
9
|
+
requestId: string;
|
|
10
|
+
method: string;
|
|
11
|
+
route: string;
|
|
12
|
+
ip: string;
|
|
13
|
+
userId?: string | number | null;
|
|
14
|
+
roleCode?: string | null;
|
|
15
|
+
nickname?: string | null;
|
|
16
|
+
roleType?: string | null;
|
|
17
|
+
now: number;
|
|
18
|
+
};
|
|
19
|
+
export declare function withCtx<T>(meta: RequestMeta, fn: () => T): T;
|
|
20
|
+
export declare function getCtx(): RequestMeta | null;
|
|
21
|
+
export declare function setCtxUser(userId: string | number | null | undefined, roleCode?: string | null | undefined, nickname?: string | null | undefined, roleType?: string | null | undefined): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AsyncLocalStorage 请求级上下文
|
|
3
|
+
*
|
|
4
|
+
* 目标:
|
|
5
|
+
* - 给 Logger/慢查询日志提供 requestId/route/userId 等元信息
|
|
6
|
+
* - 避免在深层工具函数中层层传参
|
|
7
|
+
*/
|
|
8
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
9
|
+
const storage = new AsyncLocalStorage();
|
|
10
|
+
export function withCtx(meta, fn) {
|
|
11
|
+
return storage.run(meta, fn);
|
|
12
|
+
}
|
|
13
|
+
export function getCtx() {
|
|
14
|
+
const store = storage.getStore();
|
|
15
|
+
if (!store)
|
|
16
|
+
return null;
|
|
17
|
+
return store;
|
|
18
|
+
}
|
|
19
|
+
export function setCtxUser(userId, roleCode, nickname, roleType) {
|
|
20
|
+
const store = storage.getStore();
|
|
21
|
+
if (!store)
|
|
22
|
+
return;
|
|
23
|
+
store.userId = userId;
|
|
24
|
+
store.roleCode = roleCode;
|
|
25
|
+
store.nickname = nickname;
|
|
26
|
+
store.roleType = roleType;
|
|
27
|
+
}
|