befly 3.10.18 → 3.11.1
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/README.md +83 -307
- package/dist/befly.config.d.ts +7 -0
- package/{befly.config.ts → dist/befly.config.js} +11 -36
- package/dist/befly.js +15621 -0
- package/dist/befly.min.js +21 -0
- package/dist/checks/checkApi.d.ts +1 -0
- package/{checks/checkApi.ts → dist/checks/checkApi.js} +12 -30
- package/dist/checks/checkHook.d.ts +1 -0
- package/dist/checks/checkHook.js +86 -0
- package/dist/checks/checkMenu.d.ts +7 -0
- package/{checks/checkMenu.ts → dist/checks/checkMenu.js} +18 -53
- package/dist/checks/checkPlugin.d.ts +1 -0
- package/dist/checks/checkPlugin.js +86 -0
- package/dist/checks/checkTable.d.ts +6 -0
- package/{checks/checkTable.ts → dist/checks/checkTable.js} +17 -41
- package/dist/configs/presetFields.d.ts +4 -0
- package/{configs/presetFields.ts → dist/configs/presetFields.js} +1 -1
- package/dist/configs/presetRegexp.d.ts +145 -0
- package/{utils/regex.ts → dist/configs/presetRegexp.js} +8 -31
- package/dist/hooks/auth.d.ts +7 -0
- package/{hooks/auth.ts → dist/hooks/auth.js} +8 -10
- package/dist/hooks/cors.d.ts +11 -0
- package/{hooks/cors.ts → dist/hooks/cors.js} +5 -13
- package/dist/hooks/parser.d.ts +14 -0
- package/{hooks/parser.ts → dist/hooks/parser.js} +31 -45
- package/dist/hooks/permission.d.ts +14 -0
- package/{hooks/permission.ts → dist/hooks/permission.js} +16 -25
- package/dist/hooks/validator.d.ts +11 -0
- package/{hooks/validator.ts → dist/hooks/validator.js} +9 -14
- package/dist/index.d.ts +26 -0
- package/{main.ts → dist/index.js} +61 -100
- package/dist/lib/asyncContext.d.ts +21 -0
- package/dist/lib/asyncContext.js +27 -0
- package/dist/lib/cacheHelper.d.ts +95 -0
- package/{lib/cacheHelper.ts → dist/lib/cacheHelper.js} +45 -105
- package/dist/lib/cacheKeys.d.ts +23 -0
- package/{lib/cacheKeys.ts → dist/lib/cacheKeys.js} +5 -10
- package/dist/lib/cipher.d.ts +153 -0
- package/{lib/cipher.ts → dist/lib/cipher.js} +23 -44
- package/dist/lib/connect.d.ts +91 -0
- package/{lib/connect.ts → dist/lib/connect.js} +47 -88
- package/dist/lib/dbDialect.d.ts +87 -0
- package/{lib/dbDialect.ts → dist/lib/dbDialect.js} +32 -112
- package/dist/lib/dbHelper.d.ts +204 -0
- package/{lib/dbHelper.ts → dist/lib/dbHelper.js} +82 -241
- package/dist/lib/dbUtils.d.ts +68 -0
- package/{lib/dbUtils.ts → dist/lib/dbUtils.js} +51 -126
- package/dist/lib/jwt.d.ts +13 -0
- package/{lib/jwt.ts → dist/lib/jwt.js} +11 -32
- package/dist/lib/logger.d.ts +42 -0
- package/dist/lib/logger.js +1144 -0
- package/dist/lib/redisHelper.d.ts +185 -0
- package/{lib/redisHelper.ts → dist/lib/redisHelper.js} +97 -141
- package/dist/lib/sqlBuilder.d.ts +160 -0
- package/{lib/sqlBuilder.ts → dist/lib/sqlBuilder.js} +132 -278
- package/dist/lib/sqlCheck.d.ts +23 -0
- package/{lib/sqlCheck.ts → dist/lib/sqlCheck.js} +24 -41
- package/dist/lib/validator.d.ts +45 -0
- package/{lib/validator.ts → dist/lib/validator.js} +44 -61
- package/dist/loader/loadApis.d.ts +12 -0
- package/{loader/loadApis.ts → dist/loader/loadApis.js} +10 -20
- package/dist/loader/loadHooks.d.ts +7 -0
- package/dist/loader/loadHooks.js +35 -0
- package/dist/loader/loadPlugins.d.ts +8 -0
- package/{loader/loadPlugins.ts → dist/loader/loadPlugins.js} +14 -26
- package/dist/paths.d.ts +93 -0
- package/{paths.ts → dist/paths.js} +6 -19
- package/dist/plugins/cache.d.ts +16 -0
- package/{plugins/cache.ts → dist/plugins/cache.js} +7 -12
- package/dist/plugins/cipher.d.ts +12 -0
- package/{plugins/cipher.ts → dist/plugins/cipher.js} +4 -6
- package/dist/plugins/config.d.ts +12 -0
- package/dist/plugins/config.js +8 -0
- package/dist/plugins/db.d.ts +16 -0
- package/{plugins/db.ts → dist/plugins/db.js} +11 -17
- package/dist/plugins/jwt.d.ts +12 -0
- package/dist/plugins/jwt.js +12 -0
- package/dist/plugins/logger.d.ts +32 -0
- package/{plugins/logger.ts → dist/plugins/logger.js} +5 -8
- package/dist/plugins/redis.d.ts +16 -0
- package/{plugins/redis.ts → dist/plugins/redis.js} +9 -12
- package/dist/plugins/tool.d.ts +81 -0
- package/{plugins/tool.ts → dist/plugins/tool.js} +9 -30
- package/dist/router/api.d.ts +14 -0
- package/dist/router/api.js +107 -0
- package/dist/router/static.d.ts +9 -0
- package/{router/static.ts → dist/router/static.js} +20 -34
- package/dist/scripts/ensureDist.d.ts +1 -0
- package/dist/scripts/ensureDist.js +296 -0
- package/dist/sync/syncApi.d.ts +3 -0
- package/{sync/syncApi.ts → dist/sync/syncApi.js} +35 -55
- package/dist/sync/syncCache.d.ts +2 -0
- package/{sync/syncCache.ts → dist/sync/syncCache.js} +1 -6
- package/dist/sync/syncDev.d.ts +6 -0
- package/{sync/syncDev.ts → dist/sync/syncDev.js} +29 -62
- package/dist/sync/syncMenu.d.ts +14 -0
- package/{sync/syncMenu.ts → dist/sync/syncMenu.js} +65 -125
- package/dist/sync/syncTable.d.ts +151 -0
- package/{sync/syncTable.ts → dist/sync/syncTable.js} +172 -379
- package/{types → dist/types}/api.d.ts +12 -51
- package/dist/types/api.js +4 -0
- package/{types → dist/types}/befly.d.ts +32 -227
- package/dist/types/befly.js +4 -0
- package/{types → dist/types}/cache.d.ts +7 -15
- package/dist/types/cache.js +4 -0
- package/dist/types/cipher.d.ts +27 -0
- package/dist/types/cipher.js +7 -0
- package/{types → dist/types}/common.d.ts +8 -33
- package/dist/types/common.js +5 -0
- package/{types → dist/types}/context.d.ts +3 -5
- package/dist/types/context.js +4 -0
- package/{types → dist/types}/crypto.d.ts +0 -3
- package/dist/types/crypto.js +4 -0
- package/dist/types/database.d.ts +138 -0
- package/dist/types/database.js +4 -0
- package/dist/types/hook.d.ts +17 -0
- package/dist/types/hook.js +6 -0
- package/dist/types/jwt.d.ts +75 -0
- package/dist/types/jwt.js +4 -0
- package/dist/types/logger.d.ts +59 -0
- package/dist/types/logger.js +6 -0
- package/dist/types/plugin.d.ts +16 -0
- package/dist/types/plugin.js +6 -0
- package/dist/types/redis.d.ts +71 -0
- package/dist/types/redis.js +4 -0
- package/{types/roleApisCache.ts → dist/types/roleApisCache.d.ts} +0 -2
- package/dist/types/roleApisCache.js +8 -0
- package/dist/types/sync.d.ts +92 -0
- package/dist/types/sync.js +4 -0
- package/dist/types/table.d.ts +34 -0
- package/dist/types/table.js +4 -0
- package/dist/types/validate.d.ts +67 -0
- package/dist/types/validate.js +4 -0
- package/dist/utils/calcPerfTime.d.ts +4 -0
- package/{utils/calcPerfTime.ts → dist/utils/calcPerfTime.js} +3 -3
- package/dist/utils/convertBigIntFields.d.ts +11 -0
- package/{utils/convertBigIntFields.ts → dist/utils/convertBigIntFields.js} +5 -9
- package/dist/utils/cors.d.ts +8 -0
- package/{utils/cors.ts → dist/utils/cors.js} +1 -3
- package/dist/utils/disableMenusGlob.d.ts +13 -0
- package/{utils/disableMenusGlob.ts → dist/utils/disableMenusGlob.js} +9 -29
- package/dist/utils/fieldClear.d.ts +11 -0
- package/{utils/fieldClear.ts → dist/utils/fieldClear.js} +15 -33
- package/dist/utils/getClientIp.d.ts +6 -0
- package/{utils/getClientIp.ts → dist/utils/getClientIp.js} +1 -7
- package/dist/utils/importDefault.d.ts +1 -0
- package/dist/utils/importDefault.js +29 -0
- package/dist/utils/isDirentDirectory.d.ts +2 -0
- package/{utils/isDirentDirectory.ts → dist/utils/isDirentDirectory.js} +3 -8
- package/dist/utils/loadMenuConfigs.d.ts +29 -0
- package/{utils/loadMenuConfigs.ts → dist/utils/loadMenuConfigs.js} +66 -52
- package/dist/utils/mergeAndConcat.d.ts +7 -0
- package/dist/utils/mergeAndConcat.js +72 -0
- package/dist/utils/processAtSymbol.d.ts +4 -0
- package/{utils/processFields.ts → dist/utils/processAtSymbol.js} +5 -9
- package/dist/utils/processInfo.d.ts +24 -0
- package/{utils/process.ts → dist/utils/processInfo.js} +2 -18
- package/dist/utils/response.d.ts +20 -0
- package/{utils/response.ts → dist/utils/response.js} +28 -49
- package/dist/utils/scanAddons.d.ts +17 -0
- package/{utils/scanAddons.ts → dist/utils/scanAddons.js} +7 -41
- package/dist/utils/scanConfig.d.ts +26 -0
- package/{utils/scanConfig.ts → dist/utils/scanConfig.js} +28 -66
- package/dist/utils/scanCoreBuiltins.d.ts +3 -0
- package/dist/utils/scanCoreBuiltins.js +65 -0
- package/dist/utils/scanFiles.d.ts +30 -0
- package/{utils/scanFiles.ts → dist/utils/scanFiles.js} +44 -71
- package/dist/utils/scanSources.d.ts +10 -0
- package/dist/utils/scanSources.js +46 -0
- package/dist/utils/sortModules.d.ts +28 -0
- package/{utils/sortModules.ts → dist/utils/sortModules.js} +26 -66
- package/dist/utils/util.d.ts +84 -0
- package/dist/utils/util.js +262 -0
- package/package.json +26 -34
- package/.gitignore +0 -0
- package/bunfig.toml +0 -3
- package/checks/checkHook.ts +0 -48
- package/checks/checkPlugin.ts +0 -48
- package/configs/presetRegexp.ts +0 -225
- package/docs/README.md +0 -98
- package/docs/api/api.md +0 -1921
- package/docs/guide/examples.md +0 -926
- package/docs/guide/quickstart.md +0 -354
- package/docs/hooks/auth.md +0 -38
- package/docs/hooks/cors.md +0 -28
- package/docs/hooks/hook.md +0 -838
- package/docs/hooks/parser.md +0 -19
- package/docs/hooks/rateLimit.md +0 -47
- package/docs/infra/redis.md +0 -628
- package/docs/plugins/cipher.md +0 -61
- package/docs/plugins/database.md +0 -189
- package/docs/plugins/plugin.md +0 -986
- package/docs/reference/addon.md +0 -510
- package/docs/reference/config.md +0 -573
- package/docs/reference/logger.md +0 -495
- package/docs/reference/sync.md +0 -478
- package/docs/reference/table.md +0 -763
- package/docs/reference/validator.md +0 -620
- package/lib/asyncContext.ts +0 -43
- package/lib/logger.ts +0 -811
- package/loader/loadHooks.ts +0 -51
- package/plugins/config.ts +0 -13
- package/plugins/jwt.ts +0 -15
- package/router/api.ts +0 -130
- package/tsconfig.json +0 -8
- package/types/database.d.ts +0 -541
- package/types/hook.d.ts +0 -25
- package/types/jwt.d.ts +0 -118
- package/types/logger.d.ts +0 -65
- package/types/plugin.d.ts +0 -19
- package/types/redis.d.ts +0 -83
- package/types/sync.d.ts +0 -398
- package/types/table.d.ts +0 -216
- package/types/validate.d.ts +0 -69
- package/utils/arrayKeysToCamel.ts +0 -18
- package/utils/configTypes.ts +0 -3
- package/utils/genShortId.ts +0 -12
- package/utils/importDefault.ts +0 -21
- package/utils/keysToCamel.ts +0 -22
- package/utils/keysToSnake.ts +0 -22
- package/utils/pickFields.ts +0 -19
- package/utils/scanSources.ts +0 -64
- package/utils/sqlLog.ts +0 -37
|
@@ -2,108 +2,78 @@
|
|
|
2
2
|
* 数据库助手 - TypeScript 版本
|
|
3
3
|
* 提供数据库 CRUD 操作的封装
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { fieldClear } from "../utils/fieldClear.ts";
|
|
15
|
-
import { keysToCamel } from "../utils/keysToCamel.ts";
|
|
16
|
-
import { CacheKeys } from "./cacheKeys.ts";
|
|
17
|
-
import { MySqlDialect } from "./dbDialect.ts";
|
|
18
|
-
import { DbUtils } from "./dbUtils.ts";
|
|
19
|
-
import { Logger } from "./logger.ts";
|
|
20
|
-
import { SqlBuilder } from "./sqlBuilder.ts";
|
|
21
|
-
import { SqlCheck } from "./sqlCheck.ts";
|
|
22
|
-
|
|
5
|
+
import { convertBigIntFields } from "../utils/convertBigIntFields";
|
|
6
|
+
import { fieldClear } from "../utils/fieldClear";
|
|
7
|
+
import { arrayKeysToCamel, keysToCamel, snakeCase } from "../utils/util";
|
|
8
|
+
import { CacheKeys } from "./cacheKeys";
|
|
9
|
+
import { MySqlDialect } from "./dbDialect";
|
|
10
|
+
import { DbUtils } from "./dbUtils";
|
|
11
|
+
import { Logger } from "./logger";
|
|
12
|
+
import { SqlBuilder } from "./sqlBuilder";
|
|
13
|
+
import { SqlCheck } from "./sqlCheck";
|
|
23
14
|
const TABLE_COLUMNS_CACHE_TTL_SECONDS = 3600;
|
|
24
|
-
|
|
25
|
-
type RedisCacheLike = {
|
|
26
|
-
getObject<T = any>(key: string): Promise<T | null>;
|
|
27
|
-
setObject<T = any>(key: string, obj: T, ttl?: number | null): Promise<string | null>;
|
|
28
|
-
genTimeID(): Promise<number>;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
15
|
/**
|
|
32
16
|
* 数据库助手类
|
|
33
17
|
*/
|
|
34
18
|
export class DbHelper {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
redis;
|
|
20
|
+
dialect;
|
|
21
|
+
sql = null;
|
|
22
|
+
isTransaction = false;
|
|
40
23
|
/**
|
|
41
24
|
* 构造函数
|
|
42
25
|
* @param redis - Redis 实例
|
|
43
26
|
* @param sql - Bun SQL 客户端(可选,用于事务)
|
|
44
27
|
*/
|
|
45
|
-
constructor(options
|
|
28
|
+
constructor(options) {
|
|
46
29
|
this.redis = options.redis;
|
|
47
30
|
this.sql = options.sql || null;
|
|
48
31
|
this.isTransaction = !!options.sql;
|
|
49
|
-
|
|
50
32
|
// 默认使用 MySQL 方言(当前 core 的表结构/语法也主要基于 MySQL)
|
|
51
33
|
this.dialect = options.dialect ? options.dialect : new MySqlDialect();
|
|
52
34
|
}
|
|
53
|
-
|
|
54
|
-
private createSqlBuilder(): SqlBuilder {
|
|
35
|
+
createSqlBuilder() {
|
|
55
36
|
return new SqlBuilder({ quoteIdent: this.dialect.quoteIdent.bind(this.dialect) });
|
|
56
37
|
}
|
|
57
|
-
|
|
58
38
|
/**
|
|
59
39
|
* 获取表的所有字段名(Redis 缓存)
|
|
60
40
|
* @param table - 表名(下划线格式)
|
|
61
41
|
* @returns 字段名数组(下划线格式)
|
|
62
42
|
*/
|
|
63
|
-
|
|
43
|
+
async getTableColumns(table) {
|
|
64
44
|
// 1. 先查 Redis 缓存
|
|
65
45
|
const cacheKey = CacheKeys.tableColumns(table);
|
|
66
|
-
const columns = await this.redis.getObject
|
|
67
|
-
|
|
46
|
+
const columns = await this.redis.getObject(cacheKey);
|
|
68
47
|
if (columns && columns.length > 0) {
|
|
69
48
|
return columns;
|
|
70
49
|
}
|
|
71
|
-
|
|
72
50
|
// 2. 缓存未命中,查询数据库
|
|
73
51
|
const query = this.dialect.getTableColumnsQuery(table);
|
|
74
52
|
const execRes = await this.executeWithConn(query.sql, query.params);
|
|
75
53
|
const result = execRes.data;
|
|
76
|
-
|
|
77
54
|
if (!result || result.length === 0) {
|
|
78
55
|
throw new Error(`表 ${table} 不存在或没有字段`);
|
|
79
56
|
}
|
|
80
|
-
|
|
81
57
|
const columnNames = this.dialect.getTableColumnsFromResult(result);
|
|
82
|
-
|
|
83
58
|
// 3. 写入 Redis 缓存
|
|
84
59
|
const cacheRes = await this.redis.setObject(cacheKey, columnNames, TABLE_COLUMNS_CACHE_TTL_SECONDS);
|
|
85
60
|
if (cacheRes === null) {
|
|
86
61
|
Logger.warn({ table: table, cacheKey: cacheKey }, "表字段缓存写入 Redis 失败");
|
|
87
62
|
}
|
|
88
|
-
|
|
89
63
|
return columnNames;
|
|
90
64
|
}
|
|
91
|
-
|
|
92
65
|
/**
|
|
93
66
|
* 统一的查询参数预处理方法
|
|
94
67
|
*/
|
|
95
|
-
|
|
68
|
+
async prepareQueryOptions(options) {
|
|
96
69
|
const cleanWhere = fieldClear(options.where || {}, { excludeValues: [null, undefined] });
|
|
97
70
|
const hasJoins = options.joins && options.joins.length > 0;
|
|
98
|
-
|
|
99
71
|
// 联查时使用特殊处理逻辑
|
|
100
72
|
if (hasJoins) {
|
|
101
73
|
// 联查时字段直接处理(支持表名.字段名格式)
|
|
102
74
|
const processedFields = (options.fields || []).map((f) => DbUtils.processJoinField(f));
|
|
103
|
-
|
|
104
75
|
const normalizedTableRef = DbUtils.normalizeTableRef(options.table);
|
|
105
76
|
const mainQualifier = DbUtils.getJoinMainQualifier(options.table);
|
|
106
|
-
|
|
107
77
|
return {
|
|
108
78
|
table: normalizedTableRef,
|
|
109
79
|
tableQualifier: mainQualifier,
|
|
@@ -115,10 +85,8 @@ export class DbHelper {
|
|
|
115
85
|
limit: options.limit || 10
|
|
116
86
|
};
|
|
117
87
|
}
|
|
118
|
-
|
|
119
88
|
// 单表查询使用原有逻辑
|
|
120
89
|
const processedFields = await DbUtils.fieldsToSnake(snakeCase(options.table), options.fields || [], this.getTableColumns.bind(this));
|
|
121
|
-
|
|
122
90
|
return {
|
|
123
91
|
table: snakeCase(options.table),
|
|
124
92
|
tableQualifier: snakeCase(options.table),
|
|
@@ -130,17 +98,15 @@ export class DbHelper {
|
|
|
130
98
|
limit: options.limit || 10
|
|
131
99
|
};
|
|
132
100
|
}
|
|
133
|
-
|
|
134
101
|
/**
|
|
135
102
|
* 为 builder 添加 JOIN
|
|
136
103
|
*/
|
|
137
|
-
|
|
138
|
-
if (!joins || joins.length === 0)
|
|
139
|
-
|
|
104
|
+
applyJoins(builder, joins) {
|
|
105
|
+
if (!joins || joins.length === 0)
|
|
106
|
+
return;
|
|
140
107
|
for (const join of joins) {
|
|
141
108
|
const processedTable = DbUtils.normalizeTableRef(join.table);
|
|
142
109
|
const type = join.type || "left";
|
|
143
|
-
|
|
144
110
|
switch (type) {
|
|
145
111
|
case "inner":
|
|
146
112
|
builder.innerJoin(processedTable, join.on);
|
|
@@ -161,47 +127,41 @@ export class DbHelper {
|
|
|
161
127
|
* - DbHelper 不再负责打印 SQL 调试日志
|
|
162
128
|
* - SQL 信息由调用方基于返回值中的 sql 自行输出
|
|
163
129
|
*/
|
|
164
|
-
|
|
130
|
+
async executeWithConn(sqlStr, params) {
|
|
165
131
|
if (!this.sql) {
|
|
166
132
|
throw new Error("数据库连接未初始化");
|
|
167
133
|
}
|
|
168
|
-
|
|
169
134
|
// 强制类型检查:只接受字符串类型的 SQL
|
|
170
135
|
if (typeof sqlStr !== "string") {
|
|
171
136
|
throw new Error(`executeWithConn 只接受字符串类型的 SQL,收到类型: ${typeof sqlStr},值: ${JSON.stringify(sqlStr)}`);
|
|
172
137
|
}
|
|
173
|
-
|
|
174
138
|
// 记录开始时间
|
|
175
139
|
const startTime = Date.now();
|
|
176
|
-
|
|
177
140
|
const safeParams = Array.isArray(params) ? params : [];
|
|
178
|
-
|
|
179
141
|
try {
|
|
180
142
|
// 使用 sql.unsafe 执行查询
|
|
181
143
|
let result;
|
|
182
144
|
if (safeParams.length > 0) {
|
|
183
145
|
result = await this.sql.unsafe(sqlStr, safeParams);
|
|
184
|
-
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
185
148
|
result = await this.sql.unsafe(sqlStr);
|
|
186
149
|
}
|
|
187
|
-
|
|
188
150
|
// 计算执行时间
|
|
189
151
|
const duration = Date.now() - startTime;
|
|
190
|
-
|
|
191
|
-
const sql: SqlInfo = {
|
|
152
|
+
const sql = {
|
|
192
153
|
sql: sqlStr,
|
|
193
154
|
params: safeParams,
|
|
194
155
|
duration: duration
|
|
195
156
|
};
|
|
196
|
-
|
|
197
157
|
return {
|
|
198
158
|
data: result,
|
|
199
159
|
sql: sql
|
|
200
160
|
};
|
|
201
|
-
}
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
202
163
|
const duration = Date.now() - startTime;
|
|
203
|
-
|
|
204
|
-
const enhancedError: any = new Error(`SQL执行失败: ${error.message}`);
|
|
164
|
+
const enhancedError = new Error(`SQL执行失败: ${error.message}`);
|
|
205
165
|
enhancedError.originalError = error;
|
|
206
166
|
enhancedError.params = safeParams;
|
|
207
167
|
enhancedError.duration = duration;
|
|
@@ -213,36 +173,31 @@ export class DbHelper {
|
|
|
213
173
|
throw enhancedError;
|
|
214
174
|
}
|
|
215
175
|
}
|
|
216
|
-
|
|
217
176
|
/**
|
|
218
177
|
* 执行原生 SQL(内部工具/同步脚本专用)
|
|
219
178
|
*
|
|
220
179
|
* - 复用当前 DbHelper 持有的连接/事务
|
|
221
180
|
* - 统一走 executeWithConn,保持参数校验与错误行为一致
|
|
222
181
|
*/
|
|
223
|
-
|
|
182
|
+
async unsafe(sqlStr, params) {
|
|
224
183
|
return await this.executeWithConn(sqlStr, params);
|
|
225
184
|
}
|
|
226
|
-
|
|
227
185
|
/**
|
|
228
186
|
* 检查表是否存在
|
|
229
187
|
* @param tableName - 表名(支持小驼峰,会自动转换为下划线)
|
|
230
188
|
* @returns 表是否存在
|
|
231
189
|
*/
|
|
232
|
-
async tableExists(tableName
|
|
190
|
+
async tableExists(tableName) {
|
|
233
191
|
// 将表名转换为下划线格式
|
|
234
192
|
const snakeTableName = snakeCase(tableName);
|
|
235
|
-
|
|
236
193
|
const query = this.dialect.tableExistsQuery(snakeTableName);
|
|
237
194
|
const execRes = await this.executeWithConn(query.sql, query.params);
|
|
238
195
|
const exists = (execRes.data?.[0]?.count || 0) > 0;
|
|
239
|
-
|
|
240
196
|
return {
|
|
241
197
|
data: exists,
|
|
242
198
|
sql: execRes.sql
|
|
243
199
|
};
|
|
244
200
|
}
|
|
245
|
-
|
|
246
201
|
/**
|
|
247
202
|
* 查询记录数
|
|
248
203
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
@@ -260,27 +215,22 @@ export class DbHelper {
|
|
|
260
215
|
* where: { 'o.state': 1 }
|
|
261
216
|
* });
|
|
262
217
|
*/
|
|
263
|
-
async getCount(options
|
|
264
|
-
const { table, where, joins, tableQualifier } = await this.prepareQueryOptions(options
|
|
265
|
-
|
|
218
|
+
async getCount(options) {
|
|
219
|
+
const { table, where, joins, tableQualifier } = await this.prepareQueryOptions(options);
|
|
266
220
|
const builder = this.createSqlBuilder()
|
|
267
221
|
.selectRaw("COUNT(*) as count")
|
|
268
222
|
.from(table)
|
|
269
223
|
.where(DbUtils.addDefaultStateFilter(where, tableQualifier, !!joins));
|
|
270
|
-
|
|
271
224
|
// 添加 JOIN
|
|
272
225
|
this.applyJoins(builder, joins);
|
|
273
|
-
|
|
274
226
|
const { sql, params } = builder.toSelectSql();
|
|
275
227
|
const execRes = await this.executeWithConn(sql, params);
|
|
276
228
|
const count = execRes.data?.[0]?.count || 0;
|
|
277
|
-
|
|
278
229
|
return {
|
|
279
230
|
data: count,
|
|
280
231
|
sql: execRes.sql
|
|
281
232
|
};
|
|
282
233
|
}
|
|
283
|
-
|
|
284
234
|
/**
|
|
285
235
|
* 查询单条数据
|
|
286
236
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换;联查时可带别名如 'order o')
|
|
@@ -297,21 +247,17 @@ export class DbHelper {
|
|
|
297
247
|
* where: { 'o.id': 1 }
|
|
298
248
|
* })
|
|
299
249
|
*/
|
|
300
|
-
async getOne
|
|
250
|
+
async getOne(options) {
|
|
301
251
|
const { table, fields, where, joins, tableQualifier } = await this.prepareQueryOptions(options);
|
|
302
|
-
|
|
303
252
|
const builder = this.createSqlBuilder()
|
|
304
253
|
.select(fields)
|
|
305
254
|
.from(table)
|
|
306
255
|
.where(DbUtils.addDefaultStateFilter(where, tableQualifier, !!joins));
|
|
307
|
-
|
|
308
256
|
// 添加 JOIN
|
|
309
257
|
this.applyJoins(builder, joins);
|
|
310
|
-
|
|
311
258
|
const { sql, params } = builder.toSelectSql();
|
|
312
259
|
const execRes = await this.executeWithConn(sql, params);
|
|
313
260
|
const result = execRes.data;
|
|
314
|
-
|
|
315
261
|
// 字段名转换:下划线 → 小驼峰
|
|
316
262
|
const row = result?.[0] || null;
|
|
317
263
|
if (!row) {
|
|
@@ -320,26 +266,22 @@ export class DbHelper {
|
|
|
320
266
|
sql: execRes.sql
|
|
321
267
|
};
|
|
322
268
|
}
|
|
323
|
-
|
|
324
|
-
const camelRow = keysToCamel<T>(row);
|
|
325
|
-
|
|
269
|
+
const camelRow = keysToCamel(row);
|
|
326
270
|
// 反序列化数组字段(JSON 字符串 → 数组)
|
|
327
|
-
const deserialized = DbUtils.deserializeArrayFields
|
|
271
|
+
const deserialized = DbUtils.deserializeArrayFields(camelRow);
|
|
328
272
|
if (!deserialized) {
|
|
329
273
|
return {
|
|
330
274
|
data: null,
|
|
331
275
|
sql: execRes.sql
|
|
332
276
|
};
|
|
333
277
|
}
|
|
334
|
-
|
|
335
278
|
// 转换 BIGINT 字段(id, pid 等)为数字类型
|
|
336
|
-
const data = convertBigIntFields
|
|
279
|
+
const data = convertBigIntFields([deserialized])[0];
|
|
337
280
|
return {
|
|
338
281
|
data: data,
|
|
339
282
|
sql: execRes.sql
|
|
340
283
|
};
|
|
341
284
|
}
|
|
342
|
-
|
|
343
285
|
/**
|
|
344
286
|
* 查询列表(带分页)
|
|
345
287
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换;联查时可带别名)
|
|
@@ -362,9 +304,8 @@ export class DbHelper {
|
|
|
362
304
|
* limit: 10
|
|
363
305
|
* })
|
|
364
306
|
*/
|
|
365
|
-
async getList
|
|
307
|
+
async getList(options) {
|
|
366
308
|
const prepared = await this.prepareQueryOptions(options);
|
|
367
|
-
|
|
368
309
|
// 参数上限校验
|
|
369
310
|
if (prepared.page < 1 || prepared.page > 10000) {
|
|
370
311
|
throw new Error(`页码必须在 1 到 10000 之间 (table: ${options.table}, page: ${prepared.page}, limit: ${prepared.limit})`);
|
|
@@ -372,20 +313,15 @@ export class DbHelper {
|
|
|
372
313
|
if (prepared.limit < 1 || prepared.limit > 1000) {
|
|
373
314
|
throw new Error(`每页数量必须在 1 到 1000 之间 (table: ${options.table}, page: ${prepared.page}, limit: ${prepared.limit})`);
|
|
374
315
|
}
|
|
375
|
-
|
|
376
316
|
// 构建查询
|
|
377
317
|
const whereFiltered = DbUtils.addDefaultStateFilter(prepared.where, prepared.tableQualifier, !!prepared.joins);
|
|
378
|
-
|
|
379
318
|
// 查询总数
|
|
380
319
|
const countBuilder = this.createSqlBuilder().selectRaw("COUNT(*) as total").from(prepared.table).where(whereFiltered);
|
|
381
|
-
|
|
382
320
|
// 添加 JOIN(计数也需要)
|
|
383
321
|
this.applyJoins(countBuilder, prepared.joins);
|
|
384
|
-
|
|
385
322
|
const { sql: countSql, params: countParams } = countBuilder.toSelectSql();
|
|
386
323
|
const countExecRes = await this.executeWithConn(countSql, countParams);
|
|
387
324
|
const total = countExecRes.data?.[0]?.total || 0;
|
|
388
|
-
|
|
389
325
|
// 如果总数为 0,直接返回,不执行第二次查询
|
|
390
326
|
if (total === 0) {
|
|
391
327
|
return {
|
|
@@ -401,33 +337,26 @@ export class DbHelper {
|
|
|
401
337
|
}
|
|
402
338
|
};
|
|
403
339
|
}
|
|
404
|
-
|
|
405
340
|
// 查询数据
|
|
406
341
|
const offset = (prepared.page - 1) * prepared.limit;
|
|
407
342
|
const dataBuilder = this.createSqlBuilder().select(prepared.fields).from(prepared.table).where(whereFiltered).limit(prepared.limit).offset(offset);
|
|
408
|
-
|
|
409
343
|
// 添加 JOIN
|
|
410
344
|
this.applyJoins(dataBuilder, prepared.joins);
|
|
411
|
-
|
|
412
345
|
// 只有用户明确指定了 orderBy 才添加排序
|
|
413
346
|
if (prepared.orderBy && prepared.orderBy.length > 0) {
|
|
414
347
|
dataBuilder.orderBy(prepared.orderBy);
|
|
415
348
|
}
|
|
416
|
-
|
|
417
349
|
const { sql: dataSql, params: dataParams } = dataBuilder.toSelectSql();
|
|
418
350
|
const dataExecRes = await this.executeWithConn(dataSql, dataParams);
|
|
419
351
|
const list = dataExecRes.data || [];
|
|
420
|
-
|
|
421
352
|
// 字段名转换:下划线 → 小驼峰
|
|
422
|
-
const camelList = arrayKeysToCamel
|
|
423
|
-
|
|
353
|
+
const camelList = arrayKeysToCamel(list);
|
|
424
354
|
// 反序列化数组字段
|
|
425
|
-
const deserializedList = camelList.map((item) => DbUtils.deserializeArrayFields
|
|
426
|
-
|
|
355
|
+
const deserializedList = camelList.map((item) => DbUtils.deserializeArrayFields(item)).filter((item) => item !== null);
|
|
427
356
|
// 转换 BIGINT 字段(id, pid 等)为数字类型
|
|
428
357
|
return {
|
|
429
358
|
data: {
|
|
430
|
-
lists: convertBigIntFields
|
|
359
|
+
lists: convertBigIntFields(deserializedList),
|
|
431
360
|
total: total,
|
|
432
361
|
page: prepared.page,
|
|
433
362
|
limit: prepared.limit,
|
|
@@ -439,7 +368,6 @@ export class DbHelper {
|
|
|
439
368
|
}
|
|
440
369
|
};
|
|
441
370
|
}
|
|
442
|
-
|
|
443
371
|
/**
|
|
444
372
|
* 查询所有数据(不分页,有上限保护)
|
|
445
373
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换;联查时可带别名)
|
|
@@ -457,25 +385,19 @@ export class DbHelper {
|
|
|
457
385
|
* where: { 'o.state': 1 }
|
|
458
386
|
* })
|
|
459
387
|
*/
|
|
460
|
-
async getAll
|
|
388
|
+
async getAll(options) {
|
|
461
389
|
// 添加硬性上限保护,防止内存溢出
|
|
462
390
|
const MAX_LIMIT = 10000;
|
|
463
391
|
const WARNING_LIMIT = 1000;
|
|
464
|
-
|
|
465
392
|
const prepared = await this.prepareQueryOptions({ ...options, page: 1, limit: 10 });
|
|
466
|
-
|
|
467
393
|
const whereFiltered = DbUtils.addDefaultStateFilter(prepared.where, prepared.tableQualifier, !!prepared.joins);
|
|
468
|
-
|
|
469
394
|
// 查询真实总数
|
|
470
395
|
const countBuilder = this.createSqlBuilder().selectRaw("COUNT(*) as total").from(prepared.table).where(whereFiltered);
|
|
471
|
-
|
|
472
396
|
// 添加 JOIN(计数也需要)
|
|
473
397
|
this.applyJoins(countBuilder, prepared.joins);
|
|
474
|
-
|
|
475
398
|
const { sql: countSql, params: countParams } = countBuilder.toSelectSql();
|
|
476
399
|
const countExecRes = await this.executeWithConn(countSql, countParams);
|
|
477
400
|
const total = countExecRes.data?.[0]?.total || 0;
|
|
478
|
-
|
|
479
401
|
// 如果总数为 0,直接返回
|
|
480
402
|
if (total === 0) {
|
|
481
403
|
return {
|
|
@@ -488,40 +410,30 @@ export class DbHelper {
|
|
|
488
410
|
}
|
|
489
411
|
};
|
|
490
412
|
}
|
|
491
|
-
|
|
492
413
|
// 查询数据(受上限保护)
|
|
493
414
|
const dataBuilder = this.createSqlBuilder().select(prepared.fields).from(prepared.table).where(whereFiltered).limit(MAX_LIMIT);
|
|
494
|
-
|
|
495
415
|
// 添加 JOIN
|
|
496
416
|
this.applyJoins(dataBuilder, prepared.joins);
|
|
497
|
-
|
|
498
417
|
if (prepared.orderBy && prepared.orderBy.length > 0) {
|
|
499
418
|
dataBuilder.orderBy(prepared.orderBy);
|
|
500
419
|
}
|
|
501
|
-
|
|
502
420
|
const { sql: dataSql, params: dataParams } = dataBuilder.toSelectSql();
|
|
503
421
|
const dataExecRes = await this.executeWithConn(dataSql, dataParams);
|
|
504
422
|
const result = dataExecRes.data || [];
|
|
505
|
-
|
|
506
423
|
// 警告日志:返回数据超过警告阈值
|
|
507
424
|
if (result.length >= WARNING_LIMIT) {
|
|
508
425
|
Logger.warn({ table: options.table, count: result.length, total: total }, "getAll 返回数据过多,建议使用 getList 分页查询");
|
|
509
426
|
}
|
|
510
|
-
|
|
511
427
|
// 如果达到上限,额外警告
|
|
512
428
|
if (result.length >= MAX_LIMIT) {
|
|
513
429
|
Logger.warn({ table: options.table, limit: MAX_LIMIT, total: total }, `getAll 达到最大限制 ${MAX_LIMIT},实际总数 ${total},只返回前 ${MAX_LIMIT} 条`);
|
|
514
430
|
}
|
|
515
|
-
|
|
516
431
|
// 字段名转换:下划线 → 小驼峰
|
|
517
|
-
const camelResult = arrayKeysToCamel
|
|
518
|
-
|
|
432
|
+
const camelResult = arrayKeysToCamel(result);
|
|
519
433
|
// 反序列化数组字段
|
|
520
|
-
const deserializedList = camelResult.map((item) => DbUtils.deserializeArrayFields
|
|
521
|
-
|
|
434
|
+
const deserializedList = camelResult.map((item) => DbUtils.deserializeArrayFields(item)).filter((item) => item !== null);
|
|
522
435
|
// 转换 BIGINT 字段(id, pid 等)为数字类型
|
|
523
|
-
const lists = convertBigIntFields
|
|
524
|
-
|
|
436
|
+
const lists = convertBigIntFields(deserializedList);
|
|
525
437
|
return {
|
|
526
438
|
data: {
|
|
527
439
|
lists: lists,
|
|
@@ -533,34 +445,27 @@ export class DbHelper {
|
|
|
533
445
|
}
|
|
534
446
|
};
|
|
535
447
|
}
|
|
536
|
-
|
|
537
448
|
/**
|
|
538
449
|
* 插入数据(自动生成 ID、时间戳、state)
|
|
539
450
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
540
451
|
*/
|
|
541
|
-
async insData(options
|
|
452
|
+
async insData(options) {
|
|
542
453
|
const { table, data } = options;
|
|
543
|
-
|
|
544
454
|
const snakeTable = snakeCase(table);
|
|
545
|
-
|
|
546
455
|
const now = Date.now();
|
|
547
|
-
|
|
548
|
-
let id: number;
|
|
456
|
+
let id;
|
|
549
457
|
try {
|
|
550
458
|
id = await this.redis.genTimeID();
|
|
551
|
-
}
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
552
461
|
throw new Error(`生成 ID 失败,Redis 可能不可用 (table: ${table})`, { cause: error });
|
|
553
462
|
}
|
|
554
|
-
|
|
555
463
|
const processed = DbUtils.buildInsertRow({ data: data, id: id, now: now });
|
|
556
|
-
|
|
557
464
|
// 入口校验:保证进入 SqlBuilder 的数据无 undefined
|
|
558
|
-
SqlCheck.assertNoUndefinedInRecord(processed
|
|
559
|
-
|
|
465
|
+
SqlCheck.assertNoUndefinedInRecord(processed, `insData 插入数据 (table: ${snakeTable})`);
|
|
560
466
|
// 构建 SQL
|
|
561
467
|
const builder = this.createSqlBuilder();
|
|
562
468
|
const { sql, params } = builder.toInsertSql(snakeTable, processed);
|
|
563
|
-
|
|
564
469
|
// 执行
|
|
565
470
|
const execRes = await this.executeWithConn(sql, params);
|
|
566
471
|
const insertedId = processed.id || execRes.data?.lastInsertRowid || 0;
|
|
@@ -569,51 +474,43 @@ export class DbHelper {
|
|
|
569
474
|
sql: execRes.sql
|
|
570
475
|
};
|
|
571
476
|
}
|
|
572
|
-
|
|
573
477
|
/**
|
|
574
478
|
* 批量插入数据(真正的批量操作)
|
|
575
479
|
* 使用 INSERT INTO ... VALUES (...), (...), (...) 语法
|
|
576
480
|
* 自动生成系统字段并包装在事务中
|
|
577
481
|
* @param table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
578
482
|
*/
|
|
579
|
-
async insBatch(table
|
|
483
|
+
async insBatch(table, dataList) {
|
|
580
484
|
// 空数组直接返回
|
|
581
485
|
if (dataList.length === 0) {
|
|
582
|
-
const sql
|
|
486
|
+
const sql = { sql: "", params: [], duration: 0 };
|
|
583
487
|
return {
|
|
584
488
|
data: [],
|
|
585
489
|
sql: sql
|
|
586
490
|
};
|
|
587
491
|
}
|
|
588
|
-
|
|
589
492
|
// 限制批量大小
|
|
590
493
|
const MAX_BATCH_SIZE = 1000;
|
|
591
494
|
if (dataList.length > MAX_BATCH_SIZE) {
|
|
592
495
|
throw new Error(`批量插入数量 ${dataList.length} 超过最大限制 ${MAX_BATCH_SIZE}`);
|
|
593
496
|
}
|
|
594
|
-
|
|
595
497
|
// 转换表名:小驼峰 → 下划线
|
|
596
498
|
const snakeTable = snakeCase(table);
|
|
597
|
-
|
|
598
499
|
// 批量生成 ID(逐个获取)
|
|
599
|
-
const ids
|
|
500
|
+
const ids = [];
|
|
600
501
|
for (let i = 0; i < dataList.length; i++) {
|
|
601
502
|
ids.push(await this.redis.genTimeID());
|
|
602
503
|
}
|
|
603
504
|
const now = Date.now();
|
|
604
|
-
|
|
605
505
|
// 处理所有数据(自动添加系统字段)
|
|
606
506
|
const processedList = dataList.map((data, index) => {
|
|
607
507
|
return DbUtils.buildInsertRow({ data: data, id: ids[index], now: now });
|
|
608
508
|
});
|
|
609
|
-
|
|
610
509
|
// 入口校验:保证进入 SqlBuilder 的批量数据结构一致且无 undefined
|
|
611
|
-
const insertFields = SqlCheck.assertBatchInsertRowsConsistent(processedList
|
|
612
|
-
|
|
510
|
+
const insertFields = SqlCheck.assertBatchInsertRowsConsistent(processedList, { table: snakeTable });
|
|
613
511
|
// 构建批量插入 SQL
|
|
614
512
|
const builder = this.createSqlBuilder();
|
|
615
513
|
const { sql, params } = builder.toInsertSql(snakeTable, processedList);
|
|
616
|
-
|
|
617
514
|
// 在事务中执行批量插入
|
|
618
515
|
try {
|
|
619
516
|
const execRes = await this.executeWithConn(sql, params);
|
|
@@ -621,32 +518,27 @@ export class DbHelper {
|
|
|
621
518
|
data: ids,
|
|
622
519
|
sql: execRes.sql
|
|
623
520
|
};
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
"批量插入失败"
|
|
634
|
-
);
|
|
521
|
+
}
|
|
522
|
+
catch (error) {
|
|
523
|
+
Logger.error({
|
|
524
|
+
err: error,
|
|
525
|
+
table: table,
|
|
526
|
+
snakeTable: snakeTable,
|
|
527
|
+
count: dataList.length,
|
|
528
|
+
fields: insertFields
|
|
529
|
+
}, "批量插入失败");
|
|
635
530
|
throw error;
|
|
636
531
|
}
|
|
637
532
|
}
|
|
638
|
-
|
|
639
|
-
async delForceBatch(table: string, ids: number[]): Promise<DbResult<number>> {
|
|
533
|
+
async delForceBatch(table, ids) {
|
|
640
534
|
if (ids.length === 0) {
|
|
641
|
-
const sql
|
|
535
|
+
const sql = { sql: "", params: [], duration: 0 };
|
|
642
536
|
return {
|
|
643
537
|
data: 0,
|
|
644
538
|
sql: sql
|
|
645
539
|
};
|
|
646
540
|
}
|
|
647
|
-
|
|
648
541
|
const snakeTable = snakeCase(table);
|
|
649
|
-
|
|
650
542
|
const query = SqlBuilder.toDeleteInSql({
|
|
651
543
|
table: snakeTable,
|
|
652
544
|
idField: "id",
|
|
@@ -660,41 +552,33 @@ export class DbHelper {
|
|
|
660
552
|
sql: execRes.sql
|
|
661
553
|
};
|
|
662
554
|
}
|
|
663
|
-
|
|
664
|
-
async updBatch(table: string, dataList: Array<{ id: number; data: Record<string, any> }>): Promise<DbResult<number>> {
|
|
555
|
+
async updBatch(table, dataList) {
|
|
665
556
|
if (dataList.length === 0) {
|
|
666
|
-
const sql
|
|
557
|
+
const sql = { sql: "", params: [], duration: 0 };
|
|
667
558
|
return {
|
|
668
559
|
data: 0,
|
|
669
560
|
sql: sql
|
|
670
561
|
};
|
|
671
562
|
}
|
|
672
|
-
|
|
673
563
|
const snakeTable = snakeCase(table);
|
|
674
564
|
const now = Date.now();
|
|
675
|
-
|
|
676
|
-
const
|
|
677
|
-
const fieldSet = new Set<string>();
|
|
678
|
-
|
|
565
|
+
const processedList = [];
|
|
566
|
+
const fieldSet = new Set();
|
|
679
567
|
for (const item of dataList) {
|
|
680
568
|
const userData = DbUtils.buildPartialUpdateData({ data: item.data, allowState: true });
|
|
681
|
-
|
|
682
569
|
for (const key of Object.keys(userData)) {
|
|
683
570
|
fieldSet.add(key);
|
|
684
571
|
}
|
|
685
|
-
|
|
686
572
|
processedList.push({ id: item.id, data: userData });
|
|
687
573
|
}
|
|
688
|
-
|
|
689
574
|
const fields = Array.from(fieldSet).sort();
|
|
690
575
|
if (fields.length === 0) {
|
|
691
|
-
const sql
|
|
576
|
+
const sql = { sql: "", params: [], duration: 0 };
|
|
692
577
|
return {
|
|
693
578
|
data: 0,
|
|
694
579
|
sql: sql
|
|
695
580
|
};
|
|
696
581
|
}
|
|
697
|
-
|
|
698
582
|
const query = SqlBuilder.toUpdateCaseByIdSql({
|
|
699
583
|
table: snakeTable,
|
|
700
584
|
idField: "id",
|
|
@@ -706,7 +590,6 @@ export class DbHelper {
|
|
|
706
590
|
stateField: "state",
|
|
707
591
|
stateGtZero: true
|
|
708
592
|
});
|
|
709
|
-
|
|
710
593
|
const execRes = await this.executeWithConn(query.sql, query.params);
|
|
711
594
|
const changes = execRes.data?.changes || 0;
|
|
712
595
|
return {
|
|
@@ -714,28 +597,22 @@ export class DbHelper {
|
|
|
714
597
|
sql: execRes.sql
|
|
715
598
|
};
|
|
716
599
|
}
|
|
717
|
-
|
|
718
600
|
/**
|
|
719
601
|
* 更新数据(强制更新时间戳,系统字段不可修改)
|
|
720
602
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
721
603
|
*/
|
|
722
|
-
async updData(options
|
|
604
|
+
async updData(options) {
|
|
723
605
|
const { table, data, where } = options;
|
|
724
|
-
|
|
725
606
|
// 清理条件(排除 null 和 undefined)
|
|
726
607
|
const cleanWhere = fieldClear(where, { excludeValues: [null, undefined] });
|
|
727
|
-
|
|
728
608
|
// 转换表名:小驼峰 → 下划线
|
|
729
609
|
const snakeTable = snakeCase(table);
|
|
730
610
|
const snakeWhere = DbUtils.whereKeysToSnake(cleanWhere);
|
|
731
|
-
|
|
732
611
|
const processed = DbUtils.buildUpdateRow({ data: data, now: Date.now(), allowState: true });
|
|
733
|
-
|
|
734
612
|
// 构建 SQL
|
|
735
613
|
const whereFiltered = DbUtils.addDefaultStateFilter(snakeWhere, snakeTable, false);
|
|
736
614
|
const builder = this.createSqlBuilder().where(whereFiltered);
|
|
737
615
|
const { sql, params } = builder.toUpdateSql(snakeTable, processed);
|
|
738
|
-
|
|
739
616
|
// 执行
|
|
740
617
|
const execRes = await this.executeWithConn(sql, params);
|
|
741
618
|
const changes = execRes.data?.changes || 0;
|
|
@@ -744,39 +621,32 @@ export class DbHelper {
|
|
|
744
621
|
sql: execRes.sql
|
|
745
622
|
};
|
|
746
623
|
}
|
|
747
|
-
|
|
748
624
|
/**
|
|
749
625
|
* 软删除数据(deleted_at 设置为当前时间,state 设置为 0)
|
|
750
626
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
751
627
|
*/
|
|
752
|
-
async delData(options
|
|
628
|
+
async delData(options) {
|
|
753
629
|
const { table, where } = options;
|
|
754
|
-
|
|
755
630
|
return await this.updData({
|
|
756
631
|
table: table,
|
|
757
632
|
data: { state: 0, deleted_at: Date.now() },
|
|
758
633
|
where: where
|
|
759
634
|
});
|
|
760
635
|
}
|
|
761
|
-
|
|
762
636
|
/**
|
|
763
637
|
* 硬删除数据(物理删除,不可恢复)
|
|
764
638
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
765
639
|
*/
|
|
766
|
-
async delForce(options
|
|
640
|
+
async delForce(options) {
|
|
767
641
|
const { table, where } = options;
|
|
768
|
-
|
|
769
642
|
// 转换表名:小驼峰 → 下划线
|
|
770
643
|
const snakeTable = snakeCase(table);
|
|
771
|
-
|
|
772
644
|
// 清理条件字段
|
|
773
645
|
const cleanWhere = fieldClear(where, { excludeValues: [null, undefined] });
|
|
774
646
|
const snakeWhere = DbUtils.whereKeysToSnake(cleanWhere);
|
|
775
|
-
|
|
776
647
|
// 物理删除
|
|
777
648
|
const builder = this.createSqlBuilder().where(snakeWhere);
|
|
778
649
|
const { sql, params } = builder.toDeleteSql(snakeTable);
|
|
779
|
-
|
|
780
650
|
const execRes = await this.executeWithConn(sql, params);
|
|
781
651
|
const changes = execRes.data?.changes || 0;
|
|
782
652
|
return {
|
|
@@ -784,14 +654,12 @@ export class DbHelper {
|
|
|
784
654
|
sql: execRes.sql
|
|
785
655
|
};
|
|
786
656
|
}
|
|
787
|
-
|
|
788
657
|
/**
|
|
789
658
|
* 禁用数据(设置 state=2)
|
|
790
659
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
791
660
|
*/
|
|
792
|
-
async disableData(options
|
|
661
|
+
async disableData(options) {
|
|
793
662
|
const { table, where } = options;
|
|
794
|
-
|
|
795
663
|
return await this.updData({
|
|
796
664
|
table: table,
|
|
797
665
|
data: {
|
|
@@ -800,14 +668,12 @@ export class DbHelper {
|
|
|
800
668
|
where: where
|
|
801
669
|
});
|
|
802
670
|
}
|
|
803
|
-
|
|
804
671
|
/**
|
|
805
672
|
* 启用数据(设置 state=1)
|
|
806
673
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
807
674
|
*/
|
|
808
|
-
async enableData(options
|
|
675
|
+
async enableData(options) {
|
|
809
676
|
const { table, where } = options;
|
|
810
|
-
|
|
811
677
|
return await this.updData({
|
|
812
678
|
table: table,
|
|
813
679
|
data: {
|
|
@@ -816,73 +682,62 @@ export class DbHelper {
|
|
|
816
682
|
where: where
|
|
817
683
|
});
|
|
818
684
|
}
|
|
819
|
-
|
|
820
685
|
/**
|
|
821
686
|
* 执行事务
|
|
822
687
|
* 使用 Bun SQL 的 begin 方法开启事务
|
|
823
688
|
*/
|
|
824
|
-
async trans
|
|
689
|
+
async trans(callback) {
|
|
825
690
|
if (this.isTransaction) {
|
|
826
691
|
// 已经在事务中,直接执行回调
|
|
827
692
|
return await callback(this);
|
|
828
693
|
}
|
|
829
|
-
|
|
830
694
|
// 使用 Bun SQL 的 begin 方法开启事务
|
|
831
695
|
// begin 方法会自动处理 commit/rollback
|
|
832
|
-
return await this.sql.begin(async (tx
|
|
696
|
+
return await this.sql.begin(async (tx) => {
|
|
833
697
|
const trans = new DbHelper({ redis: this.redis, sql: tx, dialect: this.dialect });
|
|
834
698
|
return await callback(trans);
|
|
835
699
|
});
|
|
836
700
|
}
|
|
837
|
-
|
|
838
701
|
/**
|
|
839
702
|
* 执行原始 SQL
|
|
840
703
|
*/
|
|
841
|
-
async query(sql
|
|
704
|
+
async query(sql, params) {
|
|
842
705
|
return await this.executeWithConn(sql, params);
|
|
843
706
|
}
|
|
844
|
-
|
|
845
707
|
/**
|
|
846
708
|
* 检查数据是否存在(优化性能)
|
|
847
709
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
848
710
|
*/
|
|
849
|
-
async exists(options
|
|
850
|
-
const { table, where, tableQualifier } = await this.prepareQueryOptions({ ...options, page: 1, limit: 1 }
|
|
851
|
-
|
|
711
|
+
async exists(options) {
|
|
712
|
+
const { table, where, tableQualifier } = await this.prepareQueryOptions({ ...options, page: 1, limit: 1 });
|
|
852
713
|
// 使用 COUNT(1) 性能更好
|
|
853
714
|
const builder = this.createSqlBuilder()
|
|
854
715
|
.selectRaw("COUNT(1) as cnt")
|
|
855
716
|
.from(table)
|
|
856
717
|
.where(DbUtils.addDefaultStateFilter(where, tableQualifier, false))
|
|
857
718
|
.limit(1);
|
|
858
|
-
|
|
859
719
|
const { sql, params } = builder.toSelectSql();
|
|
860
720
|
const execRes = await this.executeWithConn(sql, params);
|
|
861
721
|
const exists = (execRes.data?.[0]?.cnt || 0) > 0;
|
|
862
|
-
|
|
863
722
|
return {
|
|
864
723
|
data: exists,
|
|
865
724
|
sql: execRes.sql
|
|
866
725
|
};
|
|
867
726
|
}
|
|
868
|
-
|
|
869
727
|
/**
|
|
870
728
|
* 查询单个字段值(带字段名验证)
|
|
871
729
|
* @param field - 字段名(支持小驼峰或下划线格式)
|
|
872
730
|
*/
|
|
873
|
-
async getFieldValue
|
|
731
|
+
async getFieldValue(options) {
|
|
874
732
|
const { field, ...queryOptions } = options;
|
|
875
|
-
|
|
876
733
|
// 验证字段名格式(只允许字母、数字、下划线)
|
|
877
734
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {
|
|
878
735
|
throw new Error(`无效的字段名: ${field},只允许字母、数字和下划线`);
|
|
879
736
|
}
|
|
880
|
-
|
|
881
737
|
const oneRes = await this.getOne({
|
|
882
738
|
...queryOptions,
|
|
883
739
|
fields: [field]
|
|
884
740
|
});
|
|
885
|
-
|
|
886
741
|
const result = oneRes.data;
|
|
887
742
|
if (!result) {
|
|
888
743
|
return {
|
|
@@ -890,7 +745,6 @@ export class DbHelper {
|
|
|
890
745
|
sql: oneRes.sql
|
|
891
746
|
};
|
|
892
747
|
}
|
|
893
|
-
|
|
894
748
|
// 尝试直接访问字段(小驼峰)
|
|
895
749
|
if (field in result) {
|
|
896
750
|
return {
|
|
@@ -898,7 +752,6 @@ export class DbHelper {
|
|
|
898
752
|
sql: oneRes.sql
|
|
899
753
|
};
|
|
900
754
|
}
|
|
901
|
-
|
|
902
755
|
// 转换为小驼峰格式再尝试访问(支持用户传入下划线格式)
|
|
903
756
|
const camelField = field.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
904
757
|
if (camelField !== field && camelField in result) {
|
|
@@ -907,7 +760,6 @@ export class DbHelper {
|
|
|
907
760
|
sql: oneRes.sql
|
|
908
761
|
};
|
|
909
762
|
}
|
|
910
|
-
|
|
911
763
|
// 转换为下划线格式再尝试访问(支持用户传入小驼峰格式)
|
|
912
764
|
const snakeField = field.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
913
765
|
if (snakeField !== field && snakeField in result) {
|
|
@@ -916,54 +768,44 @@ export class DbHelper {
|
|
|
916
768
|
sql: oneRes.sql
|
|
917
769
|
};
|
|
918
770
|
}
|
|
919
|
-
|
|
920
771
|
return {
|
|
921
772
|
data: null,
|
|
922
773
|
sql: oneRes.sql
|
|
923
774
|
};
|
|
924
775
|
}
|
|
925
|
-
|
|
926
776
|
/**
|
|
927
777
|
* 自增字段(安全实现,防止 SQL 注入)
|
|
928
778
|
* @param table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
929
779
|
* @param field - 字段名(支持小驼峰或下划线格式,会自动转换)
|
|
930
780
|
*/
|
|
931
|
-
async increment(table
|
|
781
|
+
async increment(table, field, where, value = 1) {
|
|
932
782
|
// 转换表名和字段名:小驼峰 → 下划线
|
|
933
783
|
const snakeTable = snakeCase(table);
|
|
934
784
|
const snakeField = snakeCase(field);
|
|
935
|
-
|
|
936
785
|
// 验证表名格式(只允许字母、数字、下划线)
|
|
937
786
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(snakeTable)) {
|
|
938
787
|
throw new Error(`无效的表名: ${snakeTable}`);
|
|
939
788
|
}
|
|
940
|
-
|
|
941
789
|
// 验证字段名格式(只允许字母、数字、下划线)
|
|
942
790
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(snakeField)) {
|
|
943
791
|
throw new Error(`无效的字段名: ${field}`);
|
|
944
792
|
}
|
|
945
|
-
|
|
946
793
|
// 验证 value 必须是数字
|
|
947
794
|
if (typeof value !== "number" || isNaN(value)) {
|
|
948
795
|
throw new Error(`自增值必须是有效的数字 (table: ${table}, field: ${field}, value: ${value})`);
|
|
949
796
|
}
|
|
950
|
-
|
|
951
797
|
// 清理 where 条件(排除 null 和 undefined)
|
|
952
798
|
const cleanWhere = fieldClear(where, { excludeValues: [null, undefined] });
|
|
953
|
-
|
|
954
799
|
// 转换 where 条件字段名:小驼峰 → 下划线
|
|
955
800
|
const snakeWhere = DbUtils.whereKeysToSnake(cleanWhere);
|
|
956
|
-
|
|
957
801
|
// 使用 SqlBuilder 构建安全的 WHERE 条件
|
|
958
802
|
const whereFiltered = DbUtils.addDefaultStateFilter(snakeWhere, snakeTable, false);
|
|
959
803
|
const builder = this.createSqlBuilder().where(whereFiltered);
|
|
960
804
|
const { sql: whereClause, params: whereParams } = builder.getWhereConditions();
|
|
961
|
-
|
|
962
805
|
// 构建安全的 UPDATE SQL(表名和字段名使用反引号转义,已经是下划线格式)
|
|
963
806
|
const quotedTable = this.dialect.quoteIdent(snakeTable);
|
|
964
807
|
const quotedField = this.dialect.quoteIdent(snakeField);
|
|
965
808
|
const sql = whereClause ? `UPDATE ${quotedTable} SET ${quotedField} = ${quotedField} + ? WHERE ${whereClause}` : `UPDATE ${quotedTable} SET ${quotedField} = ${quotedField} + ?`;
|
|
966
|
-
|
|
967
809
|
const execRes = await this.executeWithConn(sql, [value, ...whereParams]);
|
|
968
810
|
const changes = execRes.data?.changes || 0;
|
|
969
811
|
return {
|
|
@@ -971,13 +813,12 @@ export class DbHelper {
|
|
|
971
813
|
sql: execRes.sql
|
|
972
814
|
};
|
|
973
815
|
}
|
|
974
|
-
|
|
975
816
|
/**
|
|
976
817
|
* 自减字段
|
|
977
818
|
* @param table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
978
819
|
* @param field - 字段名(支持小驼峰或下划线格式,会自动转换)
|
|
979
820
|
*/
|
|
980
|
-
async decrement(table
|
|
821
|
+
async decrement(table, field, where, value = 1) {
|
|
981
822
|
return await this.increment(table, field, where, -value);
|
|
982
823
|
}
|
|
983
824
|
}
|