befly 2.0.14 → 2.1.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/plugins/db.js CHANGED
@@ -1,76 +1,59 @@
1
+ import { SQL } from 'bun';
1
2
  import { Env } from '../config/env.js';
2
3
  import { Logger } from '../utils/logger.js';
3
4
  import { createQueryBuilder } from '../utils/curd.js';
4
5
 
6
+ // 使用 Promise 封装 SQL 客户端的创建与连通性校验(内部读取 Env 并构建 URL/参数)
7
+ async function createSqlClient() {
8
+ return new Promise(async (resolve, reject) => {
9
+ const sql = new SQL(Env.MYSQL_URL);
10
+
11
+ // 触发一次简单查询来确保连接可用;
12
+ try {
13
+ const result = await sql`SELECT VERSION() AS version`;
14
+ Logger.info(`数据库连接成功,MySQL 版本: ${result?.[0]?.version}`);
15
+ resolve(sql);
16
+ } catch (error) {
17
+ Logger.error('数据库连接测试失败:', error);
18
+ try {
19
+ await client.close();
20
+ } catch (e) {
21
+ Logger.error('关闭失败(创建阶段):', e);
22
+ }
23
+ reject(error);
24
+ }
25
+ });
26
+ }
27
+
5
28
  export default {
6
29
  after: ['_redis'],
7
30
  async onInit(befly) {
8
- let pool = null;
31
+ let sql = null;
9
32
 
10
33
  try {
11
34
  if (Env.MYSQL_ENABLE === 1) {
12
- // 动态导入 MariaDB 连接器
13
- const mariadb = await import('mariadb');
14
-
15
- // 创建 MariaDB 连接池配置
16
- const config = {
17
- host: Env.MYSQL_HOST || '127.0.0.1',
18
- port: Env.MYSQL_PORT || 3306,
19
- database: Env.MYSQL_DB || 'test',
20
- user: Env.MYSQL_USER || 'root',
21
- password: Env.MYSQL_PASSWORD || 'root',
22
- connectionLimit: Env.MYSQL_POOL_MAX || 10,
23
- charset: 'utf8mb4',
24
- timezone: Env.TIMEZONE || 'local',
25
- debug: Env.MYSQL_DEBUG === 1,
26
- acquireTimeout: 60000,
27
- timeout: 60000,
28
- // 连接保活设置
29
- idleTimeout: 1800000, // 30分钟
30
- minimumIdle: 2,
31
- // 重连设置
32
- reconnect: true,
33
- // 避免连接超时
34
- keepAliveDelay: 30000,
35
- insertIdAsNumber: true,
36
- decimalAsNumber: true,
37
- bigIntAsNumber: true
38
- };
39
-
40
- // 创建连接池
41
- pool = mariadb.createPool(config);
42
-
43
- // 测试数据库连接
44
- let conn;
45
- try {
46
- conn = await pool.getConnection();
47
- const result = await conn.query('SELECT VERSION() AS version');
48
- Logger.info(`数据库连接成功,MariaDB 版本: ${result[0].version}`);
49
- } catch (error) {
50
- Logger.error('数据库连接测试失败:', error);
51
- throw error;
52
- } finally {
53
- if (conn) {
54
- try {
55
- conn.release();
56
- } catch (releaseError) {
57
- Logger.warn('连接释放警告:', releaseError.message);
58
- }
59
- }
60
- }
35
+ // 创建 Bun SQL 客户端(内置连接池),并确保连接验证成功后再继续
36
+ sql = await createSqlClient();
61
37
 
62
38
  // 数据库管理类
63
39
  class DatabaseManager {
64
40
  // 私有属性
65
- #pool;
41
+ #sql;
66
42
 
67
- constructor(pool) {
68
- this.#pool = pool;
43
+ constructor(client) {
44
+ this.#sql = client;
69
45
  }
70
46
 
71
47
  // 原始连接池访问
72
48
  get pool() {
73
- return this.#pool;
49
+ // 兼容旧接口,返回占位对象
50
+ return {
51
+ // 这些方法在 Bun SQL 中不可用,这里提供空实现以避免调用错误
52
+ activeConnections: () => 0,
53
+ totalConnections: () => 0,
54
+ idleConnections: () => 0,
55
+ taskQueueSize: () => 0
56
+ };
74
57
  }
75
58
 
76
59
  // 创建查询构造器
@@ -116,40 +99,64 @@ export default {
116
99
  return where;
117
100
  }
118
101
 
119
- // 私有方法:执行 SQL(支持传入连接对象)
120
- async #executeWithConn(sql, params = [], conn = null) {
121
- if (!sql || typeof sql !== 'string') {
102
+ // '?' 占位符转换为 Bun SQL 使用的 $1, $2 ...
103
+ #toDollarParams(query, params) {
104
+ if (!params || params.length === 0) return query;
105
+ let i = 0;
106
+ return query.replace(/\?/g, () => `$${++i}`);
107
+ }
108
+
109
+ // 私有方法:执行 SQL(支持传入事务连接对象)
110
+ async #executeWithConn(query, params = [], conn = null) {
111
+ if (!query || typeof query !== 'string') {
122
112
  throw new Error('SQL 语句是必需的');
123
113
  }
124
114
 
125
- let providedConn = conn;
126
- let shouldRelease = false;
127
-
115
+ const isSelectLike = /^\s*(with|select|show|desc|explain)\b/i.test(query);
116
+ const isWriteLike = /^\s*(insert|update|delete|replace)\b/i.test(query);
117
+ const client = conn || this.#sql;
128
118
  try {
129
- // 如果没有提供连接,从池中获取
130
- if (!providedConn) {
131
- providedConn = await this.#pool.getConnection();
132
- shouldRelease = true;
133
- }
134
-
135
119
  if (Env.MYSQL_DEBUG === 1) {
136
- Logger.debug('执行SQL:', { sql, params });
120
+ Logger.debug('执行SQL:', { sql: query, params });
121
+ }
122
+ // 读查询,直接返回行
123
+ if (isSelectLike) {
124
+ if (params && params.length > 0) {
125
+ const q = this.#toDollarParams(query, params);
126
+ return await client.unsafe(q, params);
127
+ } else {
128
+ return await client.unsafe(query);
129
+ }
137
130
  }
138
131
 
139
- const result = await providedConn.query(sql, params);
140
- return result;
141
- } catch (error) {
142
- Logger.error('SQL 执行失败:', { sql, params, error: error.message });
143
- throw error;
144
- } finally {
145
- // 只有当连接是我们获取的时候才释放
146
- if (shouldRelease && providedConn) {
132
+ // 写查询需要返回受影响行数/插入ID,必须保证同连接
133
+ if (isWriteLike) {
134
+ const runOn = conn ? client : await this.#sql.reserve();
147
135
  try {
148
- providedConn.release();
149
- } catch (releaseError) {
150
- Logger.warn('连接释放警告:', releaseError.message);
136
+ if (params && params.length > 0) {
137
+ const q = this.#toDollarParams(query, params);
138
+ await runOn.unsafe(q, params);
139
+ } else {
140
+ await runOn.unsafe(query);
141
+ }
142
+ const [{ affectedRows }] = await runOn`SELECT ROW_COUNT() AS affectedRows`;
143
+ const [{ insertId }] = await runOn`SELECT LAST_INSERT_ID() AS insertId`;
144
+ return { affectedRows: Number(affectedRows) || 0, insertId: Number(insertId) || 0 };
145
+ } finally {
146
+ if (!conn && runOn && typeof runOn.release === 'function') runOn.release();
151
147
  }
152
148
  }
149
+
150
+ // 其他(DDL等),直接执行并返回空数组以与旧行为尽可能兼容
151
+ if (params && params.length > 0) {
152
+ const q = this.#toDollarParams(query, params);
153
+ return await client.unsafe(q, params);
154
+ } else {
155
+ return await client.unsafe(query);
156
+ }
157
+ } catch (error) {
158
+ Logger.error('SQL 执行失败:', { sql: query, params, error: error.message });
159
+ throw error;
153
160
  }
154
161
  }
155
162
 
@@ -178,8 +185,8 @@ export default {
178
185
  }
179
186
  });
180
187
 
181
- const { sql, params } = builder.toSelectSql();
182
- const result = await this.#executeWithConn(sql, params, conn);
188
+ const { sql: q, params } = builder.toSelectSql();
189
+ const result = await this.#executeWithConn(q, params, conn);
183
190
  return result[0] || null;
184
191
  } catch (error) {
185
192
  Logger.error('getDetail 执行失败:', error);
@@ -234,8 +241,8 @@ export default {
234
241
  builder.limit(numPageSize, offset);
235
242
  }
236
243
 
237
- const { sql, params } = builder.toSelectSql();
238
- const rows = await this.#executeWithConn(sql, params, conn);
244
+ const { sql: q, params } = builder.toSelectSql();
245
+ const rows = await this.#executeWithConn(q, params, conn);
239
246
 
240
247
  // 获取总数(如果需要分页)
241
248
  let total = 0;
@@ -300,8 +307,8 @@ export default {
300
307
  builder.orderBy(orderBy);
301
308
  }
302
309
 
303
- const { sql, params } = builder.toSelectSql();
304
- const result = await this.#executeWithConn(sql, params, conn);
310
+ const { sql: q, params } = builder.toSelectSql();
311
+ const result = await this.#executeWithConn(q, params, conn);
305
312
  return Array.isArray(result) ? result : [];
306
313
  } catch (error) {
307
314
  Logger.error('getAll 执行失败:', error);
@@ -322,8 +329,8 @@ export default {
322
329
  try {
323
330
  const processedData = await this.#processDataForInsert(data);
324
331
  const builder = createQueryBuilder();
325
- const { sql, params } = builder.toInsertSql(table, processedData);
326
- return await this.#executeWithConn(sql, params, conn);
332
+ const { sql: q, params } = builder.toInsertSql(table, processedData);
333
+ return await this.#executeWithConn(q, params, conn);
327
334
  } catch (error) {
328
335
  Logger.error('insData 执行失败:', error);
329
336
  throw error;
@@ -355,8 +362,8 @@ export default {
355
362
  };
356
363
 
357
364
  const builder = createQueryBuilder().where(where);
358
- const { sql, params } = builder.toUpdateSql(table, updateData);
359
- return await this.#executeWithConn(sql, params, conn);
365
+ const { sql: q, params } = builder.toUpdateSql(table, updateData);
366
+ return await this.#executeWithConn(q, params, conn);
360
367
  } catch (error) {
361
368
  Logger.error('updData 执行失败:', error);
362
369
  throw error;
@@ -375,8 +382,8 @@ export default {
375
382
 
376
383
  try {
377
384
  const builder = createQueryBuilder().where(where);
378
- const { sql, params } = builder.toDeleteSql(table);
379
- return await this.#executeWithConn(sql, params, conn);
385
+ const { sql: q, params } = builder.toDeleteSql(table);
386
+ return await this.#executeWithConn(q, params, conn);
380
387
  } catch (error) {
381
388
  Logger.error('delData 执行失败:', error);
382
389
  throw error;
@@ -401,8 +408,8 @@ export default {
401
408
  };
402
409
 
403
410
  const builder = createQueryBuilder().where(where);
404
- const { sql, params } = builder.toUpdateSql(table, updateData);
405
- return await this.#executeWithConn(sql, params, conn);
411
+ const { sql: q, params } = builder.toUpdateSql(table, updateData);
412
+ return await this.#executeWithConn(q, params, conn);
406
413
  } catch (error) {
407
414
  Logger.error('delData2 执行失败:', error);
408
415
  throw error;
@@ -422,8 +429,8 @@ export default {
422
429
  try {
423
430
  const processedDataArray = await this.#processDataForInsert(dataArray);
424
431
  const builder = createQueryBuilder();
425
- const { sql, params } = builder.toInsertSql(table, processedDataArray);
426
- return await this.#executeWithConn(sql, params, conn);
432
+ const { sql: q, params } = builder.toInsertSql(table, processedDataArray);
433
+ return await this.#executeWithConn(q, params, conn);
427
434
  } catch (error) {
428
435
  Logger.error('insBatch 执行失败:', error);
429
436
  throw error;
@@ -455,8 +462,8 @@ export default {
455
462
  }
456
463
  });
457
464
 
458
- const { sql, params } = builder.toCountSql();
459
- const result = await this.#executeWithConn(sql, params, conn);
465
+ const { sql: q, params } = builder.toCountSql();
466
+ const result = await this.#executeWithConn(q, params, conn);
460
467
  return result[0]?.total || 0;
461
468
  } catch (error) {
462
469
  Logger.error('getCount 执行失败:', error);
@@ -520,102 +527,54 @@ export default {
520
527
  throw new Error('事务回调函数是必需的');
521
528
  }
522
529
 
523
- let conn;
524
530
  try {
525
- conn = await this.#pool.getConnection();
526
- await conn.beginTransaction();
527
-
528
- // 为回调函数提供连接对象和高级方法
529
- const txMethods = {
530
- // 原始SQL执行方法
531
- query: async (sql, params = []) => {
532
- return await conn.query(sql, params);
533
- },
534
- execute: async (sql, params = []) => {
535
- return await conn.query(sql, params);
536
- },
537
-
538
- // 高级数据操作方法 - 直接调用私有方法,传入事务连接
539
- getDetail: async (table, options = {}) => {
540
- return await this.#getDetailWithConn(table, options, conn);
541
- },
542
-
543
- getList: async (table, options = {}) => {
544
- return await this.#getListWithConn(table, options, conn);
545
- },
546
-
547
- getAll: async (table, options = {}) => {
548
- return await this.#getAllWithConn(table, options, conn);
549
- },
550
-
551
- insData: async (table, data) => {
552
- return await this.#insDataWithConn(table, data, conn);
553
- },
554
-
555
- updData: async (table, data, where) => {
556
- return await this.#updDataWithConn(table, data, where, conn);
557
- },
558
-
559
- delData: async (table, where) => {
560
- return await this.#delDataWithConn(table, where, conn);
561
- },
562
-
563
- delData2: async (table, where) => {
564
- return await this.#delData2WithConn(table, where, conn);
565
- },
566
-
567
- getCount: async (table, options = {}) => {
568
- return await this.#getCountWithConn(table, options, conn);
569
- },
570
-
571
- insBatch: async (table, dataArray) => {
572
- return await this.#insBatchWithConn(table, dataArray, conn);
573
- }
574
- };
575
-
576
- const result = await callback(txMethods);
577
-
578
- await conn.commit();
531
+ const result = await this.#sql.begin(async (tx) => {
532
+ // 为回调函数提供连接对象和高级方法(基于事务连接)
533
+ const txMethods = {
534
+ // 原始SQL执行方法
535
+ query: async (query, params = []) => this.#executeWithConn(query, params, tx),
536
+ execute: async (query, params = []) => this.#executeWithConn(query, params, tx),
537
+
538
+ // 高级数据操作方法 - 直接调用私有方法,传入事务连接
539
+ getDetail: async (table, options = {}) => this.#getDetailWithConn(table, options, tx),
540
+ getList: async (table, options = {}) => this.#getListWithConn(table, options, tx),
541
+ getAll: async (table, options = {}) => this.#getAllWithConn(table, options, tx),
542
+ insData: async (table, data) => this.#insDataWithConn(table, data, tx),
543
+ updData: async (table, data, where) => this.#updDataWithConn(table, data, where, tx),
544
+ delData: async (table, where) => this.#delDataWithConn(table, where, tx),
545
+ delData2: async (table, where) => this.#delData2WithConn(table, where, tx),
546
+ getCount: async (table, options = {}) => this.#getCountWithConn(table, options, tx),
547
+ insBatch: async (table, dataArray) => this.#insBatchWithConn(table, dataArray, tx)
548
+ };
549
+
550
+ return await callback(txMethods);
551
+ });
579
552
  return result;
580
553
  } catch (error) {
581
- if (conn) {
582
- try {
583
- await conn.rollback();
584
- Logger.info('事务已回滚');
585
- } catch (rollbackError) {
586
- Logger.error('事务回滚失败:', rollbackError);
587
- }
588
- }
554
+ // Bun SQL 会自动回滚
555
+ Logger.info('事务已回滚');
589
556
  throw error;
590
- } finally {
591
- if (conn) {
592
- try {
593
- conn.release();
594
- } catch (releaseError) {
595
- Logger.warn('连接释放警告:', releaseError.message);
596
- }
597
- }
598
557
  }
599
558
  }
600
559
 
601
560
  // 获取连接池状态
602
561
  getPoolStatus() {
603
562
  return {
604
- activeConnections: this.#pool.activeConnections(),
605
- totalConnections: this.#pool.totalConnections(),
606
- idleConnections: this.#pool.idleConnections(),
607
- taskQueueSize: this.#pool.taskQueueSize()
563
+ activeConnections: 0,
564
+ totalConnections: 0,
565
+ idleConnections: 0,
566
+ taskQueueSize: 0
608
567
  };
609
568
  }
610
569
 
611
570
  // 关闭连接池
612
571
  async close() {
613
- if (this.#pool) {
572
+ if (this.#sql) {
614
573
  try {
615
- await this.#pool.end();
616
- Logger.info('数据库连接池已关闭');
574
+ await this.#sql.close();
575
+ Logger.info('数据库连接已关闭');
617
576
  } catch (error) {
618
- Logger.error('关闭数据库连接池失败:', error);
577
+ Logger.error('关闭数据库连接失败:', error);
619
578
  throw error;
620
579
  }
621
580
  }
@@ -623,7 +582,7 @@ export default {
623
582
  }
624
583
 
625
584
  // 创建数据库管理器实例
626
- const dbManager = new DatabaseManager(pool);
585
+ const dbManager = new DatabaseManager(sql);
627
586
 
628
587
  // 监听进程退出事件,确保连接池正确关闭
629
588
  const gracefulShutdown = async (signal) => {
@@ -653,9 +612,9 @@ export default {
653
612
  });
654
613
 
655
614
  // 清理资源
656
- if (pool) {
615
+ if (sql) {
657
616
  try {
658
- await pool.end();
617
+ await sql.close();
659
618
  } catch (cleanupError) {
660
619
  Logger.error('清理连接池失败:', cleanupError);
661
620
  }