befly 3.76.1 → 3.76.3

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 (149) hide show
  1. package/Befly.js +159 -0
  2. package/README.md +50 -11
  3. package/apis/admin/delete.js +1 -3
  4. package/apis/admin/select.js +2 -7
  5. package/apis/admin/update.js +1 -5
  6. package/apis/api/select.js +2 -7
  7. package/apis/auth/login.js +16 -20
  8. package/apis/dict/delete.js +1 -1
  9. package/apis/dict/detail.js +1 -3
  10. package/apis/dict/select.js +1 -2
  11. package/apis/dict/update.js +1 -1
  12. package/apis/dictType/delete.js +1 -1
  13. package/apis/dictType/detail.js +1 -1
  14. package/apis/dictType/insert.js +1 -1
  15. package/apis/dictType/select.js +2 -7
  16. package/apis/dictType/update.js +2 -2
  17. package/apis/email/config.js +4 -7
  18. package/apis/email/logList.js +2 -7
  19. package/apis/email/send.js +1 -2
  20. package/apis/email/verify.js +1 -3
  21. package/apis/loginLog/select.js +2 -7
  22. package/apis/menu/select.js +2 -7
  23. package/apis/operateLog/select.js +2 -7
  24. package/apis/role/apiSave.js +2 -2
  25. package/apis/role/menuSave.js +2 -2
  26. package/apis/role/select.js +2 -7
  27. package/apis/source/imageList.js +2 -5
  28. package/apis/tongJi/dailyReport.js +12 -13
  29. package/apis/tongJi/dailyReportSelect.js +1 -2
  30. package/apis/tongJi/dailyStats.js +1 -1
  31. package/apis/tongJi/dailyStatsDistribution.js +2 -2
  32. package/apis/tongJi/errorList.js +1 -2
  33. package/apis/tongJi/errorReport.js +1 -2
  34. package/apis/tongJi/projectDelete.js +1 -3
  35. package/apis/tongJi/projectSelect.js +2 -4
  36. package/apis/tongJi/projectUpdate.js +2 -2
  37. package/apis/upload/file.js +7 -5
  38. package/checks/api.js +43 -66
  39. package/checks/config.js +17 -12
  40. package/checks/corns.js +28 -46
  41. package/checks/field.js +18 -0
  42. package/checks/items.js +16 -60
  43. package/checks/menu.js +28 -39
  44. package/checks/table.js +106 -26
  45. package/checks/validation.js +13 -0
  46. package/configs/beflyConfig.json +6 -6
  47. package/configs/constConfig.js +2 -63
  48. package/exports.js +2 -0
  49. package/hooks/auth.js +11 -7
  50. package/hooks/parser.js +55 -59
  51. package/hooks/permission.js +1 -0
  52. package/hooks/rateLimit.js +25 -62
  53. package/hooks/validator.js +43 -31
  54. package/index.js +43 -344
  55. package/{lib → libs}/cacheHelper.js +3 -8
  56. package/libs/cron.js +60 -0
  57. package/libs/logger/index.js +1 -0
  58. package/libs/logger/logger.js +245 -0
  59. package/libs/logger/sanitize.js +115 -0
  60. package/{lib → libs/mysql}/dbHelper.js +100 -73
  61. package/{lib → libs/mysql}/dbParse.js +16 -9
  62. package/{lib → libs/mysql}/dbUtil.js +15 -32
  63. package/libs/mysql/error.js +29 -0
  64. package/libs/mysql/index.js +6 -0
  65. package/libs/mysql/is.js +29 -0
  66. package/libs/mysql/mysql.js +43 -0
  67. package/libs/mysql/sql/identifier.js +85 -0
  68. package/libs/mysql/sql/index.js +2 -0
  69. package/{lib → libs/mysql/sql}/sqlBuilder.js +2 -3
  70. package/libs/mysql/util.js +44 -0
  71. package/libs/redis/index.js +2 -0
  72. package/libs/redis/redis.js +204 -0
  73. package/libs/redis/redisError.js +8 -0
  74. package/libs/tableModel.js +72 -0
  75. package/libs/tool.js +33 -0
  76. package/libs/validator/compiler.js +248 -0
  77. package/libs/validator/index.js +10 -0
  78. package/libs/validator/parser.js +173 -0
  79. package/libs/validator/util.js +5 -0
  80. package/package.json +10 -8
  81. package/paths.js +2 -3
  82. package/plugins/cache.js +2 -1
  83. package/plugins/email.js +4 -4
  84. package/plugins/mysql.js +5 -15
  85. package/plugins/redis.js +4 -12
  86. package/router/api.js +14 -10
  87. package/router/root.js +14 -0
  88. package/router/static.js +3 -3
  89. package/schemas/api.json +99 -0
  90. package/schemas/config.json +255 -0
  91. package/schemas/corn.json +39 -0
  92. package/schemas/item.json +43 -0
  93. package/schemas/menu.json +44 -0
  94. package/schemas/table.json +80 -0
  95. package/scripts/syncDb/apply.js +87 -0
  96. package/scripts/syncDb/ddl.js +43 -0
  97. package/scripts/syncDb/index.js +175 -30
  98. package/scripts/syncDb/model.js +60 -0
  99. package/scripts/syncDb/plan.js +160 -0
  100. package/scripts/syncDb/preflight.js +98 -0
  101. package/scripts/syncDb/report.js +177 -162
  102. package/scripts/syncDb/snapshot.js +118 -0
  103. package/tables/admin.json +51 -53
  104. package/tables/api.json +28 -39
  105. package/tables/dailyReport.json +85 -73
  106. package/tables/dict.json +27 -39
  107. package/tables/dictType.json +20 -34
  108. package/tables/emailLog.json +49 -47
  109. package/tables/errorReport.json +86 -60
  110. package/tables/file.json +47 -50
  111. package/tables/loginLog.json +68 -55
  112. package/tables/menu.json +20 -34
  113. package/tables/operateLog.json +53 -47
  114. package/tables/project.json +18 -30
  115. package/tables/role.json +33 -37
  116. package/utils/cors.js +37 -41
  117. package/utils/deepMerge.js +3 -1
  118. package/utils/error.js +1 -56
  119. package/utils/id.js +44 -0
  120. package/utils/is.js +6 -243
  121. package/utils/response.js +12 -1
  122. package/utils/scanFiles.js +13 -4
  123. package/utils/scanSources.js +58 -20
  124. package/utils/util.js +11 -163
  125. package/apis/_apis.js +0 -20
  126. package/apis/auth/sendSmsCode.js +0 -24
  127. package/configs/regexpAlias.json +0 -55
  128. package/lib/connect.js +0 -119
  129. package/lib/logger.js +0 -430
  130. package/lib/redisHelper.js +0 -399
  131. package/lib/validator.js +0 -274
  132. package/lib/xmlParse.js +0 -149
  133. package/plugins/config.js +0 -13
  134. package/plugins/cron.js +0 -115
  135. package/plugins/logger.js +0 -17
  136. package/plugins/tool.js +0 -131
  137. package/plugins/xmlParse.js +0 -10
  138. package/schemas/config.js +0 -67
  139. package/scripts/syncDb/context.js +0 -120
  140. package/scripts/syncDb/diff.js +0 -279
  141. package/scripts/syncDb/transform.js +0 -113
  142. package/sql/befly.sql +0 -271
  143. package/utils/email.js +0 -3
  144. package/utils/fieldClear.js +0 -22
  145. package/utils/formatValidationIssues.js +0 -28
  146. package/utils/getClientIp.js +0 -47
  147. package/utils/loggerUtils.js +0 -196
  148. package/utils/regexpUtil.js +0 -52
  149. /package/{lib → libs}/smtpText.js +0 -0
