befly 3.10.12 → 3.10.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/befly.config.ts +4 -3
- package/lib/cacheHelper.ts +8 -8
- package/lib/dbHelper.ts +214 -129
- package/lib/logger.ts +14 -7
- package/package.json +2 -2
- package/plugins/db.ts +1 -4
- package/sync/syncApi.ts +2 -2
- package/sync/syncDev.ts +13 -13
- package/sync/syncMenu.ts +2 -2
- package/sync/syncTable.ts +83 -32
- package/types/befly.d.ts +0 -3
- package/types/database.d.ts +50 -24
- package/types/logger.d.ts +15 -0
- package/utils/sqlLog.ts +37 -0
package/lib/dbHelper.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { WhereConditions, JoinOption } from "../types/common.js";
|
|
7
|
-
import type { QueryOptions, InsertOptions, UpdateOptions, DeleteOptions, ListResult, AllResult, TransactionCallback } from "../types/database.js";
|
|
7
|
+
import type { QueryOptions, InsertOptions, UpdateOptions, DeleteOptions, ListResult, AllResult, TransactionCallback, DbResult, SqlInfo, ListSql } from "../types/database.js";
|
|
8
8
|
import type { DbDialect } from "./dbDialect.js";
|
|
9
9
|
|
|
10
10
|
import { snakeCase } from "es-toolkit/string";
|
|
@@ -36,18 +36,16 @@ export class DbHelper {
|
|
|
36
36
|
private dialect: DbDialect;
|
|
37
37
|
private sql: any = null;
|
|
38
38
|
private isTransaction: boolean = false;
|
|
39
|
-
private debug: number = 0;
|
|
40
39
|
|
|
41
40
|
/**
|
|
42
41
|
* 构造函数
|
|
43
42
|
* @param redis - Redis 实例
|
|
44
43
|
* @param sql - Bun SQL 客户端(可选,用于事务)
|
|
45
44
|
*/
|
|
46
|
-
constructor(options: { redis: RedisCacheLike; sql?: any | null; dialect?: DbDialect
|
|
45
|
+
constructor(options: { redis: RedisCacheLike; sql?: any | null; dialect?: DbDialect }) {
|
|
47
46
|
this.redis = options.redis;
|
|
48
47
|
this.sql = options.sql || null;
|
|
49
48
|
this.isTransaction = !!options.sql;
|
|
50
|
-
this.debug = options.debug === 1 ? 1 : 0;
|
|
51
49
|
|
|
52
50
|
// 默认使用 MySQL 方言(当前 core 的表结构/语法也主要基于 MySQL)
|
|
53
51
|
this.dialect = options.dialect ? options.dialect : new MySqlDialect();
|
|
@@ -73,7 +71,8 @@ export class DbHelper {
|
|
|
73
71
|
|
|
74
72
|
// 2. 缓存未命中,查询数据库
|
|
75
73
|
const query = this.dialect.getTableColumnsQuery(table);
|
|
76
|
-
const
|
|
74
|
+
const execRes = await this.executeWithConn(query.sql, query.params);
|
|
75
|
+
const result = execRes.data;
|
|
77
76
|
|
|
78
77
|
if (!result || result.length === 0) {
|
|
79
78
|
throw new Error(`表 ${table} 不存在或没有字段`);
|
|
@@ -157,9 +156,12 @@ export class DbHelper {
|
|
|
157
156
|
}
|
|
158
157
|
}
|
|
159
158
|
/**
|
|
160
|
-
* 执行 SQL(使用 sql.unsafe
|
|
159
|
+
* 执行 SQL(使用 sql.unsafe)
|
|
160
|
+
*
|
|
161
|
+
* - DbHelper 不再负责打印 SQL 调试日志
|
|
162
|
+
* - SQL 信息由调用方基于返回值中的 sql 自行输出
|
|
161
163
|
*/
|
|
162
|
-
private async executeWithConn(sqlStr: string, params?: any[]): Promise<any
|
|
164
|
+
private async executeWithConn(sqlStr: string, params?: any[]): Promise<DbResult<any>> {
|
|
163
165
|
if (!this.sql) {
|
|
164
166
|
throw new Error("数据库连接未初始化");
|
|
165
167
|
}
|
|
@@ -172,11 +174,13 @@ export class DbHelper {
|
|
|
172
174
|
// 记录开始时间
|
|
173
175
|
const startTime = Date.now();
|
|
174
176
|
|
|
177
|
+
const safeParams = Array.isArray(params) ? params : [];
|
|
178
|
+
|
|
175
179
|
try {
|
|
176
180
|
// 使用 sql.unsafe 执行查询
|
|
177
181
|
let result;
|
|
178
|
-
if (
|
|
179
|
-
result = await this.sql.unsafe(sqlStr,
|
|
182
|
+
if (safeParams.length > 0) {
|
|
183
|
+
result = await this.sql.unsafe(sqlStr, safeParams);
|
|
180
184
|
} else {
|
|
181
185
|
result = await this.sql.unsafe(sqlStr);
|
|
182
186
|
}
|
|
@@ -184,57 +188,28 @@ export class DbHelper {
|
|
|
184
188
|
// 计算执行时间
|
|
185
189
|
const duration = Date.now() - startTime;
|
|
186
190
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
subsystem: "db",
|
|
193
|
-
event: "query",
|
|
194
|
-
duration: duration,
|
|
195
|
-
sqlPreview: sqlPreview,
|
|
196
|
-
paramsCount: (params || []).length,
|
|
197
|
-
params: params || []
|
|
198
|
-
},
|
|
199
|
-
"DB"
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// 慢查询警告(超过 5000ms)
|
|
204
|
-
if (duration > 5000) {
|
|
205
|
-
Logger.warn(
|
|
206
|
-
{
|
|
207
|
-
subsystem: "db",
|
|
208
|
-
event: "slow",
|
|
209
|
-
duration: duration,
|
|
210
|
-
sqlPreview: sqlStr,
|
|
211
|
-
params: params || [],
|
|
212
|
-
paramsCount: (params || []).length
|
|
213
|
-
},
|
|
214
|
-
"🐌 检测到慢查询"
|
|
215
|
-
);
|
|
216
|
-
}
|
|
191
|
+
const sql: SqlInfo = {
|
|
192
|
+
sql: sqlStr,
|
|
193
|
+
params: safeParams,
|
|
194
|
+
duration: duration
|
|
195
|
+
};
|
|
217
196
|
|
|
218
|
-
return
|
|
197
|
+
return {
|
|
198
|
+
data: result,
|
|
199
|
+
sql: sql
|
|
200
|
+
};
|
|
219
201
|
} catch (error: any) {
|
|
220
202
|
const duration = Date.now() - startTime;
|
|
221
203
|
|
|
222
|
-
const sqlPreview = sqlStr.length > 200 ? sqlStr.substring(0, 200) + "..." : sqlStr;
|
|
223
|
-
Logger.error(
|
|
224
|
-
{
|
|
225
|
-
err: error,
|
|
226
|
-
sqlPreview: sqlPreview,
|
|
227
|
-
params: params || [],
|
|
228
|
-
duration: duration
|
|
229
|
-
},
|
|
230
|
-
"SQL 执行错误"
|
|
231
|
-
);
|
|
232
|
-
|
|
233
204
|
const enhancedError: any = new Error(`SQL执行失败: ${error.message}`);
|
|
234
205
|
enhancedError.originalError = error;
|
|
235
|
-
enhancedError.
|
|
236
|
-
enhancedError.params = params || [];
|
|
206
|
+
enhancedError.params = safeParams;
|
|
237
207
|
enhancedError.duration = duration;
|
|
208
|
+
enhancedError.sqlInfo = {
|
|
209
|
+
sql: sqlStr,
|
|
210
|
+
params: safeParams,
|
|
211
|
+
duration: duration
|
|
212
|
+
};
|
|
238
213
|
throw enhancedError;
|
|
239
214
|
}
|
|
240
215
|
}
|
|
@@ -245,7 +220,7 @@ export class DbHelper {
|
|
|
245
220
|
* - 复用当前 DbHelper 持有的连接/事务
|
|
246
221
|
* - 统一走 executeWithConn,保持参数校验与错误行为一致
|
|
247
222
|
*/
|
|
248
|
-
public async unsafe(sqlStr: string, params?: any[]): Promise<any
|
|
223
|
+
public async unsafe(sqlStr: string, params?: any[]): Promise<DbResult<any>> {
|
|
249
224
|
return await this.executeWithConn(sqlStr, params);
|
|
250
225
|
}
|
|
251
226
|
|
|
@@ -254,14 +229,18 @@ export class DbHelper {
|
|
|
254
229
|
* @param tableName - 表名(支持小驼峰,会自动转换为下划线)
|
|
255
230
|
* @returns 表是否存在
|
|
256
231
|
*/
|
|
257
|
-
async tableExists(tableName: string): Promise<boolean
|
|
232
|
+
async tableExists(tableName: string): Promise<DbResult<boolean>> {
|
|
258
233
|
// 将表名转换为下划线格式
|
|
259
234
|
const snakeTableName = snakeCase(tableName);
|
|
260
235
|
|
|
261
236
|
const query = this.dialect.tableExistsQuery(snakeTableName);
|
|
262
|
-
const
|
|
237
|
+
const execRes = await this.executeWithConn(query.sql, query.params);
|
|
238
|
+
const exists = (execRes.data?.[0]?.count || 0) > 0;
|
|
263
239
|
|
|
264
|
-
return
|
|
240
|
+
return {
|
|
241
|
+
data: exists,
|
|
242
|
+
sql: execRes.sql
|
|
243
|
+
};
|
|
265
244
|
}
|
|
266
245
|
|
|
267
246
|
/**
|
|
@@ -281,7 +260,7 @@ export class DbHelper {
|
|
|
281
260
|
* where: { 'o.state': 1 }
|
|
282
261
|
* });
|
|
283
262
|
*/
|
|
284
|
-
async getCount(options: Omit<QueryOptions, "fields" | "page" | "limit" | "orderBy">): Promise<number
|
|
263
|
+
async getCount(options: Omit<QueryOptions, "fields" | "page" | "limit" | "orderBy">): Promise<DbResult<number>> {
|
|
285
264
|
const { table, where, joins, tableQualifier } = await this.prepareQueryOptions(options as QueryOptions);
|
|
286
265
|
|
|
287
266
|
const builder = this.createSqlBuilder()
|
|
@@ -293,9 +272,13 @@ export class DbHelper {
|
|
|
293
272
|
this.applyJoins(builder, joins);
|
|
294
273
|
|
|
295
274
|
const { sql, params } = builder.toSelectSql();
|
|
296
|
-
const
|
|
275
|
+
const execRes = await this.executeWithConn(sql, params);
|
|
276
|
+
const count = execRes.data?.[0]?.count || 0;
|
|
297
277
|
|
|
298
|
-
return
|
|
278
|
+
return {
|
|
279
|
+
data: count,
|
|
280
|
+
sql: execRes.sql
|
|
281
|
+
};
|
|
299
282
|
}
|
|
300
283
|
|
|
301
284
|
/**
|
|
@@ -314,7 +297,7 @@ export class DbHelper {
|
|
|
314
297
|
* where: { 'o.id': 1 }
|
|
315
298
|
* })
|
|
316
299
|
*/
|
|
317
|
-
async getOne<T extends Record<string, any> = Record<string, any>>(options: QueryOptions): Promise<T | null
|
|
300
|
+
async getOne<T extends Record<string, any> = Record<string, any>>(options: QueryOptions): Promise<DbResult<T | null>> {
|
|
318
301
|
const { table, fields, where, joins, tableQualifier } = await this.prepareQueryOptions(options);
|
|
319
302
|
|
|
320
303
|
const builder = this.createSqlBuilder()
|
|
@@ -326,20 +309,35 @@ export class DbHelper {
|
|
|
326
309
|
this.applyJoins(builder, joins);
|
|
327
310
|
|
|
328
311
|
const { sql, params } = builder.toSelectSql();
|
|
329
|
-
const
|
|
312
|
+
const execRes = await this.executeWithConn(sql, params);
|
|
313
|
+
const result = execRes.data;
|
|
330
314
|
|
|
331
315
|
// 字段名转换:下划线 → 小驼峰
|
|
332
316
|
const row = result?.[0] || null;
|
|
333
|
-
if (!row)
|
|
317
|
+
if (!row) {
|
|
318
|
+
return {
|
|
319
|
+
data: null,
|
|
320
|
+
sql: execRes.sql
|
|
321
|
+
};
|
|
322
|
+
}
|
|
334
323
|
|
|
335
324
|
const camelRow = keysToCamel<T>(row);
|
|
336
325
|
|
|
337
326
|
// 反序列化数组字段(JSON 字符串 → 数组)
|
|
338
327
|
const deserialized = DbUtils.deserializeArrayFields<T>(camelRow);
|
|
339
|
-
if (!deserialized)
|
|
328
|
+
if (!deserialized) {
|
|
329
|
+
return {
|
|
330
|
+
data: null,
|
|
331
|
+
sql: execRes.sql
|
|
332
|
+
};
|
|
333
|
+
}
|
|
340
334
|
|
|
341
335
|
// 转换 BIGINT 字段(id, pid 等)为数字类型
|
|
342
|
-
|
|
336
|
+
const data = convertBigIntFields<T>([deserialized])[0];
|
|
337
|
+
return {
|
|
338
|
+
data: data,
|
|
339
|
+
sql: execRes.sql
|
|
340
|
+
};
|
|
343
341
|
}
|
|
344
342
|
|
|
345
343
|
/**
|
|
@@ -364,7 +362,7 @@ export class DbHelper {
|
|
|
364
362
|
* limit: 10
|
|
365
363
|
* })
|
|
366
364
|
*/
|
|
367
|
-
async getList<T extends Record<string, any> = Record<string, any>>(options: QueryOptions): Promise<ListResult<T>> {
|
|
365
|
+
async getList<T extends Record<string, any> = Record<string, any>>(options: QueryOptions): Promise<DbResult<ListResult<T>, ListSql>> {
|
|
368
366
|
const prepared = await this.prepareQueryOptions(options);
|
|
369
367
|
|
|
370
368
|
// 参数上限校验
|
|
@@ -385,17 +383,22 @@ export class DbHelper {
|
|
|
385
383
|
this.applyJoins(countBuilder, prepared.joins);
|
|
386
384
|
|
|
387
385
|
const { sql: countSql, params: countParams } = countBuilder.toSelectSql();
|
|
388
|
-
const
|
|
389
|
-
const total =
|
|
386
|
+
const countExecRes = await this.executeWithConn(countSql, countParams);
|
|
387
|
+
const total = countExecRes.data?.[0]?.total || 0;
|
|
390
388
|
|
|
391
389
|
// 如果总数为 0,直接返回,不执行第二次查询
|
|
392
390
|
if (total === 0) {
|
|
393
391
|
return {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
392
|
+
data: {
|
|
393
|
+
lists: [],
|
|
394
|
+
total: 0,
|
|
395
|
+
page: prepared.page,
|
|
396
|
+
limit: prepared.limit,
|
|
397
|
+
pages: 0
|
|
398
|
+
},
|
|
399
|
+
sql: {
|
|
400
|
+
count: countExecRes.sql
|
|
401
|
+
}
|
|
399
402
|
};
|
|
400
403
|
}
|
|
401
404
|
|
|
@@ -412,7 +415,8 @@ export class DbHelper {
|
|
|
412
415
|
}
|
|
413
416
|
|
|
414
417
|
const { sql: dataSql, params: dataParams } = dataBuilder.toSelectSql();
|
|
415
|
-
const
|
|
418
|
+
const dataExecRes = await this.executeWithConn(dataSql, dataParams);
|
|
419
|
+
const list = dataExecRes.data || [];
|
|
416
420
|
|
|
417
421
|
// 字段名转换:下划线 → 小驼峰
|
|
418
422
|
const camelList = arrayKeysToCamel<T>(list);
|
|
@@ -422,11 +426,17 @@ export class DbHelper {
|
|
|
422
426
|
|
|
423
427
|
// 转换 BIGINT 字段(id, pid 等)为数字类型
|
|
424
428
|
return {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
429
|
+
data: {
|
|
430
|
+
lists: convertBigIntFields<T>(deserializedList),
|
|
431
|
+
total: total,
|
|
432
|
+
page: prepared.page,
|
|
433
|
+
limit: prepared.limit,
|
|
434
|
+
pages: Math.ceil(total / prepared.limit)
|
|
435
|
+
},
|
|
436
|
+
sql: {
|
|
437
|
+
count: countExecRes.sql,
|
|
438
|
+
data: dataExecRes.sql
|
|
439
|
+
}
|
|
430
440
|
};
|
|
431
441
|
}
|
|
432
442
|
|
|
@@ -447,7 +457,7 @@ export class DbHelper {
|
|
|
447
457
|
* where: { 'o.state': 1 }
|
|
448
458
|
* })
|
|
449
459
|
*/
|
|
450
|
-
async getAll<T extends Record<string, any> = Record<string, any>>(options: Omit<QueryOptions, "page" | "limit">): Promise<AllResult<T>> {
|
|
460
|
+
async getAll<T extends Record<string, any> = Record<string, any>>(options: Omit<QueryOptions, "page" | "limit">): Promise<DbResult<AllResult<T>, ListSql>> {
|
|
451
461
|
// 添加硬性上限保护,防止内存溢出
|
|
452
462
|
const MAX_LIMIT = 10000;
|
|
453
463
|
const WARNING_LIMIT = 1000;
|
|
@@ -463,14 +473,19 @@ export class DbHelper {
|
|
|
463
473
|
this.applyJoins(countBuilder, prepared.joins);
|
|
464
474
|
|
|
465
475
|
const { sql: countSql, params: countParams } = countBuilder.toSelectSql();
|
|
466
|
-
const
|
|
467
|
-
const total =
|
|
476
|
+
const countExecRes = await this.executeWithConn(countSql, countParams);
|
|
477
|
+
const total = countExecRes.data?.[0]?.total || 0;
|
|
468
478
|
|
|
469
479
|
// 如果总数为 0,直接返回
|
|
470
480
|
if (total === 0) {
|
|
471
481
|
return {
|
|
472
|
-
|
|
473
|
-
|
|
482
|
+
data: {
|
|
483
|
+
lists: [],
|
|
484
|
+
total: 0
|
|
485
|
+
},
|
|
486
|
+
sql: {
|
|
487
|
+
count: countExecRes.sql
|
|
488
|
+
}
|
|
474
489
|
};
|
|
475
490
|
}
|
|
476
491
|
|
|
@@ -485,7 +500,8 @@ export class DbHelper {
|
|
|
485
500
|
}
|
|
486
501
|
|
|
487
502
|
const { sql: dataSql, params: dataParams } = dataBuilder.toSelectSql();
|
|
488
|
-
const
|
|
503
|
+
const dataExecRes = await this.executeWithConn(dataSql, dataParams);
|
|
504
|
+
const result = dataExecRes.data || [];
|
|
489
505
|
|
|
490
506
|
// 警告日志:返回数据超过警告阈值
|
|
491
507
|
if (result.length >= WARNING_LIMIT) {
|
|
@@ -507,8 +523,14 @@ export class DbHelper {
|
|
|
507
523
|
const lists = convertBigIntFields<T>(deserializedList);
|
|
508
524
|
|
|
509
525
|
return {
|
|
510
|
-
|
|
511
|
-
|
|
526
|
+
data: {
|
|
527
|
+
lists: lists,
|
|
528
|
+
total: total
|
|
529
|
+
},
|
|
530
|
+
sql: {
|
|
531
|
+
count: countExecRes.sql,
|
|
532
|
+
data: dataExecRes.sql
|
|
533
|
+
}
|
|
512
534
|
};
|
|
513
535
|
}
|
|
514
536
|
|
|
@@ -516,7 +538,7 @@ export class DbHelper {
|
|
|
516
538
|
* 插入数据(自动生成 ID、时间戳、state)
|
|
517
539
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
518
540
|
*/
|
|
519
|
-
async insData(options: InsertOptions): Promise<number
|
|
541
|
+
async insData(options: InsertOptions): Promise<DbResult<number>> {
|
|
520
542
|
const { table, data } = options;
|
|
521
543
|
|
|
522
544
|
const snakeTable = snakeCase(table);
|
|
@@ -540,8 +562,12 @@ export class DbHelper {
|
|
|
540
562
|
const { sql, params } = builder.toInsertSql(snakeTable, processed);
|
|
541
563
|
|
|
542
564
|
// 执行
|
|
543
|
-
const
|
|
544
|
-
|
|
565
|
+
const execRes = await this.executeWithConn(sql, params);
|
|
566
|
+
const insertedId = processed.id || execRes.data?.lastInsertRowid || 0;
|
|
567
|
+
return {
|
|
568
|
+
data: insertedId,
|
|
569
|
+
sql: execRes.sql
|
|
570
|
+
};
|
|
545
571
|
}
|
|
546
572
|
|
|
547
573
|
/**
|
|
@@ -550,10 +576,14 @@ export class DbHelper {
|
|
|
550
576
|
* 自动生成系统字段并包装在事务中
|
|
551
577
|
* @param table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
552
578
|
*/
|
|
553
|
-
async insBatch(table: string, dataList: Record<string, any>[]): Promise<number[]
|
|
579
|
+
async insBatch(table: string, dataList: Record<string, any>[]): Promise<DbResult<number[]>> {
|
|
554
580
|
// 空数组直接返回
|
|
555
581
|
if (dataList.length === 0) {
|
|
556
|
-
|
|
582
|
+
const sql: SqlInfo = { sql: "", params: [], duration: 0 };
|
|
583
|
+
return {
|
|
584
|
+
data: [],
|
|
585
|
+
sql: sql
|
|
586
|
+
};
|
|
557
587
|
}
|
|
558
588
|
|
|
559
589
|
// 限制批量大小
|
|
@@ -586,8 +616,11 @@ export class DbHelper {
|
|
|
586
616
|
|
|
587
617
|
// 在事务中执行批量插入
|
|
588
618
|
try {
|
|
589
|
-
await this.executeWithConn(sql, params);
|
|
590
|
-
return
|
|
619
|
+
const execRes = await this.executeWithConn(sql, params);
|
|
620
|
+
return {
|
|
621
|
+
data: ids,
|
|
622
|
+
sql: execRes.sql
|
|
623
|
+
};
|
|
591
624
|
} catch (error: any) {
|
|
592
625
|
Logger.error(
|
|
593
626
|
{
|
|
@@ -603,9 +636,13 @@ export class DbHelper {
|
|
|
603
636
|
}
|
|
604
637
|
}
|
|
605
638
|
|
|
606
|
-
async delForceBatch(table: string, ids: number[]): Promise<number
|
|
639
|
+
async delForceBatch(table: string, ids: number[]): Promise<DbResult<number>> {
|
|
607
640
|
if (ids.length === 0) {
|
|
608
|
-
|
|
641
|
+
const sql: SqlInfo = { sql: "", params: [], duration: 0 };
|
|
642
|
+
return {
|
|
643
|
+
data: 0,
|
|
644
|
+
sql: sql
|
|
645
|
+
};
|
|
609
646
|
}
|
|
610
647
|
|
|
611
648
|
const snakeTable = snakeCase(table);
|
|
@@ -616,13 +653,21 @@ export class DbHelper {
|
|
|
616
653
|
ids: ids,
|
|
617
654
|
quoteIdent: this.dialect.quoteIdent.bind(this.dialect)
|
|
618
655
|
});
|
|
619
|
-
const
|
|
620
|
-
|
|
656
|
+
const execRes = await this.executeWithConn(query.sql, query.params);
|
|
657
|
+
const changes = execRes.data?.changes || 0;
|
|
658
|
+
return {
|
|
659
|
+
data: changes,
|
|
660
|
+
sql: execRes.sql
|
|
661
|
+
};
|
|
621
662
|
}
|
|
622
663
|
|
|
623
|
-
async updBatch(table: string, dataList: Array<{ id: number; data: Record<string, any> }>): Promise<number
|
|
664
|
+
async updBatch(table: string, dataList: Array<{ id: number; data: Record<string, any> }>): Promise<DbResult<number>> {
|
|
624
665
|
if (dataList.length === 0) {
|
|
625
|
-
|
|
666
|
+
const sql: SqlInfo = { sql: "", params: [], duration: 0 };
|
|
667
|
+
return {
|
|
668
|
+
data: 0,
|
|
669
|
+
sql: sql
|
|
670
|
+
};
|
|
626
671
|
}
|
|
627
672
|
|
|
628
673
|
const snakeTable = snakeCase(table);
|
|
@@ -643,7 +688,11 @@ export class DbHelper {
|
|
|
643
688
|
|
|
644
689
|
const fields = Array.from(fieldSet).sort();
|
|
645
690
|
if (fields.length === 0) {
|
|
646
|
-
|
|
691
|
+
const sql: SqlInfo = { sql: "", params: [], duration: 0 };
|
|
692
|
+
return {
|
|
693
|
+
data: 0,
|
|
694
|
+
sql: sql
|
|
695
|
+
};
|
|
647
696
|
}
|
|
648
697
|
|
|
649
698
|
const query = SqlBuilder.toUpdateCaseByIdSql({
|
|
@@ -658,15 +707,19 @@ export class DbHelper {
|
|
|
658
707
|
stateGtZero: true
|
|
659
708
|
});
|
|
660
709
|
|
|
661
|
-
const
|
|
662
|
-
|
|
710
|
+
const execRes = await this.executeWithConn(query.sql, query.params);
|
|
711
|
+
const changes = execRes.data?.changes || 0;
|
|
712
|
+
return {
|
|
713
|
+
data: changes,
|
|
714
|
+
sql: execRes.sql
|
|
715
|
+
};
|
|
663
716
|
}
|
|
664
717
|
|
|
665
718
|
/**
|
|
666
719
|
* 更新数据(强制更新时间戳,系统字段不可修改)
|
|
667
720
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
668
721
|
*/
|
|
669
|
-
async updData(options: UpdateOptions): Promise<number
|
|
722
|
+
async updData(options: UpdateOptions): Promise<DbResult<number>> {
|
|
670
723
|
const { table, data, where } = options;
|
|
671
724
|
|
|
672
725
|
// 清理条件(排除 null 和 undefined)
|
|
@@ -684,15 +737,19 @@ export class DbHelper {
|
|
|
684
737
|
const { sql, params } = builder.toUpdateSql(snakeTable, processed);
|
|
685
738
|
|
|
686
739
|
// 执行
|
|
687
|
-
const
|
|
688
|
-
|
|
740
|
+
const execRes = await this.executeWithConn(sql, params);
|
|
741
|
+
const changes = execRes.data?.changes || 0;
|
|
742
|
+
return {
|
|
743
|
+
data: changes,
|
|
744
|
+
sql: execRes.sql
|
|
745
|
+
};
|
|
689
746
|
}
|
|
690
747
|
|
|
691
748
|
/**
|
|
692
749
|
* 软删除数据(deleted_at 设置为当前时间,state 设置为 0)
|
|
693
750
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
694
751
|
*/
|
|
695
|
-
async delData(options: DeleteOptions): Promise<number
|
|
752
|
+
async delData(options: DeleteOptions): Promise<DbResult<number>> {
|
|
696
753
|
const { table, where } = options;
|
|
697
754
|
|
|
698
755
|
return await this.updData({
|
|
@@ -706,7 +763,7 @@ export class DbHelper {
|
|
|
706
763
|
* 硬删除数据(物理删除,不可恢复)
|
|
707
764
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
708
765
|
*/
|
|
709
|
-
async delForce(options: Omit<DeleteOptions, "hard">): Promise<number
|
|
766
|
+
async delForce(options: Omit<DeleteOptions, "hard">): Promise<DbResult<number>> {
|
|
710
767
|
const { table, where } = options;
|
|
711
768
|
|
|
712
769
|
// 转换表名:小驼峰 → 下划线
|
|
@@ -720,15 +777,19 @@ export class DbHelper {
|
|
|
720
777
|
const builder = this.createSqlBuilder().where(snakeWhere);
|
|
721
778
|
const { sql, params } = builder.toDeleteSql(snakeTable);
|
|
722
779
|
|
|
723
|
-
const
|
|
724
|
-
|
|
780
|
+
const execRes = await this.executeWithConn(sql, params);
|
|
781
|
+
const changes = execRes.data?.changes || 0;
|
|
782
|
+
return {
|
|
783
|
+
data: changes,
|
|
784
|
+
sql: execRes.sql
|
|
785
|
+
};
|
|
725
786
|
}
|
|
726
787
|
|
|
727
788
|
/**
|
|
728
789
|
* 禁用数据(设置 state=2)
|
|
729
790
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
730
791
|
*/
|
|
731
|
-
async disableData(options: Omit<DeleteOptions, "hard">): Promise<number
|
|
792
|
+
async disableData(options: Omit<DeleteOptions, "hard">): Promise<DbResult<number>> {
|
|
732
793
|
const { table, where } = options;
|
|
733
794
|
|
|
734
795
|
return await this.updData({
|
|
@@ -744,7 +805,7 @@ export class DbHelper {
|
|
|
744
805
|
* 启用数据(设置 state=1)
|
|
745
806
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
746
807
|
*/
|
|
747
|
-
async enableData(options: Omit<DeleteOptions, "hard">): Promise<number
|
|
808
|
+
async enableData(options: Omit<DeleteOptions, "hard">): Promise<DbResult<number>> {
|
|
748
809
|
const { table, where } = options;
|
|
749
810
|
|
|
750
811
|
return await this.updData({
|
|
@@ -769,7 +830,7 @@ export class DbHelper {
|
|
|
769
830
|
// 使用 Bun SQL 的 begin 方法开启事务
|
|
770
831
|
// begin 方法会自动处理 commit/rollback
|
|
771
832
|
return await this.sql.begin(async (tx: any) => {
|
|
772
|
-
const trans = new DbHelper({ redis: this.redis, sql: tx, dialect: this.dialect
|
|
833
|
+
const trans = new DbHelper({ redis: this.redis, sql: tx, dialect: this.dialect });
|
|
773
834
|
return await callback(trans);
|
|
774
835
|
});
|
|
775
836
|
}
|
|
@@ -777,7 +838,7 @@ export class DbHelper {
|
|
|
777
838
|
/**
|
|
778
839
|
* 执行原始 SQL
|
|
779
840
|
*/
|
|
780
|
-
async query(sql: string, params?: any[]): Promise<any
|
|
841
|
+
async query(sql: string, params?: any[]): Promise<DbResult<any>> {
|
|
781
842
|
return await this.executeWithConn(sql, params);
|
|
782
843
|
}
|
|
783
844
|
|
|
@@ -785,27 +846,31 @@ export class DbHelper {
|
|
|
785
846
|
* 检查数据是否存在(优化性能)
|
|
786
847
|
* @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
787
848
|
*/
|
|
788
|
-
async exists(options: Omit<QueryOptions, "fields" | "orderBy" | "page" | "limit">): Promise<boolean
|
|
849
|
+
async exists(options: Omit<QueryOptions, "fields" | "orderBy" | "page" | "limit">): Promise<DbResult<boolean>> {
|
|
789
850
|
const { table, where, tableQualifier } = await this.prepareQueryOptions({ ...options, page: 1, limit: 1 } as any);
|
|
790
851
|
|
|
791
852
|
// 使用 COUNT(1) 性能更好
|
|
792
853
|
const builder = this.createSqlBuilder()
|
|
793
|
-
.
|
|
854
|
+
.selectRaw("COUNT(1) as cnt")
|
|
794
855
|
.from(table)
|
|
795
856
|
.where(DbUtils.addDefaultStateFilter(where, tableQualifier, false))
|
|
796
857
|
.limit(1);
|
|
797
858
|
|
|
798
859
|
const { sql, params } = builder.toSelectSql();
|
|
799
|
-
const
|
|
860
|
+
const execRes = await this.executeWithConn(sql, params);
|
|
861
|
+
const exists = (execRes.data?.[0]?.cnt || 0) > 0;
|
|
800
862
|
|
|
801
|
-
return
|
|
863
|
+
return {
|
|
864
|
+
data: exists,
|
|
865
|
+
sql: execRes.sql
|
|
866
|
+
};
|
|
802
867
|
}
|
|
803
868
|
|
|
804
869
|
/**
|
|
805
870
|
* 查询单个字段值(带字段名验证)
|
|
806
871
|
* @param field - 字段名(支持小驼峰或下划线格式)
|
|
807
872
|
*/
|
|
808
|
-
async getFieldValue<T = any>(options: Omit<QueryOptions, "fields"> & { field: string }): Promise<T | null
|
|
873
|
+
async getFieldValue<T = any>(options: Omit<QueryOptions, "fields"> & { field: string }): Promise<DbResult<T | null>> {
|
|
809
874
|
const { field, ...queryOptions } = options;
|
|
810
875
|
|
|
811
876
|
// 验证字段名格式(只允许字母、数字、下划线)
|
|
@@ -813,33 +878,49 @@ export class DbHelper {
|
|
|
813
878
|
throw new Error(`无效的字段名: ${field},只允许字母、数字和下划线`);
|
|
814
879
|
}
|
|
815
880
|
|
|
816
|
-
const
|
|
881
|
+
const oneRes = await this.getOne({
|
|
817
882
|
...queryOptions,
|
|
818
883
|
fields: [field]
|
|
819
884
|
});
|
|
820
885
|
|
|
886
|
+
const result = oneRes.data;
|
|
821
887
|
if (!result) {
|
|
822
|
-
return
|
|
888
|
+
return {
|
|
889
|
+
data: null,
|
|
890
|
+
sql: oneRes.sql
|
|
891
|
+
};
|
|
823
892
|
}
|
|
824
893
|
|
|
825
894
|
// 尝试直接访问字段(小驼峰)
|
|
826
895
|
if (field in result) {
|
|
827
|
-
return
|
|
896
|
+
return {
|
|
897
|
+
data: result[field],
|
|
898
|
+
sql: oneRes.sql
|
|
899
|
+
};
|
|
828
900
|
}
|
|
829
901
|
|
|
830
902
|
// 转换为小驼峰格式再尝试访问(支持用户传入下划线格式)
|
|
831
903
|
const camelField = field.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
832
904
|
if (camelField !== field && camelField in result) {
|
|
833
|
-
return
|
|
905
|
+
return {
|
|
906
|
+
data: result[camelField],
|
|
907
|
+
sql: oneRes.sql
|
|
908
|
+
};
|
|
834
909
|
}
|
|
835
910
|
|
|
836
911
|
// 转换为下划线格式再尝试访问(支持用户传入小驼峰格式)
|
|
837
912
|
const snakeField = field.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
838
913
|
if (snakeField !== field && snakeField in result) {
|
|
839
|
-
return
|
|
914
|
+
return {
|
|
915
|
+
data: result[snakeField],
|
|
916
|
+
sql: oneRes.sql
|
|
917
|
+
};
|
|
840
918
|
}
|
|
841
919
|
|
|
842
|
-
return
|
|
920
|
+
return {
|
|
921
|
+
data: null,
|
|
922
|
+
sql: oneRes.sql
|
|
923
|
+
};
|
|
843
924
|
}
|
|
844
925
|
|
|
845
926
|
/**
|
|
@@ -847,7 +928,7 @@ export class DbHelper {
|
|
|
847
928
|
* @param table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
848
929
|
* @param field - 字段名(支持小驼峰或下划线格式,会自动转换)
|
|
849
930
|
*/
|
|
850
|
-
async increment(table: string, field: string, where: WhereConditions, value: number = 1): Promise<number
|
|
931
|
+
async increment(table: string, field: string, where: WhereConditions, value: number = 1): Promise<DbResult<number>> {
|
|
851
932
|
// 转换表名和字段名:小驼峰 → 下划线
|
|
852
933
|
const snakeTable = snakeCase(table);
|
|
853
934
|
const snakeField = snakeCase(field);
|
|
@@ -883,8 +964,12 @@ export class DbHelper {
|
|
|
883
964
|
const quotedField = this.dialect.quoteIdent(snakeField);
|
|
884
965
|
const sql = whereClause ? `UPDATE ${quotedTable} SET ${quotedField} = ${quotedField} + ? WHERE ${whereClause}` : `UPDATE ${quotedTable} SET ${quotedField} = ${quotedField} + ?`;
|
|
885
966
|
|
|
886
|
-
const
|
|
887
|
-
|
|
967
|
+
const execRes = await this.executeWithConn(sql, [value, ...whereParams]);
|
|
968
|
+
const changes = execRes.data?.changes || 0;
|
|
969
|
+
return {
|
|
970
|
+
data: changes,
|
|
971
|
+
sql: execRes.sql
|
|
972
|
+
};
|
|
888
973
|
}
|
|
889
974
|
|
|
890
975
|
/**
|
|
@@ -892,7 +977,7 @@ export class DbHelper {
|
|
|
892
977
|
* @param table - 表名(支持小驼峰或下划线格式,会自动转换)
|
|
893
978
|
* @param field - 字段名(支持小驼峰或下划线格式,会自动转换)
|
|
894
979
|
*/
|
|
895
|
-
async decrement(table: string, field: string, where: WhereConditions, value: number = 1): Promise<number
|
|
980
|
+
async decrement(table: string, field: string, where: WhereConditions, value: number = 1): Promise<DbResult<number>> {
|
|
896
981
|
return await this.increment(table, field, where, -value);
|
|
897
982
|
}
|
|
898
983
|
}
|