befly 3.17.10 → 3.17.12
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/hooks/auth.js +1 -1
- package/lib/dbHelper/execute.js +12 -4
- package/package.json +2 -2
- package/router/api.js +59 -56
- package/utils/cors.js +17 -6
- package/utils/response.js +6 -5
- package/hooks/cors.js +0 -39
package/hooks/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { toSessionTtlSeconds } from "../utils/toSessionTtlSeconds.js";
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
|
-
deps: [
|
|
4
|
+
deps: [],
|
|
5
5
|
handler: async (befly, ctx) => {
|
|
6
6
|
const authHeader = ctx.req.headers.get("authorization");
|
|
7
7
|
const ttlSeconds = toSessionTtlSeconds(befly.config?.session?.expireDays);
|
package/lib/dbHelper/execute.js
CHANGED
|
@@ -19,10 +19,18 @@ export const executeMethods = {
|
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
21
|
let queryResult;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
// Bun 事件循环 bug workaround:
|
|
23
|
+
// 当事件循环中只有一个 I/O 源(SQL socket)时,Bun 会走单源快速路径,
|
|
24
|
+
// 在 I/O 回调内直接唤醒 JS Promise,跳过正常调度,导致 SQL 连接状态
|
|
25
|
+
// 未完全重置,下一条查询的 Promise resolver 永久丢失(远程 TCP 连接必现)。
|
|
26
|
+
// 预注册一个 timer 使事件循环中同时存在两种事件源,强制 Bun 走正常多源调度路径。
|
|
27
|
+
// timer 时长无关紧要,查询完成后会被 clearTimeout 清理。
|
|
28
|
+
const _timer = setTimeout(() => {}, 2147483647);
|
|
29
|
+
const queryPromise = safeParams.length > 0 ? this.sql.unsafe(sql, safeParams) : this.sql.unsafe(sql);
|
|
30
|
+
try {
|
|
31
|
+
queryResult = await queryPromise;
|
|
32
|
+
} finally {
|
|
33
|
+
clearTimeout(_timer);
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
const duration = Date.now() - startTime;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.17.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.17.12",
|
|
4
|
+
"gitHead": "098baa90ed47149819a515c6169b29072e4840bb",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
|
|
7
7
|
"keywords": [
|
package/router/api.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { Logger } from "../lib/logger.js";
|
|
7
7
|
// 相对导入
|
|
8
|
+
import { setCorsOptions } from "../utils/cors.js";
|
|
8
9
|
import { getClientIp } from "../utils/getClientIp.js";
|
|
9
10
|
import { FinalResponse } from "../utils/response.js";
|
|
10
11
|
import { genShortId } from "../utils/util.js";
|
|
@@ -20,16 +21,39 @@ export function apiHandler(apis, hooks, context) {
|
|
|
20
21
|
// 1. 生成请求 ID
|
|
21
22
|
const requestId = genShortId();
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
const corsHeaders = setCorsOptions(req, context.config?.cors || {});
|
|
25
|
+
corsHeaders["X-Request-ID"] = requestId;
|
|
26
|
+
|
|
27
|
+
// 2. OPTIONS 预检请求:直接返回,不走 hooks,不打日志
|
|
28
|
+
if (req.method === "OPTIONS") {
|
|
29
|
+
return new Response(null, {
|
|
30
|
+
status: 204,
|
|
31
|
+
headers: corsHeaders
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 3. 创建请求上下文
|
|
24
36
|
const url = new URL(req.url);
|
|
25
37
|
// 只用接口路径做存在性判断与匹配:不要把 method 拼进 key
|
|
26
38
|
// 说明:apisMap 的 key 来源于 scanFiles/loadApis 生成的 path(例如 /api/core/xxx)
|
|
27
39
|
|
|
28
40
|
const clientIp = getClientIp(req, server);
|
|
29
41
|
|
|
30
|
-
const
|
|
42
|
+
const apiData = apis[url.pathname];
|
|
43
|
+
if (!apiData) {
|
|
44
|
+
return Response.json(
|
|
45
|
+
{
|
|
46
|
+
code: 1,
|
|
47
|
+
msg: "接口不存在"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
headers: corsHeaders
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
}
|
|
31
54
|
|
|
32
55
|
const ctx = {
|
|
56
|
+
// 请求的参数
|
|
33
57
|
method: req.method,
|
|
34
58
|
body: {},
|
|
35
59
|
req: req,
|
|
@@ -37,24 +61,19 @@ export function apiHandler(apis, hooks, context) {
|
|
|
37
61
|
ip: clientIp,
|
|
38
62
|
headers: req.headers,
|
|
39
63
|
requestId: requestId,
|
|
40
|
-
corsHeaders:
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
corsHeaders: corsHeaders,
|
|
65
|
+
// 接口的参数
|
|
66
|
+
apiPath: apiData.apiPath,
|
|
67
|
+
apiName: apiData.name,
|
|
68
|
+
filePath: apiData.filePath,
|
|
69
|
+
handler: apiData.handler,
|
|
70
|
+
method: apiData.method,
|
|
71
|
+
body: apiData.body,
|
|
72
|
+
auth: apiData.auth,
|
|
73
|
+
fields: apiData.fields,
|
|
74
|
+
required: apiData.required
|
|
43
75
|
};
|
|
44
76
|
|
|
45
|
-
const apiData = apis[url.pathname];
|
|
46
|
-
if (apiData) {
|
|
47
|
-
ctx.apiPath = apiData.apiPath;
|
|
48
|
-
ctx.apiName = apiData.name;
|
|
49
|
-
ctx.filePath = apiData.filePath;
|
|
50
|
-
ctx.handler = apiData.handler;
|
|
51
|
-
ctx.method = apiData.method;
|
|
52
|
-
ctx.body = apiData.body;
|
|
53
|
-
ctx.auth = apiData.auth;
|
|
54
|
-
ctx.fields = apiData.fields;
|
|
55
|
-
ctx.required = apiData.required;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
77
|
try {
|
|
59
78
|
// 4. 串联执行所有钩子
|
|
60
79
|
for (const hook of hooks) {
|
|
@@ -67,49 +86,33 @@ export function apiHandler(apis, hooks, context) {
|
|
|
67
86
|
}
|
|
68
87
|
|
|
69
88
|
// hooks 全部通过后记录请求日志(拦截请求仅由 ErrorResponse 记录)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
if (ctx.body && Object.keys(ctx.body).length > 0) {
|
|
86
|
-
logData["body"] = ctx.body;
|
|
87
|
-
}
|
|
89
|
+
const logData = {
|
|
90
|
+
event: "request",
|
|
91
|
+
requestId: requestId,
|
|
92
|
+
method: req.method,
|
|
93
|
+
apiPath: ctx.apiPath,
|
|
94
|
+
apiName: ctx.apiName,
|
|
95
|
+
ip: clientIp,
|
|
96
|
+
now: now,
|
|
97
|
+
userId: ctx.userId,
|
|
98
|
+
nickname: ctx.nickname,
|
|
99
|
+
roleCode: ctx.roleCode,
|
|
100
|
+
roleType: ctx.roleType
|
|
101
|
+
};
|
|
88
102
|
|
|
89
|
-
|
|
103
|
+
if (ctx.body && Object.keys(ctx.body).length > 0) {
|
|
104
|
+
logData["body"] = ctx.body;
|
|
90
105
|
}
|
|
91
106
|
|
|
107
|
+
Logger.info("请求", logData);
|
|
108
|
+
|
|
92
109
|
// 5. 执行 API handler
|
|
93
|
-
|
|
94
|
-
if (req.method !== "OPTIONS") {
|
|
95
|
-
ctx.response = Response.json(
|
|
96
|
-
{
|
|
97
|
-
code: 1,
|
|
98
|
-
msg: "接口不存在"
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
headers: ctx.corsHeaders
|
|
102
|
-
}
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
} else if (ctx.handler) {
|
|
106
|
-
const result = await ctx.handler(context, ctx);
|
|
110
|
+
const result = await ctx.handler(context, ctx);
|
|
107
111
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
112
|
+
if (result instanceof Response) {
|
|
113
|
+
ctx.response = result;
|
|
114
|
+
} else {
|
|
115
|
+
ctx.result = result;
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
// 7. 返回响应(自动处理 response/result/日志)
|
package/utils/cors.js
CHANGED
|
@@ -5,13 +5,24 @@
|
|
|
5
5
|
* @returns CORS 响应头对象
|
|
6
6
|
*/
|
|
7
7
|
export function setCorsOptions(req, config = {}) {
|
|
8
|
-
const
|
|
8
|
+
const defaultConfig = {
|
|
9
|
+
origin: "*",
|
|
10
|
+
methods: "GET, POST, OPTIONS",
|
|
11
|
+
allowedHeaders: "Content-Type, Authorization, authorization, token",
|
|
12
|
+
exposedHeaders: "Content-Range, X-Content-Range, Authorization, authorization, token",
|
|
13
|
+
maxAge: 86400,
|
|
14
|
+
credentials: "true"
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const merged = Object.assign({}, defaultConfig, config || {});
|
|
18
|
+
const origin = merged.origin;
|
|
19
|
+
|
|
9
20
|
return {
|
|
10
21
|
"Access-Control-Allow-Origin": origin === "*" ? req.headers.get("origin") || "*" : origin,
|
|
11
|
-
"Access-Control-Allow-Methods":
|
|
12
|
-
"Access-Control-Allow-Headers":
|
|
13
|
-
"Access-Control-Expose-Headers":
|
|
14
|
-
"Access-Control-Max-Age": String(
|
|
15
|
-
"Access-Control-Allow-Credentials":
|
|
22
|
+
"Access-Control-Allow-Methods": merged.methods,
|
|
23
|
+
"Access-Control-Allow-Headers": merged.allowedHeaders,
|
|
24
|
+
"Access-Control-Expose-Headers": merged.exposedHeaders,
|
|
25
|
+
"Access-Control-Max-Age": String(merged.maxAge),
|
|
26
|
+
"Access-Control-Allow-Credentials": merged.credentials
|
|
16
27
|
};
|
|
17
28
|
}
|
package/utils/response.js
CHANGED
|
@@ -9,17 +9,17 @@ import { isString } from "./is.js";
|
|
|
9
9
|
* @param code - 错误码,默认 1
|
|
10
10
|
* @param data - 附加数据,默认 null
|
|
11
11
|
* @param detail - 详细信息,用于标记具体提示位置,默认 null
|
|
12
|
-
* @param
|
|
12
|
+
* @param reason - 拦截原因标识(用于统计/聚合),默认 null
|
|
13
13
|
* @returns Response 对象
|
|
14
14
|
*/
|
|
15
|
-
export function ErrorResponse(ctx, msg, code = 1, data = null, detail = null,
|
|
15
|
+
export function ErrorResponse(ctx, msg, code = 1, data = null, detail = null, reason = null) {
|
|
16
16
|
// 记录拦截日志
|
|
17
17
|
if (ctx.requestId) {
|
|
18
18
|
// requestId/apiPath/user/duration 等字段由调用方显式写入日志上下文,避免在 msg 中重复拼接
|
|
19
19
|
Logger.info("请求已拦截", {
|
|
20
20
|
event: "request_blocked",
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
msg: msg,
|
|
22
|
+
reason: reason,
|
|
23
23
|
code: code,
|
|
24
24
|
detail: detail
|
|
25
25
|
});
|
|
@@ -30,7 +30,8 @@ export function ErrorResponse(ctx, msg, code = 1, data = null, detail = null, re
|
|
|
30
30
|
code: code,
|
|
31
31
|
msg: msg,
|
|
32
32
|
data: data,
|
|
33
|
-
detail: detail
|
|
33
|
+
detail: detail,
|
|
34
|
+
reason: reason
|
|
34
35
|
},
|
|
35
36
|
{
|
|
36
37
|
headers: ctx.corsHeaders
|
package/hooks/cors.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// 相对导入
|
|
2
|
-
import { setCorsOptions } from "../utils/cors.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* CORS 跨域处理钩子
|
|
6
|
-
* 设置跨域响应头并处理 OPTIONS 预检请求
|
|
7
|
-
*/
|
|
8
|
-
export default {
|
|
9
|
-
deps: [],
|
|
10
|
-
handler: async (befly, ctx) => {
|
|
11
|
-
const req = ctx.req;
|
|
12
|
-
|
|
13
|
-
// 合并默认配置和用户配置
|
|
14
|
-
const defaultConfig = {
|
|
15
|
-
origin: "*",
|
|
16
|
-
methods: "GET, POST, OPTIONS",
|
|
17
|
-
allowedHeaders: "Content-Type, Authorization, authorization, token",
|
|
18
|
-
exposedHeaders: "Content-Range, X-Content-Range, Authorization, authorization, token",
|
|
19
|
-
maxAge: 86400,
|
|
20
|
-
credentials: "true"
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const corsConfig = Object.assign({}, defaultConfig, befly.config && befly.config.cors ? befly.config.cors : {});
|
|
24
|
-
|
|
25
|
-
// 设置 CORS 响应头
|
|
26
|
-
const headers = setCorsOptions(req, corsConfig);
|
|
27
|
-
|
|
28
|
-
ctx.corsHeaders = headers;
|
|
29
|
-
|
|
30
|
-
// 处理 OPTIONS 预检请求
|
|
31
|
-
if (req.method === "OPTIONS") {
|
|
32
|
-
ctx.response = new Response(null, {
|
|
33
|
-
status: 204,
|
|
34
|
-
headers: headers
|
|
35
|
-
});
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
};
|