befly 3.17.11 → 3.17.13
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/package.json +2 -2
- package/router/api.js +60 -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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.17.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.17.13",
|
|
4
|
+
"gitHead": "1259303ee55a5aa686be914e961dc42767c0136b",
|
|
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";
|
|
@@ -19,17 +20,41 @@ export function apiHandler(apis, hooks, context) {
|
|
|
19
20
|
return async (req, server) => {
|
|
20
21
|
// 1. 生成请求 ID
|
|
21
22
|
const requestId = genShortId();
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
|
|
25
|
+
const corsHeaders = setCorsOptions(req, context.config?.cors || {});
|
|
26
|
+
corsHeaders["X-Request-ID"] = requestId;
|
|
22
27
|
|
|
23
|
-
// 2.
|
|
28
|
+
// 2. OPTIONS 预检请求:直接返回,不走 hooks,不打日志
|
|
29
|
+
if (req.method === "OPTIONS") {
|
|
30
|
+
return new Response(null, {
|
|
31
|
+
status: 204,
|
|
32
|
+
headers: corsHeaders
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 3. 创建请求上下文
|
|
24
37
|
const url = new URL(req.url);
|
|
25
38
|
// 只用接口路径做存在性判断与匹配:不要把 method 拼进 key
|
|
26
39
|
// 说明:apisMap 的 key 来源于 scanFiles/loadApis 生成的 path(例如 /api/core/xxx)
|
|
27
40
|
|
|
28
41
|
const clientIp = getClientIp(req, server);
|
|
29
42
|
|
|
30
|
-
const
|
|
43
|
+
const apiData = apis[url.pathname];
|
|
44
|
+
if (!apiData) {
|
|
45
|
+
return Response.json(
|
|
46
|
+
{
|
|
47
|
+
code: 1,
|
|
48
|
+
msg: "接口不存在"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
headers: corsHeaders
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
31
55
|
|
|
32
56
|
const ctx = {
|
|
57
|
+
// 请求的参数
|
|
33
58
|
method: req.method,
|
|
34
59
|
body: {},
|
|
35
60
|
req: req,
|
|
@@ -37,24 +62,19 @@ export function apiHandler(apis, hooks, context) {
|
|
|
37
62
|
ip: clientIp,
|
|
38
63
|
headers: req.headers,
|
|
39
64
|
requestId: requestId,
|
|
40
|
-
corsHeaders:
|
|
41
|
-
|
|
42
|
-
|
|
65
|
+
corsHeaders: corsHeaders,
|
|
66
|
+
// 接口的参数
|
|
67
|
+
apiPath: apiData.apiPath,
|
|
68
|
+
apiName: apiData.name,
|
|
69
|
+
filePath: apiData.filePath,
|
|
70
|
+
handler: apiData.handler,
|
|
71
|
+
method: apiData.method,
|
|
72
|
+
body: apiData.body,
|
|
73
|
+
auth: apiData.auth,
|
|
74
|
+
fields: apiData.fields,
|
|
75
|
+
required: apiData.required
|
|
43
76
|
};
|
|
44
77
|
|
|
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
78
|
try {
|
|
59
79
|
// 4. 串联执行所有钩子
|
|
60
80
|
for (const hook of hooks) {
|
|
@@ -67,49 +87,33 @@ export function apiHandler(apis, hooks, context) {
|
|
|
67
87
|
}
|
|
68
88
|
|
|
69
89
|
// 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
|
-
}
|
|
90
|
+
const logData = {
|
|
91
|
+
event: "request",
|
|
92
|
+
requestId: requestId,
|
|
93
|
+
method: req.method,
|
|
94
|
+
apiPath: ctx.apiPath,
|
|
95
|
+
apiName: ctx.apiName,
|
|
96
|
+
ip: clientIp,
|
|
97
|
+
now: now,
|
|
98
|
+
userId: ctx.userId,
|
|
99
|
+
nickname: ctx.nickname,
|
|
100
|
+
roleCode: ctx.roleCode,
|
|
101
|
+
roleType: ctx.roleType
|
|
102
|
+
};
|
|
88
103
|
|
|
89
|
-
|
|
104
|
+
if (ctx.body && Object.keys(ctx.body).length > 0) {
|
|
105
|
+
logData["body"] = ctx.body;
|
|
90
106
|
}
|
|
91
107
|
|
|
108
|
+
Logger.info("请求", logData);
|
|
109
|
+
|
|
92
110
|
// 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);
|
|
111
|
+
const result = await ctx.handler(context, ctx);
|
|
107
112
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
+
if (result instanceof Response) {
|
|
114
|
+
ctx.response = result;
|
|
115
|
+
} else {
|
|
116
|
+
ctx.result = result;
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
// 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
|
-
};
|