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.
Files changed (150) hide show
  1. package/README.md +6 -0
  2. package/apis/admin/cacheRefresh.js +122 -0
  3. package/apis/admin/del.js +34 -0
  4. package/apis/admin/detail.js +23 -0
  5. package/apis/admin/ins.js +69 -0
  6. package/apis/admin/list.js +28 -0
  7. package/apis/admin/upd.js +95 -0
  8. package/apis/api/all.js +24 -0
  9. package/apis/api/list.js +31 -0
  10. package/apis/auth/login.js +123 -0
  11. package/apis/auth/sendSmsCode.js +24 -0
  12. package/apis/dashboard/configStatus.js +39 -0
  13. package/apis/dashboard/environmentInfo.js +43 -0
  14. package/apis/dashboard/performanceMetrics.js +20 -0
  15. package/apis/dashboard/permissionStats.js +27 -0
  16. package/apis/dashboard/serviceStatus.js +75 -0
  17. package/apis/dashboard/systemInfo.js +19 -0
  18. package/apis/dashboard/systemOverview.js +30 -0
  19. package/apis/dashboard/systemResources.js +106 -0
  20. package/apis/dict/all.js +23 -0
  21. package/apis/dict/del.js +16 -0
  22. package/apis/dict/detail.js +27 -0
  23. package/apis/dict/ins.js +51 -0
  24. package/apis/dict/items.js +30 -0
  25. package/apis/dict/list.js +36 -0
  26. package/apis/dict/upd.js +74 -0
  27. package/apis/dictType/all.js +16 -0
  28. package/apis/dictType/del.js +38 -0
  29. package/apis/dictType/detail.js +20 -0
  30. package/apis/dictType/ins.js +37 -0
  31. package/apis/dictType/list.js +26 -0
  32. package/apis/dictType/upd.js +51 -0
  33. package/apis/email/config.js +25 -0
  34. package/apis/email/logList.js +23 -0
  35. package/apis/email/send.js +66 -0
  36. package/apis/email/verify.js +21 -0
  37. package/apis/loginLog/list.js +23 -0
  38. package/apis/menu/all.js +41 -0
  39. package/apis/menu/list.js +25 -0
  40. package/apis/operateLog/list.js +23 -0
  41. package/apis/role/all.js +21 -0
  42. package/apis/role/apiSave.js +43 -0
  43. package/apis/role/apis.js +22 -0
  44. package/apis/role/del.js +49 -0
  45. package/apis/role/detail.js +32 -0
  46. package/apis/role/ins.js +46 -0
  47. package/apis/role/list.js +27 -0
  48. package/apis/role/menuSave.js +42 -0
  49. package/apis/role/menus.js +22 -0
  50. package/apis/role/save.js +40 -0
  51. package/apis/role/upd.js +50 -0
  52. package/apis/sysConfig/all.js +16 -0
  53. package/apis/sysConfig/del.js +36 -0
  54. package/apis/sysConfig/get.js +49 -0
  55. package/apis/sysConfig/ins.js +50 -0
  56. package/apis/sysConfig/list.js +24 -0
  57. package/apis/sysConfig/upd.js +62 -0
  58. package/checks/api.js +55 -0
  59. package/checks/config.js +107 -0
  60. package/checks/hook.js +38 -0
  61. package/checks/menu.js +58 -0
  62. package/checks/plugin.js +38 -0
  63. package/checks/table.js +78 -0
  64. package/configs/beflyConfig.json +61 -0
  65. package/configs/beflyMenus.json +85 -0
  66. package/configs/constConfig.js +34 -0
  67. package/configs/regexpAlias.json +55 -0
  68. package/hooks/auth.js +34 -0
  69. package/hooks/cors.js +39 -0
  70. package/hooks/parser.js +90 -0
  71. package/hooks/permission.js +71 -0
  72. package/hooks/validator.js +43 -0
  73. package/index.js +326 -0
  74. package/lib/cacheHelper.js +483 -0
  75. package/lib/cacheKeys.js +42 -0
  76. package/lib/connect.js +120 -0
  77. package/lib/dbHelper/builders.js +698 -0
  78. package/lib/dbHelper/context.js +131 -0
  79. package/lib/dbHelper/dataOps.js +505 -0
  80. package/lib/dbHelper/execute.js +65 -0
  81. package/lib/dbHelper/index.js +27 -0
  82. package/lib/dbHelper/transaction.js +43 -0
  83. package/lib/dbHelper/util.js +58 -0
  84. package/lib/dbHelper/validate.js +549 -0
  85. package/lib/emailHelper.js +110 -0
  86. package/lib/logger.js +604 -0
  87. package/lib/redisHelper.js +684 -0
  88. package/lib/sqlBuilder/batch.js +113 -0
  89. package/lib/sqlBuilder/check.js +150 -0
  90. package/lib/sqlBuilder/compiler.js +347 -0
  91. package/lib/sqlBuilder/errors.js +60 -0
  92. package/lib/sqlBuilder/index.js +218 -0
  93. package/lib/sqlBuilder/parser.js +296 -0
  94. package/lib/sqlBuilder/util.js +260 -0
  95. package/lib/validator.js +303 -0
  96. package/package.json +19 -12
  97. package/paths.js +112 -0
  98. package/plugins/cache.js +16 -0
  99. package/plugins/config.js +11 -0
  100. package/plugins/email.js +27 -0
  101. package/plugins/logger.js +20 -0
  102. package/plugins/mysql.js +36 -0
  103. package/plugins/redis.js +34 -0
  104. package/plugins/tool.js +155 -0
  105. package/router/api.js +140 -0
  106. package/router/static.js +71 -0
  107. package/sql/admin.sql +18 -0
  108. package/sql/api.sql +12 -0
  109. package/sql/dict.sql +13 -0
  110. package/sql/dictType.sql +12 -0
  111. package/sql/emailLog.sql +20 -0
  112. package/sql/loginLog.sql +25 -0
  113. package/sql/menu.sql +12 -0
  114. package/sql/operateLog.sql +22 -0
  115. package/sql/role.sql +14 -0
  116. package/sql/sysConfig.sql +16 -0
  117. package/sync/api.js +93 -0
  118. package/sync/cache.js +13 -0
  119. package/sync/dev.js +171 -0
  120. package/sync/menu.js +99 -0
  121. package/tables/admin.json +56 -0
  122. package/tables/api.json +26 -0
  123. package/tables/dict.json +30 -0
  124. package/tables/dictType.json +24 -0
  125. package/tables/emailLog.json +61 -0
  126. package/tables/loginLog.json +86 -0
  127. package/tables/menu.json +24 -0
  128. package/tables/operateLog.json +68 -0
  129. package/tables/role.json +32 -0
  130. package/tables/sysConfig.json +43 -0
  131. package/utils/calcPerfTime.js +13 -0
  132. package/utils/cors.js +17 -0
  133. package/utils/deepMerge.js +78 -0
  134. package/utils/fieldClear.js +65 -0
  135. package/utils/formatYmdHms.js +23 -0
  136. package/utils/formatZodIssues.js +109 -0
  137. package/utils/getClientIp.js +47 -0
  138. package/utils/importDefault.js +51 -0
  139. package/utils/is.js +462 -0
  140. package/utils/loggerUtils.js +185 -0
  141. package/utils/processInfo.js +39 -0
  142. package/utils/regexpUtil.js +52 -0
  143. package/utils/response.js +114 -0
  144. package/utils/scanFiles.js +124 -0
  145. package/utils/scanSources.js +68 -0
  146. package/utils/sortModules.js +75 -0
  147. package/utils/toSessionTtlSeconds.js +14 -0
  148. package/utils/util.js +374 -0
  149. package/befly.js +0 -16413
  150. 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
+ }
@@ -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
+ }
@@ -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
+ }