befly 3.17.0 → 3.17.2
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 +6 -0
- package/apis/admin/cacheRefresh.js +122 -0
- package/apis/admin/del.js +34 -0
- package/apis/admin/detail.js +23 -0
- package/apis/admin/ins.js +69 -0
- package/apis/admin/list.js +28 -0
- package/apis/admin/upd.js +95 -0
- package/apis/api/all.js +24 -0
- package/apis/api/list.js +31 -0
- package/apis/auth/login.js +123 -0
- package/apis/auth/sendSmsCode.js +24 -0
- package/apis/dashboard/configStatus.js +39 -0
- package/apis/dashboard/environmentInfo.js +43 -0
- package/apis/dashboard/performanceMetrics.js +20 -0
- package/apis/dashboard/permissionStats.js +27 -0
- package/apis/dashboard/serviceStatus.js +75 -0
- package/apis/dashboard/systemInfo.js +19 -0
- package/apis/dashboard/systemOverview.js +30 -0
- package/apis/dashboard/systemResources.js +106 -0
- package/apis/dict/all.js +23 -0
- package/apis/dict/del.js +16 -0
- package/apis/dict/detail.js +27 -0
- package/apis/dict/ins.js +51 -0
- package/apis/dict/items.js +30 -0
- package/apis/dict/list.js +36 -0
- package/apis/dict/upd.js +74 -0
- package/apis/dictType/all.js +16 -0
- package/apis/dictType/del.js +38 -0
- package/apis/dictType/detail.js +20 -0
- package/apis/dictType/ins.js +37 -0
- package/apis/dictType/list.js +26 -0
- package/apis/dictType/upd.js +51 -0
- package/apis/email/config.js +25 -0
- package/apis/email/logList.js +23 -0
- package/apis/email/send.js +66 -0
- package/apis/email/verify.js +21 -0
- package/apis/loginLog/list.js +23 -0
- package/apis/menu/all.js +41 -0
- package/apis/menu/list.js +25 -0
- package/apis/operateLog/list.js +23 -0
- package/apis/role/all.js +21 -0
- package/apis/role/apiSave.js +43 -0
- package/apis/role/apis.js +22 -0
- package/apis/role/del.js +49 -0
- package/apis/role/detail.js +32 -0
- package/apis/role/ins.js +46 -0
- package/apis/role/list.js +27 -0
- package/apis/role/menuSave.js +42 -0
- package/apis/role/menus.js +22 -0
- package/apis/role/save.js +40 -0
- package/apis/role/upd.js +50 -0
- package/apis/sysConfig/all.js +16 -0
- package/apis/sysConfig/del.js +36 -0
- package/apis/sysConfig/get.js +49 -0
- package/apis/sysConfig/ins.js +50 -0
- package/apis/sysConfig/list.js +24 -0
- package/apis/sysConfig/upd.js +62 -0
- package/checks/api.js +55 -0
- package/checks/config.js +107 -0
- package/checks/hook.js +38 -0
- package/checks/menu.js +58 -0
- package/checks/plugin.js +38 -0
- package/checks/table.js +78 -0
- package/configs/beflyConfig.json +61 -0
- package/configs/beflyMenus.json +85 -0
- package/configs/constConfig.js +34 -0
- package/configs/regexpAlias.json +55 -0
- package/hooks/auth.js +34 -0
- package/hooks/cors.js +39 -0
- package/hooks/parser.js +90 -0
- package/hooks/permission.js +71 -0
- package/hooks/validator.js +43 -0
- package/index.js +326 -0
- package/lib/cacheHelper.js +483 -0
- package/lib/cacheKeys.js +42 -0
- package/lib/connect.js +120 -0
- package/lib/dbHelper/builders.js +698 -0
- package/lib/dbHelper/context.js +131 -0
- package/lib/dbHelper/dataOps.js +505 -0
- package/lib/dbHelper/execute.js +65 -0
- package/lib/dbHelper/index.js +27 -0
- package/lib/dbHelper/transaction.js +43 -0
- package/lib/dbHelper/util.js +58 -0
- package/lib/dbHelper/validate.js +549 -0
- package/lib/emailHelper.js +110 -0
- package/lib/logger.js +604 -0
- package/lib/redisHelper.js +684 -0
- package/lib/sqlBuilder/batch.js +113 -0
- package/lib/sqlBuilder/check.js +150 -0
- package/lib/sqlBuilder/compiler.js +347 -0
- package/lib/sqlBuilder/errors.js +60 -0
- package/lib/sqlBuilder/index.js +218 -0
- package/lib/sqlBuilder/parser.js +296 -0
- package/lib/sqlBuilder/util.js +260 -0
- package/lib/validator.js +303 -0
- package/package.json +19 -12
- package/paths.js +112 -0
- package/plugins/cache.js +16 -0
- package/plugins/config.js +11 -0
- package/plugins/email.js +27 -0
- package/plugins/logger.js +20 -0
- package/plugins/mysql.js +36 -0
- package/plugins/redis.js +34 -0
- package/plugins/tool.js +155 -0
- package/router/api.js +140 -0
- package/router/static.js +71 -0
- package/sql/admin.sql +18 -0
- package/sql/api.sql +12 -0
- package/sql/dict.sql +13 -0
- package/sql/dictType.sql +12 -0
- package/sql/emailLog.sql +20 -0
- package/sql/loginLog.sql +25 -0
- package/sql/menu.sql +12 -0
- package/sql/operateLog.sql +22 -0
- package/sql/role.sql +14 -0
- package/sql/sysConfig.sql +16 -0
- package/sync/api.js +93 -0
- package/sync/cache.js +13 -0
- package/sync/dev.js +171 -0
- package/sync/menu.js +99 -0
- package/tables/admin.json +56 -0
- package/tables/api.json +26 -0
- package/tables/dict.json +30 -0
- package/tables/dictType.json +24 -0
- package/tables/emailLog.json +61 -0
- package/tables/loginLog.json +86 -0
- package/tables/menu.json +24 -0
- package/tables/operateLog.json +68 -0
- package/tables/role.json +32 -0
- package/tables/sysConfig.json +43 -0
- package/utils/calcPerfTime.js +13 -0
- package/utils/cors.js +17 -0
- package/utils/deepMerge.js +78 -0
- package/utils/fieldClear.js +65 -0
- package/utils/formatYmdHms.js +23 -0
- package/utils/formatZodIssues.js +109 -0
- package/utils/getClientIp.js +47 -0
- package/utils/importDefault.js +51 -0
- package/utils/is.js +462 -0
- package/utils/loggerUtils.js +185 -0
- package/utils/processInfo.js +39 -0
- package/utils/regexpUtil.js +52 -0
- package/utils/response.js +114 -0
- package/utils/scanFiles.js +124 -0
- package/utils/scanSources.js +68 -0
- package/utils/sortModules.js +75 -0
- package/utils/toSessionTtlSeconds.js +14 -0
- package/utils/util.js +374 -0
- package/befly.js +0 -16413
- package/befly.min.js +0 -72
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adminId": {
|
|
3
|
+
"name": "发送人ID",
|
|
4
|
+
"input": "number"
|
|
5
|
+
},
|
|
6
|
+
"username": {
|
|
7
|
+
"name": "发送人账号",
|
|
8
|
+
"input": "string",
|
|
9
|
+
"max": 100
|
|
10
|
+
},
|
|
11
|
+
"nickname": {
|
|
12
|
+
"name": "发送人昵称",
|
|
13
|
+
"input": "string",
|
|
14
|
+
"max": 100
|
|
15
|
+
},
|
|
16
|
+
"toEmail": {
|
|
17
|
+
"name": "收件人邮箱",
|
|
18
|
+
"input": "@email",
|
|
19
|
+
"min": 5,
|
|
20
|
+
"max": 200
|
|
21
|
+
},
|
|
22
|
+
"subject": {
|
|
23
|
+
"name": "邮件主题",
|
|
24
|
+
"min": 1,
|
|
25
|
+
"max": 200,
|
|
26
|
+
"input": "string"
|
|
27
|
+
},
|
|
28
|
+
"content": {
|
|
29
|
+
"name": "邮件内容",
|
|
30
|
+
"input": "string"
|
|
31
|
+
},
|
|
32
|
+
"ccEmail": {
|
|
33
|
+
"name": "抄送邮箱",
|
|
34
|
+
"input": "string",
|
|
35
|
+
"max": 500
|
|
36
|
+
},
|
|
37
|
+
"bccEmail": {
|
|
38
|
+
"name": "密送邮箱",
|
|
39
|
+
"input": "string",
|
|
40
|
+
"max": 500
|
|
41
|
+
},
|
|
42
|
+
"sendTime": {
|
|
43
|
+
"name": "发送时间",
|
|
44
|
+
"input": "number"
|
|
45
|
+
},
|
|
46
|
+
"sendResult": {
|
|
47
|
+
"name": "发送结果",
|
|
48
|
+
"input": "number",
|
|
49
|
+
"max": 1
|
|
50
|
+
},
|
|
51
|
+
"messageId": {
|
|
52
|
+
"name": "消息ID",
|
|
53
|
+
"input": "string",
|
|
54
|
+
"max": 200
|
|
55
|
+
},
|
|
56
|
+
"failReason": {
|
|
57
|
+
"name": "失败原因",
|
|
58
|
+
"input": "string",
|
|
59
|
+
"max": 500
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adminId": {
|
|
3
|
+
"name": "管理员ID",
|
|
4
|
+
"input": "number"
|
|
5
|
+
},
|
|
6
|
+
"username": {
|
|
7
|
+
"name": "用户名",
|
|
8
|
+
"input": "string",
|
|
9
|
+
"min": 1,
|
|
10
|
+
"max": 50
|
|
11
|
+
},
|
|
12
|
+
"nickname": {
|
|
13
|
+
"name": "昵称",
|
|
14
|
+
"input": "string",
|
|
15
|
+
"max": 50
|
|
16
|
+
},
|
|
17
|
+
"ip": {
|
|
18
|
+
"name": "登录IP",
|
|
19
|
+
"input": "string",
|
|
20
|
+
"max": 50
|
|
21
|
+
},
|
|
22
|
+
"userAgent": {
|
|
23
|
+
"name": "用户代理",
|
|
24
|
+
"input": "string",
|
|
25
|
+
"max": 500
|
|
26
|
+
},
|
|
27
|
+
"browserName": {
|
|
28
|
+
"name": "浏览器名称",
|
|
29
|
+
"input": "string",
|
|
30
|
+
"max": 50
|
|
31
|
+
},
|
|
32
|
+
"browserVersion": {
|
|
33
|
+
"name": "浏览器版本",
|
|
34
|
+
"input": "string",
|
|
35
|
+
"max": 50
|
|
36
|
+
},
|
|
37
|
+
"osName": {
|
|
38
|
+
"name": "操作系统",
|
|
39
|
+
"input": "string",
|
|
40
|
+
"max": 50
|
|
41
|
+
},
|
|
42
|
+
"osVersion": {
|
|
43
|
+
"name": "系统版本",
|
|
44
|
+
"input": "string",
|
|
45
|
+
"max": 50
|
|
46
|
+
},
|
|
47
|
+
"deviceType": {
|
|
48
|
+
"name": "设备类型",
|
|
49
|
+
"input": "string",
|
|
50
|
+
"max": 20
|
|
51
|
+
},
|
|
52
|
+
"deviceVendor": {
|
|
53
|
+
"name": "设备厂商",
|
|
54
|
+
"input": "string",
|
|
55
|
+
"max": 50
|
|
56
|
+
},
|
|
57
|
+
"deviceModel": {
|
|
58
|
+
"name": "设备型号",
|
|
59
|
+
"input": "string",
|
|
60
|
+
"max": 50
|
|
61
|
+
},
|
|
62
|
+
"engineName": {
|
|
63
|
+
"name": "引擎名称",
|
|
64
|
+
"input": "string",
|
|
65
|
+
"max": 50
|
|
66
|
+
},
|
|
67
|
+
"cpuArchitecture": {
|
|
68
|
+
"name": "CPU架构",
|
|
69
|
+
"input": "string",
|
|
70
|
+
"max": 20
|
|
71
|
+
},
|
|
72
|
+
"loginTime": {
|
|
73
|
+
"name": "登录时间",
|
|
74
|
+
"input": "number"
|
|
75
|
+
},
|
|
76
|
+
"loginResult": {
|
|
77
|
+
"name": "登录结果",
|
|
78
|
+
"input": "number",
|
|
79
|
+
"max": 1
|
|
80
|
+
},
|
|
81
|
+
"failReason": {
|
|
82
|
+
"name": "失败原因",
|
|
83
|
+
"input": "string",
|
|
84
|
+
"max": 200
|
|
85
|
+
}
|
|
86
|
+
}
|
package/tables/menu.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": {
|
|
3
|
+
"name": "菜单名称",
|
|
4
|
+
"input": "string",
|
|
5
|
+
"min": 2,
|
|
6
|
+
"max": 50
|
|
7
|
+
},
|
|
8
|
+
"path": {
|
|
9
|
+
"name": "路由路径",
|
|
10
|
+
"input": "string",
|
|
11
|
+
"min": 1,
|
|
12
|
+
"max": 150
|
|
13
|
+
},
|
|
14
|
+
"sort": {
|
|
15
|
+
"name": "排序",
|
|
16
|
+
"input": "number",
|
|
17
|
+
"max": 9999
|
|
18
|
+
},
|
|
19
|
+
"parentPath": {
|
|
20
|
+
"name": "父级路径",
|
|
21
|
+
"input": "string",
|
|
22
|
+
"max": 200
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adminId": {
|
|
3
|
+
"name": "管理员ID",
|
|
4
|
+
"input": "number"
|
|
5
|
+
},
|
|
6
|
+
"username": {
|
|
7
|
+
"name": "用户名",
|
|
8
|
+
"input": "string",
|
|
9
|
+
"min": 1,
|
|
10
|
+
"max": 50
|
|
11
|
+
},
|
|
12
|
+
"nickname": {
|
|
13
|
+
"name": "昵称",
|
|
14
|
+
"input": "string",
|
|
15
|
+
"max": 50
|
|
16
|
+
},
|
|
17
|
+
"ip": {
|
|
18
|
+
"name": "操作IP",
|
|
19
|
+
"input": "string",
|
|
20
|
+
"max": 50
|
|
21
|
+
},
|
|
22
|
+
"module": {
|
|
23
|
+
"name": "操作模块",
|
|
24
|
+
"input": "string",
|
|
25
|
+
"max": 50
|
|
26
|
+
},
|
|
27
|
+
"action": {
|
|
28
|
+
"name": "操作类型",
|
|
29
|
+
"input": "string",
|
|
30
|
+
"max": 50
|
|
31
|
+
},
|
|
32
|
+
"method": {
|
|
33
|
+
"name": "请求方法",
|
|
34
|
+
"input": "string",
|
|
35
|
+
"max": 10
|
|
36
|
+
},
|
|
37
|
+
"path": {
|
|
38
|
+
"name": "请求路径",
|
|
39
|
+
"input": "string",
|
|
40
|
+
"max": 200
|
|
41
|
+
},
|
|
42
|
+
"params": {
|
|
43
|
+
"name": "请求参数",
|
|
44
|
+
"input": "string"
|
|
45
|
+
},
|
|
46
|
+
"result": {
|
|
47
|
+
"name": "操作结果",
|
|
48
|
+
"input": "number",
|
|
49
|
+
"max": 1
|
|
50
|
+
},
|
|
51
|
+
"response": {
|
|
52
|
+
"name": "响应内容",
|
|
53
|
+
"input": "string"
|
|
54
|
+
},
|
|
55
|
+
"duration": {
|
|
56
|
+
"name": "耗时毫秒",
|
|
57
|
+
"input": "number"
|
|
58
|
+
},
|
|
59
|
+
"operateTime": {
|
|
60
|
+
"name": "操作时间",
|
|
61
|
+
"input": "number"
|
|
62
|
+
},
|
|
63
|
+
"remark": {
|
|
64
|
+
"name": "备注",
|
|
65
|
+
"input": "string",
|
|
66
|
+
"max": 500
|
|
67
|
+
}
|
|
68
|
+
}
|
package/tables/role.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": {
|
|
3
|
+
"name": "角色名称",
|
|
4
|
+
"input": "string",
|
|
5
|
+
"min": 2,
|
|
6
|
+
"max": 50
|
|
7
|
+
},
|
|
8
|
+
"code": {
|
|
9
|
+
"name": "角色编码",
|
|
10
|
+
"input": "@alphanumericDash_",
|
|
11
|
+
"min": 2,
|
|
12
|
+
"max": 50
|
|
13
|
+
},
|
|
14
|
+
"description": {
|
|
15
|
+
"name": "角色描述",
|
|
16
|
+
"input": "string",
|
|
17
|
+
"max": 200
|
|
18
|
+
},
|
|
19
|
+
"menus": {
|
|
20
|
+
"name": "菜单权限",
|
|
21
|
+
"input": "array"
|
|
22
|
+
},
|
|
23
|
+
"apis": {
|
|
24
|
+
"name": "接口权限",
|
|
25
|
+
"input": "array"
|
|
26
|
+
},
|
|
27
|
+
"sort": {
|
|
28
|
+
"name": "排序",
|
|
29
|
+
"input": "number",
|
|
30
|
+
"max": 9999
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": {
|
|
3
|
+
"name": "配置名称",
|
|
4
|
+
"min": 2,
|
|
5
|
+
"max": 50,
|
|
6
|
+
"input": "string"
|
|
7
|
+
},
|
|
8
|
+
"code": {
|
|
9
|
+
"name": "配置代码",
|
|
10
|
+
"input": "@alphanumeric_",
|
|
11
|
+
"min": 2,
|
|
12
|
+
"max": 100
|
|
13
|
+
},
|
|
14
|
+
"value": {
|
|
15
|
+
"name": "配置值",
|
|
16
|
+
"input": "string"
|
|
17
|
+
},
|
|
18
|
+
"valueType": {
|
|
19
|
+
"name": "值类型",
|
|
20
|
+
"input": "string|number|boolean|json",
|
|
21
|
+
"max": 20
|
|
22
|
+
},
|
|
23
|
+
"group": {
|
|
24
|
+
"name": "配置分组",
|
|
25
|
+
"input": "string",
|
|
26
|
+
"max": 50
|
|
27
|
+
},
|
|
28
|
+
"sort": {
|
|
29
|
+
"name": "排序",
|
|
30
|
+
"input": "number",
|
|
31
|
+
"max": 9999
|
|
32
|
+
},
|
|
33
|
+
"isSystem": {
|
|
34
|
+
"name": "是否系统配置",
|
|
35
|
+
"input": "number",
|
|
36
|
+
"max": 1
|
|
37
|
+
},
|
|
38
|
+
"description": {
|
|
39
|
+
"name": "描述说明",
|
|
40
|
+
"input": "string",
|
|
41
|
+
"max": 500
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 计算性能时间差
|
|
3
|
+
*/
|
|
4
|
+
export const calcPerfTime = (startTime, endTime = Bun.nanoseconds()) => {
|
|
5
|
+
const elapsedMs = (endTime - startTime) / 1_000_000;
|
|
6
|
+
|
|
7
|
+
if (elapsedMs < 1000) {
|
|
8
|
+
return `${elapsedMs.toFixed(2)} 毫秒`;
|
|
9
|
+
} else {
|
|
10
|
+
const elapsedSeconds = elapsedMs / 1000;
|
|
11
|
+
return `${elapsedSeconds.toFixed(2)} 秒`;
|
|
12
|
+
}
|
|
13
|
+
};
|
package/utils/cors.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 设置 CORS 响应头
|
|
3
|
+
* @param req - 请求对象
|
|
4
|
+
* @param config - CORS 配置(可选)
|
|
5
|
+
* @returns CORS 响应头对象
|
|
6
|
+
*/
|
|
7
|
+
export function setCorsOptions(req, config = {}) {
|
|
8
|
+
const origin = config.origin || "*";
|
|
9
|
+
return {
|
|
10
|
+
"Access-Control-Allow-Origin": origin === "*" ? req.headers.get("origin") || "*" : origin,
|
|
11
|
+
"Access-Control-Allow-Methods": config.methods || "GET, POST, PUT, DELETE, OPTIONS",
|
|
12
|
+
"Access-Control-Allow-Headers": config.allowedHeaders || "Content-Type, Authorization, authorization, token",
|
|
13
|
+
"Access-Control-Expose-Headers": config.exposedHeaders || "Content-Range, X-Content-Range, Authorization, authorization, token",
|
|
14
|
+
"Access-Control-Max-Age": String(config.maxAge || 86400),
|
|
15
|
+
"Access-Control-Allow-Credentials": config.credentials || "true"
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { isPlainObject } from "./is.js";
|
|
2
|
+
|
|
3
|
+
function clone(value) {
|
|
4
|
+
return structuredClone(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function concat(targetArr, sourceArr, cloneTargetItems) {
|
|
8
|
+
const nextArr = [];
|
|
9
|
+
|
|
10
|
+
if (cloneTargetItems) {
|
|
11
|
+
for (const item of targetArr) {
|
|
12
|
+
nextArr.push(clone(item));
|
|
13
|
+
}
|
|
14
|
+
} else {
|
|
15
|
+
for (const item of targetArr) {
|
|
16
|
+
nextArr.push(item);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (const item of sourceArr) {
|
|
21
|
+
nextArr.push(clone(item));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return nextArr;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function mergeValue(baseValue, incomingValue) {
|
|
28
|
+
if (Array.isArray(baseValue) && Array.isArray(incomingValue)) {
|
|
29
|
+
return concat(baseValue, incomingValue, false);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (isPlainObject(baseValue) && isPlainObject(incomingValue)) {
|
|
33
|
+
mergeInto(baseValue, incomingValue);
|
|
34
|
+
return baseValue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return clone(incomingValue);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function mergeInto(base, incoming) {
|
|
41
|
+
for (const key of Object.keys(incoming)) {
|
|
42
|
+
const incomingValue = incoming[key];
|
|
43
|
+
if (incomingValue === undefined) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
base[key] = mergeValue(base[key], incomingValue);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return base;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 深度合并对象,并对数组执行 concat(保持 scanConfig 现有语义)。
|
|
55
|
+
* - undefined 会被忽略
|
|
56
|
+
* - plain object 深合并
|
|
57
|
+
* - array 与 array 合并为新数组(保持输入不被污染)
|
|
58
|
+
*/
|
|
59
|
+
export function deepMerge(source, target) {
|
|
60
|
+
if (Array.isArray(source) && Array.isArray(target)) {
|
|
61
|
+
const base = clone(source);
|
|
62
|
+
const incoming = clone(target);
|
|
63
|
+
return concat(base, incoming, false);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (isPlainObject(source) && isPlainObject(target)) {
|
|
67
|
+
const base = clone(source);
|
|
68
|
+
const incoming = clone(target);
|
|
69
|
+
return mergeInto(base, incoming);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
throw new Error("deepMerge: source/target 必须同为数组或同为普通对象", {
|
|
73
|
+
cause: null,
|
|
74
|
+
code: "validation",
|
|
75
|
+
subsystem: "utils",
|
|
76
|
+
operation: "deepMerge"
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// fieldClear 工具函数实现
|
|
2
|
+
// 支持 pick/omit/keepValues/excludeValues,处理对象和数组
|
|
3
|
+
|
|
4
|
+
function isObject(val) {
|
|
5
|
+
return val !== null && typeof val === "object" && !Array.isArray(val);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function isArray(val) {
|
|
9
|
+
return Array.isArray(val);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function fieldClear(data, options = {}) {
|
|
13
|
+
const { pickKeys, omitKeys, keepValues, excludeValues, keepMap } = options;
|
|
14
|
+
|
|
15
|
+
const filterObj = (obj) => {
|
|
16
|
+
let result = {};
|
|
17
|
+
let keys = Object.keys(obj);
|
|
18
|
+
if (pickKeys && pickKeys.length) {
|
|
19
|
+
keys = keys.filter((k) => pickKeys.includes(k));
|
|
20
|
+
}
|
|
21
|
+
if (omitKeys && omitKeys.length) {
|
|
22
|
+
keys = keys.filter((k) => !omitKeys.includes(k));
|
|
23
|
+
}
|
|
24
|
+
for (const key of keys) {
|
|
25
|
+
const value = obj[key];
|
|
26
|
+
|
|
27
|
+
// 1. 优先检查 keepMap
|
|
28
|
+
if (keepMap && key in keepMap) {
|
|
29
|
+
if (Object.is(keepMap[key], value)) {
|
|
30
|
+
result[key] = value;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. 检查 keepValues (只保留指定值)
|
|
36
|
+
if (keepValues && keepValues.length && !keepValues.includes(value)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 3. 检查 excludeValues (排除指定值)
|
|
41
|
+
if (excludeValues && excludeValues.length && excludeValues.includes(value)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
result[key] = value;
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (isArray(data)) {
|
|
50
|
+
return data
|
|
51
|
+
.map((item) => (isObject(item) ? filterObj(item) : item))
|
|
52
|
+
.filter((item) => {
|
|
53
|
+
if (isObject(item)) {
|
|
54
|
+
// 只保留有内容的对象
|
|
55
|
+
return Object.keys(item).length > 0;
|
|
56
|
+
}
|
|
57
|
+
// 原始值直接保留
|
|
58
|
+
return true;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (isObject(data)) {
|
|
62
|
+
return filterObj(data);
|
|
63
|
+
}
|
|
64
|
+
return data;
|
|
65
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function formatYmdHms(date, format = "dateTime") {
|
|
2
|
+
const y = date.getFullYear();
|
|
3
|
+
const m = date.getMonth() + 1;
|
|
4
|
+
const d = date.getDate();
|
|
5
|
+
const h = date.getHours();
|
|
6
|
+
const mi = date.getMinutes();
|
|
7
|
+
const s = date.getSeconds();
|
|
8
|
+
|
|
9
|
+
const mm = m < 10 ? `0${m}` : String(m);
|
|
10
|
+
const dd = d < 10 ? `0${d}` : String(d);
|
|
11
|
+
const hh = h < 10 ? `0${h}` : String(h);
|
|
12
|
+
const mii = mi < 10 ? `0${mi}` : String(mi);
|
|
13
|
+
const ss = s < 10 ? `0${s}` : String(s);
|
|
14
|
+
|
|
15
|
+
if (format === "date") {
|
|
16
|
+
return `${y}-${mm}-${dd}`;
|
|
17
|
+
}
|
|
18
|
+
if (format === "time") {
|
|
19
|
+
return `${hh}:${mii}:${ss}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return `${y}-${mm}-${dd} ${hh}:${mii}:${ss}`;
|
|
23
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
function formatValue(value) {
|
|
13
|
+
if (value === undefined) return "undefined";
|
|
14
|
+
if (value === null) return "null";
|
|
15
|
+
if (typeof value === "string") return value;
|
|
16
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
17
|
+
if (typeof value === "function") return "[Function]";
|
|
18
|
+
if (Array.isArray(value)) return value.map((item) => formatValue(item)).join(", ");
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
return JSON.stringify(value);
|
|
22
|
+
} catch {
|
|
23
|
+
return String(value);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function pickFileLabel(item, itemLabel, index) {
|
|
28
|
+
if (item) {
|
|
29
|
+
if (item.filePath) return item.filePath;
|
|
30
|
+
if (item.relativePath) return item.relativePath;
|
|
31
|
+
if (item.fileName) return item.fileName;
|
|
32
|
+
if (item.name) return item.name;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (typeof index === "number") return `${itemLabel}[${index}]`;
|
|
36
|
+
|
|
37
|
+
return itemLabel;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function buildExpectedMessage(issue, fieldPath) {
|
|
41
|
+
if (issue.code === "invalid_enum_value" && Array.isArray(issue.options)) {
|
|
42
|
+
return `字段 ${fieldPath} 仅允许 ${issue.options.join("|")}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (issue.code === "invalid_literal") {
|
|
46
|
+
return `字段 ${fieldPath} 仅允许 ${formatValue(issue.expected)}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (issue.code === "invalid_type") {
|
|
50
|
+
return `字段 ${fieldPath} 必须是 ${issue.expected}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (issue.code === "too_small") {
|
|
54
|
+
if (typeof issue.minimum === "number") {
|
|
55
|
+
const sign = issue.inclusive ? ">=" : ">";
|
|
56
|
+
return `字段 ${fieldPath} ${sign}${issue.minimum}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (issue.code === "too_big") {
|
|
61
|
+
if (typeof issue.maximum === "number") {
|
|
62
|
+
const sign = issue.inclusive ? "<=" : "<";
|
|
63
|
+
return `字段 ${fieldPath} ${sign}${issue.maximum}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (issue.message) {
|
|
68
|
+
return `字段 ${fieldPath} ${issue.message}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return `字段 ${fieldPath} 无效`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function formatZodIssues(issues, options = {}) {
|
|
75
|
+
const result = [];
|
|
76
|
+
const items = Array.isArray(options.items) ? options.items : null;
|
|
77
|
+
const item = options.item || null;
|
|
78
|
+
const itemLabel = options.itemLabel || "item";
|
|
79
|
+
|
|
80
|
+
for (const issue of issues) {
|
|
81
|
+
const issuePath = Array.isArray(issue.path) ? issue.path : [];
|
|
82
|
+
const hasIndex = items && typeof issuePath[0] === "number";
|
|
83
|
+
const index = hasIndex ? issuePath[0] : null;
|
|
84
|
+
const path = hasIndex ? issuePath.slice(1) : issuePath;
|
|
85
|
+
|
|
86
|
+
const targetItem = hasIndex ? items[index] : item;
|
|
87
|
+
const fileLabel = pickFileLabel(targetItem, itemLabel, index);
|
|
88
|
+
const fieldPath = path.length > 0 ? path.join(".") : "(root)";
|
|
89
|
+
|
|
90
|
+
const expectedMessage = buildExpectedMessage(issue, fieldPath);
|
|
91
|
+
|
|
92
|
+
if (issue.code === "unrecognized_keys" && Array.isArray(issue.keys)) {
|
|
93
|
+
for (const key of issue.keys) {
|
|
94
|
+
result.push({
|
|
95
|
+
file: fileLabel,
|
|
96
|
+
expected: `字段 ${key} 禁止出现`
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
result.push({
|
|
103
|
+
file: fileLabel,
|
|
104
|
+
expected: expectedMessage
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isNonEmptyString } from "./is.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 获取客户端 IP(优先代理头,其次 Bun server.requestIP 兜底)
|
|
5
|
+
*
|
|
6
|
+
* 注意:目前策略是“尽量取到 IP”,未做 trustProxy 防伪造控制。
|
|
7
|
+
*/
|
|
8
|
+
export function getClientIp(req, server) {
|
|
9
|
+
// 1) 代理/网关常见头(优先取)
|
|
10
|
+
const xForwardedFor = req.headers.get("x-forwarded-for");
|
|
11
|
+
if (isNonEmptyString(xForwardedFor)) {
|
|
12
|
+
const first = xForwardedFor.split(",")[0];
|
|
13
|
+
if (isNonEmptyString(first)) {
|
|
14
|
+
return first.trim();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const xRealIp = req.headers.get("x-real-ip");
|
|
19
|
+
if (isNonEmptyString(xRealIp)) {
|
|
20
|
+
return xRealIp.trim();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const cfConnectingIp = req.headers.get("cf-connecting-ip");
|
|
24
|
+
if (isNonEmptyString(cfConnectingIp)) {
|
|
25
|
+
return cfConnectingIp.trim();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const xClientIp = req.headers.get("x-client-ip");
|
|
29
|
+
if (isNonEmptyString(xClientIp)) {
|
|
30
|
+
return xClientIp.trim();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const trueClientIp = req.headers.get("true-client-ip");
|
|
34
|
+
if (isNonEmptyString(trueClientIp)) {
|
|
35
|
+
return trueClientIp.trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2) 连接层兜底:Bun server.requestIP(req)
|
|
39
|
+
if (server && typeof server.requestIP === "function") {
|
|
40
|
+
const ipInfo = server.requestIP(req);
|
|
41
|
+
if (ipInfo && isNonEmptyString(ipInfo.address)) {
|
|
42
|
+
return ipInfo.address.trim();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return "unknown";
|
|
47
|
+
}
|