befly 3.22.8 → 3.23.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 +1 -1
- package/checks/api.js +11 -8
- package/checks/config.js +12 -9
- package/checks/hook.js +9 -5
- package/checks/menu.js +11 -8
- package/checks/plugin.js +9 -5
- package/checks/table.js +39 -10
- package/index.js +13 -22
- package/lib/connect.js +37 -37
- package/lib/dbHelper.js +43 -102
- package/lib/dbParse.js +49 -49
- package/lib/dbUtil.js +16 -30
- package/lib/logger.js +16 -18
- package/lib/sqlBuilder.js +39 -66
- package/package.json +5 -5
- package/scripts/syncDb/diff.js +25 -10
- package/scripts/syncDb/transform.js +2 -2
- package/sync/api.js +30 -47
- package/sync/menu.js +24 -44
- package/sync/syncUtil.js +35 -0
- package/utils/util.js +13 -0
package/apis/auth/login.js
CHANGED
package/checks/api.js
CHANGED
|
@@ -98,14 +98,17 @@ const apiSchema = z
|
|
|
98
98
|
const apiListSchema = z.array(apiSchema);
|
|
99
99
|
|
|
100
100
|
export async function checkApi(apis) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (!schemaResult.success) {
|
|
105
|
-
const errors = formatZodIssues(schemaResult.error.issues, { items: apis, itemLabel: "api" });
|
|
106
|
-
Logger.warn("接口校验失败", { errors: errors }, false);
|
|
107
|
-
hasError = true;
|
|
101
|
+
const result = apiListSchema.safeParse(apis);
|
|
102
|
+
if (result.success) {
|
|
103
|
+
return false;
|
|
108
104
|
}
|
|
109
105
|
|
|
110
|
-
|
|
106
|
+
Logger.warn(
|
|
107
|
+
"接口校验失败",
|
|
108
|
+
{
|
|
109
|
+
errors: formatZodIssues(result.error.issues, { items: apis, itemLabel: "api" })
|
|
110
|
+
},
|
|
111
|
+
false
|
|
112
|
+
);
|
|
113
|
+
return true;
|
|
111
114
|
}
|
package/checks/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
|
|
3
|
-
import { Logger } from "../lib/logger.js";
|
|
4
3
|
import { RUN_MODE_VALUES } from "../configs/constConfig.js";
|
|
4
|
+
import { Logger } from "../lib/logger.js";
|
|
5
5
|
import { formatZodIssues } from "../utils/formatZodIssues.js";
|
|
6
6
|
import { isNoTrimStringAllowEmpty, isValidTimeZone } from "../utils/is.js";
|
|
7
7
|
|
|
@@ -109,14 +109,17 @@ const configSchema = z
|
|
|
109
109
|
* - checkConfig 校验的是“最终合并后的运行时配置对象”,用于阻断错误配置带病启动。
|
|
110
110
|
*/
|
|
111
111
|
export async function checkConfig(config) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (!schemaResult.success) {
|
|
116
|
-
const errors = formatZodIssues(schemaResult.error.issues, { item: config, itemLabel: "config" });
|
|
117
|
-
Logger.warn("配置校验失败", { errors: errors }, false);
|
|
118
|
-
hasError = true;
|
|
112
|
+
const result = configSchema.safeParse(config);
|
|
113
|
+
if (result.success) {
|
|
114
|
+
return false;
|
|
119
115
|
}
|
|
120
116
|
|
|
121
|
-
|
|
117
|
+
Logger.warn(
|
|
118
|
+
"配置校验失败",
|
|
119
|
+
{
|
|
120
|
+
errors: formatZodIssues(result.error.issues, { item: config, itemLabel: "config" })
|
|
121
|
+
},
|
|
122
|
+
false
|
|
123
|
+
);
|
|
124
|
+
return true;
|
|
122
125
|
}
|
package/checks/hook.js
CHANGED
|
@@ -25,12 +25,16 @@ export async function checkHook(hooks) {
|
|
|
25
25
|
let hasError = false;
|
|
26
26
|
|
|
27
27
|
for (const hook of hooks) {
|
|
28
|
-
const
|
|
29
|
-
if (!
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
const result = hookSchema.safeParse(hook);
|
|
29
|
+
if (!result.success) {
|
|
30
|
+
Logger.warn(
|
|
31
|
+
"钩子校验失败",
|
|
32
|
+
{
|
|
33
|
+
errors: formatZodIssues(result.error.issues, { item: hook, itemLabel: "hook" })
|
|
34
|
+
},
|
|
35
|
+
false
|
|
36
|
+
);
|
|
32
37
|
hasError = true;
|
|
33
|
-
continue;
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
|
package/checks/menu.js
CHANGED
|
@@ -45,14 +45,17 @@ const menuListSchema = z.array(menuSchema).superRefine((menuList, refineCtx) =>
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
export const checkMenu = async (menus) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (!schemaResult.success) {
|
|
52
|
-
const errors = formatZodIssues(schemaResult.error.issues, { items: menus, itemLabel: "menus" });
|
|
53
|
-
Logger.warn("菜单校验失败", { errors: errors }, false);
|
|
54
|
-
hasError = true;
|
|
48
|
+
const result = menuListSchema.safeParse(menus);
|
|
49
|
+
if (result.success) {
|
|
50
|
+
return false;
|
|
55
51
|
}
|
|
56
52
|
|
|
57
|
-
|
|
53
|
+
Logger.warn(
|
|
54
|
+
"菜单校验失败",
|
|
55
|
+
{
|
|
56
|
+
errors: formatZodIssues(result.error.issues, { items: menus, itemLabel: "menus" })
|
|
57
|
+
},
|
|
58
|
+
false
|
|
59
|
+
);
|
|
60
|
+
return true;
|
|
58
61
|
};
|
package/checks/plugin.js
CHANGED
|
@@ -25,12 +25,16 @@ export async function checkPlugin(plugins) {
|
|
|
25
25
|
let hasError = false;
|
|
26
26
|
|
|
27
27
|
for (const plugin of plugins) {
|
|
28
|
-
const
|
|
29
|
-
if (!
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
const result = pluginSchema.safeParse(plugin);
|
|
29
|
+
if (!result.success) {
|
|
30
|
+
Logger.warn(
|
|
31
|
+
"插件校验失败",
|
|
32
|
+
{
|
|
33
|
+
errors: formatZodIssues(result.error.issues, { item: plugin, itemLabel: "plugin" })
|
|
34
|
+
},
|
|
35
|
+
false
|
|
36
|
+
);
|
|
32
37
|
hasError = true;
|
|
33
|
-
continue;
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
|
package/checks/table.js
CHANGED
|
@@ -4,13 +4,23 @@ import { FIELD_RULE_DEFAULT_MAX, FIELD_RULE_DEFAULT_MIN, FIELD_RULE_INPUT_TYPES
|
|
|
4
4
|
import { Logger } from "../lib/logger.js";
|
|
5
5
|
import { formatZodIssues } from "../utils/formatZodIssues.js";
|
|
6
6
|
import { isNoTrimStringAllowEmpty, isNullable, isRegexInput } from "../utils/is.js";
|
|
7
|
+
import { snakeCase } from "../utils/util.js";
|
|
7
8
|
|
|
8
9
|
z.config(z.locales.zhCN());
|
|
9
10
|
|
|
10
11
|
const lowerCamelRegex = /^_?[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/;
|
|
12
|
+
const lowerSnakeRegex = /^_?[a-z][a-z0-9]*(?:_[a-z0-9]+)+$/;
|
|
11
13
|
const noTrimString = z.string().refine(isNoTrimStringAllowEmpty, "不允许首尾空格");
|
|
12
14
|
const inputSchema = z.enum(FIELD_RULE_INPUT_TYPES);
|
|
13
15
|
|
|
16
|
+
function isValidFieldName(fieldName) {
|
|
17
|
+
if (String(fieldName).includes("_")) {
|
|
18
|
+
return lowerSnakeRegex.test(fieldName);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return lowerCamelRegex.test(fieldName);
|
|
22
|
+
}
|
|
23
|
+
|
|
14
24
|
function addIssue(context, path, message) {
|
|
15
25
|
context.addIssue({
|
|
16
26
|
path: path,
|
|
@@ -75,7 +85,21 @@ const fieldDefSchema = z
|
|
|
75
85
|
}
|
|
76
86
|
});
|
|
77
87
|
|
|
78
|
-
const tableContentSchema = z.record(z.string().
|
|
88
|
+
const tableContentSchema = z.record(z.string().refine(isValidFieldName, "字段名必须为 lowerCamelCase 或 snake_case"), fieldDefSchema).superRefine((value, context) => {
|
|
89
|
+
const fieldNameMap = new Map();
|
|
90
|
+
|
|
91
|
+
for (const fieldName of Object.keys(value)) {
|
|
92
|
+
const dbFieldName = snakeCase(fieldName);
|
|
93
|
+
const previousFieldName = fieldNameMap.get(dbFieldName);
|
|
94
|
+
|
|
95
|
+
if (previousFieldName) {
|
|
96
|
+
addIssue(context, [fieldName], `字段名 ${fieldName} 与 ${previousFieldName} 指向同一数据库字段 ${dbFieldName}`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fieldNameMap.set(dbFieldName, fieldName);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
79
103
|
const tableRegistrySchema = z.record(z.string().regex(lowerCamelRegex), tableContentSchema);
|
|
80
104
|
|
|
81
105
|
/**
|
|
@@ -83,15 +107,20 @@ const tableRegistrySchema = z.record(z.string().regex(lowerCamelRegex), tableCon
|
|
|
83
107
|
* @throws 当检查失败时抛出异常
|
|
84
108
|
*/
|
|
85
109
|
export async function checkTable(tables) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const schemaResult = tableRegistrySchema.safeParse(tables);
|
|
90
|
-
if (!schemaResult.success) {
|
|
91
|
-
const errors = formatZodIssues(schemaResult.error.issues, { items: tables, itemLabel: "table" });
|
|
92
|
-
Logger.warn("表结构校验失败", { errors: errors }, false);
|
|
93
|
-
hasError = true;
|
|
110
|
+
const result = tableRegistrySchema.safeParse(tables);
|
|
111
|
+
if (result.success) {
|
|
112
|
+
return false;
|
|
94
113
|
}
|
|
95
114
|
|
|
96
|
-
|
|
115
|
+
Logger.warn(
|
|
116
|
+
"表结构校验失败",
|
|
117
|
+
{
|
|
118
|
+
errors: formatZodIssues(result.error.issues, {
|
|
119
|
+
items: tables,
|
|
120
|
+
itemLabel: "table"
|
|
121
|
+
})
|
|
122
|
+
},
|
|
123
|
+
false
|
|
124
|
+
);
|
|
125
|
+
return true;
|
|
97
126
|
}
|
package/index.js
CHANGED
|
@@ -35,35 +35,27 @@ import { deepMerge } from "./utils/deepMerge.js";
|
|
|
35
35
|
export { syncDbApply as syncDb } from "./scripts/syncDb/index.js";
|
|
36
36
|
|
|
37
37
|
function prefixMenuPaths(menus, prefix) {
|
|
38
|
-
|
|
39
|
-
for (const menu of menus) {
|
|
40
|
-
const nextPath = menu.path === "/" ? `/${prefix}` : `/${prefix}${menu.path}`;
|
|
38
|
+
return menus.map((menu) => {
|
|
41
39
|
const nextMenu = {
|
|
42
40
|
name: menu.name,
|
|
43
|
-
path:
|
|
41
|
+
path: menu.path === "/" ? `/${prefix}` : `/${prefix}${menu.path}`,
|
|
44
42
|
sort: menu.sort
|
|
45
43
|
};
|
|
44
|
+
|
|
46
45
|
if (Array.isArray(menu.children)) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
sort: child.sort
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
nextMenu.children = children;
|
|
46
|
+
nextMenu.children = menu.children.map((child) => ({
|
|
47
|
+
name: child.name,
|
|
48
|
+
path: child.path,
|
|
49
|
+
sort: child.sort
|
|
50
|
+
}));
|
|
56
51
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
|
|
53
|
+
return nextMenu;
|
|
54
|
+
});
|
|
60
55
|
}
|
|
61
56
|
|
|
62
57
|
async function ensureSyncPrerequisites(ctx) {
|
|
63
|
-
const missingCtxKeys = [];
|
|
64
|
-
if (!ctx.redis) missingCtxKeys.push("ctx.redis");
|
|
65
|
-
if (!ctx.mysql) missingCtxKeys.push("ctx.mysql");
|
|
66
|
-
if (!ctx.cache) missingCtxKeys.push("ctx.cache");
|
|
58
|
+
const missingCtxKeys = ["ctx.redis", "ctx.mysql", "ctx.cache"].filter((key) => !ctx[key.slice(4)]);
|
|
67
59
|
if (missingCtxKeys.length > 0) {
|
|
68
60
|
throw new Error(`启动失败:${missingCtxKeys.join("、")} 未初始化`, {
|
|
69
61
|
cause: null,
|
|
@@ -78,8 +70,7 @@ async function ensureSyncPrerequisites(ctx) {
|
|
|
78
70
|
const missingTables = [];
|
|
79
71
|
|
|
80
72
|
for (const table of requiredTables) {
|
|
81
|
-
|
|
82
|
-
if (!tableExistsResult.data) {
|
|
73
|
+
if (!(await ctx.mysql.tableExists(table)).data) {
|
|
83
74
|
missingTables.push(table);
|
|
84
75
|
}
|
|
85
76
|
}
|
package/lib/connect.js
CHANGED
|
@@ -6,6 +6,25 @@
|
|
|
6
6
|
import { RedisClient, SQL } from "bun";
|
|
7
7
|
import { Logger } from "./logger.js";
|
|
8
8
|
|
|
9
|
+
function getRunMode() {
|
|
10
|
+
return Bun.env.RUN_MODE || "unknown";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function buildRedisUrl(config) {
|
|
14
|
+
if (config.username) {
|
|
15
|
+
const encodedUser = encodeURIComponent(config.username);
|
|
16
|
+
const encodedPass = encodeURIComponent(config.password || "");
|
|
17
|
+
return `redis://${encodedUser}:${encodedPass}@${config.hostname}:${config.port}/${config.db}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (config.password) {
|
|
21
|
+
const encodedPass = encodeURIComponent(config.password);
|
|
22
|
+
return `redis://:${encodedPass}@${config.hostname}:${config.port}/${config.db}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return `redis://${config.hostname}:${config.port}/${config.db}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
9
28
|
/**
|
|
10
29
|
* 连接管理器
|
|
11
30
|
*/
|
|
@@ -29,13 +48,26 @@ export class Connect {
|
|
|
29
48
|
return this.redisClient;
|
|
30
49
|
}
|
|
31
50
|
|
|
51
|
+
static async handleConnectError(clientKey, label, subsystem, hostname, error) {
|
|
52
|
+
await this[clientKey]?.close();
|
|
53
|
+
this[clientKey] = null;
|
|
54
|
+
Logger.error(`${label} 连接失败 (${hostname} ${getRunMode()})`, error);
|
|
55
|
+
throw new Error(`${label} 连接失败 (${hostname})`, {
|
|
56
|
+
cause: error,
|
|
57
|
+
code: "runtime",
|
|
58
|
+
subsystem: subsystem,
|
|
59
|
+
operation: "connect",
|
|
60
|
+
runMode: getRunMode()
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
32
64
|
/**
|
|
33
65
|
* 连接数据库
|
|
34
66
|
* @param config - 数据库配置
|
|
35
67
|
*/
|
|
36
68
|
static async connectMysql(config) {
|
|
37
69
|
try {
|
|
38
|
-
|
|
70
|
+
this.mysqlClient = new SQL({
|
|
39
71
|
adapter: "mysql",
|
|
40
72
|
hostname: config.hostname,
|
|
41
73
|
port: config.port,
|
|
@@ -44,23 +76,12 @@ export class Connect {
|
|
|
44
76
|
password: config.password,
|
|
45
77
|
max: config.max,
|
|
46
78
|
bigint: true
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
this.mysqlClient = new SQL(sqlConfig);
|
|
79
|
+
});
|
|
50
80
|
|
|
51
81
|
await this.mysqlClient`SELECT 1`;
|
|
52
82
|
Logger.info(`Mysql 连接成功 (${config.hostname})`);
|
|
53
83
|
} catch (error) {
|
|
54
|
-
await this.mysqlClient
|
|
55
|
-
this.mysqlClient = null;
|
|
56
|
-
Logger.error(`Mysql 连接失败 (${config.hostname} ${Bun.env.RUN_MODE || "unkonw"})`, error);
|
|
57
|
-
throw new Error(`Mysql 连接失败 (${config.hostname})`, {
|
|
58
|
-
cause: error,
|
|
59
|
-
code: "runtime",
|
|
60
|
-
subsystem: "mysql",
|
|
61
|
-
operation: "connect",
|
|
62
|
-
runMode: Bun.env.RUN_MODE || "unknown"
|
|
63
|
-
});
|
|
84
|
+
await this.handleConnectError("mysqlClient", "Mysql", "mysql", config.hostname, error);
|
|
64
85
|
}
|
|
65
86
|
}
|
|
66
87
|
|
|
@@ -70,19 +91,7 @@ export class Connect {
|
|
|
70
91
|
*/
|
|
71
92
|
static async connectRedis(config) {
|
|
72
93
|
try {
|
|
73
|
-
|
|
74
|
-
if (config.username) {
|
|
75
|
-
const encodedUser = encodeURIComponent(config.username);
|
|
76
|
-
const encodedPass = encodeURIComponent(config.password || "");
|
|
77
|
-
authPart = `${encodedUser}:${encodedPass}@`;
|
|
78
|
-
} else if (config.password) {
|
|
79
|
-
const encodedPass = encodeURIComponent(config.password);
|
|
80
|
-
authPart = `:${encodedPass}@`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const redisUrl = `redis://${authPart}${config.hostname}:${config.port}/${config.db}`;
|
|
84
|
-
|
|
85
|
-
this.redisClient = new RedisClient(redisUrl);
|
|
94
|
+
this.redisClient = new RedisClient(buildRedisUrl(config));
|
|
86
95
|
// Called when successfully connected to Redis server
|
|
87
96
|
this.redisClient.onconnect = () => {
|
|
88
97
|
Logger.info(`Redis 连接成功 (${config.hostname})`);
|
|
@@ -100,16 +109,7 @@ export class Connect {
|
|
|
100
109
|
// Eagerly verify the connection before returning
|
|
101
110
|
await this.redisClient.ping();
|
|
102
111
|
} catch (error) {
|
|
103
|
-
await this.redisClient
|
|
104
|
-
this.redisClient = null;
|
|
105
|
-
Logger.error(`Redis 连接失败 (${config.hostname} ${Bun.env.RUN_MODE || "unkonw"})`, error);
|
|
106
|
-
throw new Error(`Redis 连接失败 (${config.hostname})`, {
|
|
107
|
-
cause: error,
|
|
108
|
-
code: "runtime",
|
|
109
|
-
subsystem: "redis",
|
|
110
|
-
operation: "connect",
|
|
111
|
-
runMode: Bun.env.RUN_MODE || "unknown"
|
|
112
|
-
});
|
|
112
|
+
await this.handleConnectError("redisClient", "Redis", "redis", config.hostname, error);
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
package/lib/dbHelper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fieldClear } from "../utils/fieldClear.js";
|
|
2
2
|
import { isNonEmptyString, isNullable, isNumber, isPlainObject, isString } from "../utils/is.js";
|
|
3
|
-
import {
|
|
3
|
+
import { camelCase, canConvertToNumber, keysToSnake, snakeCase } from "../utils/util.js";
|
|
4
4
|
import { Logger } from "./logger.js";
|
|
5
5
|
import { DbParse } from "./dbParse.js";
|
|
6
6
|
import { SqlBuilder } from "./sqlBuilder.js";
|
|
@@ -26,19 +26,7 @@ function quoteIdentMySql(identifier) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function normalizeSqlMetaNumber(value) {
|
|
29
|
-
|
|
30
|
-
return 0;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (typeof value !== "number") {
|
|
34
|
-
return 0;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!Number.isFinite(value)) {
|
|
38
|
-
return 0;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return value;
|
|
29
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
42
30
|
}
|
|
43
31
|
|
|
44
32
|
function normalizeBigIntValues(value) {
|
|
@@ -232,17 +220,8 @@ function assertBatchInsertRowsConsistent(rows, options) {
|
|
|
232
220
|
code: "validation"
|
|
233
221
|
});
|
|
234
222
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
for (const field of fields) {
|
|
238
|
-
if (!(field in row)) {
|
|
239
|
-
throw new Error(`批量插入缺少字段 (table: ${options.table}, rowIndex: ${i}, field: ${field})`, {
|
|
240
|
-
cause: null,
|
|
241
|
-
code: "validation"
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
if (row[field] === undefined) {
|
|
245
|
-
throw new Error(`批量插入字段值不能为 undefined (table: ${options.table}, rowIndex: ${i}, field: ${field})`, {
|
|
223
|
+
if (row[key] === undefined) {
|
|
224
|
+
throw new Error(`批量插入字段值不能为 undefined (table: ${options.table}, rowIndex: ${i}, field: ${key})`, {
|
|
246
225
|
cause: null,
|
|
247
226
|
code: "validation"
|
|
248
227
|
});
|
|
@@ -294,6 +273,20 @@ class DbHelper {
|
|
|
294
273
|
return tableInfo;
|
|
295
274
|
}
|
|
296
275
|
|
|
276
|
+
normalizeDbFieldNames(row) {
|
|
277
|
+
if (!row || !isPlainObject(row)) {
|
|
278
|
+
return row;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const result = {};
|
|
282
|
+
|
|
283
|
+
for (const [key, value] of Object.entries(row)) {
|
|
284
|
+
result[camelCase(key)] = value;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
|
|
297
290
|
async execute(sql, params) {
|
|
298
291
|
if (!this.sql) {
|
|
299
292
|
throw new Error("数据库连接未初始化", {
|
|
@@ -422,7 +415,7 @@ class DbHelper {
|
|
|
422
415
|
return {};
|
|
423
416
|
}
|
|
424
417
|
|
|
425
|
-
const camelRow =
|
|
418
|
+
const camelRow = this.normalizeDbFieldNames(row);
|
|
426
419
|
const deserialized = deserializeArrayFields(camelRow);
|
|
427
420
|
if (!deserialized) {
|
|
428
421
|
return {};
|
|
@@ -437,7 +430,7 @@ class DbHelper {
|
|
|
437
430
|
}
|
|
438
431
|
|
|
439
432
|
normalizeListData(list) {
|
|
440
|
-
const camelList =
|
|
433
|
+
const camelList = list.map((item) => this.normalizeDbFieldNames(item));
|
|
441
434
|
const deserializedList = camelList.map((item) => deserializeArrayFields(item)).filter((item) => item !== null);
|
|
442
435
|
return normalizeBigIntValues(deserializedList);
|
|
443
436
|
}
|
|
@@ -709,7 +702,11 @@ class DbHelper {
|
|
|
709
702
|
const result = listExecuteRes.data || [];
|
|
710
703
|
|
|
711
704
|
if (result.length >= WARNING_LIMIT) {
|
|
712
|
-
Logger.warn("getAll 返回数据过多,建议使用 getList 分页查询", {
|
|
705
|
+
Logger.warn("getAll 返回数据过多,建议使用 getList 分页查询", {
|
|
706
|
+
table: options.table,
|
|
707
|
+
count: result.length,
|
|
708
|
+
total: total
|
|
709
|
+
});
|
|
713
710
|
}
|
|
714
711
|
|
|
715
712
|
if (result.length >= MAX_LIMIT) {
|
|
@@ -740,47 +737,6 @@ class DbHelper {
|
|
|
740
737
|
return { data: exists, sql: executeRes.sql };
|
|
741
738
|
}
|
|
742
739
|
|
|
743
|
-
async getFieldValue(options) {
|
|
744
|
-
const parsed = await this.createDbParse().parseFieldValue(options);
|
|
745
|
-
const builder = this.createSqlBuilder().select(parsed.prepared.fields).from(parsed.prepared.table).where(parsed.prepared.where);
|
|
746
|
-
this.applyLeftJoins(builder, parsed.prepared.leftJoins);
|
|
747
|
-
if (parsed.prepared.orderBy && parsed.prepared.orderBy.length > 0) {
|
|
748
|
-
builder.orderBy(parsed.prepared.orderBy);
|
|
749
|
-
}
|
|
750
|
-
const { sql, params } = builder.toSelectSql();
|
|
751
|
-
const executeRes = await this.execute(sql, params);
|
|
752
|
-
const result = this.normalizeRowData(executeRes.data?.[0] || null);
|
|
753
|
-
let value = null;
|
|
754
|
-
let hasValue = false;
|
|
755
|
-
|
|
756
|
-
if (isPlainObject(result)) {
|
|
757
|
-
if (Object.hasOwn(result, parsed.field)) {
|
|
758
|
-
value = result[parsed.field];
|
|
759
|
-
hasValue = true;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
if (!hasValue) {
|
|
763
|
-
const camelField = parsed.field.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
764
|
-
if (camelField !== parsed.field && Object.hasOwn(result, camelField)) {
|
|
765
|
-
value = result[camelField];
|
|
766
|
-
hasValue = true;
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
if (!hasValue) {
|
|
771
|
-
const snakeField = parsed.field.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
772
|
-
if (snakeField !== parsed.field && Object.hasOwn(result, snakeField)) {
|
|
773
|
-
value = result[snakeField];
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
return {
|
|
779
|
-
data: value,
|
|
780
|
-
sql: executeRes.sql
|
|
781
|
-
};
|
|
782
|
-
}
|
|
783
|
-
|
|
784
740
|
async insData(options) {
|
|
785
741
|
const parsed = this.createDbParse().parseInsert(options);
|
|
786
742
|
const { table, snakeTable, data } = parsed;
|
|
@@ -939,7 +895,12 @@ class DbHelper {
|
|
|
939
895
|
const inputData = this._prepareWriteInputData(parsed.data);
|
|
940
896
|
assertWriteDataHasFields(inputData, "更新数据必须至少有一个字段", parsed.snakeTable);
|
|
941
897
|
|
|
942
|
-
const processed = this._buildUpdateRow({
|
|
898
|
+
const processed = this._buildUpdateRow({
|
|
899
|
+
data: parsed.data,
|
|
900
|
+
now: Date.now(),
|
|
901
|
+
allowState: true,
|
|
902
|
+
beflyMode: this.beflyMode
|
|
903
|
+
});
|
|
943
904
|
assertWriteDataHasFields(processed, "更新数据必须至少有一个字段", parsed.snakeTable);
|
|
944
905
|
const builder = this.createSqlBuilder().where(parsed.where);
|
|
945
906
|
const { sql, params } = builder.toUpdateSql(parsed.snakeTable, processed);
|
|
@@ -954,14 +915,18 @@ class DbHelper {
|
|
|
954
915
|
|
|
955
916
|
async delData(options) {
|
|
956
917
|
const parsed = this.createDbParse().parseDelete(options, false);
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
processed
|
|
918
|
+
let processed;
|
|
919
|
+
|
|
920
|
+
if (parsed.deleteMode === "manual") {
|
|
921
|
+
processed = this._prepareWriteInputData(parsed.data);
|
|
922
|
+
assertWriteDataHasFields(processed, "delData 在 beflyMode=manual 时 data 必须至少有一个字段", parsed.snakeTable);
|
|
923
|
+
} else {
|
|
924
|
+
const now = Date.now();
|
|
925
|
+
processed = {
|
|
926
|
+
state: 0,
|
|
927
|
+
deleted_at: now,
|
|
928
|
+
updated_at: now
|
|
929
|
+
};
|
|
965
930
|
}
|
|
966
931
|
|
|
967
932
|
const builder = this.createSqlBuilder().where(parsed.where);
|
|
@@ -989,30 +954,6 @@ class DbHelper {
|
|
|
989
954
|
};
|
|
990
955
|
}
|
|
991
956
|
|
|
992
|
-
async disableData(options) {
|
|
993
|
-
const { table, where } = options;
|
|
994
|
-
|
|
995
|
-
return await this.updData({
|
|
996
|
-
table: table,
|
|
997
|
-
data: {
|
|
998
|
-
state: 2
|
|
999
|
-
},
|
|
1000
|
-
where: where
|
|
1001
|
-
});
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
async enableData(options) {
|
|
1005
|
-
const { table, where } = options;
|
|
1006
|
-
|
|
1007
|
-
return await this.updData({
|
|
1008
|
-
table: table,
|
|
1009
|
-
data: {
|
|
1010
|
-
state: 1
|
|
1011
|
-
},
|
|
1012
|
-
where: where
|
|
1013
|
-
});
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
957
|
async increment(table, field, where, value = 1) {
|
|
1017
958
|
const parsed = this.createDbParse().parseIncrement(table, field, where, value, "increment");
|
|
1018
959
|
|