mm_mysql 2.5.6 → 2.5.7
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/example/index.js +40 -0
- package/index.js +24 -574
- package/lib/mysql.js +532 -0
- package/{sql.js → lib/sql.js} +4 -4
- package/package.json +10 -3
- package/test.js +12 -6
- /package/{db.js → lib/db.js} +0 -0
package/example/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mm_mysql 最小可运行示例
|
|
3
|
+
* @description 演示 Mysql 类的实例化、工厂获取实例与核心方法存在性。
|
|
4
|
+
* 注意:不会真正连接 MySQL 服务;host/user/password 为示例假值,实际使用时请替换为真实配置。
|
|
5
|
+
*/
|
|
6
|
+
const { Mysql, mysqlAdmin } = require('../index.js');
|
|
7
|
+
|
|
8
|
+
/** 示例假配置(实际使用时替换为真实 MySQL 连接信息) */
|
|
9
|
+
const CONFIG = {
|
|
10
|
+
host: '127.0.0.1',
|
|
11
|
+
port: 3306,
|
|
12
|
+
user: 'root',
|
|
13
|
+
password: 'change_me',
|
|
14
|
+
database: 'example_db'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 主函数
|
|
19
|
+
*/
|
|
20
|
+
function main() {
|
|
21
|
+
console.log('== mm_mysql 示例开始 ==');
|
|
22
|
+
|
|
23
|
+
// 实例化(仅合并配置,不建立真实连接)
|
|
24
|
+
const db = new Mysql(CONFIG);
|
|
25
|
+
console.log('实例化 Mysql 成功');
|
|
26
|
+
console.log('配置读取:', db.config.host + ':' + db.config.port + '/' + db.config.database);
|
|
27
|
+
|
|
28
|
+
// 通过工厂函数获取按作用域缓存的实例
|
|
29
|
+
const cache = mysqlAdmin('example', CONFIG);
|
|
30
|
+
console.log('mysqlAdmin 获取实例:', cache instanceof Mysql);
|
|
31
|
+
|
|
32
|
+
// 核心方法存在性检查(不真正执行,避免连接外部服务)
|
|
33
|
+
const METHODS = ['open', 'close', 'getConn', 'run', 'exec', 'transaction', 'db', 'table', 'healthCheck'];
|
|
34
|
+
const missing = METHODS.filter((name) => typeof cache[name] !== 'function');
|
|
35
|
+
console.log('核心方法检查:', missing.length === 0 ? '全部存在' : '缺失: ' + missing.join(','));
|
|
36
|
+
|
|
37
|
+
console.log('== mm_mysql 示例结束 ==');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
main();
|
package/index.js
CHANGED
|
@@ -1,574 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// 连接池优化配置(针对高并发写入场景)
|
|
27
|
-
connection_limit: 100, // 减少连接数,避免资源竞争
|
|
28
|
-
acquire_timeout: 30000, // 延长获取连接超时时间
|
|
29
|
-
wait_for_connections: true, // 保持连接等待机制
|
|
30
|
-
idle_timeout: 300000, // 延长空闲连接超时时间(5分钟)
|
|
31
|
-
max_idle: 30, // 减少最大空闲连接数
|
|
32
|
-
queue_timeout: 60000, // 队列超时时间
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* 构造函数(统一使用连接池)
|
|
37
|
-
* @param {object} config - 配置对象
|
|
38
|
-
*/
|
|
39
|
-
constructor(config = {}) {
|
|
40
|
-
super(config);
|
|
41
|
-
this._pool = null;
|
|
42
|
-
// 移除单连接支持,统一使用连接池
|
|
43
|
-
|
|
44
|
-
// 错误信息(注意:同 scope 下为单例共享,并发时以最后一次写入为准)
|
|
45
|
-
this.error = null;
|
|
46
|
-
// 最近执行的SQL语句
|
|
47
|
-
this.sql = '';
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* 获取MySQL2支持的配置参数
|
|
53
|
-
* 根据连接类型(连接池或单连接)过滤参数,避免警告
|
|
54
|
-
* @param {boolean} is_pool - 是否为连接池配置
|
|
55
|
-
* @returns {object} 过滤后的配置对象
|
|
56
|
-
*/
|
|
57
|
-
Mysql.prototype._getValidConfig = function (is_pool = false) {
|
|
58
|
-
// 基础连接参数(适用于所有连接类型)
|
|
59
|
-
let base_map = {
|
|
60
|
-
'host': 'host',
|
|
61
|
-
'port': 'port',
|
|
62
|
-
'user': 'user',
|
|
63
|
-
'password': 'password',
|
|
64
|
-
'database': 'database',
|
|
65
|
-
'charset': 'charset',
|
|
66
|
-
'timezone': 'timezone',
|
|
67
|
-
'connect_timeout': 'connectTimeout'
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// 连接池专用参数
|
|
71
|
-
let pool_map = {
|
|
72
|
-
'connection_limit': 'connectionLimit',
|
|
73
|
-
'wait_for_connections': 'waitForConnections',
|
|
74
|
-
'idle_timeout': 'idleTimeout',
|
|
75
|
-
'max_idle': 'maxIdle'
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
let config = {};
|
|
79
|
-
|
|
80
|
-
// 添加基础参数
|
|
81
|
-
for (let key in base_map) {
|
|
82
|
-
if (this.config[key] !== undefined) {
|
|
83
|
-
config[base_map[key]] = this.config[key];
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// 如果是连接池,添加连接池专用参数
|
|
88
|
-
if (is_pool) {
|
|
89
|
-
for (let key in pool_map) {
|
|
90
|
-
if (this.config[key] !== undefined) {
|
|
91
|
-
config[pool_map[key]] = this.config[key];
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// 如果是单连接,确保不包含任何连接池专用参数
|
|
96
|
-
else {
|
|
97
|
-
// 移除所有连接池专用参数,避免mysql2内部警告
|
|
98
|
-
for (let key in pool_map) {
|
|
99
|
-
if (pool_map[key] in config) {
|
|
100
|
-
delete config[pool_map[key]];
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return config;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* 打开数据库连接(统一使用连接池)
|
|
110
|
-
* @returns {Promise<boolean>}
|
|
111
|
-
* @throws {TypeError} 当配置参数无效时
|
|
112
|
-
*/
|
|
113
|
-
Mysql.prototype.open = async function () {
|
|
114
|
-
// 参数校验
|
|
115
|
-
if (!this.config || typeof this.config !== 'object') {
|
|
116
|
-
throw new TypeError('config must be object');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
// 统一使用连接池模式
|
|
121
|
-
let valid_config = this._getValidConfig(true);
|
|
122
|
-
this._pool = mysql.createPool(valid_config);
|
|
123
|
-
|
|
124
|
-
// 测试连接池连接
|
|
125
|
-
let conn = await this._pool.getConnection();
|
|
126
|
-
conn.release();
|
|
127
|
-
return true;
|
|
128
|
-
} catch (error) {
|
|
129
|
-
this.log('error', '数据库连接池创建失败', error);
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* 关闭数据库连接(统一使用连接池)
|
|
136
|
-
* @returns {Promise<boolean>}
|
|
137
|
-
*/
|
|
138
|
-
Mysql.prototype.close = async function () {
|
|
139
|
-
if (!this._pool) {
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
try {
|
|
143
|
-
await this._pool.end();
|
|
144
|
-
this._pool = null;
|
|
145
|
-
this.log('info', '数据库连接池已关闭');
|
|
146
|
-
return true;
|
|
147
|
-
} catch (error) {
|
|
148
|
-
this.log('error', '关闭连接池失败', error);
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* 获取数据库连接(统一使用连接池)
|
|
155
|
-
* @returns {Promise<object>}
|
|
156
|
-
* @throws {TypeError} 当连接池不存在时
|
|
157
|
-
*/
|
|
158
|
-
Mysql.prototype.getConn = async function () {
|
|
159
|
-
// 连接池不存在则创建
|
|
160
|
-
if (!this._pool) {
|
|
161
|
-
await this.open();
|
|
162
|
-
}
|
|
163
|
-
// 连接池可能已被外部关闭,检测并重建
|
|
164
|
-
if (this._pool._closed) {
|
|
165
|
-
await this.open();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return await this._pool.getConnection();
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* 执行SQL查询
|
|
173
|
-
* @param {string} sql - SQL语句
|
|
174
|
-
* @param {Array} params - 参数数组
|
|
175
|
-
* @returns {Promise<object>}
|
|
176
|
-
* @throws {TypeError} 当sql参数无效时
|
|
177
|
-
*/
|
|
178
|
-
Mysql.prototype.run = async function (sql, params = []) {
|
|
179
|
-
// 参数校验
|
|
180
|
-
if (typeof sql !== 'string' || sql.trim() === '') {
|
|
181
|
-
throw new TypeError('sql must be non-empty string');
|
|
182
|
-
}
|
|
183
|
-
if (!Array.isArray(params)) {
|
|
184
|
-
throw new TypeError('params must be array');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
this.error = null;
|
|
188
|
-
this.sql = '';
|
|
189
|
-
let conn = null;
|
|
190
|
-
let ret = [];
|
|
191
|
-
try {
|
|
192
|
-
conn = await this.getConn();
|
|
193
|
-
this.sql = sql;
|
|
194
|
-
let [rows] = await conn.query(sql, params);
|
|
195
|
-
ret = rows;
|
|
196
|
-
} catch (err) {
|
|
197
|
-
console.error('SQL执行失败', err);
|
|
198
|
-
this.sql = err.sql || sql;
|
|
199
|
-
this.error = {
|
|
200
|
-
code: err.errno,
|
|
201
|
-
message: err.sqlMessage
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
finally {
|
|
205
|
-
await releaseConn(conn);
|
|
206
|
-
}
|
|
207
|
-
return ret;
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* 执行SQL语句(优化版,支持预处理语句缓存)
|
|
212
|
-
* @param {string} sql - SQL语句
|
|
213
|
-
* @param {Array} params - 参数数组
|
|
214
|
-
* @returns {Promise<object>}
|
|
215
|
-
* @throws {TypeError} 当sql参数无效时
|
|
216
|
-
*/
|
|
217
|
-
Mysql.prototype.exec = async function (sql, params = []) {
|
|
218
|
-
// 参数校验
|
|
219
|
-
if (typeof sql !== 'string' || sql.trim() === '') {
|
|
220
|
-
throw new TypeError('sql must be non-empty string');
|
|
221
|
-
}
|
|
222
|
-
if (!Array.isArray(params)) {
|
|
223
|
-
throw new TypeError('params must be array');
|
|
224
|
-
}
|
|
225
|
-
this.error = null;
|
|
226
|
-
this.sql = '';
|
|
227
|
-
let conn = null;
|
|
228
|
-
let ret = 0;
|
|
229
|
-
try {
|
|
230
|
-
conn = await this.getConn();
|
|
231
|
-
this.sql = sql;
|
|
232
|
-
let [result] = await conn.execute(sql, params);
|
|
233
|
-
ret = result.insertId || result.affectedRows || result.changedRows || 1;
|
|
234
|
-
} catch (error) {
|
|
235
|
-
console.error('SQL执行失败', error);
|
|
236
|
-
this.sql = error.sql || sql;
|
|
237
|
-
this.error = {
|
|
238
|
-
code: error.errno,
|
|
239
|
-
message: error.sqlMessage
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
finally {
|
|
243
|
-
await releaseConn(conn);
|
|
244
|
-
}
|
|
245
|
-
return ret;
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* 执行多语句SQL脚本(专用于导入)
|
|
250
|
-
* 通过独立连接执行,不影响连接池默认配置
|
|
251
|
-
* @param {string} sql - SQL脚本内容,可含多条以分号分隔的语句
|
|
252
|
-
* @returns {Promise<object>} 执行结果统计对象
|
|
253
|
-
* @throws {TypeError} 当sql参数无效时
|
|
254
|
-
*/
|
|
255
|
-
Mysql.prototype.runSql = async function (sql) {
|
|
256
|
-
// 参数校验
|
|
257
|
-
if (typeof sql !== 'string' || sql.trim() === '') {
|
|
258
|
-
throw new TypeError('sql must be non-empty string');
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
let conn = null;
|
|
262
|
-
let ret = { success: false, statements: 0, affected_rows: 0, error: null };
|
|
263
|
-
try {
|
|
264
|
-
// 创建专用连接,仅导入连接开启多语句支持
|
|
265
|
-
let valid_config = this._getValidConfig(false);
|
|
266
|
-
valid_config.multipleStatements = true;
|
|
267
|
-
conn = await mysql.createConnection(valid_config);
|
|
268
|
-
|
|
269
|
-
// 执行整段SQL脚本
|
|
270
|
-
let [results] = await conn.query(sql);
|
|
271
|
-
// 统计语句数和受影响行数
|
|
272
|
-
if (Array.isArray(results)) {
|
|
273
|
-
ret.statements = results.length;
|
|
274
|
-
for (let item of results) {
|
|
275
|
-
ret.affected_rows += item.affectedRows || 0;
|
|
276
|
-
}
|
|
277
|
-
} else {
|
|
278
|
-
ret.statements = 1;
|
|
279
|
-
ret.affected_rows = results.affectedRows || 0;
|
|
280
|
-
}
|
|
281
|
-
ret.success = true;
|
|
282
|
-
} catch (error) {
|
|
283
|
-
this.error = {
|
|
284
|
-
code: error.errno,
|
|
285
|
-
message: error.sqlMessage || error.message
|
|
286
|
-
};
|
|
287
|
-
this.sql = error.sql || sql;
|
|
288
|
-
ret.error = this.error.message;
|
|
289
|
-
} finally {
|
|
290
|
-
if (conn) {
|
|
291
|
-
try {
|
|
292
|
-
await conn.end();
|
|
293
|
-
} catch (end_error) {
|
|
294
|
-
this.log('error', '关闭导入连接失败', end_error);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return ret;
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* 开始事务(优化版)
|
|
303
|
-
* @returns {Promise<object>} 事务连接对象
|
|
304
|
-
*/
|
|
305
|
-
Mysql.prototype.beginTransaction = async function () {
|
|
306
|
-
let conn = await this.getConn();
|
|
307
|
-
|
|
308
|
-
try {
|
|
309
|
-
await conn.beginTransaction();
|
|
310
|
-
|
|
311
|
-
let ended = false;
|
|
312
|
-
|
|
313
|
-
return {
|
|
314
|
-
connection: conn,
|
|
315
|
-
commit: async () => {
|
|
316
|
-
if (ended) return;
|
|
317
|
-
await conn.commit();
|
|
318
|
-
ended = true;
|
|
319
|
-
await releaseConn(conn);
|
|
320
|
-
},
|
|
321
|
-
rollback: async () => {
|
|
322
|
-
if (ended) return;
|
|
323
|
-
await conn.rollback();
|
|
324
|
-
ended = true;
|
|
325
|
-
await releaseConn(conn);
|
|
326
|
-
},
|
|
327
|
-
_is_ended: () => ended
|
|
328
|
-
};
|
|
329
|
-
} catch (error) {
|
|
330
|
-
// 如果事务开始失败,确保连接被释放
|
|
331
|
-
await releaseConn(conn);
|
|
332
|
-
this.log('error', '事务开始失败', error);
|
|
333
|
-
throw error;
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* 在事务中执行多个操作(优化版)
|
|
339
|
-
* @param {Function} callback - 包含事务操作的回调函数
|
|
340
|
-
* @returns {Promise<*>} 回调函数的返回值
|
|
341
|
-
* @throws {TypeError} 当callback参数无效时
|
|
342
|
-
*/
|
|
343
|
-
Mysql.prototype.transaction = async function (callback) {
|
|
344
|
-
if (typeof callback !== 'function') {
|
|
345
|
-
throw new TypeError('callback must be function');
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
let transaction = await this.beginTransaction();
|
|
349
|
-
|
|
350
|
-
try {
|
|
351
|
-
let result = await callback(transaction);
|
|
352
|
-
|
|
353
|
-
// 如果事务未结束,自动提交
|
|
354
|
-
if (!transaction._is_ended()) {
|
|
355
|
-
await transaction.commit();
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return result;
|
|
359
|
-
} catch (error) {
|
|
360
|
-
// 如果事务未结束,自动回滚
|
|
361
|
-
if (!transaction._is_ended()) {
|
|
362
|
-
await transaction.rollback().catch(() => {
|
|
363
|
-
// 简化错误处理,避免频繁日志
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
throw error;
|
|
368
|
-
}
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* 读取整张表的数据(改进版)
|
|
373
|
-
* @param {string} table - 表名
|
|
374
|
-
* @param {object} condition - 查询条件
|
|
375
|
-
* @param {object} options - 选项(orderBy, limit, offset等)
|
|
376
|
-
* @returns {Promise<Array>}
|
|
377
|
-
* @throws {TypeError} 当table参数无效时
|
|
378
|
-
*/
|
|
379
|
-
Mysql.prototype.read = async function (table, condition = {}, options = {}) {
|
|
380
|
-
// 参数校验
|
|
381
|
-
if (typeof table !== 'string' || table.trim() === '') {
|
|
382
|
-
throw new TypeError('table must be non-empty string');
|
|
383
|
-
}
|
|
384
|
-
if (condition && typeof condition !== 'object') {
|
|
385
|
-
throw new TypeError('condition must be object');
|
|
386
|
-
}
|
|
387
|
-
if (options && typeof options !== 'object') {
|
|
388
|
-
throw new TypeError('options must be object');
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
try {
|
|
392
|
-
let sql = `SELECT * FROM ${table}`;
|
|
393
|
-
let params = [];
|
|
394
|
-
|
|
395
|
-
// 处理条件
|
|
396
|
-
if (Object.keys(condition).length > 0) {
|
|
397
|
-
let whereClauses = [];
|
|
398
|
-
for (let [field, value] of Object.entries(condition)) {
|
|
399
|
-
whereClauses.push(`${field} = ?`);
|
|
400
|
-
params.push(value);
|
|
401
|
-
}
|
|
402
|
-
sql += ` WHERE ${whereClauses.join(' AND ')}`;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// 处理排序
|
|
406
|
-
if (options.orderBy) {
|
|
407
|
-
sql += ` ORDER BY ${options.orderBy}`;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// 处理分页
|
|
411
|
-
if (options.limit) {
|
|
412
|
-
sql += ` LIMIT ${options.limit}`;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (options.offset) {
|
|
416
|
-
sql += ` OFFSET ${options.offset}`;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// 执行查询
|
|
420
|
-
return await this.run(sql, params);
|
|
421
|
-
} catch (error) {
|
|
422
|
-
this.log('error', '查询失败', error);
|
|
423
|
-
throw error; // 重新抛出错误,让调用方知道查询失败
|
|
424
|
-
}
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* 获取连接池状态信息
|
|
429
|
-
* @returns {object} 连接池状态对象
|
|
430
|
-
*/
|
|
431
|
-
Mysql.prototype.getPoolStats = function () {
|
|
432
|
-
if (!this._pool) {
|
|
433
|
-
return {
|
|
434
|
-
status: 'not_initialized',
|
|
435
|
-
message: '连接池未初始化'
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
return {
|
|
440
|
-
status: 'healthy'
|
|
441
|
-
};
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* 健康检查
|
|
446
|
-
* @returns {Promise<object>} 健康检查结果
|
|
447
|
-
*/
|
|
448
|
-
Mysql.prototype.healthCheck = async function () {
|
|
449
|
-
try {
|
|
450
|
-
if (!this._pool) {
|
|
451
|
-
return {
|
|
452
|
-
status: 'error',
|
|
453
|
-
message: '数据库连接未初始化'
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// 执行简单的查询来检查连接状态
|
|
458
|
-
let result = await this.run('SELECT 1 as health_check');
|
|
459
|
-
|
|
460
|
-
if (result && result.length > 0 && result[0].health_check === 1) {
|
|
461
|
-
return {
|
|
462
|
-
status: 'healthy',
|
|
463
|
-
message: '数据库连接正常',
|
|
464
|
-
...this.getPoolStats()
|
|
465
|
-
};
|
|
466
|
-
} else {
|
|
467
|
-
return {
|
|
468
|
-
status: 'error',
|
|
469
|
-
message: '健康检查查询失败'
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
} catch (error) {
|
|
473
|
-
this.log('error', '健康检查失败', error);
|
|
474
|
-
return {
|
|
475
|
-
status: 'error',
|
|
476
|
-
message: `健康检查失败: ${error.message}`
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* 获取数据库管理器(保持兼容性)
|
|
483
|
-
* @returns {object} DB实例
|
|
484
|
-
*/
|
|
485
|
-
Mysql.prototype.db = function () {
|
|
486
|
-
return new DB(this);
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* 获取表管理器(语法糖,等同于 db().new(name, key))
|
|
491
|
-
* @param {string} name 表名
|
|
492
|
-
* @param {string} key 主键字段名
|
|
493
|
-
* @returns {object} DB表管理器实例
|
|
494
|
-
*/
|
|
495
|
-
Mysql.prototype.table = function (name, key) {
|
|
496
|
-
if (typeof name !== 'string' || name.trim() === '') {
|
|
497
|
-
throw new TypeError('name must be non-empty string');
|
|
498
|
-
}
|
|
499
|
-
var db = this.db();
|
|
500
|
-
db.table = name;
|
|
501
|
-
if (key) {
|
|
502
|
-
db.key = key;
|
|
503
|
-
}
|
|
504
|
-
return db;
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* 初始化MySQL服务(保持兼容性)
|
|
509
|
-
* @returns {Promise<void>}
|
|
510
|
-
*/
|
|
511
|
-
Mysql.prototype.init = async function () {
|
|
512
|
-
await this.open();
|
|
513
|
-
};
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* 导出模块
|
|
517
|
-
*/
|
|
518
|
-
exports.Mysql = Mysql;
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* 确保连接池对象存在
|
|
522
|
-
*/
|
|
523
|
-
if (!$.pool) {
|
|
524
|
-
$.pool = {};
|
|
525
|
-
}
|
|
526
|
-
if (!$.pool.mysql) {
|
|
527
|
-
$.pool.mysql = {};
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* @description Mysql管理器,用于创建缓存(线程安全版)
|
|
532
|
-
* @param {string} scope 作用域
|
|
533
|
-
* @param {object} config 配置参数
|
|
534
|
-
* @returns {object} 返回一个Mysql类实例
|
|
535
|
-
*/
|
|
536
|
-
function mysqlAdmin(scope, config) {
|
|
537
|
-
if (!scope) {
|
|
538
|
-
scope = 'sys';
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// 检查是否已存在实例
|
|
542
|
-
if ($.pool.mysql[scope]) {
|
|
543
|
-
return $.pool.mysql[scope];
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// 同步方式创建实例(无需锁,因为Node.js是单线程的)
|
|
547
|
-
if (!$.pool.mysql[scope]) {
|
|
548
|
-
$.pool.mysql[scope] = new Mysql(config);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
return $.pool.mysql[scope];
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* 安全释放数据库连接
|
|
556
|
-
* @param {object} conn - 数据库连接对象
|
|
557
|
-
* @returns {Promise<void>}
|
|
558
|
-
*/
|
|
559
|
-
async function releaseConn(conn) {
|
|
560
|
-
if (!conn) return;
|
|
561
|
-
|
|
562
|
-
try {
|
|
563
|
-
if (typeof conn.release === 'function') {
|
|
564
|
-
await conn.release();
|
|
565
|
-
}
|
|
566
|
-
} catch (release_err) {
|
|
567
|
-
$.log.error('释放连接失败', release_err);
|
|
568
|
-
}
|
|
569
|
-
};
|
|
570
|
-
|
|
571
|
-
/**
|
|
572
|
-
* @module 导出Mysql管理器
|
|
573
|
-
*/
|
|
574
|
-
exports.mysqlAdmin = mysqlAdmin;
|
|
1
|
+
/**
|
|
2
|
+
* mm_mysql MySQL数据库模块
|
|
3
|
+
* @description 参考 mm_redis 结构,index.js 为薄包装,类定义在 lib/mysql.js
|
|
4
|
+
*/
|
|
5
|
+
const Mysql = require('./lib/mysql');
|
|
6
|
+
const Admin = require('mm_corejs').Admin;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Mysql 管理器实例(Admin 工厂)
|
|
10
|
+
* 支持 os.js 调用:require('mm_mysql').admin.setServer(os) -> admin.get('os', config)
|
|
11
|
+
*/
|
|
12
|
+
let admin = new Admin();
|
|
13
|
+
admin.setClass(Mysql);
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
Mysql,
|
|
17
|
+
admin,
|
|
18
|
+
mysqlAdmin(scope, config) {
|
|
19
|
+
if (!scope) {
|
|
20
|
+
scope = 'os';
|
|
21
|
+
}
|
|
22
|
+
return admin.get(scope, config);
|
|
23
|
+
}
|
|
24
|
+
};
|
package/lib/mysql.js
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
const mysql = require('mysql2/promise');
|
|
2
|
+
const { Base } = require('mm_mod');
|
|
3
|
+
const { DB } = require('./db');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 优化版MySQL数据库操作类
|
|
7
|
+
* 保持必要功能,简化过度封装,直接使用mysql2模块
|
|
8
|
+
* @class Mysql
|
|
9
|
+
* @augments Base
|
|
10
|
+
*/
|
|
11
|
+
class Mysql extends Base {
|
|
12
|
+
/**
|
|
13
|
+
* 默认配置(优化版,统一使用连接池)
|
|
14
|
+
*/
|
|
15
|
+
static config = {
|
|
16
|
+
name: 'default',
|
|
17
|
+
host: '127.0.0.1',
|
|
18
|
+
port: 3306,
|
|
19
|
+
user: 'root',
|
|
20
|
+
password: 'Asd159357',
|
|
21
|
+
database: '',
|
|
22
|
+
charset: 'utf8mb4',
|
|
23
|
+
timezone: '+08:00',
|
|
24
|
+
connect_timeout: 15000,
|
|
25
|
+
|
|
26
|
+
// 连接池优化配置(针对高并发写入场景)
|
|
27
|
+
connection_limit: 100, // 减少连接数,避免资源竞争
|
|
28
|
+
acquire_timeout: 30000, // 延长获取连接超时时间
|
|
29
|
+
wait_for_connections: true, // 保持连接等待机制
|
|
30
|
+
idle_timeout: 300000, // 延长空闲连接超时时间(5分钟)
|
|
31
|
+
max_idle: 30, // 减少最大空闲连接数
|
|
32
|
+
queue_timeout: 60000, // 队列超时时间
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 构造函数(统一使用连接池)
|
|
37
|
+
* @param {object} config - 配置对象
|
|
38
|
+
*/
|
|
39
|
+
constructor(config = {}) {
|
|
40
|
+
super(config);
|
|
41
|
+
this._pool = null;
|
|
42
|
+
// 移除单连接支持,统一使用连接池
|
|
43
|
+
|
|
44
|
+
// 错误信息(注意:同 scope 下为单例共享,并发时以最后一次写入为准)
|
|
45
|
+
this.error = null;
|
|
46
|
+
// 最近执行的SQL语句
|
|
47
|
+
this.sql = '';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 获取MySQL2支持的配置参数
|
|
53
|
+
* 根据连接类型(连接池或单连接)过滤参数,避免警告
|
|
54
|
+
* @param {boolean} is_pool - 是否为连接池配置
|
|
55
|
+
* @returns {object} 过滤后的配置对象
|
|
56
|
+
*/
|
|
57
|
+
Mysql.prototype._getValidConfig = function (is_pool = false) {
|
|
58
|
+
// 基础连接参数(适用于所有连接类型)
|
|
59
|
+
let base_map = {
|
|
60
|
+
'host': 'host',
|
|
61
|
+
'port': 'port',
|
|
62
|
+
'user': 'user',
|
|
63
|
+
'password': 'password',
|
|
64
|
+
'database': 'database',
|
|
65
|
+
'charset': 'charset',
|
|
66
|
+
'timezone': 'timezone',
|
|
67
|
+
'connect_timeout': 'connectTimeout'
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// 连接池专用参数
|
|
71
|
+
let pool_map = {
|
|
72
|
+
'connection_limit': 'connectionLimit',
|
|
73
|
+
'wait_for_connections': 'waitForConnections',
|
|
74
|
+
'idle_timeout': 'idleTimeout',
|
|
75
|
+
'max_idle': 'maxIdle'
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
let config = {};
|
|
79
|
+
|
|
80
|
+
// 添加基础参数
|
|
81
|
+
for (let key in base_map) {
|
|
82
|
+
if (this.config[key] !== undefined) {
|
|
83
|
+
config[base_map[key]] = this.config[key];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 如果是连接池,添加连接池专用参数
|
|
88
|
+
if (is_pool) {
|
|
89
|
+
for (let key in pool_map) {
|
|
90
|
+
if (this.config[key] !== undefined) {
|
|
91
|
+
config[pool_map[key]] = this.config[key];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// 如果是单连接,确保不包含任何连接池专用参数
|
|
96
|
+
else {
|
|
97
|
+
// 移除所有连接池专用参数,避免mysql2内部警告
|
|
98
|
+
for (let key in pool_map) {
|
|
99
|
+
if (pool_map[key] in config) {
|
|
100
|
+
delete config[pool_map[key]];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return config;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 打开数据库连接(统一使用连接池)
|
|
110
|
+
* @returns {Promise<boolean>}
|
|
111
|
+
* @throws {TypeError} 当配置参数无效时
|
|
112
|
+
*/
|
|
113
|
+
Mysql.prototype.open = async function () {
|
|
114
|
+
// 参数校验
|
|
115
|
+
if (!this.config || typeof this.config !== 'object') {
|
|
116
|
+
throw new TypeError('config must be object');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// 统一使用连接池模式
|
|
121
|
+
let valid_config = this._getValidConfig(true);
|
|
122
|
+
this._pool = mysql.createPool(valid_config);
|
|
123
|
+
|
|
124
|
+
// 测试连接池连接
|
|
125
|
+
let conn = await this._pool.getConnection();
|
|
126
|
+
conn.release();
|
|
127
|
+
return true;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
this.log('error', '数据库连接池创建失败', error);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 关闭数据库连接(统一使用连接池)
|
|
136
|
+
* @returns {Promise<boolean>}
|
|
137
|
+
*/
|
|
138
|
+
Mysql.prototype.close = async function () {
|
|
139
|
+
if (!this._pool) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
await this._pool.end();
|
|
144
|
+
this._pool = null;
|
|
145
|
+
this.log('info', '数据库连接池已关闭');
|
|
146
|
+
return true;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
this.log('error', '关闭连接池失败', error);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 获取数据库连接(统一使用连接池)
|
|
155
|
+
* @returns {Promise<object>}
|
|
156
|
+
* @throws {TypeError} 当连接池不存在时
|
|
157
|
+
*/
|
|
158
|
+
Mysql.prototype.getConn = async function () {
|
|
159
|
+
// 连接池不存在则创建
|
|
160
|
+
if (!this._pool) {
|
|
161
|
+
await this.open();
|
|
162
|
+
}
|
|
163
|
+
// 连接池可能已被外部关闭,检测并重建
|
|
164
|
+
if (this._pool._closed) {
|
|
165
|
+
await this.open();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return await this._pool.getConnection();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 执行SQL查询
|
|
173
|
+
* @param {string} sql - SQL语句
|
|
174
|
+
* @param {Array} params - 参数数组
|
|
175
|
+
* @returns {Promise<object>}
|
|
176
|
+
* @throws {TypeError} 当sql参数无效时
|
|
177
|
+
*/
|
|
178
|
+
Mysql.prototype.run = async function (sql, params = []) {
|
|
179
|
+
// 参数校验
|
|
180
|
+
if (typeof sql !== 'string' || sql.trim() === '') {
|
|
181
|
+
throw new TypeError('sql must be non-empty string');
|
|
182
|
+
}
|
|
183
|
+
if (!Array.isArray(params)) {
|
|
184
|
+
throw new TypeError('params must be array');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.error = null;
|
|
188
|
+
this.sql = '';
|
|
189
|
+
let conn = null;
|
|
190
|
+
let ret = [];
|
|
191
|
+
try {
|
|
192
|
+
conn = await this.getConn();
|
|
193
|
+
this.sql = sql;
|
|
194
|
+
let [rows] = await conn.query(sql, params);
|
|
195
|
+
ret = rows;
|
|
196
|
+
} catch (err) {
|
|
197
|
+
console.error('SQL执行失败', err);
|
|
198
|
+
this.sql = err.sql || sql;
|
|
199
|
+
this.error = {
|
|
200
|
+
code: err.errno,
|
|
201
|
+
message: err.sqlMessage
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
await releaseConn(conn);
|
|
206
|
+
}
|
|
207
|
+
return ret;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 执行SQL语句(优化版,支持预处理语句缓存)
|
|
212
|
+
* @param {string} sql - SQL语句
|
|
213
|
+
* @param {Array} params - 参数数组
|
|
214
|
+
* @returns {Promise<object>}
|
|
215
|
+
* @throws {TypeError} 当sql参数无效时
|
|
216
|
+
*/
|
|
217
|
+
Mysql.prototype.exec = async function (sql, params = []) {
|
|
218
|
+
// 参数校验
|
|
219
|
+
if (typeof sql !== 'string' || sql.trim() === '') {
|
|
220
|
+
throw new TypeError('sql must be non-empty string');
|
|
221
|
+
}
|
|
222
|
+
if (!Array.isArray(params)) {
|
|
223
|
+
throw new TypeError('params must be array');
|
|
224
|
+
}
|
|
225
|
+
this.error = null;
|
|
226
|
+
this.sql = '';
|
|
227
|
+
let conn = null;
|
|
228
|
+
let ret = 0;
|
|
229
|
+
try {
|
|
230
|
+
conn = await this.getConn();
|
|
231
|
+
this.sql = sql;
|
|
232
|
+
let [result] = await conn.execute(sql, params);
|
|
233
|
+
ret = result.insertId || result.affectedRows || result.changedRows || 1;
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error('SQL执行失败', error);
|
|
236
|
+
this.sql = error.sql || sql;
|
|
237
|
+
this.error = {
|
|
238
|
+
code: error.errno,
|
|
239
|
+
message: error.sqlMessage
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
await releaseConn(conn);
|
|
244
|
+
}
|
|
245
|
+
return ret;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 执行多语句SQL脚本(专用于导入)
|
|
250
|
+
* 通过独立连接执行,不影响连接池默认配置
|
|
251
|
+
* @param {string} sql - SQL脚本内容,可含多条以分号分隔的语句
|
|
252
|
+
* @returns {Promise<object>} 执行结果统计对象
|
|
253
|
+
* @throws {TypeError} 当sql参数无效时
|
|
254
|
+
*/
|
|
255
|
+
Mysql.prototype.runSql = async function (sql) {
|
|
256
|
+
// 参数校验
|
|
257
|
+
if (typeof sql !== 'string' || sql.trim() === '') {
|
|
258
|
+
throw new TypeError('sql must be non-empty string');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let conn = null;
|
|
262
|
+
let ret = { success: false, statements: 0, affected_rows: 0, error: null };
|
|
263
|
+
try {
|
|
264
|
+
// 创建专用连接,仅导入连接开启多语句支持
|
|
265
|
+
let valid_config = this._getValidConfig(false);
|
|
266
|
+
valid_config.multipleStatements = true;
|
|
267
|
+
conn = await mysql.createConnection(valid_config);
|
|
268
|
+
|
|
269
|
+
// 执行整段SQL脚本
|
|
270
|
+
let [results] = await conn.query(sql);
|
|
271
|
+
// 统计语句数和受影响行数
|
|
272
|
+
if (Array.isArray(results)) {
|
|
273
|
+
ret.statements = results.length;
|
|
274
|
+
for (let item of results) {
|
|
275
|
+
ret.affected_rows += item.affectedRows || 0;
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
ret.statements = 1;
|
|
279
|
+
ret.affected_rows = results.affectedRows || 0;
|
|
280
|
+
}
|
|
281
|
+
ret.success = true;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
this.error = {
|
|
284
|
+
code: error.errno,
|
|
285
|
+
message: error.sqlMessage || error.message
|
|
286
|
+
};
|
|
287
|
+
this.sql = error.sql || sql;
|
|
288
|
+
ret.error = this.error.message;
|
|
289
|
+
} finally {
|
|
290
|
+
if (conn) {
|
|
291
|
+
try {
|
|
292
|
+
await conn.end();
|
|
293
|
+
} catch (end_error) {
|
|
294
|
+
this.log('error', '关闭导入连接失败', end_error);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return ret;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 开始事务(优化版)
|
|
303
|
+
* @returns {Promise<object>} 事务连接对象
|
|
304
|
+
*/
|
|
305
|
+
Mysql.prototype.beginTransaction = async function () {
|
|
306
|
+
let conn = await this.getConn();
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
await conn.beginTransaction();
|
|
310
|
+
|
|
311
|
+
let ended = false;
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
connection: conn,
|
|
315
|
+
commit: async () => {
|
|
316
|
+
if (ended) return;
|
|
317
|
+
await conn.commit();
|
|
318
|
+
ended = true;
|
|
319
|
+
await releaseConn(conn);
|
|
320
|
+
},
|
|
321
|
+
rollback: async () => {
|
|
322
|
+
if (ended) return;
|
|
323
|
+
await conn.rollback();
|
|
324
|
+
ended = true;
|
|
325
|
+
await releaseConn(conn);
|
|
326
|
+
},
|
|
327
|
+
_is_ended: () => ended
|
|
328
|
+
};
|
|
329
|
+
} catch (error) {
|
|
330
|
+
// 如果事务开始失败,确保连接被释放
|
|
331
|
+
await releaseConn(conn);
|
|
332
|
+
this.log('error', '事务开始失败', error);
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* 在事务中执行多个操作(优化版)
|
|
339
|
+
* @param {Function} callback - 包含事务操作的回调函数
|
|
340
|
+
* @returns {Promise<*>} 回调函数的返回值
|
|
341
|
+
* @throws {TypeError} 当callback参数无效时
|
|
342
|
+
*/
|
|
343
|
+
Mysql.prototype.transaction = async function (callback) {
|
|
344
|
+
if (typeof callback !== 'function') {
|
|
345
|
+
throw new TypeError('callback must be function');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
let transaction = await this.beginTransaction();
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
let result = await callback(transaction);
|
|
352
|
+
|
|
353
|
+
// 如果事务未结束,自动提交
|
|
354
|
+
if (!transaction._is_ended()) {
|
|
355
|
+
await transaction.commit();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return result;
|
|
359
|
+
} catch (error) {
|
|
360
|
+
// 如果事务未结束,自动回滚
|
|
361
|
+
if (!transaction._is_ended()) {
|
|
362
|
+
await transaction.rollback().catch(() => {
|
|
363
|
+
// 简化错误处理,避免频繁日志
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* 读取整张表的数据(改进版)
|
|
373
|
+
* @param {string} table - 表名
|
|
374
|
+
* @param {object} condition - 查询条件
|
|
375
|
+
* @param {object} options - 选项(orderBy, limit, offset等)
|
|
376
|
+
* @returns {Promise<Array>}
|
|
377
|
+
* @throws {TypeError} 当table参数无效时
|
|
378
|
+
*/
|
|
379
|
+
Mysql.prototype.read = async function (table, condition = {}, options = {}) {
|
|
380
|
+
// 参数校验
|
|
381
|
+
if (typeof table !== 'string' || table.trim() === '') {
|
|
382
|
+
throw new TypeError('table must be non-empty string');
|
|
383
|
+
}
|
|
384
|
+
if (condition && typeof condition !== 'object') {
|
|
385
|
+
throw new TypeError('condition must be object');
|
|
386
|
+
}
|
|
387
|
+
if (options && typeof options !== 'object') {
|
|
388
|
+
throw new TypeError('options must be object');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
let sql = `SELECT * FROM ${table}`;
|
|
393
|
+
let params = [];
|
|
394
|
+
|
|
395
|
+
// 处理条件
|
|
396
|
+
if (Object.keys(condition).length > 0) {
|
|
397
|
+
let whereClauses = [];
|
|
398
|
+
for (let [field, value] of Object.entries(condition)) {
|
|
399
|
+
whereClauses.push(`${field} = ?`);
|
|
400
|
+
params.push(value);
|
|
401
|
+
}
|
|
402
|
+
sql += ` WHERE ${whereClauses.join(' AND ')}`;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// 处理排序
|
|
406
|
+
if (options.orderBy) {
|
|
407
|
+
sql += ` ORDER BY ${options.orderBy}`;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// 处理分页
|
|
411
|
+
if (options.limit) {
|
|
412
|
+
sql += ` LIMIT ${options.limit}`;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (options.offset) {
|
|
416
|
+
sql += ` OFFSET ${options.offset}`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// 执行查询
|
|
420
|
+
return await this.run(sql, params);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
this.log('error', '查询失败', error);
|
|
423
|
+
throw error; // 重新抛出错误,让调用方知道查询失败
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* 获取连接池状态信息
|
|
429
|
+
* @returns {object} 连接池状态对象
|
|
430
|
+
*/
|
|
431
|
+
Mysql.prototype.getPoolStats = function () {
|
|
432
|
+
if (!this._pool) {
|
|
433
|
+
return {
|
|
434
|
+
status: 'not_initialized',
|
|
435
|
+
message: '连接池未初始化'
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
status: 'healthy'
|
|
441
|
+
};
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* 健康检查
|
|
446
|
+
* @returns {Promise<object>} 健康检查结果
|
|
447
|
+
*/
|
|
448
|
+
Mysql.prototype.healthCheck = async function () {
|
|
449
|
+
try {
|
|
450
|
+
if (!this._pool) {
|
|
451
|
+
return {
|
|
452
|
+
status: 'error',
|
|
453
|
+
message: '数据库连接未初始化'
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// 执行简单的查询来检查连接状态
|
|
458
|
+
let result = await this.run('SELECT 1 as health_check');
|
|
459
|
+
|
|
460
|
+
if (result && result.length > 0 && result[0].health_check === 1) {
|
|
461
|
+
return {
|
|
462
|
+
status: 'healthy',
|
|
463
|
+
message: '数据库连接正常',
|
|
464
|
+
...this.getPoolStats()
|
|
465
|
+
};
|
|
466
|
+
} else {
|
|
467
|
+
return {
|
|
468
|
+
status: 'error',
|
|
469
|
+
message: '健康检查查询失败'
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
} catch (error) {
|
|
473
|
+
this.log('error', '健康检查失败', error);
|
|
474
|
+
return {
|
|
475
|
+
status: 'error',
|
|
476
|
+
message: `健康检查失败: ${error.message}`
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* 获取数据库管理器(保持兼容性)
|
|
483
|
+
* @returns {object} DB实例
|
|
484
|
+
*/
|
|
485
|
+
Mysql.prototype.db = function () {
|
|
486
|
+
return new DB(this);
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* 获取表管理器(语法糖,等同于 db().new(name, key))
|
|
491
|
+
* @param {string} name 表名
|
|
492
|
+
* @param {string} key 主键字段名
|
|
493
|
+
* @returns {object} DB表管理器实例
|
|
494
|
+
*/
|
|
495
|
+
Mysql.prototype.table = function (name, key) {
|
|
496
|
+
if (typeof name !== 'string' || name.trim() === '') {
|
|
497
|
+
throw new TypeError('name must be non-empty string');
|
|
498
|
+
}
|
|
499
|
+
var db = this.db();
|
|
500
|
+
db.table = name;
|
|
501
|
+
if (key) {
|
|
502
|
+
db.key = key;
|
|
503
|
+
}
|
|
504
|
+
return db;
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* 初始化钩子(由 Lifecycle.init 模板方法调用,保证状态机流转到 inited)
|
|
509
|
+
* @returns {Promise<void>}
|
|
510
|
+
*/
|
|
511
|
+
Mysql.prototype.onInit = async function () {
|
|
512
|
+
await this.open();
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* 安全释放数据库连接
|
|
517
|
+
* @param {object} conn - 数据库连接对象
|
|
518
|
+
* @returns {Promise<void>}
|
|
519
|
+
*/
|
|
520
|
+
async function releaseConn(conn) {
|
|
521
|
+
if (!conn) return;
|
|
522
|
+
|
|
523
|
+
try {
|
|
524
|
+
if (typeof conn.release === 'function') {
|
|
525
|
+
await conn.release();
|
|
526
|
+
}
|
|
527
|
+
} catch (release_err) {
|
|
528
|
+
console.error('释放连接失败', release_err);
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
module.exports = Mysql;
|
package/{sql.js → lib/sql.js}
RENAMED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* @version 1.2
|
|
5
5
|
*/
|
|
6
6
|
const SqlString = require('sqlstring');
|
|
7
|
-
const { Base } = require('
|
|
7
|
+
const { Base } = require('mm_mod');
|
|
8
|
+
const { loadText, hasFile } = require('mm_helper');
|
|
8
9
|
|
|
9
10
|
function escape(value, stringifyObjects, timeZone) {
|
|
10
11
|
return SqlString.escape(value, stringifyObjects, timeZone);
|
|
@@ -1233,7 +1234,6 @@ Sql.prototype.tplBody = function (param_dt, sql_dt) {
|
|
|
1233
1234
|
* @returns {Promise<boolean>} 是否加载成功
|
|
1234
1235
|
*/
|
|
1235
1236
|
Sql.prototype.load = async function (file) {
|
|
1236
|
-
|
|
1237
1237
|
await this._logLoadStart(file);
|
|
1238
1238
|
await this._validateFile(file);
|
|
1239
1239
|
let sql_stmts = await this._readAndSplitSql(file);
|
|
@@ -1259,7 +1259,7 @@ Sql.prototype._logLoadStart = async function (file) {
|
|
|
1259
1259
|
* @returns {Promise<void>}
|
|
1260
1260
|
*/
|
|
1261
1261
|
Sql.prototype._validateFile = async function (file) {
|
|
1262
|
-
if (!
|
|
1262
|
+
if (!hasFile(file)) {
|
|
1263
1263
|
throw new Error(`SQL文件不存在: ${file}`);
|
|
1264
1264
|
}
|
|
1265
1265
|
};
|
|
@@ -1270,7 +1270,7 @@ Sql.prototype._validateFile = async function (file) {
|
|
|
1270
1270
|
* @returns {Promise<string[]>} SQL语句数组
|
|
1271
1271
|
*/
|
|
1272
1272
|
Sql.prototype._readAndSplitSql = async function (file) {
|
|
1273
|
-
let sql_content =
|
|
1273
|
+
let sql_content = loadText(file);
|
|
1274
1274
|
if (!sql_content || sql_content.trim() === '') {
|
|
1275
1275
|
throw new Error(`SQL文件内容为空: ${file}`);
|
|
1276
1276
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mm_mysql",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.7",
|
|
4
4
|
"description": "这是超级美眉mysql帮助函数模块,用于便捷操作mysql,使用await方式,可以避免嵌套函数",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"
|
|
7
|
+
"mm_corejs": "^1.1.4",
|
|
8
|
+
"mm_mod": "^1.0.1",
|
|
9
|
+
"mm_helper": "^1.0.0",
|
|
8
10
|
"mysql2": "^3.24.2",
|
|
9
11
|
"sqlstring": "^2.3.3"
|
|
10
12
|
},
|
|
@@ -19,6 +21,9 @@
|
|
|
19
21
|
"url": "https://gitee.com/qiuwenwu91/mm_mysql.git"
|
|
20
22
|
},
|
|
21
23
|
"keywords": [
|
|
24
|
+
"mm_mod",
|
|
25
|
+
"mm_adapter",
|
|
26
|
+
"mm_persist",
|
|
22
27
|
"mysql",
|
|
23
28
|
"async",
|
|
24
29
|
"await",
|
|
@@ -30,6 +35,8 @@
|
|
|
30
35
|
"sql",
|
|
31
36
|
"db"
|
|
32
37
|
],
|
|
38
|
+
"layer": "adapter",
|
|
39
|
+
"group": "persist",
|
|
33
40
|
"author": "qww",
|
|
34
41
|
"license": "ISC",
|
|
35
42
|
"engines": {
|
|
@@ -38,6 +45,6 @@
|
|
|
38
45
|
"devDependencies": {
|
|
39
46
|
"eslint": "^10.9.1",
|
|
40
47
|
"eslint-plugin-jsdoc": "^64.2.1",
|
|
41
|
-
"mm_eslint": "^1.7.
|
|
48
|
+
"mm_eslint": "^1.7.7"
|
|
42
49
|
}
|
|
43
50
|
}
|
package/test.js
CHANGED
|
@@ -40,24 +40,30 @@ class TestMysql {
|
|
|
40
40
|
TestMysql.prototype._init = async function() {
|
|
41
41
|
try {
|
|
42
42
|
console.log('=== 初始化测试环境(优化版) ===');
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
// 直接使用指定数据库,避免重复连接
|
|
45
45
|
if (!this.config.database) {
|
|
46
46
|
this.config.database = 'test_db';
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
|
|
49
|
+
// 先以空 database 连接并创建测试数据库,避免 Unknown database 报错
|
|
50
|
+
const bootstrap = new Mysql({ ...this.config, database: '' });
|
|
51
|
+
await bootstrap.open();
|
|
52
|
+
await bootstrap.exec(`CREATE DATABASE IF NOT EXISTS ${this.config.database}`);
|
|
53
|
+
await bootstrap.close();
|
|
54
|
+
|
|
49
55
|
// 创建Mysql实例
|
|
50
56
|
this._mysql = new Mysql(this.config);
|
|
51
|
-
|
|
57
|
+
|
|
52
58
|
// 连接数据库
|
|
53
59
|
await this._mysql.open();
|
|
54
|
-
|
|
60
|
+
|
|
55
61
|
// 创建测试数据库(如果不存在)
|
|
56
62
|
await this._createTestDatabase();
|
|
57
|
-
|
|
63
|
+
|
|
58
64
|
// 创建测试表
|
|
59
65
|
await this._createTestTable();
|
|
60
|
-
|
|
66
|
+
|
|
61
67
|
console.log('测试环境初始化完成');
|
|
62
68
|
return true;
|
|
63
69
|
} catch (error) {
|
/package/{db.js → lib/db.js}
RENAMED
|
File without changes
|