befly 3.76.0 → 3.76.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/Befly.js +159 -0
- package/README.md +50 -11
- package/apis/admin/delete.js +1 -3
- package/apis/admin/insert.js +2 -1
- package/apis/admin/select.js +2 -7
- package/apis/admin/update.js +3 -8
- package/apis/api/select.js +2 -7
- package/apis/auth/login.js +16 -20
- package/apis/dict/delete.js +1 -1
- package/apis/dict/detail.js +1 -3
- package/apis/dict/select.js +1 -2
- package/apis/dict/update.js +1 -1
- package/apis/dictType/delete.js +1 -1
- package/apis/dictType/detail.js +1 -1
- package/apis/dictType/insert.js +1 -1
- package/apis/dictType/select.js +2 -7
- package/apis/dictType/update.js +2 -2
- package/apis/email/config.js +4 -7
- package/apis/email/logList.js +2 -7
- package/apis/email/send.js +1 -2
- package/apis/email/verify.js +1 -3
- package/apis/loginLog/select.js +2 -7
- package/apis/menu/select.js +2 -7
- package/apis/operateLog/select.js +2 -7
- package/apis/role/apiSave.js +2 -2
- package/apis/role/menuSave.js +2 -2
- package/apis/role/select.js +2 -7
- package/apis/source/imageList.js +2 -5
- package/apis/tongJi/dailyReport.js +12 -13
- package/apis/tongJi/dailyReportSelect.js +1 -2
- package/apis/tongJi/dailyStats.js +1 -1
- package/apis/tongJi/dailyStatsDistribution.js +2 -2
- package/apis/tongJi/errorList.js +1 -2
- package/apis/tongJi/errorReport.js +1 -2
- package/apis/tongJi/projectDelete.js +1 -3
- package/apis/tongJi/projectSelect.js +2 -4
- package/apis/tongJi/projectUpdate.js +2 -2
- package/apis/upload/file.js +7 -5
- package/checks/api.js +43 -66
- package/checks/config.js +17 -12
- package/checks/corns.js +28 -46
- package/checks/field.js +18 -0
- package/checks/items.js +16 -60
- package/checks/menu.js +28 -39
- package/checks/table.js +106 -26
- package/checks/validation.js +13 -0
- package/configs/beflyConfig.json +6 -6
- package/configs/constConfig.js +2 -63
- package/exports.js +2 -0
- package/hooks/auth.js +11 -7
- package/hooks/parser.js +55 -59
- package/hooks/permission.js +5 -2
- package/hooks/rateLimit.js +25 -62
- package/hooks/validator.js +43 -31
- package/index.js +43 -344
- package/{lib → libs}/cacheHelper.js +3 -8
- package/libs/cron.js +60 -0
- package/libs/logger/index.js +1 -0
- package/libs/logger/logger.js +245 -0
- package/libs/logger/sanitize.js +115 -0
- package/{lib → libs/mysql}/dbHelper.js +100 -73
- package/{lib → libs/mysql}/dbParse.js +16 -9
- package/{lib → libs/mysql}/dbUtil.js +15 -32
- package/libs/mysql/error.js +29 -0
- package/libs/mysql/index.js +6 -0
- package/libs/mysql/is.js +29 -0
- package/libs/mysql/mysql.js +43 -0
- package/libs/mysql/sql/identifier.js +85 -0
- package/libs/mysql/sql/index.js +2 -0
- package/{lib → libs/mysql/sql}/sqlBuilder.js +2 -3
- package/libs/mysql/util.js +44 -0
- package/libs/redis/index.js +2 -0
- package/libs/redis/redis.js +204 -0
- package/libs/redis/redisError.js +8 -0
- package/libs/tableModel.js +72 -0
- package/libs/tool.js +33 -0
- package/libs/validator/compiler.js +248 -0
- package/libs/validator/index.js +10 -0
- package/libs/validator/parser.js +173 -0
- package/libs/validator/util.js +5 -0
- package/package.json +10 -8
- package/paths.js +2 -3
- package/plugins/cache.js +2 -1
- package/plugins/email.js +4 -4
- package/plugins/mysql.js +5 -15
- package/plugins/redis.js +4 -12
- package/router/api.js +14 -10
- package/router/root.js +14 -0
- package/router/static.js +3 -3
- package/schemas/api.json +99 -0
- package/schemas/config.json +255 -0
- package/schemas/corn.json +39 -0
- package/schemas/item.json +43 -0
- package/schemas/menu.json +44 -0
- package/schemas/table.json +80 -0
- package/scripts/syncDb/apply.js +87 -0
- package/scripts/syncDb/ddl.js +43 -0
- package/scripts/syncDb/index.js +175 -30
- package/scripts/syncDb/model.js +60 -0
- package/scripts/syncDb/plan.js +160 -0
- package/scripts/syncDb/preflight.js +98 -0
- package/scripts/syncDb/report.js +177 -162
- package/scripts/syncDb/snapshot.js +118 -0
- package/sync/syncUtil.js +7 -13
- package/tables/admin.json +51 -53
- package/tables/api.json +29 -40
- package/tables/dailyReport.json +85 -73
- package/tables/dict.json +27 -39
- package/tables/dictType.json +20 -34
- package/tables/emailLog.json +49 -47
- package/tables/errorReport.json +86 -60
- package/tables/file.json +47 -50
- package/tables/loginLog.json +68 -55
- package/tables/menu.json +20 -34
- package/tables/operateLog.json +53 -47
- package/tables/project.json +18 -30
- package/tables/role.json +33 -37
- package/utils/cors.js +37 -41
- package/utils/deepMerge.js +3 -1
- package/utils/error.js +1 -56
- package/utils/id.js +44 -0
- package/utils/is.js +6 -243
- package/utils/response.js +12 -1
- package/utils/scanFiles.js +13 -4
- package/utils/scanSources.js +58 -20
- package/utils/util.js +11 -163
- package/apis/_apis.js +0 -20
- package/apis/auth/sendSmsCode.js +0 -24
- package/apis/role/delete.js +0 -41
- package/apis/role/insert.js +0 -45
- package/apis/role/save.js +0 -36
- package/apis/role/update.js +0 -66
- package/configs/regexpAlias.json +0 -55
- package/lib/connect.js +0 -119
- package/lib/logger.js +0 -430
- package/lib/redisHelper.js +0 -399
- package/lib/validator.js +0 -274
- package/lib/xmlParse.js +0 -149
- package/plugins/config.js +0 -13
- package/plugins/cron.js +0 -115
- package/plugins/logger.js +0 -17
- package/plugins/tool.js +0 -131
- package/plugins/xmlParse.js +0 -10
- package/schemas/config.js +0 -67
- package/scripts/syncDb/context.js +0 -120
- package/scripts/syncDb/diff.js +0 -279
- package/scripts/syncDb/transform.js +0 -113
- package/sql/befly.sql +0 -271
- package/utils/email.js +0 -3
- package/utils/fieldClear.js +0 -22
- package/utils/formatValidationIssues.js +0 -28
- package/utils/getClientIp.js +0 -47
- package/utils/loggerUtils.js +0 -196
- package/utils/regexpUtil.js +0 -52
- /package/{lib → libs}/smtpText.js +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { isLowerCamelCaseName, isNonEmptyString, isNullable, isNumber, isPlainObject, isString } from "../utils/is.js";
|
|
4
|
-
import { camelCase, canConvertToNumber, keysToSnake, snakeCase } from "../utils/util.js";
|
|
1
|
+
import { genId } from "../../utils/id.js";
|
|
2
|
+
import { Logger } from "../logger/index.js";
|
|
5
3
|
import { DbParse } from "./dbParse.js";
|
|
6
4
|
import { deserializeArrayFields, parseTableRef, serializeArrayFields } from "./dbUtil.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { createError, getRootCause, validationError } from "./error.js";
|
|
6
|
+
import { isLowerCamelCaseName, isNonEmptyString, isNullable, isNumber, isPlainObject, isString } from "./is.js";
|
|
7
|
+
import { SqlBuilder } from "./sql/index.js";
|
|
8
|
+
import { camelCase, canConvertToNumber, fieldClear, keysToSnake, snakeCase } from "./util.js";
|
|
9
9
|
|
|
10
10
|
function quoteIdentMySql(identifier) {
|
|
11
11
|
if (!isString(identifier)) {
|
|
@@ -221,7 +221,7 @@ function assertBatchInsertRowsConsistent(rows, options) {
|
|
|
221
221
|
|
|
222
222
|
class DbHelper {
|
|
223
223
|
constructor(options = {}) {
|
|
224
|
-
this.
|
|
224
|
+
this.closeHandler = options.close;
|
|
225
225
|
|
|
226
226
|
if (!isNonEmptyString(options.dbName)) {
|
|
227
227
|
throw validationError("DbHelper 初始化失败:dbName 必须为非空字符串");
|
|
@@ -236,21 +236,24 @@ class DbHelper {
|
|
|
236
236
|
this.queryTimeout = (options.queryTimeout || 30) * 1000;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
getTableModel(table) {
|
|
240
240
|
const tableName = parseTableRef(table).table;
|
|
241
241
|
|
|
242
|
-
if (!isLowerCamelCaseName(tableName)) {
|
|
243
|
-
|
|
242
|
+
if (!isLowerCamelCaseName(tableName)) throw validationError(`表名必须使用小驼峰写法 (table: ${tableName})`);
|
|
243
|
+
const tableModel = this.tables[tableName];
|
|
244
|
+
if (!isPlainObject(tableModel) || !isPlainObject(tableModel.fields)) {
|
|
245
|
+
const sourceLabel = tableName.startsWith("befly") ? "opensource/core/tables" : "app tables";
|
|
246
|
+
throw createError(`表 ${tableName} 缺少标准 tables 模型,请检查 ${sourceLabel}`, { code: "runtime" });
|
|
244
247
|
}
|
|
248
|
+
return tableModel;
|
|
249
|
+
}
|
|
245
250
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const sourceLabel = tableName.startsWith("befly") ? "packages/core/tables" : "app tables";
|
|
250
|
-
throw createError(`表 ${tableName} 缺少本地 tables 定义,请检查 ${sourceLabel}`, { code: "runtime" });
|
|
251
|
-
}
|
|
251
|
+
getTableMode(table) {
|
|
252
|
+
return parseTableRef(table).table.startsWith("befly") ? "auto" : this.beflyMode;
|
|
253
|
+
}
|
|
252
254
|
|
|
253
|
-
|
|
255
|
+
getTableFields(table) {
|
|
256
|
+
return this.getTableModel(table).fields;
|
|
254
257
|
}
|
|
255
258
|
|
|
256
259
|
normalizeDbFieldNames(row) {
|
|
@@ -297,17 +300,20 @@ class DbHelper {
|
|
|
297
300
|
}
|
|
298
301
|
}
|
|
299
302
|
|
|
303
|
+
let queryTimedOut = false;
|
|
300
304
|
try {
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
305
|
+
const query = safeParams.length > 0 ? this.sql.unsafe(sql, safeParams) : this.sql.unsafe(sql);
|
|
306
|
+
const timeoutId = setTimeout(() => {
|
|
307
|
+
queryTimedOut = true;
|
|
308
|
+
query.cancel();
|
|
309
|
+
}, this.queryTimeout);
|
|
310
|
+
|
|
311
|
+
let queryResult;
|
|
312
|
+
try {
|
|
313
|
+
queryResult = await query;
|
|
314
|
+
} finally {
|
|
315
|
+
clearTimeout(timeoutId);
|
|
316
|
+
}
|
|
311
317
|
|
|
312
318
|
const duration = Date.now() - startTime;
|
|
313
319
|
|
|
@@ -320,10 +326,19 @@ class DbHelper {
|
|
|
320
326
|
}
|
|
321
327
|
};
|
|
322
328
|
} catch (error) {
|
|
329
|
+
const executeError = queryTimedOut
|
|
330
|
+
? createError(`SQL 执行超时(${this.queryTimeout}ms): ${sql.slice(0, 100)}`, {
|
|
331
|
+
cause: error,
|
|
332
|
+
code: "runtime",
|
|
333
|
+
subsystem: "sql",
|
|
334
|
+
operation: "execute"
|
|
335
|
+
})
|
|
336
|
+
: error;
|
|
337
|
+
|
|
323
338
|
const duration = Date.now() - startTime;
|
|
324
|
-
const msg = getExecuteErrorMessage(
|
|
339
|
+
const msg = getExecuteErrorMessage(executeError);
|
|
325
340
|
|
|
326
|
-
if (retryOnConnectionClosed && this.isConnectionClosedError(
|
|
341
|
+
if (retryOnConnectionClosed && this.isConnectionClosedError(executeError, msg)) {
|
|
327
342
|
return this.execute(sql, params, false);
|
|
328
343
|
}
|
|
329
344
|
|
|
@@ -333,7 +348,7 @@ class DbHelper {
|
|
|
333
348
|
duration: duration
|
|
334
349
|
};
|
|
335
350
|
throw createError(`SQL执行失败: ${msg}`, {
|
|
336
|
-
cause:
|
|
351
|
+
cause: executeError,
|
|
337
352
|
code: "runtime",
|
|
338
353
|
subsystem: "sql",
|
|
339
354
|
operation: "execute",
|
|
@@ -350,9 +365,6 @@ class DbHelper {
|
|
|
350
365
|
if (rootCause?.code === "ERR_MYSQL_CONNECTION_CLOSED") {
|
|
351
366
|
return true;
|
|
352
367
|
}
|
|
353
|
-
if (rootCause?.code === "ERR_POSTGRES_CONNECTION_CLOSED") {
|
|
354
|
-
return true;
|
|
355
|
-
}
|
|
356
368
|
if (typeof msg === "string" && msg.includes("Connection closed")) {
|
|
357
369
|
return true;
|
|
358
370
|
}
|
|
@@ -396,13 +408,13 @@ class DbHelper {
|
|
|
396
408
|
};
|
|
397
409
|
}
|
|
398
410
|
|
|
399
|
-
normalizeRowData(row) {
|
|
411
|
+
normalizeRowData(row, tableInfo = {}) {
|
|
400
412
|
if (!row) {
|
|
401
413
|
return {};
|
|
402
414
|
}
|
|
403
415
|
|
|
404
416
|
const camelRow = this.normalizeDbFieldNames(row);
|
|
405
|
-
const deserialized = deserializeArrayFields(camelRow);
|
|
417
|
+
const deserialized = deserializeArrayFields(camelRow, tableInfo);
|
|
406
418
|
|
|
407
419
|
const convertedList = normalizeBigIntValues([deserialized]);
|
|
408
420
|
if (convertedList && convertedList.length > 0) {
|
|
@@ -412,20 +424,20 @@ class DbHelper {
|
|
|
412
424
|
return deserialized;
|
|
413
425
|
}
|
|
414
426
|
|
|
415
|
-
normalizeListData(list) {
|
|
427
|
+
normalizeListData(list, tableInfo = {}) {
|
|
416
428
|
const camelList = list.map((item) => this.normalizeDbFieldNames(item));
|
|
417
|
-
const deserializedList = camelList.map((item) => deserializeArrayFields(item)).filter((item) => item !== null);
|
|
429
|
+
const deserializedList = camelList.map((item) => deserializeArrayFields(item, tableInfo)).filter((item) => item !== null);
|
|
418
430
|
return normalizeBigIntValues(deserializedList);
|
|
419
431
|
}
|
|
420
432
|
|
|
421
|
-
prepareWriteInputData(data) {
|
|
422
|
-
return serializeArrayFields(keysToSnake(fieldClear(data, { excludeValues: [undefined] })));
|
|
433
|
+
prepareWriteInputData(data, tableInfo = {}) {
|
|
434
|
+
return serializeArrayFields(keysToSnake(fieldClear(data, { excludeValues: [undefined] })), tableInfo);
|
|
423
435
|
}
|
|
424
436
|
|
|
425
|
-
prepareWriteUserData(data, allowState) {
|
|
437
|
+
prepareWriteUserData(data, allowState, tableInfo = {}) {
|
|
426
438
|
const result = {};
|
|
427
439
|
|
|
428
|
-
for (const [key, value] of Object.entries(this.prepareWriteInputData(data))) {
|
|
440
|
+
for (const [key, value] of Object.entries(this.prepareWriteInputData(data, tableInfo))) {
|
|
429
441
|
if (key === "id") continue;
|
|
430
442
|
if (key === "created_at") continue;
|
|
431
443
|
if (key === "updated_at") continue;
|
|
@@ -438,7 +450,7 @@ class DbHelper {
|
|
|
438
450
|
}
|
|
439
451
|
|
|
440
452
|
buildInsertRow(options) {
|
|
441
|
-
const result = options.beflyMode === "manual" ? this.prepareWriteInputData(options.data) : this.prepareWriteUserData(options.data, false);
|
|
453
|
+
const result = options.beflyMode === "manual" ? this.prepareWriteInputData(options.data, options.tableInfo) : this.prepareWriteUserData(options.data, false, options.tableInfo);
|
|
442
454
|
|
|
443
455
|
if (options.beflyMode === "auto") {
|
|
444
456
|
assertTimeIdValue(options.id);
|
|
@@ -455,7 +467,7 @@ class DbHelper {
|
|
|
455
467
|
}
|
|
456
468
|
|
|
457
469
|
buildUpdateRow(options) {
|
|
458
|
-
const result = options.beflyMode === "manual" ? this.prepareWriteInputData(options.data) : this.prepareWriteUserData(options.data, options.allowState);
|
|
470
|
+
const result = options.beflyMode === "manual" ? this.prepareWriteInputData(options.data, options.tableInfo) : this.prepareWriteUserData(options.data, options.allowState, options.tableInfo);
|
|
459
471
|
if (options.beflyMode !== "manual") {
|
|
460
472
|
result.updated_at = options.now;
|
|
461
473
|
}
|
|
@@ -463,7 +475,7 @@ class DbHelper {
|
|
|
463
475
|
}
|
|
464
476
|
|
|
465
477
|
async getTableColumns(table) {
|
|
466
|
-
const tableInfo = this.
|
|
478
|
+
const tableInfo = this.getTableFields(table);
|
|
467
479
|
const fieldNames = Object.keys(tableInfo);
|
|
468
480
|
|
|
469
481
|
if (fieldNames.length === 0) {
|
|
@@ -484,14 +496,17 @@ class DbHelper {
|
|
|
484
496
|
}
|
|
485
497
|
|
|
486
498
|
async createInsertRows(table, snakeTable, dataList, now) {
|
|
487
|
-
|
|
499
|
+
const tableInfo = this.getTableFields(table);
|
|
500
|
+
const tableMode = this.getTableMode(table);
|
|
501
|
+
if (tableMode === "manual") {
|
|
488
502
|
return {
|
|
489
503
|
ids: [],
|
|
490
504
|
processedList: dataList.map((data) =>
|
|
491
505
|
this.buildInsertRow({
|
|
492
506
|
data: data,
|
|
493
507
|
now: now,
|
|
494
|
-
beflyMode:
|
|
508
|
+
beflyMode: tableMode,
|
|
509
|
+
tableInfo: tableInfo
|
|
495
510
|
})
|
|
496
511
|
)
|
|
497
512
|
};
|
|
@@ -500,15 +515,15 @@ class DbHelper {
|
|
|
500
515
|
const ids = [];
|
|
501
516
|
try {
|
|
502
517
|
for (let i = 0; i < dataList.length; i++) {
|
|
503
|
-
ids.push(
|
|
518
|
+
ids.push(genId());
|
|
504
519
|
}
|
|
505
520
|
} catch (error) {
|
|
506
521
|
if (dataList.length === 1) {
|
|
507
|
-
throw createError(`生成 ID
|
|
522
|
+
throw createError(`生成 ID 失败 (table: ${table})`, {
|
|
508
523
|
cause: error,
|
|
509
524
|
code: "runtime",
|
|
510
525
|
subsystem: "db",
|
|
511
|
-
operation: "
|
|
526
|
+
operation: "genId",
|
|
512
527
|
table: table
|
|
513
528
|
});
|
|
514
529
|
}
|
|
@@ -525,7 +540,8 @@ class DbHelper {
|
|
|
525
540
|
data: data,
|
|
526
541
|
id: id,
|
|
527
542
|
now: now,
|
|
528
|
-
beflyMode:
|
|
543
|
+
beflyMode: tableMode,
|
|
544
|
+
tableInfo: tableInfo
|
|
529
545
|
});
|
|
530
546
|
});
|
|
531
547
|
|
|
@@ -538,7 +554,7 @@ class DbHelper {
|
|
|
538
554
|
resolveInsertedData(table, count, executeRes, generatedIds, operation) {
|
|
539
555
|
const lastInsertRowidNum = normalizeSqlMetaNumber(executeRes.data?.lastInsertRowid);
|
|
540
556
|
|
|
541
|
-
if (this.
|
|
557
|
+
if (this.getTableMode(table) === "manual") {
|
|
542
558
|
if (lastInsertRowidNum <= 0) {
|
|
543
559
|
throw createError(operation === "insBatch" ? `批量插入失败:beflyMode=manual 时无法获取 lastInsertRowid (table: ${table})` : `插入失败:beflyMode=manual 时无法获取 lastInsertRowid (table: ${table})`, { code: "runtime" });
|
|
544
560
|
}
|
|
@@ -581,7 +597,7 @@ class DbHelper {
|
|
|
581
597
|
const executeRes = await this.execute(sql, params);
|
|
582
598
|
const result = executeRes.data;
|
|
583
599
|
|
|
584
|
-
const data = this.normalizeRowData(result?.[0] || null);
|
|
600
|
+
const data = this.normalizeRowData(result?.[0] || null, this.getTableFields(prepared.codeTable));
|
|
585
601
|
return {
|
|
586
602
|
data: data,
|
|
587
603
|
sql: executeRes.sql
|
|
@@ -625,7 +641,7 @@ class DbHelper {
|
|
|
625
641
|
|
|
626
642
|
return {
|
|
627
643
|
data: {
|
|
628
|
-
lists: this.normalizeListData(list),
|
|
644
|
+
lists: this.normalizeListData(list, this.getTableFields(prepared.codeTable)),
|
|
629
645
|
total: total,
|
|
630
646
|
page: prepared.page,
|
|
631
647
|
limit: prepared.limit,
|
|
@@ -694,7 +710,7 @@ class DbHelper {
|
|
|
694
710
|
});
|
|
695
711
|
}
|
|
696
712
|
|
|
697
|
-
const lists = this.normalizeListData(result);
|
|
713
|
+
const lists = this.normalizeListData(result, this.getTableFields(prepared.codeTable));
|
|
698
714
|
|
|
699
715
|
return {
|
|
700
716
|
data: {
|
|
@@ -721,8 +737,8 @@ class DbHelper {
|
|
|
721
737
|
async insData(options) {
|
|
722
738
|
const parsed = this.createDbParse().parseInsert(options);
|
|
723
739
|
const { table, snakeTable, data } = parsed;
|
|
724
|
-
const tableInfo = this.
|
|
725
|
-
const inputData = this.prepareWriteInputData(data);
|
|
740
|
+
const tableInfo = this.getTableFields(table);
|
|
741
|
+
const inputData = this.prepareWriteInputData(data, tableInfo);
|
|
726
742
|
assertWriteDataHasFields(inputData, "插入数据必须至少有一个字段", snakeTable);
|
|
727
743
|
const now = Date.now();
|
|
728
744
|
const insertRows = await this.createInsertRows(table, snakeTable, [data], now);
|
|
@@ -758,7 +774,7 @@ class DbHelper {
|
|
|
758
774
|
assertInsertBatchSize(dataList.length, MAX_BATCH_SIZE);
|
|
759
775
|
|
|
760
776
|
const snakeTable = parsed.snakeTable;
|
|
761
|
-
const tableInfo = this.
|
|
777
|
+
const tableInfo = this.getTableFields(table);
|
|
762
778
|
const now = Date.now();
|
|
763
779
|
const insertRows = await this.createInsertRows(table, snakeTable, dataList, now);
|
|
764
780
|
const processedList = insertRows.processedList;
|
|
@@ -800,9 +816,10 @@ class DbHelper {
|
|
|
800
816
|
}
|
|
801
817
|
|
|
802
818
|
const snakeTable = parsed.snakeTable;
|
|
819
|
+
const primaryField = snakeCase(this.getTableModel(table).primary || "id");
|
|
803
820
|
const query = SqlBuilder.toDeleteInSql({
|
|
804
821
|
table: snakeTable,
|
|
805
|
-
idField:
|
|
822
|
+
idField: primaryField,
|
|
806
823
|
ids: ids,
|
|
807
824
|
quoteIdent: quoteIdentMySql
|
|
808
825
|
});
|
|
@@ -826,20 +843,24 @@ class DbHelper {
|
|
|
826
843
|
}
|
|
827
844
|
|
|
828
845
|
const snakeTable = parsed.snakeTable;
|
|
829
|
-
const
|
|
846
|
+
const tableModel = this.getTableModel(table);
|
|
847
|
+
const tableInfo = tableModel.fields;
|
|
848
|
+
const tableMode = this.getTableMode(table);
|
|
849
|
+
const primaryCodeName = tableModel.primary || "id";
|
|
850
|
+
const primaryDbName = snakeCase(primaryCodeName);
|
|
830
851
|
const now = Date.now();
|
|
831
852
|
|
|
832
853
|
const processedList = [];
|
|
833
854
|
const fieldSet = new Set();
|
|
834
855
|
|
|
835
856
|
for (const item of dataList) {
|
|
836
|
-
const userData = this.prepareWriteUserData(item.data, true);
|
|
857
|
+
const userData = this.prepareWriteUserData(item.data, true, tableInfo);
|
|
837
858
|
|
|
838
859
|
for (const key of Object.keys(userData)) {
|
|
839
860
|
fieldSet.add(key);
|
|
840
861
|
}
|
|
841
862
|
|
|
842
|
-
processedList.push({ id: item
|
|
863
|
+
processedList.push({ id: item[primaryCodeName], data: userData });
|
|
843
864
|
}
|
|
844
865
|
|
|
845
866
|
const fields = Array.from(fieldSet).toSorted();
|
|
@@ -850,21 +871,21 @@ class DbHelper {
|
|
|
850
871
|
};
|
|
851
872
|
}
|
|
852
873
|
const writeFields = fields.slice();
|
|
853
|
-
if (
|
|
874
|
+
if (tableMode === "auto") {
|
|
854
875
|
writeFields.push("updated_at");
|
|
855
876
|
}
|
|
856
877
|
assertWriteFieldNamesDefined(tableInfo, writeFields, table, "updBatch.dataList");
|
|
857
878
|
|
|
858
879
|
const query = SqlBuilder.toUpdateCaseByIdSql({
|
|
859
880
|
table: snakeTable,
|
|
860
|
-
idField:
|
|
881
|
+
idField: primaryDbName,
|
|
861
882
|
rows: processedList,
|
|
862
883
|
fields: fields,
|
|
863
884
|
quoteIdent: quoteIdentMySql,
|
|
864
|
-
updatedAtField:
|
|
865
|
-
updatedAtValue:
|
|
885
|
+
updatedAtField: tableMode === "auto" ? "updated_at" : "",
|
|
886
|
+
updatedAtValue: tableMode === "auto" ? now : null,
|
|
866
887
|
stateField: "state",
|
|
867
|
-
stateGtZero:
|
|
888
|
+
stateGtZero: tableMode === "auto"
|
|
868
889
|
});
|
|
869
890
|
|
|
870
891
|
const executeRes = await this.execute(query.sql, query.params);
|
|
@@ -877,15 +898,16 @@ class DbHelper {
|
|
|
877
898
|
|
|
878
899
|
async updData(options) {
|
|
879
900
|
const parsed = this.createDbParse().parseUpdate(options);
|
|
880
|
-
const tableInfo = this.
|
|
881
|
-
const inputData = this.prepareWriteInputData(parsed.data);
|
|
901
|
+
const tableInfo = this.getTableFields(parsed.table);
|
|
902
|
+
const inputData = this.prepareWriteInputData(parsed.data, tableInfo);
|
|
882
903
|
assertWriteDataHasFields(inputData, "更新数据必须至少有一个字段", parsed.snakeTable);
|
|
883
904
|
|
|
884
905
|
const processed = this.buildUpdateRow({
|
|
885
906
|
data: parsed.data,
|
|
886
907
|
now: Date.now(),
|
|
887
908
|
allowState: true,
|
|
888
|
-
beflyMode: this.
|
|
909
|
+
beflyMode: this.getTableMode(parsed.table),
|
|
910
|
+
tableInfo: tableInfo
|
|
889
911
|
});
|
|
890
912
|
assertWriteDataHasFields(processed, "更新数据必须至少有一个字段", parsed.snakeTable);
|
|
891
913
|
assertWriteFieldsDefined(tableInfo, processed, parsed.table, "updData.data");
|
|
@@ -902,11 +924,11 @@ class DbHelper {
|
|
|
902
924
|
|
|
903
925
|
async delData(options) {
|
|
904
926
|
const parsed = this.createDbParse().parseDelete(options, false);
|
|
905
|
-
const tableInfo = this.
|
|
927
|
+
const tableInfo = this.getTableFields(parsed.table);
|
|
906
928
|
let processed;
|
|
907
929
|
|
|
908
930
|
if (parsed.deleteMode === "manual") {
|
|
909
|
-
processed = this.prepareWriteInputData(parsed.data);
|
|
931
|
+
processed = this.prepareWriteInputData(parsed.data, tableInfo);
|
|
910
932
|
assertWriteDataHasFields(processed, "delData 在 beflyMode=manual 时 data 必须至少有一个字段", parsed.snakeTable);
|
|
911
933
|
} else {
|
|
912
934
|
const now = Date.now();
|
|
@@ -945,7 +967,7 @@ class DbHelper {
|
|
|
945
967
|
|
|
946
968
|
async increment(table, field, where, value = 1) {
|
|
947
969
|
const parsed = this.createDbParse().parseIncrement(table, field, where, value, "increment");
|
|
948
|
-
const tableInfo = this.
|
|
970
|
+
const tableInfo = this.getTableFields(table);
|
|
949
971
|
assertWriteFieldNamesDefined(tableInfo, [parsed.snakeField], table, "increment.field");
|
|
950
972
|
|
|
951
973
|
const builder = this.createSqlBuilder().where(parsed.where);
|
|
@@ -972,6 +994,11 @@ class DbHelper {
|
|
|
972
994
|
return await this.increment(table, field, where, -value);
|
|
973
995
|
}
|
|
974
996
|
|
|
997
|
+
async close() {
|
|
998
|
+
if (this.isTransaction) return;
|
|
999
|
+
await this.closeHandler?.();
|
|
1000
|
+
}
|
|
1001
|
+
|
|
975
1002
|
async trans(callback) {
|
|
976
1003
|
if (this.isTransaction) {
|
|
977
1004
|
throw createError("不支持嵌套事务", { code: "policy" });
|
|
@@ -983,11 +1010,11 @@ class DbHelper {
|
|
|
983
1010
|
|
|
984
1011
|
return await this.sql.begin(async (tx) => {
|
|
985
1012
|
const trans = new this.constructor({
|
|
986
|
-
redis: this.redis,
|
|
987
1013
|
dbName: this.dbName,
|
|
988
1014
|
sql: tx,
|
|
989
1015
|
isTransaction: true,
|
|
990
1016
|
beflyMode: this.beflyMode,
|
|
1017
|
+
queryTimeout: this.queryTimeout / 1000,
|
|
991
1018
|
tables: this.tables
|
|
992
1019
|
});
|
|
993
1020
|
return await callback(trans);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { validationError } from "../utils/error.js";
|
|
2
|
-
import { isNonEmptyString, isNullable, isPlainObject, isString } from "../utils/is.js";
|
|
3
|
-
import { snakeCase } from "../utils/util.js";
|
|
4
1
|
import { assertNoExprField, clearDeep, fieldsToSnake, normalizeQualifierField, orderByToSnake, parseFieldAliasParts, parseFlatWhereKey, parseTableRef, processJoinField, processJoinOn, processJoinOrderBy, processJoinWhere, whereKeysToSnake } from "./dbUtil.js";
|
|
2
|
+
import { validationError } from "./error.js";
|
|
3
|
+
import { isNonEmptyString, isNullable, isPlainObject, isString } from "./is.js";
|
|
4
|
+
import { snakeCase } from "./util.js";
|
|
5
5
|
|
|
6
6
|
function assertNonEmptyString(value, label) {
|
|
7
7
|
if (!isNonEmptyString(value)) {
|
|
@@ -577,6 +577,10 @@ export class DbParse {
|
|
|
577
577
|
this.getTableColumns = options.getTableColumns;
|
|
578
578
|
}
|
|
579
579
|
|
|
580
|
+
getTableMode(table) {
|
|
581
|
+
return parseTableRef(table).table.startsWith("befly") ? "auto" : this.beflyMode;
|
|
582
|
+
}
|
|
583
|
+
|
|
580
584
|
async parseRead(options, mode) {
|
|
581
585
|
const label = mode === "one" ? "getOne.options" : mode === "list" ? "getList.options" : "getAll.options";
|
|
582
586
|
validateQueryOptions(options, label);
|
|
@@ -609,7 +613,7 @@ export class DbParse {
|
|
|
609
613
|
}
|
|
610
614
|
|
|
611
615
|
const snakeTable = snakeCase(Array.isArray(options.table) ? options.table[0] : options.table);
|
|
612
|
-
const normalizedWhere = applyDefaultStateFilter(whereKeysToSnake(cleanWhere), snakeTable, false, this.
|
|
616
|
+
const normalizedWhere = applyDefaultStateFilter(whereKeysToSnake(cleanWhere), snakeTable, false, this.getTableMode(Array.isArray(options.table) ? options.table[0] : options.table));
|
|
613
617
|
return {
|
|
614
618
|
table: snakeTable,
|
|
615
619
|
fields: [],
|
|
@@ -662,7 +666,7 @@ export class DbParse {
|
|
|
662
666
|
const label = force ? "delForce" : "delData";
|
|
663
667
|
assertNonEmptyString(options.table, `${label}.table`);
|
|
664
668
|
validateWhereObject(options.where, `${label}.where`, true);
|
|
665
|
-
const deleteMode = resolveDeleteMode(options.table, this.
|
|
669
|
+
const deleteMode = resolveDeleteMode(options.table, this.getTableMode(options.table));
|
|
666
670
|
const result = {
|
|
667
671
|
table: options.table,
|
|
668
672
|
snakeTable: snakeCase(options.table),
|
|
@@ -719,10 +723,11 @@ export class DbParse {
|
|
|
719
723
|
const normalizedTableRef = normalizeTableRef(mainTableRef);
|
|
720
724
|
const mainQualifier = getTableQualifier(mainTableRef);
|
|
721
725
|
const joins = options.leftJoin.map((joinItem, index) => parseLeftJoinItem(joinTableRefs[index], joinItem));
|
|
722
|
-
const normalizedWhere = applyDefaultStateFilter(processJoinWhere(cleanWhere), mainQualifier, true, this.
|
|
726
|
+
const normalizedWhere = applyDefaultStateFilter(processJoinWhere(cleanWhere), mainQualifier, true, this.getTableMode(mainTableRef));
|
|
723
727
|
|
|
724
728
|
return {
|
|
725
729
|
table: normalizedTableRef,
|
|
730
|
+
codeTable: parseTableRef(mainTableRef).table,
|
|
726
731
|
fields: processedFields,
|
|
727
732
|
where: parseWhereObject(normalizedWhere),
|
|
728
733
|
leftJoins: joins,
|
|
@@ -738,10 +743,11 @@ export class DbParse {
|
|
|
738
743
|
const normalizedTableRef = normalizeTableRef(mainTableRef);
|
|
739
744
|
const mainQualifier = getTableQualifier(mainTableRef);
|
|
740
745
|
const joins = options.leftJoin.map((joinItem, index) => parseLeftJoinItem(joinTableRefs[index], joinItem));
|
|
741
|
-
const normalizedWhere = applyDefaultStateFilter(processJoinWhere(cleanWhere), mainQualifier, true, this.
|
|
746
|
+
const normalizedWhere = applyDefaultStateFilter(processJoinWhere(cleanWhere), mainQualifier, true, this.getTableMode(mainTableRef));
|
|
742
747
|
|
|
743
748
|
return {
|
|
744
749
|
table: normalizedTableRef,
|
|
750
|
+
codeTable: parseTableRef(mainTableRef).table,
|
|
745
751
|
fields: [],
|
|
746
752
|
where: parseWhereObject(normalizedWhere),
|
|
747
753
|
leftJoins: joins,
|
|
@@ -755,10 +761,11 @@ export class DbParse {
|
|
|
755
761
|
const tableRef = Array.isArray(options.table) ? options.table[0] : options.table;
|
|
756
762
|
const snakeTable = snakeCase(tableRef);
|
|
757
763
|
const processedFields = await fieldsToSnake(tableRef, classifiedFields, this.getTableColumns);
|
|
758
|
-
const normalizedWhere = applyDefaultStateFilter(whereKeysToSnake(cleanWhere), snakeTable, false, this.
|
|
764
|
+
const normalizedWhere = applyDefaultStateFilter(whereKeysToSnake(cleanWhere), snakeTable, false, this.getTableMode(tableRef));
|
|
759
765
|
|
|
760
766
|
return {
|
|
761
767
|
table: snakeTable,
|
|
768
|
+
codeTable: parseTableRef(tableRef).table,
|
|
762
769
|
fields: processedFields,
|
|
763
770
|
where: parseWhereObject(normalizedWhere),
|
|
764
771
|
leftJoins: [],
|
|
@@ -768,7 +775,7 @@ export class DbParse {
|
|
|
768
775
|
};
|
|
769
776
|
}
|
|
770
777
|
|
|
771
|
-
prepareSingleTableWhere(table, where, useDefaultStateFilter, required, label, beflyMode = this.
|
|
778
|
+
prepareSingleTableWhere(table, where, useDefaultStateFilter, required, label, beflyMode = this.getTableMode(table)) {
|
|
772
779
|
const snakeTable = snakeCase(table);
|
|
773
780
|
const normalizedWhere = whereKeysToSnake(clearDeep(where || {}));
|
|
774
781
|
if (required) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { validationError } from "
|
|
2
|
-
import { isFiniteNumber, isNonEmptyString, isNullable, isString } from "
|
|
3
|
-
import { snakeCase } from "
|
|
1
|
+
import { validationError } from "./error.js";
|
|
2
|
+
import { isFiniteNumber, isNonEmptyString, isNullable, isString } from "./is.js";
|
|
3
|
+
import { camelCase, snakeCase } from "./util.js";
|
|
4
4
|
|
|
5
5
|
export function assertNoExprField(field) {
|
|
6
6
|
if (!isString(field)) {
|
|
@@ -613,45 +613,28 @@ export function whereKeysToSnake(where) {
|
|
|
613
613
|
});
|
|
614
614
|
}
|
|
615
615
|
|
|
616
|
-
export function serializeArrayFields(data) {
|
|
616
|
+
export function serializeArrayFields(data, tableInfo = {}) {
|
|
617
617
|
const serialized = {};
|
|
618
618
|
for (const [key, value] of Object.entries(data)) {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
if (Array.isArray(value)) {
|
|
624
|
-
serialized[key] = JSON.stringify(value);
|
|
625
|
-
continue;
|
|
626
|
-
}
|
|
627
|
-
serialized[key] = value;
|
|
619
|
+
const fieldRule = tableInfo[camelCase(key)] || tableInfo[key];
|
|
620
|
+
const complexParam = ["array", "object", "record"].includes(fieldRule?.paramType);
|
|
621
|
+
serialized[key] = !isNullable(value) && fieldRule?.fieldType === "text" && complexParam && typeof value === "object" ? JSON.stringify(value) : value;
|
|
628
622
|
}
|
|
629
623
|
return serialized;
|
|
630
624
|
}
|
|
631
625
|
|
|
632
|
-
export function deserializeArrayFields(data) {
|
|
633
|
-
if (!data)
|
|
634
|
-
|
|
635
|
-
}
|
|
636
|
-
const deserialized = {};
|
|
637
|
-
for (const [key, value] of Object.entries(data)) {
|
|
638
|
-
deserialized[key] = value;
|
|
639
|
-
}
|
|
626
|
+
export function deserializeArrayFields(data, tableInfo = {}) {
|
|
627
|
+
if (!data) return null;
|
|
628
|
+
const deserialized = { ...data };
|
|
640
629
|
|
|
641
630
|
for (const [key, value] of Object.entries(deserialized)) {
|
|
642
|
-
|
|
631
|
+
const fieldRule = tableInfo[camelCase(key)] || tableInfo[key];
|
|
632
|
+
if (!isString(value) || fieldRule?.fieldType !== "text" || !["array", "object", "record"].includes(fieldRule.paramType)) continue;
|
|
633
|
+
try {
|
|
634
|
+
deserialized[key] = JSON.parse(value);
|
|
635
|
+
} catch {
|
|
643
636
|
continue;
|
|
644
637
|
}
|
|
645
|
-
if (value.startsWith("[") && value.endsWith("]")) {
|
|
646
|
-
try {
|
|
647
|
-
const parsed = JSON.parse(value);
|
|
648
|
-
if (Array.isArray(parsed)) {
|
|
649
|
-
deserialized[key] = parsed;
|
|
650
|
-
}
|
|
651
|
-
} catch {
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
638
|
}
|
|
656
639
|
|
|
657
640
|
return deserialized;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function createError(message, options = {}) {
|
|
2
|
+
const error = new Error(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
3
|
+
if (options.code) error.code = options.code;
|
|
4
|
+
if (options.subsystem) error.subsystem = options.subsystem;
|
|
5
|
+
if (options.operation) error.operation = options.operation;
|
|
6
|
+
for (const [key, value] of Object.entries(options)) {
|
|
7
|
+
if (key !== "cause" && key !== "code" && key !== "subsystem" && key !== "operation") error[key] = value;
|
|
8
|
+
}
|
|
9
|
+
return error;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function validationError(message) {
|
|
13
|
+
return createError(message, { code: "validation" });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getRootCause(error) {
|
|
17
|
+
let current = error;
|
|
18
|
+
while (current?.cause instanceof Error) current = current.cause;
|
|
19
|
+
return current;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class MysqlError extends Error {
|
|
23
|
+
constructor(message, options = {}) {
|
|
24
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
25
|
+
this.name = "MysqlError";
|
|
26
|
+
this.code = options.code || "runtime";
|
|
27
|
+
this.operation = options.operation;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { DbHelper } from "./dbHelper.js";
|
|
2
|
+
export { DbParse, applyDefaultStateFilter, parseFieldSelection, parseJoinFieldSelection, parseLeftJoinItem } from "./dbParse.js";
|
|
3
|
+
export { clearDeep, deserializeArrayFields, fieldsToSnake, parseTableRef, processJoinOn, processJoinOrderBy, processJoinWhere, serializeArrayFields, whereKeysToSnake } from "./dbUtil.js";
|
|
4
|
+
export { MysqlError } from "./error.js";
|
|
5
|
+
export { createMysql } from "./mysql.js";
|
|
6
|
+
export { escapeField, escapeTable, resolveQuoteIdent, SqlBuilder } from "./sql/index.js";
|
package/libs/mysql/is.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function isPlainObject(value) {
|
|
2
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3
|
+
const proto = Object.getPrototypeOf(value);
|
|
4
|
+
return proto === Object.prototype || proto === null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isFiniteNumber(value) {
|
|
8
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isString(value) {
|
|
12
|
+
return typeof value === "string";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isNumber(value) {
|
|
16
|
+
return Object.prototype.toString.call(value) === "[object Number]";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isNullable(value) {
|
|
20
|
+
return value === null || value === undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isNonEmptyString(value) {
|
|
24
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isLowerCamelCaseName(value) {
|
|
28
|
+
return isNonEmptyString(value) && /^_?[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/.test(value);
|
|
29
|
+
}
|