befly 2.0.12 → 2.1.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/.gitignore +94 -0
- package/.npmrc +3 -0
- package/.prettierignore +2 -0
- package/.prettierrc +11 -0
- package/README.md +2 -2
- package/checks/table.js +215 -0
- package/config/env.js +1 -0
- package/main.js +43 -60
- package/package.json +25 -5
- package/plugins/db.js +137 -178
- package/scripts/syncDb.js +367 -0
- package/system.js +118 -0
- package/tables/common.json +16 -0
- package/tables/tool.json +6 -0
- package/utils/util.js +123 -0
- package/utils/validate.js +115 -99
- package/checks/schema.js +0 -132
- package/schema/common.json +0 -17
- package/schema/debug.json +0 -1
- package/schema/health.json +0 -1
- package/schema/tool.json +0 -6
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
|
|
31
|
+
let sql = null;
|
|
9
32
|
|
|
10
33
|
try {
|
|
11
34
|
if (Env.MYSQL_ENABLE === 1) {
|
|
12
|
-
//
|
|
13
|
-
|
|
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
|
-
#
|
|
41
|
+
#sql;
|
|
66
42
|
|
|
67
|
-
constructor(
|
|
68
|
-
this.#
|
|
43
|
+
constructor(client) {
|
|
44
|
+
this.#sql = client;
|
|
69
45
|
}
|
|
70
46
|
|
|
71
47
|
// 原始连接池访问
|
|
72
48
|
get pool() {
|
|
73
|
-
|
|
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
|
-
//
|
|
120
|
-
|
|
121
|
-
if (!
|
|
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
|
-
|
|
126
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
582
|
-
|
|
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:
|
|
605
|
-
totalConnections:
|
|
606
|
-
idleConnections:
|
|
607
|
-
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.#
|
|
572
|
+
if (this.#sql) {
|
|
614
573
|
try {
|
|
615
|
-
await this.#
|
|
616
|
-
Logger.info('
|
|
574
|
+
await this.#sql.close();
|
|
575
|
+
Logger.info('数据库连接已关闭');
|
|
617
576
|
} catch (error) {
|
|
618
|
-
Logger.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(
|
|
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 (
|
|
615
|
+
if (sql) {
|
|
657
616
|
try {
|
|
658
|
-
await
|
|
617
|
+
await sql.close();
|
|
659
618
|
} catch (cleanupError) {
|
|
660
619
|
Logger.error('清理连接池失败:', cleanupError);
|
|
661
620
|
}
|