befly 3.17.13 → 3.17.15
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/apis/auth/login.js +0 -1
- package/checks/table.js +0 -2
- package/hooks/auth.js +1 -1
- package/hooks/parser.js +9 -21
- package/hooks/permission.js +4 -5
- package/hooks/validator.js +2 -2
- package/index.js +0 -1
- package/lib/connect.js +1 -1
- package/lib/dbHelper/dataOps.js +1 -1
- package/lib/logger.js +4 -4
- package/lib/redisHelper.js +0 -1
- package/package.json +2 -2
- package/router/api.js +10 -10
- package/scripts/syncDb/diff.js +1 -1
- package/sql/befly.sql +1 -0
- package/sync/api.js +2 -0
- package/tables/api.json +6 -0
- package/utils/formatZodIssues.js +0 -11
- package/utils/scanSources.js +0 -2
package/apis/auth/login.js
CHANGED
package/checks/table.js
CHANGED
|
@@ -8,10 +8,8 @@ z.config(z.locales.zhCN());
|
|
|
8
8
|
|
|
9
9
|
const lowerCamelRegex = /^_?[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/;
|
|
10
10
|
const noTrimString = z.string().refine(isNoTrimStringAllowEmpty, "不允许首尾空格");
|
|
11
|
-
const FIELD_NAME_REGEX_SOURCE = "^[\\u4e00-\\u9fa5a-zA-Z0-9_]+$";
|
|
12
11
|
const INPUT_TYPES = ["number", "integer", "string", "char", "array", "array_number", "array_integer", "json", "json_number", "json_integer"];
|
|
13
12
|
|
|
14
|
-
const fieldNameRegex = new RegExp(FIELD_NAME_REGEX_SOURCE);
|
|
15
13
|
const inputRegexLiteral = /^\/.*?\/[gimsuy]*$/;
|
|
16
14
|
const inputEnumRegex = /^[^/].*\|.*$/;
|
|
17
15
|
const inputAliasRegex = /^@.+$/;
|
package/hooks/auth.js
CHANGED
|
@@ -3,7 +3,7 @@ import { toSessionTtlSeconds } from "../utils/toSessionTtlSeconds.js";
|
|
|
3
3
|
export default {
|
|
4
4
|
deps: [],
|
|
5
5
|
handler: async (befly, ctx) => {
|
|
6
|
-
const authHeader = ctx.
|
|
6
|
+
const authHeader = ctx.headers.get("authorization");
|
|
7
7
|
const ttlSeconds = toSessionTtlSeconds(befly.config?.session?.expireDays);
|
|
8
8
|
|
|
9
9
|
if (authHeader && authHeader.startsWith("Bearer ")) {
|
package/hooks/parser.js
CHANGED
|
@@ -15,36 +15,25 @@ const xmlParser = new XMLParser();
|
|
|
15
15
|
export default {
|
|
16
16
|
deps: ["auth"],
|
|
17
17
|
handler: async (befly, ctx) => {
|
|
18
|
-
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// body=raw 模式:跳过解析,保留原始请求供 handler 自行处理
|
|
18
|
+
// apiBody=raw 模式:跳过解析,保留原始请求供 handler 自行处理
|
|
23
19
|
// 适用于:微信回调、支付回调、webhook 等需要手动解密/验签的场景
|
|
24
|
-
if (ctx.
|
|
20
|
+
if (ctx.apiBody === "raw") {
|
|
25
21
|
ctx.body = {};
|
|
26
22
|
return;
|
|
27
23
|
}
|
|
28
|
-
|
|
24
|
+
const queryParams = Object.fromEntries(new URL(ctx.url).searchParams);
|
|
25
|
+
const contentType = ctx.headers.get("content-type") || "";
|
|
29
26
|
// GET 请求:解析查询参数
|
|
30
|
-
if (ctx.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
ctx.body = params;
|
|
34
|
-
} else if (ctx.req.method === "POST") {
|
|
27
|
+
if (ctx.method === "GET") {
|
|
28
|
+
ctx.body = queryParams;
|
|
29
|
+
} else if (ctx.method === "POST") {
|
|
35
30
|
// POST 请求:解析请求体
|
|
36
|
-
const contentType = ctx.req.headers.get("content-type") || "";
|
|
37
|
-
// 获取 URL 查询参数(POST 请求也可能带参数)
|
|
38
|
-
const url = new URL(ctx.req.url);
|
|
39
|
-
const queryParams = Object.fromEntries(url.searchParams);
|
|
40
|
-
|
|
41
31
|
try {
|
|
42
32
|
// JSON 格式
|
|
43
33
|
if (contentType.includes("application/json")) {
|
|
44
34
|
const body = await ctx.req.json();
|
|
45
35
|
// 合并 URL 参数和请求体(请求体优先)
|
|
46
|
-
|
|
47
|
-
ctx.body = merged;
|
|
36
|
+
ctx.body = Object.assign({}, queryParams, body);
|
|
48
37
|
} else if (contentType.includes("application/xml") || contentType.includes("text/xml")) {
|
|
49
38
|
// XML 格式
|
|
50
39
|
const text = await ctx.req.text();
|
|
@@ -53,8 +42,7 @@ export default {
|
|
|
53
42
|
const rootKey = Object.keys(parsed)[0];
|
|
54
43
|
const body = rootKey && typeof parsed[rootKey] === "object" ? parsed[rootKey] : parsed;
|
|
55
44
|
// 合并 URL 参数和请求体(请求体优先)
|
|
56
|
-
|
|
57
|
-
ctx.body = merged;
|
|
45
|
+
ctx.body = Object.assign({}, queryParams, body);
|
|
58
46
|
} else {
|
|
59
47
|
// 不支持的 Content-Type
|
|
60
48
|
ctx.response = ErrorResponse(
|
package/hooks/permission.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { CacheKeys } from "../lib/cacheKeys.js";
|
|
2
|
-
import { Logger } from "../lib/logger.js";
|
|
3
2
|
// 相对导入
|
|
4
3
|
import { ErrorResponse } from "../utils/response.js";
|
|
5
|
-
import {
|
|
4
|
+
import { isValidPositiveInt } from "../utils/is.js";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* 权限检查钩子
|
|
@@ -16,7 +15,7 @@ export default {
|
|
|
16
15
|
deps: ["validator"],
|
|
17
16
|
handler: async (befly, ctx) => {
|
|
18
17
|
// 1. 接口无需权限
|
|
19
|
-
if (ctx.
|
|
18
|
+
if (ctx.apiAuth === false) {
|
|
20
19
|
return;
|
|
21
20
|
}
|
|
22
21
|
|
|
@@ -31,8 +30,8 @@ export default {
|
|
|
31
30
|
return;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
// 3.5
|
|
35
|
-
if (Array.isArray(ctx.
|
|
33
|
+
// 3.5 apiAuth 为角色类型白名单时,仅做 ctx.roleType 校验
|
|
34
|
+
if (Array.isArray(ctx.apiAuth) && ctx.apiAuth.includes(ctx.roleType) === false) {
|
|
36
35
|
ctx.response = ErrorResponse(
|
|
37
36
|
ctx,
|
|
38
37
|
`无权访问 ${ctx.apiName} 接口`,
|
package/hooks/validator.js
CHANGED
|
@@ -15,7 +15,7 @@ export default {
|
|
|
15
15
|
const rawBody = isPlainObject(ctx.body) ? ctx.body : {};
|
|
16
16
|
const nextBody = {};
|
|
17
17
|
|
|
18
|
-
for (const [field] of Object.entries(ctx.
|
|
18
|
+
for (const [field] of Object.entries(ctx.apiFields)) {
|
|
19
19
|
let value = rawBody[field];
|
|
20
20
|
|
|
21
21
|
if (value === undefined) {
|
|
@@ -33,7 +33,7 @@ export default {
|
|
|
33
33
|
ctx.body = nextBody;
|
|
34
34
|
|
|
35
35
|
// 验证参数
|
|
36
|
-
const result = Validator.validate(ctx.body, ctx.
|
|
36
|
+
const result = Validator.validate(ctx.body, ctx.apiFields, ctx.apiRequired);
|
|
37
37
|
|
|
38
38
|
if (result.code !== 0) {
|
|
39
39
|
ctx.response = ErrorResponse(ctx, result.firstError || "参数验证失败", 1, null, result.fieldErrors, "validator");
|
package/index.js
CHANGED
|
@@ -32,7 +32,6 @@ import { calcPerfTime } from "./utils/calcPerfTime.js";
|
|
|
32
32
|
import { scanSources } from "./utils/scanSources.js";
|
|
33
33
|
import { isPrimaryProcess } from "./utils/is.js";
|
|
34
34
|
import { deepMerge } from "./utils/deepMerge.js";
|
|
35
|
-
import { omit } from "./utils/util.js";
|
|
36
35
|
import { sortModules } from "./utils/sortModules.js";
|
|
37
36
|
|
|
38
37
|
function prefixMenuPaths(menus, prefix) {
|
package/lib/connect.js
CHANGED
package/lib/dbHelper/dataOps.js
CHANGED
package/lib/logger.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { createWriteStream, existsSync, mkdirSync } from "node:fs";
|
|
6
6
|
import { stat } from "node:fs/promises";
|
|
7
|
-
import {
|
|
7
|
+
import { join as nodePathJoin, resolve as nodePathResolve } from "node:path";
|
|
8
8
|
|
|
9
9
|
import { formatYmdHms } from "../utils/formatYmdHms.js";
|
|
10
10
|
import { buildSensitiveKeyMatcher, sanitizeLogObject } from "../utils/loggerUtils.js";
|
|
@@ -132,7 +132,7 @@ class LogFileSink {
|
|
|
132
132
|
this.scheduledTimer = setTimeout(() => {
|
|
133
133
|
// timer 触发时先清空句柄,避免 flush 内再次 schedule 时被认为“已安排”。
|
|
134
134
|
this.scheduledTimer = null;
|
|
135
|
-
|
|
135
|
+
this.flush();
|
|
136
136
|
}, this.flushDelayMs);
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -220,7 +220,7 @@ class LogFileSink {
|
|
|
220
220
|
this.stream.on("error", (error) => {
|
|
221
221
|
safeWriteStderr(`[Logger] file sink error (${this.prefix}): ${error?.message || error}`);
|
|
222
222
|
this.disabled = true;
|
|
223
|
-
|
|
223
|
+
this.closeStream();
|
|
224
224
|
});
|
|
225
225
|
} catch (error) {
|
|
226
226
|
safeWriteStderr(`[Logger] createWriteStream failed (${this.prefix}): ${error?.message || error}`);
|
|
@@ -349,7 +349,7 @@ function ensureLogDirExists() {
|
|
|
349
349
|
*/
|
|
350
350
|
export function configure(cfg) {
|
|
351
351
|
// 旧实例可能仍持有文件句柄;这里异步关闭(不阻塞主流程)
|
|
352
|
-
|
|
352
|
+
shutdown();
|
|
353
353
|
|
|
354
354
|
// 方案B:每次 configure 都从默认配置重新构建(避免继承上一次配置造成测试/运行时污染)
|
|
355
355
|
config = Object.assign(
|
package/lib/redisHelper.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.17.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.17.15",
|
|
4
|
+
"gitHead": "aa84ccde6c76bc45727464c30d4680c379815110",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
|
|
7
7
|
"keywords": [
|
package/router/api.js
CHANGED
|
@@ -56,6 +56,7 @@ export function apiHandler(apis, hooks, context) {
|
|
|
56
56
|
const ctx = {
|
|
57
57
|
// 请求的参数
|
|
58
58
|
method: req.method,
|
|
59
|
+
url: req.url,
|
|
59
60
|
body: {},
|
|
60
61
|
req: req,
|
|
61
62
|
now: now,
|
|
@@ -66,13 +67,13 @@ export function apiHandler(apis, hooks, context) {
|
|
|
66
67
|
// 接口的参数
|
|
67
68
|
apiPath: apiData.apiPath,
|
|
68
69
|
apiName: apiData.name,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
apiMethod: apiData.method,
|
|
71
|
+
apiBody: apiData.body,
|
|
72
|
+
apiHandler: apiData.handler,
|
|
73
|
+
apiAuth: apiData.auth,
|
|
74
|
+
apiFields: apiData.fields,
|
|
75
|
+
apiRequired: apiData.required,
|
|
76
|
+
apiFile: apiData.filePath
|
|
76
77
|
};
|
|
77
78
|
|
|
78
79
|
try {
|
|
@@ -108,7 +109,7 @@ export function apiHandler(apis, hooks, context) {
|
|
|
108
109
|
Logger.info("请求", logData);
|
|
109
110
|
|
|
110
111
|
// 5. 执行 API handler
|
|
111
|
-
const result = await ctx.
|
|
112
|
+
const result = await ctx.apiHandler(context, ctx);
|
|
112
113
|
|
|
113
114
|
if (result instanceof Response) {
|
|
114
115
|
ctx.response = result;
|
|
@@ -120,9 +121,8 @@ export function apiHandler(apis, hooks, context) {
|
|
|
120
121
|
return FinalResponse(ctx);
|
|
121
122
|
} catch (err) {
|
|
122
123
|
// 全局错误处理
|
|
123
|
-
const errorPath = ctx.apiPath ? ctx.apiPath : req.url;
|
|
124
124
|
Logger.error("请求错误", err, {
|
|
125
|
-
path:
|
|
125
|
+
path: ctx.apiPath,
|
|
126
126
|
requestId: requestId,
|
|
127
127
|
method: req.method,
|
|
128
128
|
apiPath: ctx.apiPath,
|
package/scripts/syncDb/diff.js
CHANGED
|
@@ -57,7 +57,7 @@ export function buildSyncDbDiff(groupedDbColumns, existingTableMap) {
|
|
|
57
57
|
const missingFields = [];
|
|
58
58
|
for (const columnMeta of columns) {
|
|
59
59
|
const fieldInfo = toSyncDbFieldDef(columnMeta);
|
|
60
|
-
if (Object.
|
|
60
|
+
if (Object.hasOwn(existingFields, fieldInfo.fieldName)) {
|
|
61
61
|
continue;
|
|
62
62
|
}
|
|
63
63
|
|
package/sql/befly.sql
CHANGED
|
@@ -23,6 +23,7 @@ CREATE TABLE IF NOT EXISTS `befly_api` (
|
|
|
23
23
|
`auth` VARCHAR(200) NOT NULL DEFAULT '',
|
|
24
24
|
`path` VARCHAR(200) NOT NULL DEFAULT '',
|
|
25
25
|
`parent_path` VARCHAR(200) NOT NULL DEFAULT '',
|
|
26
|
+
`method` VARCHAR(20) NOT NULL DEFAULT '',
|
|
26
27
|
`state` TINYINT NOT NULL DEFAULT 1,
|
|
27
28
|
`created_at` BIGINT NOT NULL DEFAULT 0,
|
|
28
29
|
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
package/sync/api.js
CHANGED
|
@@ -57,6 +57,7 @@ export async function syncApi(ctx, apis) {
|
|
|
57
57
|
data: {
|
|
58
58
|
name: api.name,
|
|
59
59
|
path: api.apiPath,
|
|
60
|
+
method: api.method,
|
|
60
61
|
parentPath: parentPath,
|
|
61
62
|
auth: auth
|
|
62
63
|
}
|
|
@@ -67,6 +68,7 @@ export async function syncApi(ctx, apis) {
|
|
|
67
68
|
insList.push({
|
|
68
69
|
name: api.name,
|
|
69
70
|
path: api.apiPath,
|
|
71
|
+
method: api.method,
|
|
70
72
|
parentPath: parentPath,
|
|
71
73
|
auth: auth
|
|
72
74
|
});
|
package/tables/api.json
CHANGED
package/utils/formatZodIssues.js
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
function getValueByPath(source, path) {
|
|
2
|
-
if (!Array.isArray(path)) return undefined;
|
|
3
|
-
|
|
4
|
-
let current = source;
|
|
5
|
-
for (const segment of path) {
|
|
6
|
-
if (current === null || current === undefined) return undefined;
|
|
7
|
-
current = current[segment];
|
|
8
|
-
}
|
|
9
|
-
return current;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
1
|
function formatValue(value) {
|
|
13
2
|
if (value === undefined) return "undefined";
|
|
14
3
|
if (value === null) return "null";
|