package/lib/xmlParse.js DELETED
@@ -1,149 +0,0 @@
1
- import { createError } from "#befly/utils/error.js";
2
- import { isString } from "#befly/utils/is.js";
3
-
4
- function decodeXmlText(value) {
5
- return String(value)
6
- .replace(/&lt;/g, "<")
7
- .replace(/&gt;/g, ">")
8
- .replace(/&amp;/g, "&")
9
- .replace(/&apos;/g, "'")
10
- .replace(/&quot;/g, '"');
11
- }
12
-
13
- function normalizeXmlValue(value) {
14
- const text = decodeXmlText(value.trim());
15
- if (text === "") {
16
- return "";
17
- }
18
- const numberValue = Number(text);
19
- if (Number.isFinite(numberValue) && String(numberValue) === text) {
20
- return numberValue;
21
- }
22
- return text;
23
- }
24
-
25
- function assignNodeValue(target, key, value) {
26
- if (Object.hasOwn(target, key)) {
27
- if (Array.isArray(target[key])) {
28
- target[key].push(value);
29
- return;
30
- }
31
- target[key] = [target[key], value];
32
- return;
33
- }
34
-
35
- target[key] = value;
36
- }
37
-
38
- function skipIgnoredMarkup(rawXml, startIndex) {
39
- let index = startIndex;
40
-
41
- while (index < rawXml.length) {
42
- while (/\s/.test(rawXml[index])) {
43
- index += 1;
44
- }
45
-
46
- if (rawXml.startsWith("<?", index)) {
47
- const declarationEnd = rawXml.indexOf("?>", index);
48
- if (declarationEnd === -1) {
49
- throw createError("XML 声明未闭合", { code: "validation", subsystem: "xml", operation: "skipIgnoredMarkup" });
50
- }
51
- index = declarationEnd + 2;
52
- continue;
53
- }
54
-
55
- if (rawXml.startsWith("<!--", index)) {
56
- const commentEnd = rawXml.indexOf("-->", index);
57
- if (commentEnd === -1) {
58
- throw createError("XML 注释未闭合", { code: "validation", subsystem: "xml", operation: "skipIgnoredMarkup" });
59
- }
60
- index = commentEnd + 3;
61
- continue;
62
- }
63
-
64
- return index;
65
- }
66
-
67
- return index;
68
- }
69
-
70
- function parseNode(rawXml, startIndex) {
71
- const openEnd = rawXml.indexOf(">", startIndex);
72
- if (openEnd === -1) {
73
- throw createError("XML 开始标签不完整", { code: "validation", subsystem: "xml", operation: "parseNode" });
74
- }
75
-
76
- const openContent = rawXml.slice(startIndex + 1, openEnd).trim();
77
- const isSelfClosing = openContent.endsWith("/");
78
- const tagName = (isSelfClosing ? openContent.slice(0, -1).trim() : openContent).split(/\s+/)[0];
79
- if (!tagName || tagName.startsWith("/")) {
80
- throw createError("XML 标签格式不正确", { code: "validation", subsystem: "xml", operation: "parseNode" });
81
- }
82
- if (isSelfClosing) {
83
- return {
84
- key: tagName,
85
- value: "",
86
- index: openEnd + 1
87
- };
88
- }
89
-
90
- let index = openEnd + 1;
91
- let text = "";
92
- const children = {};
93
- let hasChild = false;
94
-
95
- while (index < rawXml.length) {
96
- index = skipIgnoredMarkup(rawXml, index);
97
-
98
- if (rawXml.startsWith("<![CDATA[", index)) {
99
- const cdataEnd = rawXml.indexOf("]]>", index);
100
- if (cdataEnd === -1) {
101
- throw createError("XML CDATA 未闭合", { code: "validation", subsystem: "xml", operation: "parseNode" });
102
- }
103
- text += rawXml.slice(index + 9, cdataEnd);
104
- index = cdataEnd + 3;
105
- continue;
106
- }
107
-
108
- if (rawXml.startsWith(`</${tagName}>`, index)) {
109
- index += tagName.length + 3;
110
- return {
111
- key: tagName,
112
- value: hasChild ? children : normalizeXmlValue(text),
113
- index: index
114
- };
115
- }
116
-
117
- if (rawXml[index] === "<") {
118
- const child = parseNode(rawXml, index);
119
- hasChild = true;
120
- assignNodeValue(children, child.key, child.value);
121
- index = child.index;
122
- continue;
123
- }
124
-
125
- const nextTagIndex = rawXml.indexOf("<", index);
126
- if (nextTagIndex === -1) {
127
- throw createError("XML 结束标签缺失", { code: "validation", subsystem: "xml", operation: "parseNode" });
128
- }
129
- text += rawXml.slice(index, nextTagIndex);
130
- index = nextTagIndex;
131
- }
132
-
133
- throw createError("XML 标签未闭合", { code: "validation", subsystem: "xml", operation: "parseNode" });
134
- }
135
-
136
- export function xmlParse(value) {
137
- if (!isString(value) || value.length < 1) {
138
- throw createError("xmlParse 输入必须是非空字符串", { code: "validation", subsystem: "xml", operation: "xmlParse" });
139
- }
140
-
141
- const normalized = value.trim();
142
- const startIndex = skipIgnoredMarkup(normalized, 0);
143
- if (normalized[startIndex] !== "<") {
144
- throw createError("XML 根节点缺失", { code: "validation", subsystem: "xml", operation: "xmlParse" });
145
- }
146
-
147
- const node = parseNode(normalized, startIndex);
148
- return node.key === "xml" && Object.prototype.toString.call(node.value) === "[object Object]" ? node.value : { [node.key]: node.value };
149
- }
package/plugins/config.js DELETED
@@ -1,13 +0,0 @@
1
- import { PLUGIN_ORDER } from "../configs/constConfig.js";
2
-
3
- /**
4
- * 配置插件
5
- * 提供访问项目配置的能力
6
- */
7
-
8
- export default {
9
- order: PLUGIN_ORDER.CONFIG,
10
- handler: (befly) => {
11
- return befly.config;
12
- }
13
- };
package/plugins/cron.js DELETED
@@ -1,115 +0,0 @@
1
- import { PLUGIN_ORDER } from "../configs/constConfig.js";
2
- /**
3
- * 定时任务插件
4
- * 基于 Bun.cron 提供进程内定时任务能力。
5
- */
6
- import { Logger } from "../lib/logger.js";
7
- import { createError } from "../utils/error.js";
8
- import { isPrimaryProcess, isValidCronSchedule } from "../utils/is.js";
9
-
10
- const jobs = new Map();
11
-
12
- /**
13
- * 注册一个定时任务
14
- * @param {string} name - 任务名称,用于日志标识
15
- * @param {string} schedule - cron 表达式或预定义昵称
16
- * @param {function} task - 任务函数,接收 befly 上下文
17
- * @returns {CronJob|null}
18
- */
19
- function register(context, name, schedule, task) {
20
- if (!isPrimaryProcess()) {
21
- return null;
22
- }
23
-
24
- if (jobs.has(name)) {
25
- return jobs.get(name);
26
- }
27
-
28
- // 前置校验:表达式非法时启动即报错,避免 Bun.cron 注册时抛裸错
29
- if (!isValidCronSchedule(schedule)) {
30
- throw createError(`定时任务 ${name} 的 schedule 不是合法的 cron 表达式`, { code: "validation", subsystem: "cron", operation: "register" });
31
- }
32
-
33
- // 时区统一走 config.tz(checkConfig 已校验 IANA 合法性),未配置时使用系统时区
34
- const tz = context.config?.tz;
35
- const job = Bun.cron(
36
- schedule,
37
- async function () {
38
- Logger.info(`[定时任务] ${name} 开始执行`);
39
- try {
40
- await task(context);
41
- Logger.info(`[定时任务] ${name} 执行完成`);
42
- } catch (error) {
43
- Logger.error(`[定时任务] ${name} 执行失败`, error);
44
- }
45
- },
46
- tz ? { tz: tz } : undefined
47
- );
48
-
49
- jobs.set(name, job);
50
- return job;
51
- }
52
-
53
- /**
54
- * 注册一个按秒执行的定时任务
55
- */
56
- function second(context, name, seconds, task) {
57
- if (!isPrimaryProcess()) {
58
- return null;
59
- }
60
-
61
- if (jobs.has(name)) {
62
- return jobs.get(name);
63
- }
64
-
65
- let running = false;
66
- const timer = setInterval(async () => {
67
- if (running) {
68
- return;
69
- }
70
-
71
- running = true;
72
- try {
73
- await task(context);
74
- } catch (error) {
75
- Logger.error(`[定时任务] ${name} 执行失败`, error);
76
- } finally {
77
- running = false;
78
- }
79
- }, seconds * 1000);
80
- const job = {
81
- stop: () => clearInterval(timer)
82
- };
83
-
84
- jobs.set(name, job);
85
- return job;
86
- }
87
-
88
- /**
89
- * 批量注册扫描到的定时器(corns/*.js),任务名取文件名
90
- */
91
- export function registerCorns(context, corns) {
92
- for (const corn of corns) {
93
- register(context, corn.fileName, corn.schedule, corn.handler);
94
- }
95
- }
96
-
97
- /**
98
- * 停止所有已注册的定时任务
99
- */
100
- export function stopAll() {
101
- for (const job of jobs.values()) {
102
- job.stop();
103
- }
104
- jobs.clear();
105
- }
106
-
107
- export default {
108
- order: PLUGIN_ORDER.CRON,
109
- handler: async function (befly) {
110
- return {
111
- register: (name, schedule, task) => register(befly, name, schedule, task),
112
- second: (name, seconds, task) => second(befly, name, seconds, task)
113
- };
114
- }
115
- };
package/plugins/logger.js DELETED
@@ -1,17 +0,0 @@
1
- import { PLUGIN_ORDER } from "../configs/constConfig.js";
2
- /**
3
- * 日志插件
4
- * 提供全局日志功能
5
- */
6
- import { Logger } from "../lib/logger.js";
7
-
8
- /**
9
- * 日志插件
10
- */
11
- export default {
12
- order: PLUGIN_ORDER.LOGGER,
13
- handler: async function () {
14
- // 日志已在 createBefly 中按 runMode 初始化,这里只注入 Logger 实例
15
- return Logger;
16
- }
17
- };
package/plugins/tool.js DELETED
@@ -1,131 +0,0 @@
1
- import { PLUGIN_ORDER } from "../configs/constConfig.js";
2
- /**
3
- * 工具插件
4
- * 提供常用的工具函数
5
- */
6
- import { isNumber, isString } from "../utils/is.js";
7
-
8
- /**
9
- * 成功响应
10
- * @param msg - 消息
11
- * @param data - 数据(可选)
12
- * @param other - 其他字段(可选)
13
- * @returns 成功响应对象
14
- */
15
- export function Yes(msg, data = {}, other = {}) {
16
- const out = {
17
- code: 0,
18
- msg: msg,
19
- data: data
20
- };
21
- if (other && typeof other === "object") {
22
- for (const key of Object.keys(other)) {
23
- out[key] = other[key];
24
- }
25
- }
26
- return out;
27
- }
28
-
29
- /**
30
- * 失败响应
31
- * @param msg - 消息
32
- * @param data - 数据(可选)
33
- * @param other - 其他字段(可选)
34
- * @returns 失败响应对象
35
- */
36
- export function No(msg, data = null, other = {}) {
37
- const out = {
38
- code: 1,
39
- msg: msg,
40
- data: data
41
- };
42
- if (other && typeof other === "object") {
43
- for (const key of Object.keys(other)) {
44
- out[key] = other[key];
45
- }
46
- }
47
- return out;
48
- }
49
-
50
- /**
51
- * 业务失败并抛出错误
52
- *
53
- * 仅用于事务内需要回滚的业务失败;抛出后由路由统一转换为 { code: 1 } 响应。
54
- * @param msg - 消息
55
- * @param data - 数据(可选)
56
- * @param other - 其他字段(可选)
57
- */
58
- export function Fail(msg, data = null, other = {}) {
59
- const error = new Error(msg);
60
- error.isBeflyResponse = true;
61
- error.payload = No(msg, data, other);
62
- throw error;
63
- }
64
-
65
- /**
66
- * 统一响应函数
67
- *
68
- * 自动识别数据类型并设置正确的 Content-Type:
69
- * - 对象 → application/json
70
- * - 字符串 → text/plain
71
- * - 可通过 options.contentType 手动指定
72
- *
73
- * @param ctx 请求上下文
74
- * @param data 响应数据(对象或字符串)
75
- * @param options 响应选项
76
- * @returns Response 对象
77
- */
78
- export function Raw(ctx, data, options = {}) {
79
- const status = isNumber(options.status) ? options.status : 200;
80
- const contentType = options.contentType;
81
- const headers = options.headers || {};
82
-
83
- // 自动判断 Content-Type
84
- let finalContentType = contentType;
85
- let body;
86
-
87
- if (isString(data)) {
88
- // 字符串类型
89
- body = data;
90
- if (!finalContentType) {
91
- // 自动判断:XML 或纯文本
92
- finalContentType = data.trim().startsWith("<") ? "application/xml" : "text/plain";
93
- }
94
- } else {
95
- // JSON 序列化(对象/数组/原始值均可)
96
- body = JSON.stringify(data);
97
- finalContentType ||= "application/json";
98
- }
99
-
100
- // 合并响应头
101
- const responseHeaders = {};
102
- const corsHeaders = ctx.corsHeaders;
103
- if (corsHeaders && typeof corsHeaders === "object") {
104
- for (const key of Object.keys(corsHeaders)) {
105
- responseHeaders[key] = corsHeaders[key];
106
- }
107
- }
108
- responseHeaders["Content-Type"] = finalContentType;
109
- if (headers && typeof headers === "object") {
110
- for (const key of Object.keys(headers)) {
111
- responseHeaders[key] = headers[key];
112
- }
113
- }
114
-
115
- return new Response(body, {
116
- status: status,
117
- headers: responseHeaders
118
- });
119
- }
120
-
121
- export default {
122
- order: PLUGIN_ORDER.TOOL,
123
- handler: () => {
124
- return {
125
- Yes: Yes,
126
- No: No,
127
- Fail: Fail,
128
- Raw: Raw
129
- };
130
- }
131
- };
@@ -1,10 +0,0 @@
1
- import { xmlParse } from "#befly/lib/xmlParse.js";
2
-
3
- import { PLUGIN_ORDER } from "../configs/constConfig.js";
4
-
5
- export default {
6
- order: PLUGIN_ORDER.XML_PARSE,
7
- handler: () => {
8
- return xmlParse;
9
- }
10
- };
package/schemas/config.js DELETED
@@ -1,67 +0,0 @@
1
- const noTrimString = { type: "string", noTrim: true };
2
- const boolInt = { type: "boolInt" };
3
- const port = { type: "number", integer: true, min: 1, max: 65535 };
4
-
5
- export const configSchema = {
6
- root: {
7
- appName: noTrimString,
8
- appPort: port,
9
- appHost: noTrimString,
10
- apiHost: noTrimString,
11
- devEmail: { type: "string", noTrim: true, check: "emailOrEmpty" },
12
- devPassword: { type: "string", min: 6 },
13
- bodyLimit: { type: "number", integer: true, min: 1 },
14
- tz: { type: "string", check: "timeZone" },
15
- excludeApisLog: { type: "stringArray", noTrim: true }
16
- },
17
- groups: {
18
- upload: {
19
- maxSize: { type: "number", integer: true, min: 1 },
20
- publicDir: { ...noTrimString, min: 1 },
21
- publicHost: noTrimString,
22
- allowedExtensions: { ...noTrimString, pattern: /^\.[a-z0-9]+(?:,\.[a-z0-9]+)*$/ },
23
- allowedMimeTypes: { ...noTrimString, pattern: /^[a-z0-9][a-z0-9.+-]*\/[a-z0-9][a-z0-9.+-]*(?:,[a-z0-9][a-z0-9.+-]*\/[a-z0-9][a-z0-9.+-]*)*$/ },
24
- forceDownloadExtensions: { ...noTrimString, pattern: /^(|\.[a-z0-9]+(?:,\.[a-z0-9]+)*)$/ }
25
- },
26
- logger: { debug: boolInt, excludeFields: { type: "stringArray", noTrim: true } },
27
- mysql: {
28
- hostname: noTrimString,
29
- port: port,
30
- username: noTrimString,
31
- password: noTrimString,
32
- database: noTrimString,
33
- max: { type: "number", min: 1 },
34
- beflyMode: { type: "values", values: ["manual", "auto"], optional: true },
35
- idleTimeout: { type: "number", min: 1, optional: true },
36
- maxLifetime: { type: "number", min: 0, optional: true },
37
- connectionTimeout: { type: "number", min: 1, optional: true },
38
- queryTimeout: { type: "number", min: 1, optional: true }
39
- },
40
- redis: {
41
- hostname: noTrimString,
42
- port: port,
43
- username: noTrimString,
44
- password: noTrimString,
45
- db: { type: "number", integer: true, min: 0, max: 16 },
46
- prefix: { ...noTrimString, check: "redisPrefix" }
47
- },
48
- session: { expireDays: { type: "number", integer: true, min: 1 } },
49
- email: { host: noTrimString, port: port, secure: boolInt, user: noTrimString, pass: noTrimString, label: { ...noTrimString, optional: true } },
50
- cors: {
51
- methods: noTrimString,
52
- allowedHeaders: noTrimString,
53
- exposedHeaders: noTrimString,
54
- maxAge: { type: "number", integer: true, min: 0 },
55
- credentials: { type: "values", values: ["true", "false", true, false] }
56
- },
57
- rateLimit: {
58
- enable: boolInt,
59
- // 限流服务不可用时是否放行(1 放行 / 0 拒绝),默认 1
60
- failOpen: boolInt,
61
- defaultLimit: { type: "number", integer: true, min: 1 },
62
- defaultWindow: { type: "number", integer: true, min: 1 },
63
- key: noTrimString,
64
- skipRoutes: { type: "stringArray", noTrim: true }
65
- }
66
- }
67
- };
@@ -1,120 +0,0 @@
1
- import { mkdir } from "node:fs/promises";
2
- import { basename, resolve, join } from "node:path";
3
-
4
- import { BEFLY_TABLE_PREFIX } from "#befly/configs/constConfig.js";
5
- import { Connect } from "#befly/lib/connect.js";
6
- import { DbHelper } from "#befly/lib/dbHelper.js";
7
- import { Logger } from "#befly/lib/logger.js";
8
- import { createError } from "#befly/utils/error.js";
9
- import { importDefault } from "#befly/utils/importDefault.js";
10
- import { isNonEmptyString, isPlainObject } from "#befly/utils/is.js";
11
- import { camelCase } from "#befly/utils/util.js";
12
-
13
- import { buildSyncDbDiff, groupSyncDbColumns } from "./diff.js";
14
- import { printSyncDbProcessLog } from "./report.js";
15
-
16
- const SYNC_DB_COLUMNS_SQL = `
17
- SELECT
18
- c.TABLE_NAME AS tableName,
19
- t.TABLE_COMMENT AS tableComment,
20
- c.COLUMN_NAME AS columnName,
21
- c.DATA_TYPE AS dataType,
22
- c.COLUMN_TYPE AS columnType,
23
- c.CHARACTER_MAXIMUM_LENGTH AS charLength,
24
- c.COLUMN_COMMENT AS columnComment,
25
- c.ORDINAL_POSITION AS ordinalPosition
26
- FROM information_schema.COLUMNS AS c
27
- LEFT JOIN information_schema.TABLES AS t
28
- ON t.TABLE_SCHEMA = c.TABLE_SCHEMA
29
- AND t.TABLE_NAME = c.TABLE_NAME
30
- WHERE c.TABLE_SCHEMA = DATABASE()
31
- ORDER BY c.TABLE_NAME ASC, c.ORDINAL_POSITION ASC
32
- `;
33
-
34
- export async function prepareSyncDbBaseContext(mysqlConfig) {
35
- const tablesDir = resolve(join(process.cwd(), "tables"));
36
- await mkdir(tablesDir, { recursive: true });
37
- printSyncDbProcessLog(`表定义目录=${tablesDir}`);
38
-
39
- if (!isPlainObject(mysqlConfig)) {
40
- throw createError("syncDb 缺少 mysql 配置", { code: "validation", subsystem: "scripts", operation: "syncDb" });
41
- }
42
-
43
- printSyncDbProcessLog(`同步模式=${mysqlConfig.beflyMode === "manual" ? "手动" : "自动"}`);
44
- printSyncDbProcessLog(`连接 MySQL,数据库名=${mysqlConfig.database}`);
45
- await Connect.connectMysql(mysqlConfig);
46
- const mysql = new DbHelper({
47
- redis: null,
48
- dbName: mysqlConfig.database,
49
- sql: Connect.getMysql(),
50
- beflyMode: mysqlConfig.beflyMode
51
- });
52
-
53
- printSyncDbProcessLog("开始读取数据库字段信息");
54
- const queryRes = await mysql.execute(SYNC_DB_COLUMNS_SQL);
55
- const dbColumns = Array.isArray(queryRes.data) ? queryRes.data : [];
56
- printSyncDbProcessLog(`数据库字段读取完成,字段数量=${dbColumns.length}`);
57
-
58
- const skippedBeflyTables = new Set();
59
- for (const row of dbColumns) {
60
- const tableName = String(row?.tableName || "");
61
- if (isNonEmptyString(tableName) && tableName.toLowerCase().startsWith(BEFLY_TABLE_PREFIX)) {
62
- skippedBeflyTables.add(tableName);
63
- }
64
- }
65
- if (skippedBeflyTables.size > 0) {
66
- const skippedTables = Array.from(skippedBeflyTables).toSorted((left, right) => left.localeCompare(right));
67
- Logger.info("已跳过 befly_ 前缀表,不参与对比", {
68
- skippedTableCount: skippedBeflyTables.size,
69
- skippedTables: skippedTables
70
- });
71
- }
72
-
73
- const groupedDbColumns = groupSyncDbColumns(dbColumns);
74
- const existingTableMap = new Map();
75
- const glob = new Bun.Glob("*.json");
76
- const files = await glob.scan({
77
- cwd: tablesDir,
78
- onlyFiles: true,
79
- absolute: true,
80
- followSymlinks: true
81
- });
82
-
83
- for await (const filePath of files) {
84
- const tableFileName = basename(filePath, ".json");
85
- if (tableFileName.startsWith("_")) {
86
- continue;
87
- }
88
-
89
- const tableKey = String(camelCase(tableFileName)).toLowerCase();
90
- const loaded = await importDefault(filePath, {});
91
- if (!isPlainObject(loaded)) {
92
- Logger.warn("tables 定义文件不是对象,已按空对象处理", {
93
- filePath: filePath
94
- });
95
- existingTableMap.set(tableKey, {
96
- tableFileName: tableFileName,
97
- filePath: filePath,
98
- tableDef: {}
99
- });
100
- continue;
101
- }
102
-
103
- existingTableMap.set(tableKey, {
104
- tableFileName: tableFileName,
105
- filePath: filePath,
106
- tableDef: loaded
107
- });
108
- }
109
-
110
- const diff = buildSyncDbDiff(groupedDbColumns, existingTableMap, mysqlConfig.beflyMode);
111
- printSyncDbProcessLog(`表定义文件数量=${existingTableMap.size}`);
112
- printSyncDbProcessLog(`差异扫描完成,缺少表数量=${diff.missingTables.length},缺少字段数量=${diff.missingFieldCount},多余表数量=${diff.extraTables.length},多余字段数量=${diff.extraFieldCount}`);
113
-
114
- return {
115
- diff: diff,
116
- groupedDbColumns: groupedDbColumns,
117
- tablesDir: tablesDir,
118
- existingTableMap: existingTableMap
119
- };
120
- }