befly 3.52.0 → 3.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apis/auth/login.js +11 -12
- package/apis/email/send.js +2 -9
- package/apis/tongJi/errorReport.js +2 -16
- package/configs/beflyMenus.json +0 -5
- package/lib/smtpText.js +283 -0
- package/lib/xmlParse.js +173 -12
- package/package.json +1 -4
- package/plugins/email.js +5 -31
- package/scripts/syncDb/transform.js +4 -2
- package/utils/userAgent.js +172 -0
- package/apis/sysConfig/all.js +0 -16
- package/apis/sysConfig/delete.js +0 -36
- package/apis/sysConfig/get.js +0 -37
- package/apis/sysConfig/insert.js +0 -45
- package/apis/sysConfig/select.js +0 -30
- package/apis/sysConfig/update.js +0 -57
- package/tables/sysConfig.json +0 -68
package/apis/auth/login.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { UAParser } from "ua-parser-js";
|
|
2
|
-
|
|
3
1
|
import adminTable from "#befly/tables/admin.json";
|
|
2
|
+
import { parseUserAgent } from "#befly/utils/userAgent.js";
|
|
4
3
|
import { toSessionTtlSeconds } from "#befly/utils/util.js";
|
|
5
4
|
|
|
6
5
|
export default {
|
|
@@ -27,7 +26,7 @@ export default {
|
|
|
27
26
|
required: ["account", "password", "loginType"],
|
|
28
27
|
handler: async (befly, ctx) => {
|
|
29
28
|
const userAgent = ctx.req.headers.get("user-agent") || "";
|
|
30
|
-
const
|
|
29
|
+
const uaData = parseUserAgent(userAgent);
|
|
31
30
|
|
|
32
31
|
const logData = {
|
|
33
32
|
adminId: 0,
|
|
@@ -35,15 +34,15 @@ export default {
|
|
|
35
34
|
nickname: "",
|
|
36
35
|
ip: ctx.ip || "",
|
|
37
36
|
userAgent: userAgent.substring(0, 500),
|
|
38
|
-
browserName:
|
|
39
|
-
browserVersion:
|
|
40
|
-
osName:
|
|
41
|
-
osVersion:
|
|
42
|
-
deviceType:
|
|
43
|
-
deviceVendor:
|
|
44
|
-
deviceModel:
|
|
45
|
-
engineName:
|
|
46
|
-
cpuArchitecture:
|
|
37
|
+
browserName: uaData.browserName,
|
|
38
|
+
browserVersion: uaData.browserVersion,
|
|
39
|
+
osName: uaData.osName,
|
|
40
|
+
osVersion: uaData.osVersion,
|
|
41
|
+
deviceType: uaData.deviceType,
|
|
42
|
+
deviceVendor: uaData.deviceVendor,
|
|
43
|
+
deviceModel: uaData.deviceModel,
|
|
44
|
+
engineName: uaData.engineName,
|
|
45
|
+
cpuArchitecture: uaData.cpuArchitecture,
|
|
47
46
|
loginTime: Date.now(),
|
|
48
47
|
loginResult: 0,
|
|
49
48
|
failReason: ""
|
package/apis/email/send.js
CHANGED
|
@@ -11,13 +11,7 @@ export default {
|
|
|
11
11
|
subject: emailLogTable.subject,
|
|
12
12
|
content: emailLogTable.content,
|
|
13
13
|
cc: emailLogTable.ccEmail,
|
|
14
|
-
bcc: emailLogTable.bccEmail
|
|
15
|
-
isHtml: {
|
|
16
|
-
name: "是否HTML",
|
|
17
|
-
min: null,
|
|
18
|
-
max: null,
|
|
19
|
-
input: "string"
|
|
20
|
-
}
|
|
14
|
+
bcc: emailLogTable.bccEmail
|
|
21
15
|
},
|
|
22
16
|
required: ["to", "subject", "content"],
|
|
23
17
|
handler: async (befly, ctx) => {
|
|
@@ -36,8 +30,7 @@ export default {
|
|
|
36
30
|
const result = await befly.email.sendMail({
|
|
37
31
|
to: ctx.body.to,
|
|
38
32
|
subject: ctx.body.subject,
|
|
39
|
-
|
|
40
|
-
text: ctx.body.isHtml ? undefined : ctx.body.content,
|
|
33
|
+
text: ctx.body.content,
|
|
41
34
|
cc: ctx.body.cc,
|
|
42
35
|
bcc: ctx.body.bcc
|
|
43
36
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { UAParser } from "ua-parser-js";
|
|
2
|
-
|
|
3
1
|
import errorReportTable from "#befly/tables/errorReport.json";
|
|
4
2
|
import { getDateYmdNumber, getTimeBucketStart } from "#befly/utils/datetime.js";
|
|
3
|
+
import { parseUserAgent } from "#befly/utils/userAgent.js";
|
|
5
4
|
|
|
6
5
|
import { expireTongJiRedisKeys, getErrorStatsDayBucketCountKey, getErrorStatsDayBucketsKey, getErrorStatsDayTypeCountKey, getErrorStatsDayTypesKey, getErrorStatsPeriodCountKey, getTongJiMonthStartDate, getTongJiWeekStartDate } from "./_tongJi.js";
|
|
7
6
|
|
|
@@ -17,20 +16,7 @@ function getErrorReportUaData(ctx) {
|
|
|
17
16
|
userAgent = ctx.headers.get("user-agent") || "";
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
userAgent: userAgent,
|
|
24
|
-
browserName: uaResult.browser.name || "",
|
|
25
|
-
browserVersion: uaResult.browser.version || "",
|
|
26
|
-
osName: uaResult.os.name || "",
|
|
27
|
-
osVersion: uaResult.os.version || "",
|
|
28
|
-
deviceType: uaResult.device.type || "desktop",
|
|
29
|
-
deviceVendor: uaResult.device.vendor || "",
|
|
30
|
-
deviceModel: uaResult.device.model || "",
|
|
31
|
-
engineName: uaResult.engine.name || "",
|
|
32
|
-
cpuArchitecture: uaResult.cpu.architecture || ""
|
|
33
|
-
};
|
|
19
|
+
return parseUserAgent(userAgent);
|
|
34
20
|
}
|
|
35
21
|
|
|
36
22
|
async function updateErrorStatsRedis(befly, now, bucketDate, bucketTime, errorType) {
|
package/configs/beflyMenus.json
CHANGED
package/lib/smtpText.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
|
|
3
|
+
function encodeBase64(value) {
|
|
4
|
+
return Buffer.from(String(value || ""), "utf8").toString("base64");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function sanitizeHeaderValue(value) {
|
|
8
|
+
return String(value || "")
|
|
9
|
+
.replace(/[\r\n]+/g, " ")
|
|
10
|
+
.trim();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function encodeHeaderValue(value) {
|
|
14
|
+
const text = sanitizeHeaderValue(value);
|
|
15
|
+
if (/^[\x20-\x7E]*$/.test(text)) {
|
|
16
|
+
return text;
|
|
17
|
+
}
|
|
18
|
+
return `=?UTF-8?B?${encodeBase64(text)}?=`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeSecureValue(value) {
|
|
22
|
+
return value === true || value === 1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseAddressList(value) {
|
|
26
|
+
return String(value || "")
|
|
27
|
+
.split(/[;,]/)
|
|
28
|
+
.map(function (item) {
|
|
29
|
+
const trimmed = item.trim();
|
|
30
|
+
const matched = /<([^<>]+)>/.exec(trimmed);
|
|
31
|
+
return matched ? matched[1].trim() : trimmed;
|
|
32
|
+
})
|
|
33
|
+
.filter(function (item) {
|
|
34
|
+
return item.length > 0;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildAddressHeader(label, email) {
|
|
39
|
+
const cleanEmail = sanitizeHeaderValue(email);
|
|
40
|
+
if (!label) {
|
|
41
|
+
return cleanEmail;
|
|
42
|
+
}
|
|
43
|
+
return `${encodeHeaderValue(label)} <${cleanEmail}>`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function dotStuffText(value) {
|
|
47
|
+
return String(value || "")
|
|
48
|
+
.replace(/\r\n/g, "\n")
|
|
49
|
+
.replace(/\r/g, "\n")
|
|
50
|
+
.split("\n")
|
|
51
|
+
.map(function (line) {
|
|
52
|
+
return line.startsWith(".") ? `.${line}` : line;
|
|
53
|
+
})
|
|
54
|
+
.join("\r\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createMessageId(config) {
|
|
58
|
+
return `<${Date.now()}.${Math.random().toString(36).slice(2)}@${String(config.host || "localhost").replace(/[^a-zA-Z0-9.-]/g, "")}>`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createSmtpTextMessage(config, options) {
|
|
62
|
+
const toList = parseAddressList(options.to);
|
|
63
|
+
const ccList = parseAddressList(options.cc);
|
|
64
|
+
const bccList = parseAddressList(options.bcc);
|
|
65
|
+
const messageId = createMessageId(config);
|
|
66
|
+
const headers = [
|
|
67
|
+
`From: ${buildAddressHeader(config.label, config.user)}`,
|
|
68
|
+
`To: ${toList.join(", ")}`,
|
|
69
|
+
ccList.length > 0 ? `Cc: ${ccList.join(", ")}` : "",
|
|
70
|
+
`Subject: ${encodeHeaderValue(options.subject)}`,
|
|
71
|
+
`Message-ID: ${messageId}`,
|
|
72
|
+
"MIME-Version: 1.0",
|
|
73
|
+
"Content-Type: text/plain; charset=utf-8",
|
|
74
|
+
"Content-Transfer-Encoding: 8bit"
|
|
75
|
+
].filter(function (item) {
|
|
76
|
+
return item.length > 0;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
messageId: messageId,
|
|
81
|
+
recipients: toList.concat(ccList, bccList),
|
|
82
|
+
message: `${headers.join("\r\n")}\r\n\r\n${dotStuffText(options.text)}`
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function createResponseReader() {
|
|
87
|
+
const decoder = new TextDecoder();
|
|
88
|
+
const waiters = [];
|
|
89
|
+
let buffer = "";
|
|
90
|
+
let closedError = null;
|
|
91
|
+
|
|
92
|
+
function readCompleteResponse() {
|
|
93
|
+
const lines = buffer.split(/\r?\n/);
|
|
94
|
+
const completeLines = lines.slice(0, -1);
|
|
95
|
+
if (completeLines.length === 0) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const responseLines = [];
|
|
100
|
+
let consumedCount = 0;
|
|
101
|
+
let code = 0;
|
|
102
|
+
for (const line of completeLines) {
|
|
103
|
+
consumedCount = consumedCount + 1;
|
|
104
|
+
responseLines.push(line);
|
|
105
|
+
const matched = /^(\d{3})([ -])/.exec(line);
|
|
106
|
+
if (!matched) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
code = Number(matched[1]);
|
|
110
|
+
if (matched[2] === " ") {
|
|
111
|
+
buffer = lines.slice(consumedCount).join("\n");
|
|
112
|
+
return {
|
|
113
|
+
code: code,
|
|
114
|
+
lines: responseLines,
|
|
115
|
+
text: responseLines.join("\n")
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function flushWaiters() {
|
|
124
|
+
while (waiters.length > 0) {
|
|
125
|
+
if (closedError) {
|
|
126
|
+
waiters.shift().reject(closedError);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const response = readCompleteResponse();
|
|
131
|
+
if (!response) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
waiters.shift().resolve(response);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
append: function (data) {
|
|
140
|
+
buffer += decoder.decode(data, { stream: true });
|
|
141
|
+
flushWaiters();
|
|
142
|
+
},
|
|
143
|
+
close: function (error) {
|
|
144
|
+
closedError =
|
|
145
|
+
error ||
|
|
146
|
+
new Error("SMTP 连接已关闭", {
|
|
147
|
+
cause: null,
|
|
148
|
+
code: "runtime",
|
|
149
|
+
subsystem: "smtp",
|
|
150
|
+
operation: "readResponse"
|
|
151
|
+
});
|
|
152
|
+
flushWaiters();
|
|
153
|
+
},
|
|
154
|
+
read: function () {
|
|
155
|
+
return new Promise(function (resolve, reject) {
|
|
156
|
+
waiters.push({
|
|
157
|
+
resolve: resolve,
|
|
158
|
+
reject: reject
|
|
159
|
+
});
|
|
160
|
+
flushWaiters();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function readResponseWithTimeout(reader, timeout) {
|
|
167
|
+
let timer = null;
|
|
168
|
+
try {
|
|
169
|
+
return await Promise.race([
|
|
170
|
+
reader.read(),
|
|
171
|
+
new Promise(function (_resolve, reject) {
|
|
172
|
+
timer = setTimeout(function () {
|
|
173
|
+
reject(
|
|
174
|
+
new Error("SMTP 响应超时", {
|
|
175
|
+
cause: null,
|
|
176
|
+
code: "runtime",
|
|
177
|
+
subsystem: "smtp",
|
|
178
|
+
operation: "readResponse"
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
}, timeout);
|
|
182
|
+
})
|
|
183
|
+
]);
|
|
184
|
+
} finally {
|
|
185
|
+
clearTimeout(timer);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function expectResponse(reader, expectedCodes, commandName, timeout) {
|
|
190
|
+
const response = await readResponseWithTimeout(reader, timeout);
|
|
191
|
+
if (!expectedCodes.includes(response.code)) {
|
|
192
|
+
throw new Error(`SMTP ${commandName} 响应异常: ${response.text}`, {
|
|
193
|
+
cause: null,
|
|
194
|
+
code: "runtime",
|
|
195
|
+
subsystem: "smtp",
|
|
196
|
+
operation: commandName,
|
|
197
|
+
response: response.text
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return response;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function writeCommand(socket, command) {
|
|
204
|
+
socket.write(`${command}\r\n`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function sendSmtpTextMail(config, options) {
|
|
208
|
+
const mail = createSmtpTextMessage(config, options);
|
|
209
|
+
if (mail.recipients.length === 0) {
|
|
210
|
+
throw new Error("邮件收件人不能为空", {
|
|
211
|
+
cause: null,
|
|
212
|
+
code: "validation",
|
|
213
|
+
subsystem: "smtp",
|
|
214
|
+
operation: "sendSmtpTextMail"
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const reader = createResponseReader();
|
|
219
|
+
const socket = await Bun.connect({
|
|
220
|
+
hostname: config.host,
|
|
221
|
+
port: config.port || 25,
|
|
222
|
+
tls: normalizeSecureValue(config.secure),
|
|
223
|
+
socket: {
|
|
224
|
+
data: function (_socket, data) {
|
|
225
|
+
reader.append(data);
|
|
226
|
+
},
|
|
227
|
+
close: function (_socket, error) {
|
|
228
|
+
reader.close(error || null);
|
|
229
|
+
},
|
|
230
|
+
error: function (_socket, error) {
|
|
231
|
+
reader.close(error);
|
|
232
|
+
},
|
|
233
|
+
connectError: function (_socket, error) {
|
|
234
|
+
reader.close(error);
|
|
235
|
+
},
|
|
236
|
+
end: function () {
|
|
237
|
+
reader.close(null);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const timeout = 10000;
|
|
244
|
+
await expectResponse(reader, [220], "connect", timeout);
|
|
245
|
+
|
|
246
|
+
writeCommand(socket, "EHLO localhost");
|
|
247
|
+
await expectResponse(reader, [250], "ehlo", timeout);
|
|
248
|
+
|
|
249
|
+
writeCommand(socket, "AUTH LOGIN");
|
|
250
|
+
await expectResponse(reader, [334], "authUser", timeout);
|
|
251
|
+
|
|
252
|
+
writeCommand(socket, encodeBase64(config.user));
|
|
253
|
+
await expectResponse(reader, [334], "authPassword", timeout);
|
|
254
|
+
|
|
255
|
+
writeCommand(socket, encodeBase64(config.pass));
|
|
256
|
+
await expectResponse(reader, [235], "auth", timeout);
|
|
257
|
+
|
|
258
|
+
writeCommand(socket, `MAIL FROM:<${config.user}>`);
|
|
259
|
+
await expectResponse(reader, [250], "mailFrom", timeout);
|
|
260
|
+
|
|
261
|
+
for (const recipient of mail.recipients) {
|
|
262
|
+
writeCommand(socket, `RCPT TO:<${recipient}>`);
|
|
263
|
+
await expectResponse(reader, [250, 251], "rcptTo", timeout);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
writeCommand(socket, "DATA");
|
|
267
|
+
await expectResponse(reader, [354], "data", timeout);
|
|
268
|
+
|
|
269
|
+
socket.write(`${mail.message}\r\n.\r\n`);
|
|
270
|
+
const response = await expectResponse(reader, [250], "message", timeout);
|
|
271
|
+
|
|
272
|
+
writeCommand(socket, "QUIT");
|
|
273
|
+
socket.end();
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
messageId: mail.messageId,
|
|
277
|
+
response: response.text
|
|
278
|
+
};
|
|
279
|
+
} catch (error) {
|
|
280
|
+
socket.end();
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
package/lib/xmlParse.js
CHANGED
|
@@ -1,21 +1,170 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isString } from "#befly/utils/is.js";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function decodeXmlText(value) {
|
|
4
|
+
return String(value)
|
|
5
|
+
.replace(/</g, "<")
|
|
6
|
+
.replace(/>/g, ">")
|
|
7
|
+
.replace(/&/g, "&")
|
|
8
|
+
.replace(/'/g, "'")
|
|
9
|
+
.replace(/"/g, '"');
|
|
10
|
+
}
|
|
4
11
|
|
|
5
|
-
|
|
12
|
+
function normalizeXmlValue(value) {
|
|
13
|
+
const text = decodeXmlText(value.trim());
|
|
14
|
+
if (text === "") {
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
const numberValue = Number(text);
|
|
18
|
+
if (Number.isFinite(numberValue) && String(numberValue) === text) {
|
|
19
|
+
return numberValue;
|
|
20
|
+
}
|
|
21
|
+
return text;
|
|
22
|
+
}
|
|
6
23
|
|
|
7
|
-
function
|
|
8
|
-
if (
|
|
9
|
-
|
|
24
|
+
function assignNodeValue(target, key, value) {
|
|
25
|
+
if (Object.hasOwn(target, key)) {
|
|
26
|
+
if (Array.isArray(target[key])) {
|
|
27
|
+
target[key].push(value);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
target[key] = [target[key], value];
|
|
31
|
+
return;
|
|
10
32
|
}
|
|
11
33
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
34
|
+
target[key] = value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function skipIgnoredMarkup(rawXml, startIndex) {
|
|
38
|
+
let index = startIndex;
|
|
39
|
+
|
|
40
|
+
while (index < rawXml.length) {
|
|
41
|
+
while (/\s/.test(rawXml[index])) {
|
|
42
|
+
index += 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (rawXml.startsWith("<?", index)) {
|
|
46
|
+
const declarationEnd = rawXml.indexOf("?>", index);
|
|
47
|
+
if (declarationEnd < 0) {
|
|
48
|
+
throw new Error("XML 声明未闭合", {
|
|
49
|
+
cause: null,
|
|
50
|
+
code: "validation",
|
|
51
|
+
subsystem: "xml",
|
|
52
|
+
operation: "skipIgnoredMarkup"
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
index = declarationEnd + 2;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (rawXml.startsWith("<!--", index)) {
|
|
60
|
+
const commentEnd = rawXml.indexOf("-->", index);
|
|
61
|
+
if (commentEnd < 0) {
|
|
62
|
+
throw new Error("XML 注释未闭合", {
|
|
63
|
+
cause: null,
|
|
64
|
+
code: "validation",
|
|
65
|
+
subsystem: "xml",
|
|
66
|
+
operation: "skipIgnoredMarkup"
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
index = commentEnd + 3;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return index;
|
|
15
74
|
}
|
|
16
75
|
|
|
17
|
-
|
|
18
|
-
|
|
76
|
+
return index;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseNode(rawXml, startIndex) {
|
|
80
|
+
const openEnd = rawXml.indexOf(">", startIndex);
|
|
81
|
+
if (openEnd < 0) {
|
|
82
|
+
throw new Error("XML 开始标签不完整", {
|
|
83
|
+
cause: null,
|
|
84
|
+
code: "validation",
|
|
85
|
+
subsystem: "xml",
|
|
86
|
+
operation: "parseNode"
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const openContent = rawXml.slice(startIndex + 1, openEnd).trim();
|
|
91
|
+
const isSelfClosing = openContent.endsWith("/");
|
|
92
|
+
const tagName = (isSelfClosing ? openContent.slice(0, -1).trim() : openContent).split(/\s+/)[0];
|
|
93
|
+
if (!tagName || tagName.startsWith("/")) {
|
|
94
|
+
throw new Error("XML 标签格式不正确", {
|
|
95
|
+
cause: null,
|
|
96
|
+
code: "validation",
|
|
97
|
+
subsystem: "xml",
|
|
98
|
+
operation: "parseNode"
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (isSelfClosing) {
|
|
102
|
+
return {
|
|
103
|
+
key: tagName,
|
|
104
|
+
value: "",
|
|
105
|
+
index: openEnd + 1
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let index = openEnd + 1;
|
|
110
|
+
let text = "";
|
|
111
|
+
const children = {};
|
|
112
|
+
let hasChild = false;
|
|
113
|
+
|
|
114
|
+
while (index < rawXml.length) {
|
|
115
|
+
index = skipIgnoredMarkup(rawXml, index);
|
|
116
|
+
|
|
117
|
+
if (rawXml.startsWith("<![CDATA[", index)) {
|
|
118
|
+
const cdataEnd = rawXml.indexOf("]]>", index);
|
|
119
|
+
if (cdataEnd < 0) {
|
|
120
|
+
throw new Error("XML CDATA 未闭合", {
|
|
121
|
+
cause: null,
|
|
122
|
+
code: "validation",
|
|
123
|
+
subsystem: "xml",
|
|
124
|
+
operation: "parseNode"
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
text += rawXml.slice(index + 9, cdataEnd);
|
|
128
|
+
index = cdataEnd + 3;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (rawXml.startsWith(`</${tagName}>`, index)) {
|
|
133
|
+
index += tagName.length + 3;
|
|
134
|
+
return {
|
|
135
|
+
key: tagName,
|
|
136
|
+
value: hasChild ? children : normalizeXmlValue(text),
|
|
137
|
+
index: index
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (rawXml[index] === "<") {
|
|
142
|
+
const child = parseNode(rawXml, index);
|
|
143
|
+
hasChild = true;
|
|
144
|
+
assignNodeValue(children, child.key, child.value);
|
|
145
|
+
index = child.index;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const nextTagIndex = rawXml.indexOf("<", index);
|
|
150
|
+
if (nextTagIndex < 0) {
|
|
151
|
+
throw new Error("XML 结束标签缺失", {
|
|
152
|
+
cause: null,
|
|
153
|
+
code: "validation",
|
|
154
|
+
subsystem: "xml",
|
|
155
|
+
operation: "parseNode"
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
text += rawXml.slice(index, nextTagIndex);
|
|
159
|
+
index = nextTagIndex;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
throw new Error("XML 标签未闭合", {
|
|
163
|
+
cause: null,
|
|
164
|
+
code: "validation",
|
|
165
|
+
subsystem: "xml",
|
|
166
|
+
operation: "parseNode"
|
|
167
|
+
});
|
|
19
168
|
}
|
|
20
169
|
|
|
21
170
|
export function xmlParse(value) {
|
|
@@ -28,5 +177,17 @@ export function xmlParse(value) {
|
|
|
28
177
|
});
|
|
29
178
|
}
|
|
30
179
|
|
|
31
|
-
|
|
180
|
+
const normalized = value.trim();
|
|
181
|
+
const startIndex = skipIgnoredMarkup(normalized, 0);
|
|
182
|
+
if (normalized[startIndex] !== "<") {
|
|
183
|
+
throw new Error("XML 根节点缺失", {
|
|
184
|
+
cause: null,
|
|
185
|
+
code: "validation",
|
|
186
|
+
subsystem: "xml",
|
|
187
|
+
operation: "xmlParse"
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const node = parseNode(normalized, startIndex);
|
|
192
|
+
return node.key === "xml" && Object.prototype.toString.call(node.value) === "[object Object]" ? node.value : { [node.key]: node.value };
|
|
32
193
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.54.0",
|
|
4
4
|
"gitHead": "49c39d36695036e85fc64083cc43c1652fff96cb",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
|
|
@@ -55,11 +55,8 @@
|
|
|
55
55
|
"test": "bun test"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"fast-xml-parser": "^5.8.0",
|
|
59
|
-
"nodemailer": "^8.0.10",
|
|
60
58
|
"pathe": "^2.0.3",
|
|
61
59
|
"picomatch": "^4.0.4",
|
|
62
|
-
"ua-parser-js": "^2.0.10",
|
|
63
60
|
"zod": "^4.4.3"
|
|
64
61
|
},
|
|
65
62
|
"engines": {
|
package/plugins/email.js
CHANGED
|
@@ -1,38 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 邮件插件
|
|
3
|
-
* 提供邮件发送功能,支持 SMTP 配置
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import nodemailer from "nodemailer";
|
|
7
|
-
|
|
8
1
|
import { Logger } from "#befly/lib/logger.js";
|
|
2
|
+
import { sendSmtpTextMail } from "#befly/lib/smtpText.js";
|
|
9
3
|
import { hasEmailConfig } from "#befly/utils/email.js";
|
|
10
4
|
|
|
11
5
|
export default {
|
|
12
6
|
order: 7,
|
|
13
7
|
handler: async function (befly) {
|
|
14
8
|
const config = befly?.config?.email || {};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (hasEmailConfig(config)) {
|
|
18
|
-
try {
|
|
19
|
-
transporter = nodemailer.createTransport({
|
|
20
|
-
host: config.host,
|
|
21
|
-
port: config.port || 25,
|
|
22
|
-
secure: config.secure ?? config.ssl,
|
|
23
|
-
auth: {
|
|
24
|
-
user: config.user,
|
|
25
|
-
pass: config.pass
|
|
26
|
-
},
|
|
27
|
-
connectionTimeout: config.timeout || 10000
|
|
28
|
-
});
|
|
29
|
-
} catch (error) {
|
|
30
|
-
Logger.warn("邮件服务初始化失败", { err: error });
|
|
31
|
-
}
|
|
32
|
-
}
|
|
9
|
+
const enabled = hasEmailConfig(config);
|
|
33
10
|
|
|
34
11
|
const sendMail = async function (options) {
|
|
35
|
-
if (!
|
|
12
|
+
if (!enabled) {
|
|
36
13
|
Logger.warn("邮件未配置,已禁用发送");
|
|
37
14
|
return {
|
|
38
15
|
success: false,
|
|
@@ -41,15 +18,12 @@ export default {
|
|
|
41
18
|
}
|
|
42
19
|
|
|
43
20
|
try {
|
|
44
|
-
const info = await
|
|
45
|
-
text: options.text,
|
|
46
|
-
html: options.html,
|
|
47
|
-
from: options.from || (config.label ? `${config.label} <${config.user}>` : config.user),
|
|
21
|
+
const info = await sendSmtpTextMail(config, {
|
|
48
22
|
to: options.to,
|
|
49
23
|
cc: options.cc,
|
|
50
24
|
bcc: options.bcc,
|
|
51
25
|
subject: options.subject,
|
|
52
|
-
|
|
26
|
+
text: options.text || ""
|
|
53
27
|
});
|
|
54
28
|
return {
|
|
55
29
|
success: true,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DECIMAL_KIND_TYPES, ENUM_KIND_TYPES, FLOAT_KIND_TYPES, INT_KIND_TYPES, JSON_KIND_TYPES, STRING_KIND_TYPES, TEXT_KIND_TYPES } from "#befly/configs/constConfig.js";
|
|
2
2
|
import { isNonEmptyString, isNumber } from "#befly/utils/is.js";
|
|
3
|
-
import {
|
|
3
|
+
import { camelCase, snakeCase } from "#befly/utils/util.js";
|
|
4
4
|
|
|
5
5
|
function resolveSyncDbEnumInput(columnType) {
|
|
6
6
|
const matched = /^enum\((.*)\)$/.exec(columnType);
|
|
@@ -90,7 +90,9 @@ function resolveSyncDbMaxByColumn(columnMeta) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
export function toSyncDbFieldDef(columnMeta) {
|
|
93
|
-
const
|
|
93
|
+
const camelFieldName = camelCase(columnMeta.columnName);
|
|
94
|
+
const columnName = String(columnMeta.columnName || "");
|
|
95
|
+
const fieldName = snakeCase(camelFieldName) === columnName ? camelFieldName : columnName;
|
|
94
96
|
const fieldDisplayName = isNonEmptyString(columnMeta.columnComment) ? String(columnMeta.columnComment).trim() : fieldName;
|
|
95
97
|
const fieldInput = resolveSyncDbInputByColumn(columnMeta);
|
|
96
98
|
const fieldMax = resolveSyncDbMaxByColumn(columnMeta);
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const WINDOWS_VERSION_MAP = {
|
|
2
|
+
"10.0": "10",
|
|
3
|
+
6.3: "8.1",
|
|
4
|
+
6.2: "8",
|
|
5
|
+
6.1: "7"
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
function getMatchedVersion(userAgent, regexp) {
|
|
9
|
+
const matched = regexp.exec(userAgent);
|
|
10
|
+
return matched ? matched[1].replace(/_/g, ".") : "";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getBrowserData(userAgent) {
|
|
14
|
+
const browserRules = [
|
|
15
|
+
["WeChat", /MicroMessenger\/([\d.]+)/],
|
|
16
|
+
["Edge", /Edg(?:e|A|iOS)?\/([\d.]+)/],
|
|
17
|
+
["Opera", /OPR\/([\d.]+)/],
|
|
18
|
+
["Firefox", /FxiOS\/([\d.]+)|Firefox\/([\d.]+)/],
|
|
19
|
+
["Chrome", /CriOS\/([\d.]+)|Chrome\/([\d.]+)/],
|
|
20
|
+
["Safari", /Version\/([\d.]+).*Safari/],
|
|
21
|
+
["IE", /MSIE\s([\d.]+)|Trident\/.*rv:([\d.]+)/]
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
for (const [name, regexp] of browserRules) {
|
|
25
|
+
const matched = regexp.exec(userAgent);
|
|
26
|
+
if (matched) {
|
|
27
|
+
return {
|
|
28
|
+
browserName: name === "Safari" && /iPhone|iPad|iPod/i.test(userAgent) ? "Mobile Safari" : name,
|
|
29
|
+
browserVersion: matched[1] || matched[2] || ""
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
browserName: "",
|
|
36
|
+
browserVersion: ""
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getOsData(userAgent) {
|
|
41
|
+
const androidVersion = getMatchedVersion(userAgent, /Android\s([\d.]+)/);
|
|
42
|
+
if (androidVersion) {
|
|
43
|
+
return {
|
|
44
|
+
osName: "Android",
|
|
45
|
+
osVersion: androidVersion
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const iosVersion = getMatchedVersion(userAgent, /(?:iPhone|iPad|iPod).*OS\s([\d_]+)/);
|
|
50
|
+
if (iosVersion) {
|
|
51
|
+
return {
|
|
52
|
+
osName: "iOS",
|
|
53
|
+
osVersion: iosVersion
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const windowsVersion = getMatchedVersion(userAgent, /Windows NT\s([\d.]+)/);
|
|
58
|
+
if (windowsVersion) {
|
|
59
|
+
return {
|
|
60
|
+
osName: "Windows",
|
|
61
|
+
osVersion: WINDOWS_VERSION_MAP[windowsVersion] || windowsVersion
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const macVersion = getMatchedVersion(userAgent, /Mac OS X\s([\d_]+)/);
|
|
66
|
+
if (macVersion) {
|
|
67
|
+
return {
|
|
68
|
+
osName: "macOS",
|
|
69
|
+
osVersion: macVersion
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (/Linux/i.test(userAgent)) {
|
|
74
|
+
return {
|
|
75
|
+
osName: "Linux",
|
|
76
|
+
osVersion: ""
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
osName: "",
|
|
82
|
+
osVersion: ""
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getDeviceType(userAgent) {
|
|
87
|
+
if (/iPad|Tablet|PlayBook|Silk/i.test(userAgent) || (/Android/i.test(userAgent) && !/Mobile/i.test(userAgent))) {
|
|
88
|
+
return "tablet";
|
|
89
|
+
}
|
|
90
|
+
if (/Mobi|iPhone|iPod|Android|Windows Phone/i.test(userAgent)) {
|
|
91
|
+
return "mobile";
|
|
92
|
+
}
|
|
93
|
+
return "desktop";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getAndroidDeviceModel(userAgent) {
|
|
97
|
+
const matched = /Android[\d.\s]*;\s*([^;)]+?)(?:\s+Build\/|;|\))/i.exec(userAgent);
|
|
98
|
+
return matched ? matched[1].trim() : "";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getDeviceData(userAgent) {
|
|
102
|
+
const deviceType = getDeviceType(userAgent);
|
|
103
|
+
if (/iPhone/i.test(userAgent)) {
|
|
104
|
+
return {
|
|
105
|
+
deviceType: deviceType,
|
|
106
|
+
deviceVendor: "Apple",
|
|
107
|
+
deviceModel: "iPhone"
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (/iPad/i.test(userAgent)) {
|
|
111
|
+
return {
|
|
112
|
+
deviceType: deviceType,
|
|
113
|
+
deviceVendor: "Apple",
|
|
114
|
+
deviceModel: "iPad"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
deviceType: deviceType,
|
|
120
|
+
deviceVendor: "",
|
|
121
|
+
deviceModel: getAndroidDeviceModel(userAgent)
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function getEngineName(userAgent) {
|
|
126
|
+
if (/Chrome|CriOS|Edg|OPR/i.test(userAgent)) {
|
|
127
|
+
return "Blink";
|
|
128
|
+
}
|
|
129
|
+
if (/Firefox|FxiOS/i.test(userAgent)) {
|
|
130
|
+
return "Gecko";
|
|
131
|
+
}
|
|
132
|
+
if (/AppleWebKit|Safari/i.test(userAgent)) {
|
|
133
|
+
return "WebKit";
|
|
134
|
+
}
|
|
135
|
+
if (/Trident|MSIE/i.test(userAgent)) {
|
|
136
|
+
return "Trident";
|
|
137
|
+
}
|
|
138
|
+
return "";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getCpuArchitecture(userAgent) {
|
|
142
|
+
if (/x86_64|Win64|x64|amd64/i.test(userAgent)) {
|
|
143
|
+
return "amd64";
|
|
144
|
+
}
|
|
145
|
+
if (/arm64|aarch64/i.test(userAgent)) {
|
|
146
|
+
return "arm64";
|
|
147
|
+
}
|
|
148
|
+
if (/arm/i.test(userAgent)) {
|
|
149
|
+
return "arm";
|
|
150
|
+
}
|
|
151
|
+
return "";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function parseUserAgent(userAgent) {
|
|
155
|
+
const text = String(userAgent || "");
|
|
156
|
+
const browserData = getBrowserData(text);
|
|
157
|
+
const osData = getOsData(text);
|
|
158
|
+
const deviceData = getDeviceData(text);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
userAgent: text,
|
|
162
|
+
browserName: browserData.browserName,
|
|
163
|
+
browserVersion: browserData.browserVersion,
|
|
164
|
+
osName: osData.osName,
|
|
165
|
+
osVersion: osData.osVersion,
|
|
166
|
+
deviceType: deviceData.deviceType,
|
|
167
|
+
deviceVendor: deviceData.deviceVendor,
|
|
168
|
+
deviceModel: deviceData.deviceModel,
|
|
169
|
+
engineName: getEngineName(text),
|
|
170
|
+
cpuArchitecture: getCpuArchitecture(text)
|
|
171
|
+
};
|
|
172
|
+
}
|
package/apis/sysConfig/all.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
name: "获取所有系统配置",
|
|
3
|
-
method: "POST",
|
|
4
|
-
body: "none",
|
|
5
|
-
auth: true,
|
|
6
|
-
fields: {},
|
|
7
|
-
required: [],
|
|
8
|
-
handler: async (befly) => {
|
|
9
|
-
const result = await befly.mysql.getAll({
|
|
10
|
-
table: "beflySysConfig",
|
|
11
|
-
orderBy: ["id#ASC"]
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
return befly.tool.Yes("操作成功", { lists: result.data.lists });
|
|
15
|
-
}
|
|
16
|
-
};
|
package/apis/sysConfig/delete.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
name: "删除系统配置",
|
|
3
|
-
method: "POST",
|
|
4
|
-
body: "none",
|
|
5
|
-
auth: true,
|
|
6
|
-
fields: {
|
|
7
|
-
id: { name: "ID", input: "integer", min: 1, max: null }
|
|
8
|
-
},
|
|
9
|
-
required: ["id"],
|
|
10
|
-
handler: async (befly, ctx) => {
|
|
11
|
-
try {
|
|
12
|
-
const config = await befly.mysql.getOne({
|
|
13
|
-
table: "beflySysConfig",
|
|
14
|
-
where: { id: ctx.body.id }
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
if (!config.data?.id) {
|
|
18
|
-
return befly.tool.No("配置不存在");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (config.data.isSystem === 1) {
|
|
22
|
-
return befly.tool.No("系统配置不允许删除");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
await befly.mysql.delData({
|
|
26
|
-
table: "beflySysConfig",
|
|
27
|
-
where: { id: ctx.body.id }
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
return befly.tool.Yes("操作成功");
|
|
31
|
-
} catch (error) {
|
|
32
|
-
befly.logger.error("删除系统配置失败", error);
|
|
33
|
-
return befly.tool.No("操作失败");
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
};
|
package/apis/sysConfig/get.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import sysConfigTable from "#befly/tables/sysConfig.json";
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
name: "根据代码获取配置值",
|
|
5
|
-
method: "POST",
|
|
6
|
-
body: "none",
|
|
7
|
-
auth: false,
|
|
8
|
-
fields: {
|
|
9
|
-
code: sysConfigTable.code
|
|
10
|
-
},
|
|
11
|
-
required: ["code"],
|
|
12
|
-
handler: async (befly, ctx) => {
|
|
13
|
-
const config = await befly.mysql.getOne({
|
|
14
|
-
table: "beflySysConfig",
|
|
15
|
-
where: { code: ctx.body.code }
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
if (!config.data?.id) {
|
|
19
|
-
return befly.tool.No("配置不存在");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let value = config.data.value;
|
|
23
|
-
if (config.data.valueType === "number") {
|
|
24
|
-
value = Number(config.data.value);
|
|
25
|
-
} else if (config.data.valueType === "boolean") {
|
|
26
|
-
value = config.data.value === "true" || config.data.value === "1";
|
|
27
|
-
} else if (config.data.valueType === "json") {
|
|
28
|
-
try {
|
|
29
|
-
value = JSON.parse(config.data.value);
|
|
30
|
-
} catch {
|
|
31
|
-
value = config.data.value;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return befly.tool.Yes("操作成功", { code: config.data.code, value: value });
|
|
36
|
-
}
|
|
37
|
-
};
|
package/apis/sysConfig/insert.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import sysConfigTable from "#befly/tables/sysConfig.json";
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
name: "添加系统配置",
|
|
5
|
-
method: "POST",
|
|
6
|
-
body: "none",
|
|
7
|
-
auth: true,
|
|
8
|
-
fields: {
|
|
9
|
-
name: sysConfigTable.name,
|
|
10
|
-
code: sysConfigTable.code,
|
|
11
|
-
value: sysConfigTable.value,
|
|
12
|
-
valueType: sysConfigTable.valueType,
|
|
13
|
-
group: sysConfigTable.group,
|
|
14
|
-
sort: sysConfigTable.sort,
|
|
15
|
-
isSystem: sysConfigTable.isSystem,
|
|
16
|
-
description: sysConfigTable.description
|
|
17
|
-
},
|
|
18
|
-
required: ["name", "code", "value"],
|
|
19
|
-
handler: async (befly, ctx) => {
|
|
20
|
-
const existing = await befly.mysql.getOne({
|
|
21
|
-
table: "beflySysConfig",
|
|
22
|
-
where: { code: ctx.body.code }
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
if (existing.data?.id) {
|
|
26
|
-
return befly.tool.No("配置代码已存在");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const configId = await befly.mysql.insData({
|
|
30
|
-
table: "beflySysConfig",
|
|
31
|
-
data: {
|
|
32
|
-
name: ctx.body.name,
|
|
33
|
-
code: ctx.body.code,
|
|
34
|
-
value: ctx.body.value,
|
|
35
|
-
valueType: ctx.body.valueType === undefined ? "string" : ctx.body.valueType,
|
|
36
|
-
group: ctx.body.group === undefined ? "" : ctx.body.group,
|
|
37
|
-
sort: ctx.body.sort === undefined ? 0 : ctx.body.sort,
|
|
38
|
-
isSystem: ctx.body.isSystem === undefined ? 0 : ctx.body.isSystem,
|
|
39
|
-
description: ctx.body.description === undefined ? "" : ctx.body.description
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
return befly.tool.Yes("操作成功", { id: configId.data });
|
|
44
|
-
}
|
|
45
|
-
};
|
package/apis/sysConfig/select.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { queryFields } from "#befly/apis/_apis.js";
|
|
2
|
-
import sysConfigTable from "#befly/tables/sysConfig.json";
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
name: "获取系统配置列表",
|
|
6
|
-
method: "POST",
|
|
7
|
-
body: "none",
|
|
8
|
-
auth: true,
|
|
9
|
-
fields: {
|
|
10
|
-
...queryFields,
|
|
11
|
-
state: sysConfigTable.state
|
|
12
|
-
},
|
|
13
|
-
required: [],
|
|
14
|
-
handler: async (befly, ctx) => {
|
|
15
|
-
const result = await befly.mysql.getList({
|
|
16
|
-
table: "beflySysConfig",
|
|
17
|
-
where: {
|
|
18
|
-
name$like$or: ctx.body.keyword,
|
|
19
|
-
code$like$or: ctx.body.keyword,
|
|
20
|
-
group$like$or: ctx.body.keyword,
|
|
21
|
-
state: ctx.body.state
|
|
22
|
-
},
|
|
23
|
-
page: ctx.body.page,
|
|
24
|
-
limit: ctx.body.limit,
|
|
25
|
-
orderBy: ["group#ASC", "sort#ASC", "id#ASC"]
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
return befly.tool.Yes("操作成功", result.data);
|
|
29
|
-
}
|
|
30
|
-
};
|
package/apis/sysConfig/update.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import sysConfigTable from "#befly/tables/sysConfig.json";
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
name: "更新系统配置",
|
|
5
|
-
method: "POST",
|
|
6
|
-
body: "none",
|
|
7
|
-
auth: true,
|
|
8
|
-
fields: {
|
|
9
|
-
id: sysConfigTable.id,
|
|
10
|
-
name: sysConfigTable.name,
|
|
11
|
-
code: sysConfigTable.code,
|
|
12
|
-
value: sysConfigTable.value,
|
|
13
|
-
valueType: sysConfigTable.valueType,
|
|
14
|
-
group: sysConfigTable.group,
|
|
15
|
-
sort: sysConfigTable.sort,
|
|
16
|
-
description: sysConfigTable.description,
|
|
17
|
-
state: sysConfigTable.state
|
|
18
|
-
},
|
|
19
|
-
required: ["id"],
|
|
20
|
-
handler: async (befly, ctx) => {
|
|
21
|
-
const config = await befly.mysql.getOne({
|
|
22
|
-
table: "beflySysConfig",
|
|
23
|
-
where: { id: ctx.body.id }
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
if (!config.data?.id) {
|
|
27
|
-
return befly.tool.No("配置不存在");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (config.data.isSystem === 1) {
|
|
31
|
-
await befly.mysql.updData({
|
|
32
|
-
table: "beflySysConfig",
|
|
33
|
-
data: {
|
|
34
|
-
value: ctx.body.value
|
|
35
|
-
},
|
|
36
|
-
where: { id: ctx.body.id }
|
|
37
|
-
});
|
|
38
|
-
} else {
|
|
39
|
-
await befly.mysql.updData({
|
|
40
|
-
table: "beflySysConfig",
|
|
41
|
-
data: {
|
|
42
|
-
name: ctx.body.name,
|
|
43
|
-
code: ctx.body.code,
|
|
44
|
-
value: ctx.body.value,
|
|
45
|
-
valueType: ctx.body.valueType,
|
|
46
|
-
group: ctx.body.group,
|
|
47
|
-
sort: ctx.body.sort,
|
|
48
|
-
description: ctx.body.description,
|
|
49
|
-
state: ctx.body.state
|
|
50
|
-
},
|
|
51
|
-
where: { id: ctx.body.id }
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return befly.tool.Yes("操作成功");
|
|
56
|
-
}
|
|
57
|
-
};
|
package/tables/sysConfig.json
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": {
|
|
3
|
-
"name": "ID",
|
|
4
|
-
"input": "integer",
|
|
5
|
-
"min": 1,
|
|
6
|
-
"max": null
|
|
7
|
-
},
|
|
8
|
-
"name": {
|
|
9
|
-
"name": "配置名称",
|
|
10
|
-
"min": 2,
|
|
11
|
-
"max": 50,
|
|
12
|
-
"input": "string"
|
|
13
|
-
},
|
|
14
|
-
"code": {
|
|
15
|
-
"name": "配置代码",
|
|
16
|
-
"input": "regexp",
|
|
17
|
-
"check": "@alphanumeric_",
|
|
18
|
-
"min": 2,
|
|
19
|
-
"max": 100
|
|
20
|
-
},
|
|
21
|
-
"value": {
|
|
22
|
-
"name": "配置值",
|
|
23
|
-
"input": "string"
|
|
24
|
-
},
|
|
25
|
-
"valueType": {
|
|
26
|
-
"name": "值类型",
|
|
27
|
-
"input": "enum",
|
|
28
|
-
"check": "string|number|boolean|json",
|
|
29
|
-
"max": 20
|
|
30
|
-
},
|
|
31
|
-
"group": {
|
|
32
|
-
"name": "配置分组",
|
|
33
|
-
"input": "string",
|
|
34
|
-
"max": 50
|
|
35
|
-
},
|
|
36
|
-
"sort": {
|
|
37
|
-
"name": "排序",
|
|
38
|
-
"input": "number",
|
|
39
|
-
"max": 9999
|
|
40
|
-
},
|
|
41
|
-
"isSystem": {
|
|
42
|
-
"name": "是否系统配置",
|
|
43
|
-
"input": "number",
|
|
44
|
-
"max": 1
|
|
45
|
-
},
|
|
46
|
-
"description": {
|
|
47
|
-
"name": "描述说明",
|
|
48
|
-
"input": "string",
|
|
49
|
-
"max": 500
|
|
50
|
-
},
|
|
51
|
-
"state": {
|
|
52
|
-
"name": "状态(0=已删除,1=正常,2=禁用)",
|
|
53
|
-
"input": "enumInteger",
|
|
54
|
-
"check": "0|1|2"
|
|
55
|
-
},
|
|
56
|
-
"createdAt": {
|
|
57
|
-
"name": "创建时间",
|
|
58
|
-
"input": "number"
|
|
59
|
-
},
|
|
60
|
-
"updatedAt": {
|
|
61
|
-
"name": "更新时间",
|
|
62
|
-
"input": "number"
|
|
63
|
-
},
|
|
64
|
-
"deletedAt": {
|
|
65
|
-
"name": "删除时间",
|
|
66
|
-
"input": "number"
|
|
67
|
-
}
|
|
68
|
-
}
|