befly 3.16.11 → 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 +20 -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/scripts/syncDb/context.js +99 -0
- package/scripts/syncDb/diff.js +133 -0
- package/scripts/syncDb/index.js +70 -0
- package/scripts/syncDb/query.js +26 -0
- package/scripts/syncDb/report.js +190 -0
- package/scripts/syncDb/transform.js +111 -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 -12769
- package/befly.min.js +0 -47
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"runMode": "development",
|
|
3
|
+
"appName": "野蜂飞舞",
|
|
4
|
+
"appPort": 3000,
|
|
5
|
+
"appHost": "127.0.0.1",
|
|
6
|
+
"devEmail": "dev@qq.com",
|
|
7
|
+
"devPassword": "111111",
|
|
8
|
+
"bodyLimit": 1048576,
|
|
9
|
+
"tz": "Asia/Shanghai",
|
|
10
|
+
|
|
11
|
+
"logger": {
|
|
12
|
+
"debug": 1,
|
|
13
|
+
"excludeFields": ["password", "token", "secret"],
|
|
14
|
+
"dir": "./logs",
|
|
15
|
+
"console": 1,
|
|
16
|
+
"maxSize": 20,
|
|
17
|
+
"maxStringLen": 100,
|
|
18
|
+
"maxArrayItems": 100
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
"mysql": {
|
|
22
|
+
"idMode": "timeId",
|
|
23
|
+
"hostname": "127.0.0.1",
|
|
24
|
+
"port": 3306,
|
|
25
|
+
"username": "root",
|
|
26
|
+
"password": "root",
|
|
27
|
+
"database": "befly_dev",
|
|
28
|
+
"max": 10
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
"redis": {
|
|
32
|
+
"hostname": "127.0.0.1",
|
|
33
|
+
"port": 6379,
|
|
34
|
+
"username": "",
|
|
35
|
+
"password": "",
|
|
36
|
+
"db": 0,
|
|
37
|
+
"prefix": "befly_demo"
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
"session": {
|
|
41
|
+
"expireDays": 7
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
"cors": {
|
|
45
|
+
"origin": "*",
|
|
46
|
+
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
|
|
47
|
+
"allowedHeaders": "Content-Type,Authorization",
|
|
48
|
+
"exposedHeaders": "",
|
|
49
|
+
"maxAge": 86400,
|
|
50
|
+
"credentials": "true"
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
"rateLimit": {
|
|
54
|
+
"enable": 1,
|
|
55
|
+
"defaultLimit": 1000,
|
|
56
|
+
"defaultWindow": 60,
|
|
57
|
+
"key": "ip",
|
|
58
|
+
"skipRoutes": [],
|
|
59
|
+
"rules": []
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "首页",
|
|
4
|
+
"path": "/",
|
|
5
|
+
"sort": 1
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"name": "人员管理",
|
|
9
|
+
"path": "/people",
|
|
10
|
+
"sort": 2,
|
|
11
|
+
"children": [
|
|
12
|
+
{
|
|
13
|
+
"name": "管理员",
|
|
14
|
+
"path": "/admin",
|
|
15
|
+
"sort": 1
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "权限设置",
|
|
21
|
+
"path": "/permission",
|
|
22
|
+
"sort": 3,
|
|
23
|
+
"children": [
|
|
24
|
+
{
|
|
25
|
+
"name": "角色管理",
|
|
26
|
+
"path": "/role",
|
|
27
|
+
"sort": 1
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "菜单列表",
|
|
31
|
+
"path": "/menu",
|
|
32
|
+
"sort": 2
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "接口列表",
|
|
36
|
+
"path": "/api",
|
|
37
|
+
"sort": 3
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "配置管理",
|
|
43
|
+
"path": "/config",
|
|
44
|
+
"sort": 4,
|
|
45
|
+
"children": [
|
|
46
|
+
{
|
|
47
|
+
"name": "字典类型",
|
|
48
|
+
"path": "/dictType",
|
|
49
|
+
"sort": 1
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "字典列表",
|
|
53
|
+
"path": "/dict",
|
|
54
|
+
"sort": 2
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "系统配置",
|
|
58
|
+
"path": "/system",
|
|
59
|
+
"sort": 3
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "日志管理",
|
|
65
|
+
"path": "/log",
|
|
66
|
+
"sort": 5,
|
|
67
|
+
"children": [
|
|
68
|
+
{
|
|
69
|
+
"name": "登录日志",
|
|
70
|
+
"path": "/login",
|
|
71
|
+
"sort": 1
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "邮件日志",
|
|
75
|
+
"path": "/email",
|
|
76
|
+
"sort": 2
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "操作日志",
|
|
80
|
+
"path": "/operate",
|
|
81
|
+
"sort": 3
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// ==========================
|
|
2
|
+
// 数据库类型分类
|
|
3
|
+
// ==========================
|
|
4
|
+
|
|
5
|
+
// 字符串类数据库类型
|
|
6
|
+
export const STRING_KIND_TYPES = ["char", "varchar", "enum", "tinytext", "text", "mediumtext", "longtext"];
|
|
7
|
+
|
|
8
|
+
// 文本类数据库类型
|
|
9
|
+
export const TEXT_KIND_TYPES = ["tinytext", "text", "mediumtext", "longtext"];
|
|
10
|
+
|
|
11
|
+
// 整数类数据库类型
|
|
12
|
+
export const INT_KIND_TYPES = ["tinyint", "smallint", "mediumint", "int", "bigint"];
|
|
13
|
+
|
|
14
|
+
// 小数类数据库类型
|
|
15
|
+
export const DECIMAL_KIND_TYPES = ["decimal"];
|
|
16
|
+
|
|
17
|
+
// 浮点类数据库类型
|
|
18
|
+
export const FLOAT_KIND_TYPES = ["float", "double"];
|
|
19
|
+
|
|
20
|
+
// JSON 类数据库类型
|
|
21
|
+
export const JSON_KIND_TYPES = ["json"];
|
|
22
|
+
|
|
23
|
+
// 枚举类数据库类型
|
|
24
|
+
export const ENUM_KIND_TYPES = ["enum"];
|
|
25
|
+
|
|
26
|
+
// ==========================
|
|
27
|
+
// 运行配置可选值
|
|
28
|
+
// ==========================
|
|
29
|
+
|
|
30
|
+
// 运行模式可选值
|
|
31
|
+
export const RUN_MODE_VALUES = ["development", "production"];
|
|
32
|
+
|
|
33
|
+
// 数据库 ID 生成模式可选值
|
|
34
|
+
export const DB_ID_MODE_VALUES = ["timeId", "autoId"];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"number": "^\\d+$",
|
|
3
|
+
"integer": "^-?\\d+$",
|
|
4
|
+
"float": "^-?\\d+(\\.\\d+)?$",
|
|
5
|
+
"positive": "^[1-9]\\d*$",
|
|
6
|
+
"negative": "^-\\d+$",
|
|
7
|
+
"zero": "^0$",
|
|
8
|
+
"word": "^[a-zA-Z]+$",
|
|
9
|
+
"alphanumeric": "^[a-zA-Z0-9]+$",
|
|
10
|
+
"alphanumeric_": "^[a-zA-Z0-9_]+$",
|
|
11
|
+
"alphanumericDash_": "^[a-zA-Z0-9_-]+$",
|
|
12
|
+
"lowercase": "^[a-z]+$",
|
|
13
|
+
"uppercase": "^[A-Z]+$",
|
|
14
|
+
"chinese": "^[\\u4e00-\\u9fa5]+$",
|
|
15
|
+
"chineseWord": "^[\\u4e00-\\u9fa5a-zA-Z]+$",
|
|
16
|
+
"email": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
|
17
|
+
"phone": "^1[3-9]\\d{9}$",
|
|
18
|
+
"telephone": "^0\\d{2,3}-?\\d{7,8}$",
|
|
19
|
+
"url": "^https?://",
|
|
20
|
+
"ip": "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$",
|
|
21
|
+
"ipv6": "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$",
|
|
22
|
+
"domain": "^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$",
|
|
23
|
+
"uuid": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
|
|
24
|
+
"hex": "^[0-9a-fA-F]+$",
|
|
25
|
+
"base64": "^[A-Za-z0-9+/=]+$",
|
|
26
|
+
"md5": "^[a-f0-9]{32}$",
|
|
27
|
+
"sha1": "^[a-f0-9]{40}$",
|
|
28
|
+
"sha256": "^[a-f0-9]{64}$",
|
|
29
|
+
"date": "^\\d{4}-\\d{2}-\\d{2}$",
|
|
30
|
+
"time": "^\\d{2}:\\d{2}:\\d{2}$",
|
|
31
|
+
"datetime": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}",
|
|
32
|
+
"year": "^\\d{4}$",
|
|
33
|
+
"month": "^(0[1-9]|1[0-2])$",
|
|
34
|
+
"day": "^(0[1-9]|[12]\\d|3[01])$",
|
|
35
|
+
"variable": "^[a-zA-Z_][a-zA-Z0-9_]*$",
|
|
36
|
+
"constant": "^[A-Z][A-Z0-9_]*$",
|
|
37
|
+
"package": "^[a-z][a-z0-9-]*$",
|
|
38
|
+
"idCard": "^\\d{17}[\\dXx]$",
|
|
39
|
+
"passport": "^[a-zA-Z0-9]{5,17}$",
|
|
40
|
+
"bankCard": "^\\d{16,19}$",
|
|
41
|
+
"wechat": "^[a-zA-Z][a-zA-Z0-9_-]{5,19}$",
|
|
42
|
+
"qq": "^[1-9]\\d{4,10}$",
|
|
43
|
+
"alipay": "^(1[3-9]\\d{9}|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})$",
|
|
44
|
+
"username": "^[a-zA-Z][a-zA-Z0-9_]{3,19}$",
|
|
45
|
+
"nickname": "^[\\u4e00-\\u9fa5a-zA-Z0-9]{2,20}$",
|
|
46
|
+
"passwordWeak": "^.{6,}$",
|
|
47
|
+
"passwordMedium": "^(?=.*[a-zA-Z])(?=.*\\d).{8,}$",
|
|
48
|
+
"passwordStrong": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*]).{8,}$",
|
|
49
|
+
"licensePlate": "^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$",
|
|
50
|
+
"postalCode": "^\\d{6}$",
|
|
51
|
+
"semver": "^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.]+)?(\\+[a-zA-Z0-9.]+)?$",
|
|
52
|
+
"colorHex": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$",
|
|
53
|
+
"empty": "^$",
|
|
54
|
+
"notempty": ".+"
|
|
55
|
+
}
|
package/hooks/auth.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { toSessionTtlSeconds } from "../utils/toSessionTtlSeconds.js";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
deps: ["cors"],
|
|
5
|
+
handler: async (befly, ctx) => {
|
|
6
|
+
const authHeader = ctx.req.headers.get("authorization");
|
|
7
|
+
const ttlSeconds = toSessionTtlSeconds(befly.config?.session?.expireDays);
|
|
8
|
+
|
|
9
|
+
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
10
|
+
const sid = authHeader.substring(7).trim();
|
|
11
|
+
|
|
12
|
+
if (!sid) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const sessionKey = `session:${sid}`;
|
|
17
|
+
const sessionData = await befly.redis.getObject(sessionKey);
|
|
18
|
+
|
|
19
|
+
if (!sessionData?.id) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await befly.redis.expire(sessionKey, ttlSeconds);
|
|
25
|
+
ctx.userId = sessionData.id;
|
|
26
|
+
ctx.nickname = sessionData.nickname;
|
|
27
|
+
ctx.roleCode = sessionData.roleCode;
|
|
28
|
+
ctx.roleType = sessionData.roleType;
|
|
29
|
+
} catch {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
package/hooks/cors.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
};
|
package/hooks/parser.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// 外部依赖
|
|
2
|
+
import { XMLParser } from "fast-xml-parser";
|
|
3
|
+
|
|
4
|
+
import { ErrorResponse } from "../utils/response.js";
|
|
5
|
+
|
|
6
|
+
const xmlParser = new XMLParser();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 请求参数解析钩子
|
|
10
|
+
* - GET 请求:解析 URL 查询参数
|
|
11
|
+
* - POST 请求:解析 JSON 或 XML 请求体
|
|
12
|
+
* - 仅负责解析与合并参数;字段过滤/映射/校验由 validator hook 处理
|
|
13
|
+
* - body: "raw" 时跳过解析,由 handler 自行处理原始请求
|
|
14
|
+
*/
|
|
15
|
+
export default {
|
|
16
|
+
deps: ["auth"],
|
|
17
|
+
handler: async (befly, ctx) => {
|
|
18
|
+
if (!ctx.apiPath) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// body=raw 模式:跳过解析,保留原始请求供 handler 自行处理
|
|
23
|
+
// 适用于:微信回调、支付回调、webhook 等需要手动解密/验签的场景
|
|
24
|
+
if (ctx.body === "raw") {
|
|
25
|
+
ctx.body = {};
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// GET 请求:解析查询参数
|
|
30
|
+
if (ctx.req.method === "GET") {
|
|
31
|
+
const url = new URL(ctx.req.url);
|
|
32
|
+
const params = Object.fromEntries(url.searchParams);
|
|
33
|
+
ctx.body = params;
|
|
34
|
+
} else if (ctx.req.method === "POST") {
|
|
35
|
+
// 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
|
+
try {
|
|
42
|
+
// JSON 格式
|
|
43
|
+
if (contentType.includes("application/json")) {
|
|
44
|
+
const body = await ctx.req.json();
|
|
45
|
+
// 合并 URL 参数和请求体(请求体优先)
|
|
46
|
+
const merged = Object.assign({}, queryParams, body);
|
|
47
|
+
ctx.body = merged;
|
|
48
|
+
} else if (contentType.includes("application/xml") || contentType.includes("text/xml")) {
|
|
49
|
+
// XML 格式
|
|
50
|
+
const text = await ctx.req.text();
|
|
51
|
+
const parsed = xmlParser.parse(text);
|
|
52
|
+
// 提取根节点内容(如 xml),使 body 扁平化
|
|
53
|
+
const rootKey = Object.keys(parsed)[0];
|
|
54
|
+
const body = rootKey && typeof parsed[rootKey] === "object" ? parsed[rootKey] : parsed;
|
|
55
|
+
// 合并 URL 参数和请求体(请求体优先)
|
|
56
|
+
const merged = Object.assign({}, queryParams, body);
|
|
57
|
+
ctx.body = merged;
|
|
58
|
+
} else {
|
|
59
|
+
// 不支持的 Content-Type
|
|
60
|
+
ctx.response = ErrorResponse(
|
|
61
|
+
ctx,
|
|
62
|
+
"无效的请求参数格式",
|
|
63
|
+
1,
|
|
64
|
+
null,
|
|
65
|
+
{
|
|
66
|
+
location: "content-type",
|
|
67
|
+
value: contentType
|
|
68
|
+
},
|
|
69
|
+
"parser"
|
|
70
|
+
);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// 解析失败:属于客户端输入错误,返回安全 detail(不回传异常栈/原始 body)
|
|
75
|
+
ctx.response = ErrorResponse(
|
|
76
|
+
ctx,
|
|
77
|
+
"无效的请求参数格式",
|
|
78
|
+
1,
|
|
79
|
+
null,
|
|
80
|
+
{
|
|
81
|
+
location: "body",
|
|
82
|
+
reason: contentType.includes("application/json") ? "invalid_json" : contentType.includes("xml") ? "invalid_xml" : "invalid_body"
|
|
83
|
+
},
|
|
84
|
+
"parser"
|
|
85
|
+
);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { CacheKeys } from "../lib/cacheKeys.js";
|
|
2
|
+
import { Logger } from "../lib/logger.js";
|
|
3
|
+
// 相对导入
|
|
4
|
+
import { ErrorResponse } from "../utils/response.js";
|
|
5
|
+
import { isNonEmptyString, isString, isValidPositiveInt } from "../utils/is.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 权限检查钩子
|
|
9
|
+
* - 接口无需权限(auth=false):直接通过
|
|
10
|
+
* - auth 为 ctx.roleType 白名单(string[]):登录后按 ctx.roleType 放行
|
|
11
|
+
* - 用户未登录:返回 401
|
|
12
|
+
* - 开发者角色(dev):最高权限,直接通过
|
|
13
|
+
* - 其他角色:检查 Redis 中的角色权限集合
|
|
14
|
+
*/
|
|
15
|
+
export default {
|
|
16
|
+
deps: ["validator"],
|
|
17
|
+
handler: async (befly, ctx) => {
|
|
18
|
+
// 1. 接口无需权限
|
|
19
|
+
if (ctx.auth === false) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 2. 用户未登录
|
|
24
|
+
if (!isValidPositiveInt(ctx.userId)) {
|
|
25
|
+
ctx.response = ErrorResponse(ctx, "未登录", 1, null, null, "auth");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 3. 开发者权限(最高权限)
|
|
30
|
+
if (ctx.roleCode === "dev") {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 3.5 auth 为角色类型白名单时,仅做 ctx.roleType 校验
|
|
35
|
+
if (Array.isArray(ctx.auth) && ctx.auth.includes(ctx.roleType) === false) {
|
|
36
|
+
ctx.response = ErrorResponse(
|
|
37
|
+
ctx,
|
|
38
|
+
`无权访问 ${ctx.apiName} 接口`,
|
|
39
|
+
1,
|
|
40
|
+
null,
|
|
41
|
+
{
|
|
42
|
+
apiName: ctx.apiName,
|
|
43
|
+
apiPath: ctx.apiPath
|
|
44
|
+
},
|
|
45
|
+
"permission"
|
|
46
|
+
);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 4. 角色权限检查
|
|
51
|
+
// apiPath 在 apiHandler 中已统一生成并写入 ctx.apiPath
|
|
52
|
+
|
|
53
|
+
// 极简方案:每个角色一个 Set,直接判断成员是否存在
|
|
54
|
+
const hasPermission = await befly.redis.sismember(CacheKeys.roleApis(ctx.roleCode), ctx.apiPath);
|
|
55
|
+
|
|
56
|
+
if (!hasPermission) {
|
|
57
|
+
ctx.response = ErrorResponse(
|
|
58
|
+
ctx,
|
|
59
|
+
`无权访问 ${ctx.apiName} 接口`,
|
|
60
|
+
1,
|
|
61
|
+
null,
|
|
62
|
+
{
|
|
63
|
+
apiName: ctx.apiName,
|
|
64
|
+
apiPath: ctx.apiPath
|
|
65
|
+
},
|
|
66
|
+
"permission"
|
|
67
|
+
);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// 相对导入
|
|
2
|
+
import { Validator } from "../lib/validator.js";
|
|
3
|
+
import { ErrorResponse } from "../utils/response.js";
|
|
4
|
+
import { isPlainObject } from "../utils/is.js";
|
|
5
|
+
import { snakeCase } from "../utils/util.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 参数验证钩子
|
|
9
|
+
* 根据 API 定义的 fields 和 required 验证请求参数
|
|
10
|
+
*/
|
|
11
|
+
export default {
|
|
12
|
+
deps: ["parser"],
|
|
13
|
+
handler: async (befly, ctx) => {
|
|
14
|
+
// 仅保留 fields 中声明的字段,并支持 snake_case 入参回退(例如 agent_id -> agentId)
|
|
15
|
+
const rawBody = isPlainObject(ctx.body) ? ctx.body : {};
|
|
16
|
+
const nextBody = {};
|
|
17
|
+
|
|
18
|
+
for (const [field] of Object.entries(ctx.fields)) {
|
|
19
|
+
let value = rawBody[field];
|
|
20
|
+
|
|
21
|
+
if (value === undefined) {
|
|
22
|
+
const snakeField = snakeCase(field);
|
|
23
|
+
if (rawBody[snakeField] !== undefined) {
|
|
24
|
+
value = rawBody[snakeField];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (value !== undefined) {
|
|
29
|
+
nextBody[field] = value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
ctx.body = nextBody;
|
|
34
|
+
|
|
35
|
+
// 验证参数
|
|
36
|
+
const result = Validator.validate(ctx.body, ctx.fields, ctx.required || []);
|
|
37
|
+
|
|
38
|
+
if (result.code !== 0) {
|
|
39
|
+
ctx.response = ErrorResponse(ctx, result.firstError || "参数验证失败", 1, null, result.fieldErrors, "validator");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|