befly 3.24.9 → 3.24.11
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/admin/cacheRefresh.js +1 -1
- package/apis/dashboard/environmentInfo.js +1 -0
- package/apis/menu/select.js +1 -1
- package/apis/role/select.js +1 -1
- package/apis/sysConfig/get.js +1 -1
- package/apis/tongJi/errorList.js +14 -1
- package/apis/tongJi/errorReport.js +1 -0
- package/apis/tongJi/infoReport.js +1 -1
- package/hooks/permission.js +1 -1
- package/hooks/validator.js +1 -1
- package/index.js +9 -15
- package/lib/cacheHelper.js +3 -3
- package/lib/connect.js +1 -0
- package/lib/dbHelper.js +44 -40
- package/lib/dbParse.js +13 -13
- package/lib/dbUtil.js +11 -8
- package/lib/logger.js +9 -9
- package/lib/sqlBuilder.js +55 -55
- package/lib/validator.js +4 -4
- package/package.json +1 -1
- package/paths.js +9 -9
- package/plugins/cache.js +1 -1
- package/plugins/email.js +3 -2
- package/plugins/logger.js +1 -1
- package/plugins/mysql.js +1 -1
- package/plugins/redis.js +1 -1
- package/scripts/syncDb/context.js +2 -1
- package/scripts/syncDb/diff.js +1 -0
- package/scripts/syncDb/index.js +2 -1
- package/scripts/syncDb/report.js +1 -0
- package/utils/fieldClear.js +1 -1
- package/utils/regexpUtil.js +1 -1
package/apis/menu/select.js
CHANGED
package/apis/role/select.js
CHANGED
package/apis/sysConfig/get.js
CHANGED
package/apis/tongJi/errorList.js
CHANGED
|
@@ -43,7 +43,20 @@ export default {
|
|
|
43
43
|
const where = {};
|
|
44
44
|
|
|
45
45
|
if (keyword) {
|
|
46
|
-
where["$or"] = [
|
|
46
|
+
where["$or"] = [
|
|
47
|
+
{ pagePath$like: keyword },
|
|
48
|
+
{ pageName$like: keyword },
|
|
49
|
+
{ message$like: keyword },
|
|
50
|
+
{ errorType$like: keyword },
|
|
51
|
+
{ productName$like: keyword },
|
|
52
|
+
{ productCode$like: keyword },
|
|
53
|
+
{ productVersion$like: keyword },
|
|
54
|
+
{ browserName$like: keyword },
|
|
55
|
+
{ osName$like: keyword },
|
|
56
|
+
{ deviceType$like: keyword },
|
|
57
|
+
{ deviceVendor$like: keyword },
|
|
58
|
+
{ deviceModel$like: keyword }
|
|
59
|
+
];
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
if (errorType) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UAParser } from "ua-parser-js";
|
|
2
2
|
|
|
3
|
-
import { isValidPositiveInt } from "#root/utils/is.js";
|
|
4
3
|
import { getDateYmdNumber } from "#root/utils/datetime.js";
|
|
4
|
+
import { isValidPositiveInt } from "#root/utils/is.js";
|
|
5
5
|
|
|
6
6
|
function getInfoStatsMember(ctx) {
|
|
7
7
|
if (isValidPositiveInt(ctx.userId)) {
|
package/hooks/permission.js
CHANGED
package/hooks/validator.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// 相对导入
|
|
2
2
|
import { Validator } from "../lib/validator.js";
|
|
3
|
-
import { ErrorResponse } from "../utils/response.js";
|
|
4
3
|
import { isPlainObject } from "../utils/is.js";
|
|
4
|
+
import { ErrorResponse } from "../utils/response.js";
|
|
5
5
|
import { snakeCase } from "../utils/util.js";
|
|
6
6
|
|
|
7
7
|
/**
|
package/index.js
CHANGED
|
@@ -3,10 +3,6 @@
|
|
|
3
3
|
* 提供简洁的框架接口,核心逻辑已提取到 loader 层
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
// 配置
|
|
7
|
-
import beflyConfig from "#root/configs/beflyConfig.json";
|
|
8
|
-
import beflyMenus from "#root/configs/beflyMenus.json";
|
|
9
|
-
|
|
10
6
|
// 检查
|
|
11
7
|
import { checkApi } from "#root/checks/api.js";
|
|
12
8
|
import { checkConfig } from "#root/checks/config.js";
|
|
@@ -14,6 +10,9 @@ import { checkHook } from "#root/checks/hook.js";
|
|
|
14
10
|
import { checkMenu } from "#root/checks/menu.js";
|
|
15
11
|
import { checkPlugin } from "#root/checks/plugin.js";
|
|
16
12
|
import { checkTable } from "#root/checks/table.js";
|
|
13
|
+
// 配置
|
|
14
|
+
import beflyConfig from "#root/configs/beflyConfig.json";
|
|
15
|
+
import beflyMenus from "#root/configs/beflyMenus.json";
|
|
17
16
|
// ========== 相对导入(项目内部文件) ==========
|
|
18
17
|
// 基础设施
|
|
19
18
|
import { Connect } from "#root/lib/connect.js";
|
|
@@ -28,9 +27,9 @@ import { syncDev } from "#root/sync/dev.js";
|
|
|
28
27
|
import { syncMenu } from "#root/sync/menu.js";
|
|
29
28
|
// 工具
|
|
30
29
|
import { calcPerfTime } from "#root/utils/calcPerfTime.js";
|
|
31
|
-
import { scanSources } from "#root/utils/scanSources.js";
|
|
32
|
-
import { isPrimaryProcess } from "#root/utils/is.js";
|
|
33
30
|
import { deepMerge } from "#root/utils/deepMerge.js";
|
|
31
|
+
import { isPrimaryProcess } from "#root/utils/is.js";
|
|
32
|
+
import { scanSources } from "#root/utils/scanSources.js";
|
|
34
33
|
|
|
35
34
|
export { syncDb } from "#root/scripts/syncDb/index.js";
|
|
36
35
|
export { xmlParse } from "#root/lib/xmlParse.js";
|
|
@@ -169,7 +168,7 @@ export class Befly {
|
|
|
169
168
|
await Connect.connectMysql(this.context.config.mysql);
|
|
170
169
|
|
|
171
170
|
// 加载插件
|
|
172
|
-
for (const item of this.plugins.
|
|
171
|
+
for (const item of this.plugins.toSorted((a, b) => a.order - b.order)) {
|
|
173
172
|
this.context[item.fileName] = await item.handler(this.context);
|
|
174
173
|
}
|
|
175
174
|
await ensureSyncPrerequisites(this.context);
|
|
@@ -183,7 +182,7 @@ export class Befly {
|
|
|
183
182
|
}
|
|
184
183
|
|
|
185
184
|
// 加载钩子
|
|
186
|
-
this.hooks = this.hooks.
|
|
185
|
+
this.hooks = this.hooks.toSorted((a, b) => a.order - b.order);
|
|
187
186
|
|
|
188
187
|
// 加载所有 API
|
|
189
188
|
this.apis = Object.fromEntries(this.apis.map((api) => [api.apiPath, api]));
|
|
@@ -198,11 +197,11 @@ export class Befly {
|
|
|
198
197
|
// 开发模式下启用详细错误信息
|
|
199
198
|
development: this.context.env.RUN_MODE === "development",
|
|
200
199
|
// 空闲连接超时时间(秒),防止恶意连接占用资源
|
|
201
|
-
fetch: async (req,
|
|
200
|
+
fetch: async (req, httpServer) => {
|
|
202
201
|
const url = new URL(req.url);
|
|
203
202
|
|
|
204
203
|
if (url.pathname.startsWith("/api/")) {
|
|
205
|
-
return apiFetch(req,
|
|
204
|
+
return apiFetch(req, httpServer);
|
|
206
205
|
}
|
|
207
206
|
|
|
208
207
|
if (url.pathname.startsWith("/public/")) {
|
|
@@ -236,11 +235,6 @@ export class Befly {
|
|
|
236
235
|
Logger.info(`服务器监听地址: ${server.url}`);
|
|
237
236
|
Logger.info(`Mysql 数据库地址:${this.context.config.mysql.hostname}`);
|
|
238
237
|
Logger.info(`Redis 缓存地址:${this.context.config.redis.hostname}`);
|
|
239
|
-
console.log(`${this.context.config.appName} 启动成功!`);
|
|
240
|
-
console.log(`服务器启动耗时: ${finalStartupTime}`);
|
|
241
|
-
console.log(`服务器监听地址: ${server.url}`);
|
|
242
|
-
console.log(`Mysql 数据库地址:${this.context.config.mysql.hostname}`);
|
|
243
|
-
console.log(`Redis 缓存地址:${this.context.config.redis.hostname}`);
|
|
244
238
|
// 注意:作为库代码,这里不注册 SIGINT/SIGTERM 处理器,也不调用 process.exit。
|
|
245
239
|
// 宿主应用应自行处理信号并决定退出策略(包括是否调用 server.stop / Connect.disconnect / Logger.flush)。
|
|
246
240
|
return server;
|
package/lib/cacheHelper.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* 负责在服务器启动时缓存接口、菜单和角色权限到 Redis
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { Logger } from "./logger.js";
|
|
7
6
|
import { isNonEmptyString, isNullable, isString } from "../utils/is.js";
|
|
7
|
+
import { Logger } from "./logger.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* 缓存助手类
|
|
@@ -110,7 +110,7 @@ export class CacheHelper {
|
|
|
110
110
|
|
|
111
111
|
for (const roleCode of roleCodes) {
|
|
112
112
|
const apiPaths = roleApiPathsMap.get(roleCode) || [];
|
|
113
|
-
const members = Array.from(new Set(apiPaths)).
|
|
113
|
+
const members = Array.from(new Set(apiPaths)).toSorted();
|
|
114
114
|
|
|
115
115
|
if (members.length > 0) {
|
|
116
116
|
items.push({ key: `role:apis:${roleCode}`, members: members });
|
|
@@ -168,7 +168,7 @@ export class CacheHelper {
|
|
|
168
168
|
|
|
169
169
|
for (const roleCode of roleCodes) {
|
|
170
170
|
const menuPaths = roleMenuPathsMap.get(roleCode) || [];
|
|
171
|
-
const members = Array.from(new Set(menuPaths)).
|
|
171
|
+
const members = Array.from(new Set(menuPaths)).toSorted();
|
|
172
172
|
|
|
173
173
|
if (members.length > 0) {
|
|
174
174
|
items.push({ key: `role:menus:${roleCode}`, members: members });
|
package/lib/connect.js
CHANGED
package/lib/dbHelper.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { fieldClear } from "../utils/fieldClear.js";
|
|
2
2
|
import { isNonEmptyString, isNullable, isNumber, isPlainObject, isString } from "../utils/is.js";
|
|
3
3
|
import { camelCase, canConvertToNumber, keysToSnake, snakeCase } from "../utils/util.js";
|
|
4
|
-
import { Logger } from "./logger.js";
|
|
5
4
|
import { DbParse } from "./dbParse.js";
|
|
6
|
-
import { SqlBuilder } from "./sqlBuilder.js";
|
|
7
5
|
import { deserializeArrayFields, parseTableRef, serializeArrayFields } from "./dbUtil.js";
|
|
6
|
+
import { Logger } from "./logger.js";
|
|
7
|
+
import { SqlBuilder } from "./sqlBuilder.js";
|
|
8
8
|
|
|
9
9
|
function quoteIdentMySql(identifier) {
|
|
10
10
|
if (!isString(identifier)) {
|
|
@@ -29,38 +29,38 @@ function normalizeSqlMetaNumber(value) {
|
|
|
29
29
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const convertRecord = (source) => {
|
|
38
|
-
const converted = {};
|
|
32
|
+
function convertBigIntRecord(source) {
|
|
33
|
+
const converted = {};
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
for (const [key, item] of Object.entries(source)) {
|
|
36
|
+
let nextValue = item;
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
38
|
+
if (typeof item === "bigint") {
|
|
39
|
+
const convertedNumber = canConvertToNumber(item);
|
|
40
|
+
if (convertedNumber !== null) {
|
|
41
|
+
nextValue = convertedNumber;
|
|
42
|
+
} else {
|
|
43
|
+
nextValue = String(item);
|
|
50
44
|
}
|
|
51
|
-
|
|
52
|
-
converted[key] = nextValue;
|
|
53
45
|
}
|
|
54
46
|
|
|
55
|
-
|
|
56
|
-
}
|
|
47
|
+
converted[key] = nextValue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return converted;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeBigIntValues(value) {
|
|
54
|
+
if (isNullable(value)) {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
57
|
|
|
58
58
|
if (Array.isArray(value)) {
|
|
59
|
-
return value.map((item) =>
|
|
59
|
+
return value.map((item) => convertBigIntRecord(item));
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
if (typeof value === "object") {
|
|
63
|
-
return
|
|
63
|
+
return convertBigIntRecord(value);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
return value;
|
|
@@ -435,14 +435,14 @@ class DbHelper {
|
|
|
435
435
|
return normalizeBigIntValues(deserializedList);
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
-
|
|
438
|
+
prepareWriteInputData(data) {
|
|
439
439
|
return serializeArrayFields(keysToSnake(fieldClear(data, { excludeValues: [null, undefined] })));
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
-
|
|
442
|
+
prepareWriteUserData(data, allowState) {
|
|
443
443
|
const result = {};
|
|
444
444
|
|
|
445
|
-
for (const [key, value] of Object.entries(this.
|
|
445
|
+
for (const [key, value] of Object.entries(this.prepareWriteInputData(data))) {
|
|
446
446
|
if (key === "id") continue;
|
|
447
447
|
if (key === "created_at") continue;
|
|
448
448
|
if (key === "updated_at") continue;
|
|
@@ -454,8 +454,8 @@ class DbHelper {
|
|
|
454
454
|
return result;
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
-
|
|
458
|
-
const result = options.beflyMode === "manual" ? this.
|
|
457
|
+
buildInsertRow(options) {
|
|
458
|
+
const result = options.beflyMode === "manual" ? this.prepareWriteInputData(options.data) : this.prepareWriteUserData(options.data, false);
|
|
459
459
|
|
|
460
460
|
if (options.beflyMode === "auto") {
|
|
461
461
|
assertTimeIdValue(options.id);
|
|
@@ -471,8 +471,8 @@ class DbHelper {
|
|
|
471
471
|
return result;
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
-
|
|
475
|
-
const result = options.beflyMode === "manual" ? this.
|
|
474
|
+
buildUpdateRow(options) {
|
|
475
|
+
const result = options.beflyMode === "manual" ? this.prepareWriteInputData(options.data) : this.prepareWriteUserData(options.data, options.allowState);
|
|
476
476
|
if (options.beflyMode !== "manual") {
|
|
477
477
|
result.updated_at = options.now;
|
|
478
478
|
}
|
|
@@ -508,7 +508,7 @@ class DbHelper {
|
|
|
508
508
|
return {
|
|
509
509
|
ids: [],
|
|
510
510
|
processedList: dataList.map((data) =>
|
|
511
|
-
this.
|
|
511
|
+
this.buildInsertRow({
|
|
512
512
|
data: data,
|
|
513
513
|
now: now,
|
|
514
514
|
beflyMode: this.beflyMode
|
|
@@ -541,7 +541,7 @@ class DbHelper {
|
|
|
541
541
|
assertGeneratedBatchId(id, snakeTable, index);
|
|
542
542
|
}
|
|
543
543
|
|
|
544
|
-
return this.
|
|
544
|
+
return this.buildInsertRow({
|
|
545
545
|
data: data,
|
|
546
546
|
id: id,
|
|
547
547
|
now: now,
|
|
@@ -710,7 +710,11 @@ class DbHelper {
|
|
|
710
710
|
}
|
|
711
711
|
|
|
712
712
|
if (result.length >= MAX_LIMIT) {
|
|
713
|
-
Logger.warn(`getAll 达到最大限制 ${MAX_LIMIT},实际总数 ${total},只返回前 ${MAX_LIMIT} 条`, {
|
|
713
|
+
Logger.warn(`getAll 达到最大限制 ${MAX_LIMIT},实际总数 ${total},只返回前 ${MAX_LIMIT} 条`, {
|
|
714
|
+
table: options.table,
|
|
715
|
+
limit: MAX_LIMIT,
|
|
716
|
+
total: total
|
|
717
|
+
});
|
|
714
718
|
}
|
|
715
719
|
|
|
716
720
|
const lists = this.normalizeListData(result);
|
|
@@ -740,7 +744,7 @@ class DbHelper {
|
|
|
740
744
|
async insData(options) {
|
|
741
745
|
const parsed = this.createDbParse().parseInsert(options);
|
|
742
746
|
const { table, snakeTable, data } = parsed;
|
|
743
|
-
const inputData = this.
|
|
747
|
+
const inputData = this.prepareWriteInputData(data);
|
|
744
748
|
assertWriteDataHasFields(inputData, "插入数据必须至少有一个字段", snakeTable);
|
|
745
749
|
const now = Date.now();
|
|
746
750
|
const insertRows = await this.createInsertRows(table, snakeTable, [data], now);
|
|
@@ -853,7 +857,7 @@ class DbHelper {
|
|
|
853
857
|
const fieldSet = new Set();
|
|
854
858
|
|
|
855
859
|
for (const item of dataList) {
|
|
856
|
-
const userData = this.
|
|
860
|
+
const userData = this.prepareWriteUserData(item.data, true);
|
|
857
861
|
|
|
858
862
|
for (const key of Object.keys(userData)) {
|
|
859
863
|
fieldSet.add(key);
|
|
@@ -862,7 +866,7 @@ class DbHelper {
|
|
|
862
866
|
processedList.push({ id: item.id, data: userData });
|
|
863
867
|
}
|
|
864
868
|
|
|
865
|
-
const fields = Array.from(fieldSet).
|
|
869
|
+
const fields = Array.from(fieldSet).toSorted();
|
|
866
870
|
if (fields.length === 0) {
|
|
867
871
|
return {
|
|
868
872
|
data: 0,
|
|
@@ -892,10 +896,10 @@ class DbHelper {
|
|
|
892
896
|
|
|
893
897
|
async updData(options) {
|
|
894
898
|
const parsed = this.createDbParse().parseUpdate(options);
|
|
895
|
-
const inputData = this.
|
|
899
|
+
const inputData = this.prepareWriteInputData(parsed.data);
|
|
896
900
|
assertWriteDataHasFields(inputData, "更新数据必须至少有一个字段", parsed.snakeTable);
|
|
897
901
|
|
|
898
|
-
const processed = this.
|
|
902
|
+
const processed = this.buildUpdateRow({
|
|
899
903
|
data: parsed.data,
|
|
900
904
|
now: Date.now(),
|
|
901
905
|
allowState: true,
|
|
@@ -918,7 +922,7 @@ class DbHelper {
|
|
|
918
922
|
let processed;
|
|
919
923
|
|
|
920
924
|
if (parsed.deleteMode === "manual") {
|
|
921
|
-
processed = this.
|
|
925
|
+
processed = this.prepareWriteInputData(parsed.data);
|
|
922
926
|
assertWriteDataHasFields(processed, "delData 在 beflyMode=manual 时 data 必须至少有一个字段", parsed.snakeTable);
|
|
923
927
|
} else {
|
|
924
928
|
const now = Date.now();
|
package/lib/dbParse.js
CHANGED
|
@@ -805,9 +805,9 @@ export class DbParse {
|
|
|
805
805
|
|
|
806
806
|
let prepared;
|
|
807
807
|
if (hasLeftJoin) {
|
|
808
|
-
prepared = await this.
|
|
808
|
+
prepared = await this.normalizeLeftJoinReadOptions(options, cleanWhere, classifiedFields);
|
|
809
809
|
} else {
|
|
810
|
-
prepared = await this.
|
|
810
|
+
prepared = await this.normalizeSingleTableReadOptions(options, cleanWhere, classifiedFields);
|
|
811
811
|
}
|
|
812
812
|
|
|
813
813
|
if (mode === "list") {
|
|
@@ -823,7 +823,7 @@ export class DbParse {
|
|
|
823
823
|
const hasLeftJoin = Array.isArray(options.leftJoin) && options.leftJoin.length > 0;
|
|
824
824
|
|
|
825
825
|
if (hasLeftJoin) {
|
|
826
|
-
return this.
|
|
826
|
+
return this.normalizeLeftJoinCountOptions(options, cleanWhere);
|
|
827
827
|
}
|
|
828
828
|
|
|
829
829
|
const snakeTable = snakeCase(Array.isArray(options.table) ? options.table[0] : options.table);
|
|
@@ -841,7 +841,7 @@ export class DbParse {
|
|
|
841
841
|
|
|
842
842
|
parseExists(options) {
|
|
843
843
|
validateNoLeftJoinReadOptions(options, "exists", "exists 不支持 leftJoin(请使用显式 query 或拆分查询)");
|
|
844
|
-
return this.
|
|
844
|
+
return this.prepareSingleTableWhere(options.table, options.where, true, false, "exists");
|
|
845
845
|
}
|
|
846
846
|
|
|
847
847
|
parseInsert(options) {
|
|
@@ -871,7 +871,7 @@ export class DbParse {
|
|
|
871
871
|
return {
|
|
872
872
|
table: options.table,
|
|
873
873
|
snakeTable: snakeCase(options.table),
|
|
874
|
-
where: this.
|
|
874
|
+
where: this.prepareSingleTableWhere(options.table, options.where, true, true, "updData").where,
|
|
875
875
|
data: options.data
|
|
876
876
|
};
|
|
877
877
|
}
|
|
@@ -884,7 +884,7 @@ export class DbParse {
|
|
|
884
884
|
const result = {
|
|
885
885
|
table: options.table,
|
|
886
886
|
snakeTable: snakeCase(options.table),
|
|
887
|
-
where: this.
|
|
887
|
+
where: this.prepareSingleTableWhere(options.table, options.where, !force, true, label, deleteMode).where,
|
|
888
888
|
deleteMode: deleteMode
|
|
889
889
|
};
|
|
890
890
|
|
|
@@ -923,7 +923,7 @@ export class DbParse {
|
|
|
923
923
|
|
|
924
924
|
parseIncrement(table, field, where, value, label = "increment") {
|
|
925
925
|
validateIncrementOptions(table, field, where, value, label);
|
|
926
|
-
const prepared = this.
|
|
926
|
+
const prepared = this.prepareSingleTableWhere(table, where, true, true, label);
|
|
927
927
|
return {
|
|
928
928
|
table: table,
|
|
929
929
|
snakeTable: prepared.snakeTable,
|
|
@@ -933,9 +933,9 @@ export class DbParse {
|
|
|
933
933
|
};
|
|
934
934
|
}
|
|
935
935
|
|
|
936
|
-
async
|
|
936
|
+
async normalizeLeftJoinReadOptions(options, cleanWhere, classifiedFields) {
|
|
937
937
|
const mainTableRef = options.table[0];
|
|
938
|
-
const processedFields = await this.
|
|
938
|
+
const processedFields = await this.joinFieldsToSnake(mainTableRef, classifiedFields);
|
|
939
939
|
const joinTableRefs = options.table.slice(1);
|
|
940
940
|
const normalizedTableRef = normalizeTableRef(mainTableRef);
|
|
941
941
|
const mainQualifier = getTableQualifier(mainTableRef);
|
|
@@ -953,7 +953,7 @@ export class DbParse {
|
|
|
953
953
|
};
|
|
954
954
|
}
|
|
955
955
|
|
|
956
|
-
|
|
956
|
+
normalizeLeftJoinCountOptions(options, cleanWhere) {
|
|
957
957
|
const mainTableRef = options.table[0];
|
|
958
958
|
const joinTableRefs = options.table.slice(1);
|
|
959
959
|
const normalizedTableRef = normalizeTableRef(mainTableRef);
|
|
@@ -972,7 +972,7 @@ export class DbParse {
|
|
|
972
972
|
};
|
|
973
973
|
}
|
|
974
974
|
|
|
975
|
-
async
|
|
975
|
+
async normalizeSingleTableReadOptions(options, cleanWhere, classifiedFields) {
|
|
976
976
|
const tableRef = Array.isArray(options.table) ? options.table[0] : options.table;
|
|
977
977
|
const snakeTable = snakeCase(tableRef);
|
|
978
978
|
const processedFields = await fieldsToSnake(tableRef, classifiedFields, this.getTableColumns);
|
|
@@ -989,7 +989,7 @@ export class DbParse {
|
|
|
989
989
|
};
|
|
990
990
|
}
|
|
991
991
|
|
|
992
|
-
|
|
992
|
+
prepareSingleTableWhere(table, where, useDefaultStateFilter, required, label, beflyMode = this.beflyMode) {
|
|
993
993
|
const snakeTable = snakeCase(table);
|
|
994
994
|
const normalizedWhere = whereKeysToSnake(clearDeep(where || {}));
|
|
995
995
|
if (required) {
|
|
@@ -1002,7 +1002,7 @@ export class DbParse {
|
|
|
1002
1002
|
};
|
|
1003
1003
|
}
|
|
1004
1004
|
|
|
1005
|
-
async
|
|
1005
|
+
async joinFieldsToSnake(mainTableRef, classifiedFields) {
|
|
1006
1006
|
if (classifiedFields.type !== "join") {
|
|
1007
1007
|
return [];
|
|
1008
1008
|
}
|
package/lib/dbUtil.js
CHANGED
|
@@ -507,6 +507,15 @@ export function processJoinOrderBy(orderBy) {
|
|
|
507
507
|
return normalizeOrderBy(orderBy, processJoinField);
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
+
function replaceJoinOnSegment(segment) {
|
|
511
|
+
return segment.replace(/([a-zA-Z_][a-zA-Z0-9_]*)(\s*\.\s*)([a-zA-Z_][a-zA-Z0-9_]*)/g, (...matchParts) => {
|
|
512
|
+
const left = matchParts[1];
|
|
513
|
+
const dot = matchParts[2];
|
|
514
|
+
const right = matchParts[3];
|
|
515
|
+
return `${snakeCase(left)}${dot}${snakeCase(right)}`;
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
510
519
|
export function processJoinOn(on) {
|
|
511
520
|
if (!isString(on)) {
|
|
512
521
|
return String(on);
|
|
@@ -517,12 +526,6 @@ export function processJoinOn(on) {
|
|
|
517
526
|
return raw;
|
|
518
527
|
}
|
|
519
528
|
|
|
520
|
-
const replaceSegment = (segment) => {
|
|
521
|
-
return segment.replace(/([a-zA-Z_][a-zA-Z0-9_]*)(\s*\.\s*)([a-zA-Z_][a-zA-Z0-9_]*)/g, (_match, left, dot, right) => {
|
|
522
|
-
return `${snakeCase(left)}${dot}${snakeCase(right)}`;
|
|
523
|
-
});
|
|
524
|
-
};
|
|
525
|
-
|
|
526
529
|
let result = "";
|
|
527
530
|
let buffer = "";
|
|
528
531
|
let quote = null;
|
|
@@ -540,7 +543,7 @@ export function processJoinOn(on) {
|
|
|
540
543
|
|
|
541
544
|
if (ch === "'" || ch === '"' || ch === "`") {
|
|
542
545
|
if (buffer.length > 0) {
|
|
543
|
-
result +=
|
|
546
|
+
result += replaceJoinOnSegment(buffer);
|
|
544
547
|
buffer = "";
|
|
545
548
|
}
|
|
546
549
|
quote = ch;
|
|
@@ -554,7 +557,7 @@ export function processJoinOn(on) {
|
|
|
554
557
|
}
|
|
555
558
|
|
|
556
559
|
if (buffer.length > 0) {
|
|
557
|
-
result +=
|
|
560
|
+
result += replaceJoinOnSegment(buffer);
|
|
558
561
|
}
|
|
559
562
|
|
|
560
563
|
return result;
|
package/lib/logger.js
CHANGED
|
@@ -7,8 +7,8 @@ import { stat } from "node:fs/promises";
|
|
|
7
7
|
import { join as nodePathJoin, resolve as nodePathResolve } from "node:path";
|
|
8
8
|
|
|
9
9
|
import { formatYmdHms } from "../utils/datetime.js";
|
|
10
|
-
import { buildSensitiveKeyMatcher, sanitizeLogObject } from "../utils/loggerUtils.js";
|
|
11
10
|
import { isFiniteNumber, isNumber, isPlainObject, isString } from "../utils/is.js";
|
|
11
|
+
import { buildSensitiveKeyMatcher, sanitizeLogObject } from "../utils/loggerUtils.js";
|
|
12
12
|
import { normalizePositiveInt } from "../utils/util.js";
|
|
13
13
|
|
|
14
14
|
// 注意:Logger 可能在运行时/测试中被 process.chdir() 影响。
|
|
@@ -531,7 +531,7 @@ function logWrite(level, input) {
|
|
|
531
531
|
* 日志实例(延迟初始化)
|
|
532
532
|
*/
|
|
533
533
|
export const Logger = {
|
|
534
|
-
info(msg, data) {
|
|
534
|
+
info: function (msg, data) {
|
|
535
535
|
if (isPlainObject(msg)) {
|
|
536
536
|
logWrite("info", msg);
|
|
537
537
|
return;
|
|
@@ -539,7 +539,7 @@ export const Logger = {
|
|
|
539
539
|
|
|
540
540
|
logWrite("info", { msg: msg, data: data });
|
|
541
541
|
},
|
|
542
|
-
warn(msg, data) {
|
|
542
|
+
warn: function (msg, data) {
|
|
543
543
|
if (isPlainObject(msg)) {
|
|
544
544
|
logWrite("warn", msg);
|
|
545
545
|
return;
|
|
@@ -547,7 +547,7 @@ export const Logger = {
|
|
|
547
547
|
|
|
548
548
|
logWrite("warn", { msg: msg, data: data });
|
|
549
549
|
},
|
|
550
|
-
error(msg, err, data) {
|
|
550
|
+
error: function (msg, err, data) {
|
|
551
551
|
if (isPlainObject(msg)) {
|
|
552
552
|
logWrite("error", msg);
|
|
553
553
|
return;
|
|
@@ -555,7 +555,7 @@ export const Logger = {
|
|
|
555
555
|
|
|
556
556
|
logWrite("error", { msg: msg, err: err, data: data });
|
|
557
557
|
},
|
|
558
|
-
debug(msg, data) {
|
|
558
|
+
debug: function (msg, data) {
|
|
559
559
|
if (isPlainObject(msg)) {
|
|
560
560
|
logWrite("debug", msg);
|
|
561
561
|
return;
|
|
@@ -563,16 +563,16 @@ export const Logger = {
|
|
|
563
563
|
|
|
564
564
|
logWrite("debug", { msg: msg, data: data });
|
|
565
565
|
},
|
|
566
|
-
async
|
|
566
|
+
flush: async function () {
|
|
567
567
|
await flush();
|
|
568
568
|
},
|
|
569
|
-
configure(cfg) {
|
|
569
|
+
configure: function (cfg) {
|
|
570
570
|
configure(cfg);
|
|
571
571
|
},
|
|
572
|
-
setMock(mock) {
|
|
572
|
+
setMock: function (mock) {
|
|
573
573
|
setMockLogger(mock);
|
|
574
574
|
},
|
|
575
|
-
async
|
|
575
|
+
shutdown: async function () {
|
|
576
576
|
await shutdown();
|
|
577
577
|
}
|
|
578
578
|
};
|
package/lib/sqlBuilder.js
CHANGED
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
* 提供链式 API 构建 SQL 查询
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { escapeField, escapeTable, resolveQuoteIdent } from "./dbUtil.js";
|
|
7
6
|
import { isNonEmptyString } from "../utils/is.js";
|
|
7
|
+
import { escapeField, escapeTable, resolveQuoteIdent } from "./dbUtil.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* SQL 构建器类
|
|
11
11
|
*/
|
|
12
12
|
export class SqlBuilder {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
queryModel;
|
|
14
|
+
quoteIdentifier;
|
|
15
15
|
|
|
16
16
|
constructor(options = {}) {
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
17
|
+
this.quoteIdentifier = resolveQuoteIdent(options);
|
|
18
|
+
this.queryModel = {
|
|
19
19
|
select: [],
|
|
20
20
|
from: null,
|
|
21
21
|
where: { type: "group", join: "AND", items: [] },
|
|
@@ -30,7 +30,7 @@ export class SqlBuilder {
|
|
|
30
30
|
* 重置构建器状态
|
|
31
31
|
*/
|
|
32
32
|
reset() {
|
|
33
|
-
this.
|
|
33
|
+
this.queryModel = {
|
|
34
34
|
select: [],
|
|
35
35
|
from: null,
|
|
36
36
|
where: { type: "group", join: "AND", items: [] },
|
|
@@ -42,8 +42,8 @@ export class SqlBuilder {
|
|
|
42
42
|
return this;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
const escapedField = escapeField(node.field, this.
|
|
45
|
+
compileOperatorNode(node) {
|
|
46
|
+
const escapedField = escapeField(node.field, this.quoteIdentifier);
|
|
47
47
|
|
|
48
48
|
switch (node.operator) {
|
|
49
49
|
case "$not":
|
|
@@ -87,7 +87,7 @@ export class SqlBuilder {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
compileWhereNode(node) {
|
|
91
91
|
if (!node) {
|
|
92
92
|
return { sql: "", params: [] };
|
|
93
93
|
}
|
|
@@ -100,7 +100,7 @@ export class SqlBuilder {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
if (node.type === "op") {
|
|
103
|
-
return this.
|
|
103
|
+
return this.compileOperatorNode(node);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
if (node.type !== "group" || !node.items || node.items.length === 0) {
|
|
@@ -111,7 +111,7 @@ export class SqlBuilder {
|
|
|
111
111
|
const params = [];
|
|
112
112
|
|
|
113
113
|
for (const item of node.items) {
|
|
114
|
-
const built = this.
|
|
114
|
+
const built = this.compileWhereNode(item);
|
|
115
115
|
if (!built.sql) {
|
|
116
116
|
continue;
|
|
117
117
|
}
|
|
@@ -136,7 +136,7 @@ export class SqlBuilder {
|
|
|
136
136
|
* 获取 WHERE 条件(供 DbHelper 使用)
|
|
137
137
|
*/
|
|
138
138
|
getWhereConditions() {
|
|
139
|
-
const result = this.
|
|
139
|
+
const result = this.compileWhereNode(this.queryModel.where);
|
|
140
140
|
return { sql: result.sql, params: result.params };
|
|
141
141
|
}
|
|
142
142
|
|
|
@@ -146,12 +146,12 @@ export class SqlBuilder {
|
|
|
146
146
|
select(fields = "*") {
|
|
147
147
|
if (Array.isArray(fields)) {
|
|
148
148
|
for (const field of fields) {
|
|
149
|
-
this.
|
|
149
|
+
this.queryModel.select.push({ type: "field", value: field });
|
|
150
150
|
}
|
|
151
151
|
return this;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
this.
|
|
154
|
+
this.queryModel.select.push({ type: "field", value: fields });
|
|
155
155
|
return this;
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -159,7 +159,7 @@ export class SqlBuilder {
|
|
|
159
159
|
* SELECT 原始表达式(不做转义)
|
|
160
160
|
*/
|
|
161
161
|
selectRaw(expr) {
|
|
162
|
-
this.
|
|
162
|
+
this.queryModel.select.push({ type: "raw", value: expr });
|
|
163
163
|
return this;
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -167,7 +167,7 @@ export class SqlBuilder {
|
|
|
167
167
|
* FROM 表名
|
|
168
168
|
*/
|
|
169
169
|
from(table) {
|
|
170
|
-
this.
|
|
170
|
+
this.queryModel.from = { type: "table", value: table.trim() };
|
|
171
171
|
return this;
|
|
172
172
|
}
|
|
173
173
|
|
|
@@ -175,7 +175,7 @@ export class SqlBuilder {
|
|
|
175
175
|
* FROM 原始表达式(不做转义)
|
|
176
176
|
*/
|
|
177
177
|
fromRaw(tableExpr) {
|
|
178
|
-
this.
|
|
178
|
+
this.queryModel.from = { type: "raw", value: tableExpr.trim() };
|
|
179
179
|
return this;
|
|
180
180
|
}
|
|
181
181
|
|
|
@@ -183,19 +183,19 @@ export class SqlBuilder {
|
|
|
183
183
|
* WHERE 条件
|
|
184
184
|
*/
|
|
185
185
|
where(conditionOrField) {
|
|
186
|
-
if (this.
|
|
187
|
-
this.
|
|
186
|
+
if (this.queryModel.where.items.length === 0) {
|
|
187
|
+
this.queryModel.where = conditionOrField;
|
|
188
188
|
return this;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
if (conditionOrField.type === "group" && conditionOrField.join === "AND") {
|
|
192
192
|
for (const item of conditionOrField.items) {
|
|
193
|
-
this.
|
|
193
|
+
this.queryModel.where.items.push(item);
|
|
194
194
|
}
|
|
195
195
|
return this;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
this.
|
|
198
|
+
this.queryModel.where.items.push(conditionOrField);
|
|
199
199
|
return this;
|
|
200
200
|
}
|
|
201
201
|
|
|
@@ -208,7 +208,7 @@ export class SqlBuilder {
|
|
|
208
208
|
paramList = params;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
this.
|
|
211
|
+
this.queryModel.where.items.push({ type: "raw", sql: sql, params: paramList });
|
|
212
212
|
return this;
|
|
213
213
|
}
|
|
214
214
|
|
|
@@ -216,7 +216,7 @@ export class SqlBuilder {
|
|
|
216
216
|
* LEFT JOIN
|
|
217
217
|
*/
|
|
218
218
|
leftJoin(table, on) {
|
|
219
|
-
this.
|
|
219
|
+
this.queryModel.joins.push({ type: "left", table: table, on: on });
|
|
220
220
|
return this;
|
|
221
221
|
}
|
|
222
222
|
|
|
@@ -226,7 +226,7 @@ export class SqlBuilder {
|
|
|
226
226
|
*/
|
|
227
227
|
orderBy(fields) {
|
|
228
228
|
for (const item of fields) {
|
|
229
|
-
this.
|
|
229
|
+
this.queryModel.orderBy.push({ field: item.field, dir: item.dir });
|
|
230
230
|
}
|
|
231
231
|
return this;
|
|
232
232
|
}
|
|
@@ -235,8 +235,8 @@ export class SqlBuilder {
|
|
|
235
235
|
* LIMIT
|
|
236
236
|
*/
|
|
237
237
|
limit(count, offset) {
|
|
238
|
-
this.
|
|
239
|
-
this.
|
|
238
|
+
this.queryModel.limit = count;
|
|
239
|
+
this.queryModel.offset = offset;
|
|
240
240
|
return this;
|
|
241
241
|
}
|
|
242
242
|
|
|
@@ -244,7 +244,7 @@ export class SqlBuilder {
|
|
|
244
244
|
* OFFSET
|
|
245
245
|
*/
|
|
246
246
|
offset(count) {
|
|
247
|
-
this.
|
|
247
|
+
this.queryModel.offset = count;
|
|
248
248
|
return this;
|
|
249
249
|
}
|
|
250
250
|
|
|
@@ -253,29 +253,29 @@ export class SqlBuilder {
|
|
|
253
253
|
*/
|
|
254
254
|
toSelectSql() {
|
|
255
255
|
let selectSql = "*";
|
|
256
|
-
if (this.
|
|
257
|
-
selectSql = this.
|
|
256
|
+
if (this.queryModel.select.length > 0) {
|
|
257
|
+
selectSql = this.queryModel.select
|
|
258
258
|
.map((item) => {
|
|
259
259
|
if (item.type === "raw") {
|
|
260
260
|
return item.value;
|
|
261
261
|
}
|
|
262
|
-
return escapeField(item.value, this.
|
|
262
|
+
return escapeField(item.value, this.quoteIdentifier);
|
|
263
263
|
})
|
|
264
264
|
.join(", ");
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
const params = [];
|
|
268
|
-
let fromSql = this.
|
|
269
|
-
if (this.
|
|
270
|
-
fromSql = escapeTable(this.
|
|
268
|
+
let fromSql = this.queryModel.from.value;
|
|
269
|
+
if (this.queryModel.from.type !== "raw") {
|
|
270
|
+
fromSql = escapeTable(this.queryModel.from.value, this.quoteIdentifier);
|
|
271
271
|
}
|
|
272
272
|
let sql = `SELECT ${selectSql} FROM ${fromSql}`;
|
|
273
273
|
|
|
274
|
-
if (this.
|
|
275
|
-
sql += ` ${this.
|
|
274
|
+
if (this.queryModel.joins.length > 0) {
|
|
275
|
+
sql += ` ${this.queryModel.joins.map((join) => `${join.type.toUpperCase()} JOIN ${escapeTable(join.table, this.quoteIdentifier)} ON ${join.on}`).join(" ")}`;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
const whereResult = this.
|
|
278
|
+
const whereResult = this.compileWhereNode(this.queryModel.where);
|
|
279
279
|
if (whereResult.sql) {
|
|
280
280
|
sql += ` WHERE ${whereResult.sql}`;
|
|
281
281
|
for (const param of whereResult.params) {
|
|
@@ -283,14 +283,14 @@ export class SqlBuilder {
|
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
if (this.
|
|
287
|
-
sql += ` ORDER BY ${this.
|
|
286
|
+
if (this.queryModel.orderBy.length > 0) {
|
|
287
|
+
sql += ` ORDER BY ${this.queryModel.orderBy.map((item) => `${escapeField(item.field, this.quoteIdentifier)} ${item.dir}`).join(", ")}`;
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
if (this.
|
|
291
|
-
sql += ` LIMIT ${this.
|
|
292
|
-
if (this.
|
|
293
|
-
sql += ` OFFSET ${this.
|
|
290
|
+
if (this.queryModel.limit !== null) {
|
|
291
|
+
sql += ` LIMIT ${this.queryModel.limit}`;
|
|
292
|
+
if (this.queryModel.offset !== null && this.queryModel.offset !== undefined) {
|
|
293
|
+
sql += ` OFFSET ${this.queryModel.offset}`;
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
|
@@ -301,7 +301,7 @@ export class SqlBuilder {
|
|
|
301
301
|
* 构建 INSERT 查询
|
|
302
302
|
*/
|
|
303
303
|
toInsertSql(table, data) {
|
|
304
|
-
const escapedTable = escapeTable(table, this.
|
|
304
|
+
const escapedTable = escapeTable(table, this.quoteIdentifier);
|
|
305
305
|
|
|
306
306
|
if (Array.isArray(data)) {
|
|
307
307
|
if (data.length === 0) {
|
|
@@ -310,7 +310,7 @@ export class SqlBuilder {
|
|
|
310
310
|
|
|
311
311
|
const firstRow = data[0] || {};
|
|
312
312
|
const fields = Object.keys(firstRow);
|
|
313
|
-
const escapedFields = fields.map((field) => escapeField(field, this.
|
|
313
|
+
const escapedFields = fields.map((field) => escapeField(field, this.quoteIdentifier));
|
|
314
314
|
const placeholders = fields.map(() => "?").join(", ");
|
|
315
315
|
const values = data.map(() => `(${placeholders})`).join(", ");
|
|
316
316
|
const params = [];
|
|
@@ -329,7 +329,7 @@ export class SqlBuilder {
|
|
|
329
329
|
|
|
330
330
|
const fields = Object.keys(data);
|
|
331
331
|
|
|
332
|
-
const escapedFields = fields.map((field) => escapeField(field, this.
|
|
332
|
+
const escapedFields = fields.map((field) => escapeField(field, this.quoteIdentifier));
|
|
333
333
|
const placeholders = fields.map(() => "?").join(", ");
|
|
334
334
|
const params = [];
|
|
335
335
|
for (const field of fields) {
|
|
@@ -353,13 +353,13 @@ export class SqlBuilder {
|
|
|
353
353
|
params.push(value);
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
const whereResult = this.
|
|
356
|
+
const whereResult = this.compileWhereNode(this.queryModel.where);
|
|
357
357
|
for (const param of whereResult.params) {
|
|
358
358
|
params.push(param);
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
return {
|
|
362
|
-
sql: `UPDATE ${escapeTable(table, this.
|
|
362
|
+
sql: `UPDATE ${escapeTable(table, this.quoteIdentifier)} SET ${fields.map((field) => `${escapeField(field, this.quoteIdentifier)} = ?`).join(", ")} WHERE ${whereResult.sql}`,
|
|
363
363
|
params: params
|
|
364
364
|
};
|
|
365
365
|
}
|
|
@@ -368,9 +368,9 @@ export class SqlBuilder {
|
|
|
368
368
|
* 构建 DELETE 查询
|
|
369
369
|
*/
|
|
370
370
|
toDeleteSql(table) {
|
|
371
|
-
const whereResult = this.
|
|
371
|
+
const whereResult = this.compileWhereNode(this.queryModel.where);
|
|
372
372
|
return {
|
|
373
|
-
sql: `DELETE FROM ${escapeTable(table, this.
|
|
373
|
+
sql: `DELETE FROM ${escapeTable(table, this.quoteIdentifier)} WHERE ${whereResult.sql}`,
|
|
374
374
|
params: whereResult.params
|
|
375
375
|
};
|
|
376
376
|
}
|
|
@@ -380,17 +380,17 @@ export class SqlBuilder {
|
|
|
380
380
|
*/
|
|
381
381
|
toCountSql() {
|
|
382
382
|
const params = [];
|
|
383
|
-
let fromSql = this.
|
|
384
|
-
if (this.
|
|
385
|
-
fromSql = escapeTable(this.
|
|
383
|
+
let fromSql = this.queryModel.from.value;
|
|
384
|
+
if (this.queryModel.from.type !== "raw") {
|
|
385
|
+
fromSql = escapeTable(this.queryModel.from.value, this.quoteIdentifier);
|
|
386
386
|
}
|
|
387
387
|
let sql = `SELECT COUNT(*) as total FROM ${fromSql}`;
|
|
388
388
|
|
|
389
|
-
if (this.
|
|
390
|
-
sql += ` ${this.
|
|
389
|
+
if (this.queryModel.joins.length > 0) {
|
|
390
|
+
sql += ` ${this.queryModel.joins.map((join) => `${join.type.toUpperCase()} JOIN ${escapeTable(join.table, this.quoteIdentifier)} ON ${join.on}`).join(" ")}`;
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
const whereResult = this.
|
|
393
|
+
const whereResult = this.compileWhereNode(this.queryModel.where);
|
|
394
394
|
if (whereResult.sql) {
|
|
395
395
|
sql += ` WHERE ${whereResult.sql}`;
|
|
396
396
|
for (const param of whereResult.params) {
|
package/lib/validator.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* 纯静态类设计,简洁易用
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import RegexAliases from "../configs/regexpAlias.json";
|
|
7
6
|
import { FIELD_RULE_DEFAULT_MAX, FIELD_RULE_DEFAULT_MIN, FIELD_RULE_INPUT_TYPES } from "../configs/constConfig.js";
|
|
8
|
-
import
|
|
7
|
+
import RegexAliases from "../configs/regexpAlias.json";
|
|
9
8
|
import { isFiniteNumber, isIntegerNumber, isJsonObject, isJsonPrimitive, isJsonStructure, isNullable, isNumber, isPlainObject, isRegexInput, isString } from "../utils/is.js";
|
|
9
|
+
import { getCompiledRegex } from "../utils/regexpUtil.js";
|
|
10
10
|
|
|
11
11
|
const INPUT_TYPE_SET = new Set(FIELD_RULE_INPUT_TYPES);
|
|
12
12
|
const RULE_ALLOWED_KEYS = new Set(["input", "check", "name", "min", "max", "detail"]);
|
|
@@ -50,10 +50,10 @@ export class Validator {
|
|
|
50
50
|
|
|
51
51
|
// 参数检查
|
|
52
52
|
if (!isPlainObject(data)) {
|
|
53
|
-
return this.buildResult({
|
|
53
|
+
return this.buildResult({ rootError: "数据必须是对象格式" });
|
|
54
54
|
}
|
|
55
55
|
if (!isPlainObject(rules)) {
|
|
56
|
-
return this.buildResult({
|
|
56
|
+
return this.buildResult({ rootError: "验证规则必须是对象格式" });
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// 检查必填字段
|
package/package.json
CHANGED
package/paths.js
CHANGED
|
@@ -15,8 +15,8 @@ import { fileURLToPath } from "node:url";
|
|
|
15
15
|
import { dirname, isAbsolute, join, normalize, resolve } from "pathe";
|
|
16
16
|
|
|
17
17
|
// 当前文件的路径信息
|
|
18
|
-
const
|
|
19
|
-
const
|
|
18
|
+
const moduleFilePath = fileURLToPath(import.meta.url);
|
|
19
|
+
const moduleDir = dirname(moduleFilePath);
|
|
20
20
|
|
|
21
21
|
// ==================== Core 框架路径 ====================
|
|
22
22
|
|
|
@@ -24,48 +24,48 @@ const __dirname = dirname(__filename);
|
|
|
24
24
|
* Core 框架根目录
|
|
25
25
|
* @description packages/core/
|
|
26
26
|
*/
|
|
27
|
-
export const coreDir =
|
|
27
|
+
export const coreDir = moduleDir;
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Core 框架 dist 目录
|
|
31
31
|
* @description 源码态为 packages/core/dist;dist 运行态为 packages/core/dist
|
|
32
32
|
*/
|
|
33
|
-
export const coreDistDir = normalize(
|
|
33
|
+
export const coreDistDir = normalize(moduleDir).endsWith("/dist") ? moduleDir : join(moduleDir, "dist");
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Core 框架检查目录
|
|
37
37
|
* @description packages/core/checks/
|
|
38
38
|
* @usage 存放启动检查模块(返回 boolean 的 default 函数)
|
|
39
39
|
*/
|
|
40
|
-
export const coreCheckDir = join(
|
|
40
|
+
export const coreCheckDir = join(moduleDir, "checks");
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Core 框架插件目录
|
|
44
44
|
* @description packages/core/plugins/
|
|
45
45
|
* @usage 存放内置插件(mysql, logger, redis, tool 等)
|
|
46
46
|
*/
|
|
47
|
-
export const corePluginDir = join(
|
|
47
|
+
export const corePluginDir = join(moduleDir, "plugins");
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Core 框架钩子目录
|
|
51
51
|
* @description packages/core/hooks/
|
|
52
52
|
* @usage 存放内置钩子(auth, cors, parser 等)
|
|
53
53
|
*/
|
|
54
|
-
export const coreHookDir = join(
|
|
54
|
+
export const coreHookDir = join(moduleDir, "hooks");
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Core 框架 API 目录
|
|
58
58
|
* @description packages/core/apis/
|
|
59
59
|
* @usage 存放框架级别的 API 接口
|
|
60
60
|
*/
|
|
61
|
-
export const coreApiDir = join(
|
|
61
|
+
export const coreApiDir = join(moduleDir, "apis");
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* Core 框架表定义目录
|
|
65
65
|
* @description packages/core/tables/
|
|
66
66
|
* @usage 存放框架核心表定义(JSON 格式)
|
|
67
67
|
*/
|
|
68
|
-
export const coreTableDir = join(
|
|
68
|
+
export const coreTableDir = join(moduleDir, "tables");
|
|
69
69
|
|
|
70
70
|
// ==================== 用户项目路径 ====================
|
|
71
71
|
|
package/plugins/cache.js
CHANGED
package/plugins/email.js
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* 提供邮件发送功能,支持 SMTP 配置
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { Logger } from "../lib/logger.js";
|
|
7
6
|
import nodemailer from "nodemailer";
|
|
8
7
|
|
|
8
|
+
import { Logger } from "../lib/logger.js";
|
|
9
|
+
|
|
9
10
|
export default {
|
|
10
11
|
order: 7,
|
|
11
|
-
async
|
|
12
|
+
handler: async function (befly) {
|
|
12
13
|
const config = befly?.config?.email || {};
|
|
13
14
|
let transporter = null;
|
|
14
15
|
|
package/plugins/logger.js
CHANGED
package/plugins/mysql.js
CHANGED
package/plugins/redis.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Logger } from "#root/lib/logger.js";
|
|
|
7
7
|
import { importDefault } from "#root/utils/importDefault.js";
|
|
8
8
|
import { isNonEmptyString, isPlainObject } from "#root/utils/is.js";
|
|
9
9
|
import { camelCase } from "#root/utils/util.js";
|
|
10
|
+
|
|
10
11
|
import { buildSyncDbDiff, groupSyncDbColumns } from "./diff.js";
|
|
11
12
|
import { printSyncDbProcessLog } from "./report.js";
|
|
12
13
|
|
|
@@ -65,7 +66,7 @@ export async function prepareSyncDbBaseContext(mysqlConfig) {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
if (skippedBeflyTables.size > 0) {
|
|
68
|
-
const skippedTables = Array.from(skippedBeflyTables).
|
|
69
|
+
const skippedTables = Array.from(skippedBeflyTables).toSorted((left, right) => left.localeCompare(right));
|
|
69
70
|
Logger.info("已跳过 befly_ 前缀表,不参与对比", {
|
|
70
71
|
skippedTableCount: skippedBeflyTables.size,
|
|
71
72
|
skippedTables: skippedTables
|
package/scripts/syncDb/diff.js
CHANGED
|
@@ -2,6 +2,7 @@ import { join } from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import { isNonEmptyString, isPlainObject } from "#root/utils/is.js";
|
|
4
4
|
import { camelCase, snakeCase } from "#root/utils/util.js";
|
|
5
|
+
|
|
5
6
|
import { printSyncDbProcessLog } from "./report.js";
|
|
6
7
|
import { toSyncDbFieldDef } from "./transform.js";
|
|
7
8
|
|
package/scripts/syncDb/index.js
CHANGED
|
@@ -2,8 +2,9 @@ import { join, resolve } from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import { Connect } from "#root/lib/connect.js";
|
|
4
4
|
import { Logger } from "#root/lib/logger.js";
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
import { prepareSyncDbBaseContext } from "./context.js";
|
|
7
|
+
import { applySyncDbDiff } from "./diff.js";
|
|
7
8
|
import { printSyncDbDiffSummary, printSyncDbProcessLog, writeSyncDbReport } from "./report.js";
|
|
8
9
|
|
|
9
10
|
export async function syncDb(mysqlConfig) {
|
package/scripts/syncDb/report.js
CHANGED
|
@@ -4,6 +4,7 @@ import { dirname } from "node:path";
|
|
|
4
4
|
import { Logger } from "#root/lib/logger.js";
|
|
5
5
|
import { isNonEmptyString } from "#root/utils/is.js";
|
|
6
6
|
import { camelCase } from "#root/utils/util.js";
|
|
7
|
+
|
|
7
8
|
import { toSyncDbFieldDef } from "./transform.js";
|
|
8
9
|
|
|
9
10
|
export function printSyncDbProcessLog(message) {
|
package/utils/fieldClear.js
CHANGED
|
@@ -13,7 +13,7 @@ export function fieldClear(data, options = {}) {
|
|
|
13
13
|
const { pickKeys, omitKeys, keepValues, excludeValues, keepMap } = options;
|
|
14
14
|
|
|
15
15
|
const filterObj = (obj) => {
|
|
16
|
-
|
|
16
|
+
const result = {};
|
|
17
17
|
let keys = Object.keys(obj);
|
|
18
18
|
if (pickKeys && pickKeys.length) {
|
|
19
19
|
keys = keys.filter((k) => pickKeys.includes(k));
|