mm_mysql 2.2.0 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -34
- package/db.js +144 -170
- package/index.js +393 -156
- package/package.json +2 -2
- package/sql.js +190 -349
- package/test.js +218 -128
- package/test_create_table.js +0 -193
package/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
const mysql = require('mysql2/promise');
|
|
2
|
-
const {
|
|
2
|
+
const { Base } = require('mm_expand');
|
|
3
3
|
const { DB } = require('./db');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* 优化版MySQL数据库操作类
|
|
7
7
|
* 保持必要功能,简化过度封装,直接使用mysql2模块
|
|
8
8
|
* @class Mysql
|
|
9
|
-
* @extends
|
|
9
|
+
* @extends Base
|
|
10
10
|
*/
|
|
11
|
-
class Mysql extends
|
|
11
|
+
class Mysql extends Base {
|
|
12
12
|
/**
|
|
13
13
|
* 默认配置
|
|
14
14
|
* MySQL2支持的连接池配置参数:
|
|
15
15
|
* - host, port, user, password, database
|
|
16
|
-
* - charset, timezone,
|
|
17
|
-
* -
|
|
16
|
+
* - charset, timezone, connect_timeout
|
|
17
|
+
* - connection_limit, acquire_timeout, wait_for_connections
|
|
18
18
|
* - queueLimit参数在MySQL2中可能不被支持,已移除
|
|
19
19
|
*/
|
|
20
20
|
static default_config = {
|
|
@@ -25,9 +25,10 @@ class Mysql extends BaseService {
|
|
|
25
25
|
database: '',
|
|
26
26
|
charset: 'utf8mb4',
|
|
27
27
|
timezone: '+08:00',
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
connect_timeout: 20000,
|
|
29
|
+
connection_limit: 10,
|
|
30
|
+
acquire_timeout: 20000,
|
|
31
|
+
wait_for_connections: true
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -35,64 +36,106 @@ class Mysql extends BaseService {
|
|
|
35
36
|
* @param {Object} config - 配置对象
|
|
36
37
|
*/
|
|
37
38
|
constructor(config = {}) {
|
|
38
|
-
|
|
39
|
-
super(mergedConfig);
|
|
40
|
-
|
|
41
|
-
this.config = mergedConfig;
|
|
39
|
+
super(Object.assign({}, Mysql.default_config, config));
|
|
42
40
|
this._pool = null;
|
|
43
41
|
this._connection = null;
|
|
44
|
-
this._usePool = this.config.
|
|
42
|
+
this._usePool = this.config.connection_limit > 1;
|
|
43
|
+
this._pool_stats = {
|
|
44
|
+
total_connections: 0,
|
|
45
|
+
active_connections: 0,
|
|
46
|
+
idle_connections: 0,
|
|
47
|
+
connection_errors: 0,
|
|
48
|
+
last_health_check: null
|
|
49
|
+
};
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
/**
|
|
49
54
|
* 获取MySQL2支持的配置参数
|
|
50
|
-
*
|
|
55
|
+
* 根据连接类型(连接池或单连接)过滤参数,避免警告
|
|
56
|
+
* @param {boolean} is_pool - 是否为连接池配置
|
|
51
57
|
* @returns {Object} 过滤后的配置对象
|
|
52
58
|
*/
|
|
53
|
-
Mysql.prototype._getValidConfig = function() {
|
|
54
|
-
//
|
|
55
|
-
const
|
|
56
|
-
'host'
|
|
57
|
-
'
|
|
58
|
-
'
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
Mysql.prototype._getValidConfig = function (is_pool = false) {
|
|
60
|
+
// 基础连接参数(适用于所有连接类型)
|
|
61
|
+
const base_map = {
|
|
62
|
+
'host': 'host',
|
|
63
|
+
'port': 'port',
|
|
64
|
+
'user': 'user',
|
|
65
|
+
'password': 'password',
|
|
66
|
+
'database': 'database',
|
|
67
|
+
'charset': 'charset',
|
|
68
|
+
'timezone': 'timezone',
|
|
69
|
+
'connect_timeout': 'connectTimeout'
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// 连接池专用参数
|
|
73
|
+
const pool_map = {
|
|
74
|
+
'connection_limit': 'connectionLimit',
|
|
75
|
+
'wait_for_connections': 'waitForConnections',
|
|
76
|
+
'idle_timeout': 'idleTimeout',
|
|
77
|
+
'max_idle': 'maxIdle'
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const config = {};
|
|
81
|
+
|
|
82
|
+
// 添加基础参数
|
|
83
|
+
for (const key in base_map) {
|
|
84
|
+
if (this.config[key] !== undefined) {
|
|
85
|
+
config[base_map[key]] = this.config[key];
|
|
65
86
|
}
|
|
66
87
|
}
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
|
|
89
|
+
// 如果是连接池,添加连接池专用参数
|
|
90
|
+
if (is_pool) {
|
|
91
|
+
for (const key in pool_map) {
|
|
92
|
+
if (this.config[key] !== undefined) {
|
|
93
|
+
config[pool_map[key]] = this.config[key];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// 如果是单连接,确保不包含任何连接池专用参数
|
|
98
|
+
else {
|
|
99
|
+
// 移除所有连接池专用参数,避免mysql2内部警告
|
|
100
|
+
for (const key in pool_map) {
|
|
101
|
+
if (pool_map[key] in config) {
|
|
102
|
+
delete config[pool_map[key]];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return config;
|
|
69
108
|
};
|
|
70
109
|
|
|
71
110
|
/**
|
|
72
111
|
* 打开数据库连接
|
|
73
112
|
* @returns {Promise<boolean>}
|
|
113
|
+
* @throws {TypeError} 当配置参数无效时
|
|
74
114
|
*/
|
|
75
|
-
Mysql.prototype.open = async function() {
|
|
115
|
+
Mysql.prototype.open = async function () {
|
|
116
|
+
// 参数校验
|
|
117
|
+
if (!this.config || typeof this.config !== 'object') {
|
|
118
|
+
throw new TypeError('config must be object');
|
|
119
|
+
}
|
|
120
|
+
|
|
76
121
|
try {
|
|
77
|
-
// 使用过滤后的配置参数,避免传递无效参数
|
|
78
|
-
const validConfig = this._getValidConfig();
|
|
79
|
-
|
|
80
122
|
if (this._usePool) {
|
|
81
|
-
|
|
123
|
+
// 根据连接类型获取对应的配置参数
|
|
124
|
+
const valid_config = this._getValidConfig(this._usePool);
|
|
125
|
+
this._pool = mysql.createPool(valid_config);
|
|
82
126
|
// 测试连接池连接
|
|
83
127
|
const conn = await this._pool.getConnection();
|
|
84
128
|
conn.release();
|
|
85
129
|
} else {
|
|
86
|
-
|
|
130
|
+
const valid_config = this._getValidConfig(this._usePool);
|
|
131
|
+
this._connection = await mysql.createConnection(valid_config);
|
|
87
132
|
}
|
|
88
|
-
|
|
89
|
-
|
|
133
|
+
|
|
134
|
+
this.logger('info', '数据库连接成功');
|
|
90
135
|
return true;
|
|
91
136
|
} catch (error) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
95
|
-
throw error;
|
|
137
|
+
this.logger('error', '数据库连接失败', error);
|
|
138
|
+
return false;
|
|
96
139
|
}
|
|
97
140
|
};
|
|
98
141
|
|
|
@@ -100,7 +143,7 @@ Mysql.prototype.open = async function() {
|
|
|
100
143
|
* 关闭数据库连接
|
|
101
144
|
* @returns {Promise<boolean>}
|
|
102
145
|
*/
|
|
103
|
-
Mysql.prototype.close = async function() {
|
|
146
|
+
Mysql.prototype.close = async function () {
|
|
104
147
|
try {
|
|
105
148
|
if (this._pool) {
|
|
106
149
|
await this._pool.end();
|
|
@@ -110,196 +153,301 @@ Mysql.prototype.close = async function() {
|
|
|
110
153
|
await this._connection.end();
|
|
111
154
|
this._connection = null;
|
|
112
155
|
}
|
|
113
|
-
|
|
114
|
-
|
|
156
|
+
|
|
157
|
+
this.logger('info', '数据库连接已关闭');
|
|
115
158
|
return true;
|
|
116
159
|
} catch (error) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
});
|
|
120
|
-
throw error;
|
|
160
|
+
this.logger('error', '关闭连接失败', error);
|
|
161
|
+
return false;
|
|
121
162
|
}
|
|
122
163
|
};
|
|
123
164
|
|
|
124
165
|
/**
|
|
125
|
-
*
|
|
126
|
-
* @param {
|
|
166
|
+
* 安全释放数据库连接
|
|
167
|
+
* @param {Object} conn - 数据库连接对象
|
|
168
|
+
* @param {boolean} is_pool_conn - 是否为连接池连接
|
|
169
|
+
* @returns {Promise<void>}
|
|
170
|
+
*/
|
|
171
|
+
Mysql.prototype._safeReleaseConnection = async function (conn, is_pool_conn) {
|
|
172
|
+
if (!conn) return;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
if (is_pool_conn && typeof conn.release === 'function') {
|
|
176
|
+
await conn.release();
|
|
177
|
+
// 更新统计信息
|
|
178
|
+
if (this._pool_stats.active_connections > 0) {
|
|
179
|
+
this._pool_stats.active_connections--;
|
|
180
|
+
}
|
|
181
|
+
this._pool_stats.idle_connections++;
|
|
182
|
+
}
|
|
183
|
+
} catch (release_err) {
|
|
184
|
+
this.logger('error', '释放连接失败', release_err);
|
|
185
|
+
this._pool_stats.connection_errors++;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 获取数据库连接(改进版)
|
|
127
191
|
* @returns {Promise<Object>}
|
|
192
|
+
* @throws {TypeError} 当连接池或连接不存在时
|
|
128
193
|
*/
|
|
129
|
-
Mysql.prototype.getConn = async function(
|
|
194
|
+
Mysql.prototype.getConn = async function () {
|
|
195
|
+
// 参数校验
|
|
196
|
+
if (!this._pool && !this._connection) {
|
|
197
|
+
throw new Error('数据库连接未初始化');
|
|
198
|
+
}
|
|
199
|
+
|
|
130
200
|
try {
|
|
131
201
|
if (this._usePool) {
|
|
132
|
-
|
|
202
|
+
const conn = await this._pool.getConnection();
|
|
203
|
+
// 更新统计信息
|
|
204
|
+
this._pool_stats.total_connections++;
|
|
205
|
+
this._pool_stats.active_connections++;
|
|
206
|
+
if (this._pool_stats.idle_connections > 0) {
|
|
207
|
+
this._pool_stats.idle_connections--;
|
|
208
|
+
}
|
|
209
|
+
return conn;
|
|
133
210
|
} else {
|
|
134
211
|
return this._connection;
|
|
135
212
|
}
|
|
136
213
|
} catch (error) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
});
|
|
140
|
-
throw error;
|
|
214
|
+
this.logger('error', '获取连接失败', error);
|
|
215
|
+
this._pool_stats.connection_errors++;
|
|
141
216
|
}
|
|
217
|
+
return null;
|
|
142
218
|
};
|
|
143
219
|
|
|
144
220
|
/**
|
|
145
|
-
* 执行SQL
|
|
221
|
+
* 执行SQL查询(改进版)
|
|
146
222
|
* @param {String} sql - SQL语句
|
|
147
223
|
* @param {Array} params - 参数数组
|
|
148
|
-
* @param {Number} timeout - 超时时间(毫秒)
|
|
149
224
|
* @returns {Promise<Object>}
|
|
225
|
+
* @throws {TypeError} 当sql参数无效时
|
|
150
226
|
*/
|
|
151
|
-
Mysql.prototype.run = async function(sql, params = []
|
|
227
|
+
Mysql.prototype.run = async function (sql, params = []) {
|
|
228
|
+
// 参数校验
|
|
229
|
+
if (typeof sql !== 'string' || sql.trim() === '') {
|
|
230
|
+
throw new TypeError('sql must be non-empty string');
|
|
231
|
+
}
|
|
232
|
+
if (!Array.isArray(params)) {
|
|
233
|
+
throw new TypeError('params must be array');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.error = null;
|
|
152
237
|
let conn = null;
|
|
153
|
-
let
|
|
154
|
-
|
|
238
|
+
let is_pool_conn = false;
|
|
239
|
+
let connection_released = false;
|
|
240
|
+
|
|
155
241
|
try {
|
|
156
242
|
// 获取连接
|
|
157
243
|
conn = await this.getConn();
|
|
158
|
-
|
|
159
|
-
|
|
244
|
+
is_pool_conn = this._usePool;
|
|
245
|
+
|
|
160
246
|
// 直接使用mysql2的query方法
|
|
161
247
|
const [rows] = await conn.query(sql, params);
|
|
162
248
|
return rows;
|
|
163
|
-
} catch (
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
249
|
+
} catch (err) {
|
|
250
|
+
this.logger('error', 'SQL执行失败', err);
|
|
251
|
+
this.sql = err.sql;
|
|
252
|
+
this.error = {
|
|
253
|
+
code: err.errno,
|
|
254
|
+
message: err.sqlMessage
|
|
255
|
+
};
|
|
256
|
+
// 在错误情况下也要确保连接释放
|
|
257
|
+
try {
|
|
258
|
+
await this._safeReleaseConnection(conn, is_pool_conn);
|
|
259
|
+
} catch (release_err) {
|
|
260
|
+
this.logger('error', '释放连接失败', release_err);
|
|
261
|
+
}
|
|
262
|
+
connection_released = true;
|
|
170
263
|
} finally {
|
|
171
|
-
//
|
|
172
|
-
if (
|
|
264
|
+
// 确保连接被释放,避免资源泄漏
|
|
265
|
+
if (!connection_released) {
|
|
173
266
|
try {
|
|
174
|
-
|
|
175
|
-
} catch (
|
|
176
|
-
|
|
177
|
-
error: releaseErr.message
|
|
178
|
-
});
|
|
267
|
+
await this._safeReleaseConnection(conn, is_pool_conn);
|
|
268
|
+
} catch (release_err) {
|
|
269
|
+
this.logger('error', '释放连接失败', release_err);
|
|
179
270
|
}
|
|
180
271
|
}
|
|
181
272
|
}
|
|
273
|
+
return [];
|
|
182
274
|
};
|
|
183
275
|
|
|
184
276
|
/**
|
|
185
|
-
* 执行SQL
|
|
277
|
+
* 执行SQL语句(改进版)
|
|
186
278
|
* @param {String} sql - SQL语句
|
|
187
279
|
* @param {Array} params - 参数数组
|
|
188
|
-
* @param {Number} timeout - 超时时间(毫秒)
|
|
189
280
|
* @returns {Promise<Object>}
|
|
281
|
+
* @throws {TypeError} 当sql参数无效时
|
|
190
282
|
*/
|
|
191
|
-
Mysql.prototype.exec = async function(sql, params = []
|
|
283
|
+
Mysql.prototype.exec = async function (sql, params = []) {
|
|
284
|
+
// 参数校验
|
|
285
|
+
if (typeof sql !== 'string' || sql.trim() === '') {
|
|
286
|
+
throw new TypeError('sql must be non-empty string');
|
|
287
|
+
}
|
|
288
|
+
if (!Array.isArray(params)) {
|
|
289
|
+
throw new TypeError('params must be array');
|
|
290
|
+
}
|
|
291
|
+
this.error = null;
|
|
192
292
|
let conn = null;
|
|
193
|
-
let
|
|
194
|
-
|
|
293
|
+
let is_pool_conn = false;
|
|
294
|
+
let connection_released = false;
|
|
295
|
+
|
|
195
296
|
try {
|
|
196
297
|
// 获取连接
|
|
197
298
|
conn = await this.getConn();
|
|
198
|
-
|
|
199
|
-
|
|
299
|
+
is_pool_conn = this._usePool;
|
|
300
|
+
|
|
200
301
|
// 直接使用mysql2的execute方法
|
|
201
302
|
const [result] = await conn.execute(sql, params);
|
|
202
|
-
|
|
203
|
-
// 返回与mm_sqlite兼容的格式
|
|
204
|
-
return {
|
|
205
|
-
affectedRows: result.affectedRows || 0,
|
|
206
|
-
insertId: result.insertId || 0,
|
|
207
|
-
changedRows: result.changedRows || 0
|
|
208
|
-
};
|
|
303
|
+
return result.insertId || result.affectedRows || result.changedRows || 1;
|
|
209
304
|
} catch (error) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
305
|
+
this.logger('error', 'SQL执行失败', error);
|
|
306
|
+
this.sql = error.sql;
|
|
307
|
+
this.error = {
|
|
308
|
+
code: error.errno,
|
|
309
|
+
message: error.sqlMessage
|
|
310
|
+
};
|
|
311
|
+
// 在错误情况下也要确保连接释放
|
|
312
|
+
try {
|
|
313
|
+
await this._safeReleaseConnection(conn, is_pool_conn);
|
|
314
|
+
} catch (release_err) {
|
|
315
|
+
this.logger('error', '释放连接失败', release_err);
|
|
316
|
+
}
|
|
317
|
+
connection_released = true;
|
|
216
318
|
} finally {
|
|
217
|
-
//
|
|
218
|
-
if (
|
|
319
|
+
// 确保连接被释放,避免资源泄漏
|
|
320
|
+
if (!connection_released) {
|
|
219
321
|
try {
|
|
220
|
-
|
|
221
|
-
} catch (
|
|
222
|
-
|
|
223
|
-
error: releaseErr.message
|
|
224
|
-
});
|
|
322
|
+
await this._safeReleaseConnection(conn, is_pool_conn);
|
|
323
|
+
} catch (release_err) {
|
|
324
|
+
this.logger('error', '释放连接失败', release_err);
|
|
225
325
|
}
|
|
226
326
|
}
|
|
227
327
|
}
|
|
328
|
+
return 0;
|
|
228
329
|
};
|
|
229
330
|
|
|
230
331
|
/**
|
|
231
|
-
*
|
|
332
|
+
* 开始事务(改进版)
|
|
232
333
|
* @returns {Promise<Object>} 事务连接对象
|
|
233
334
|
*/
|
|
234
|
-
Mysql.prototype.beginTransaction = async function() {
|
|
335
|
+
Mysql.prototype.beginTransaction = async function () {
|
|
336
|
+
let conn = null;
|
|
337
|
+
let transaction_committed = false;
|
|
338
|
+
let transaction_rolled_back = false;
|
|
339
|
+
|
|
235
340
|
try {
|
|
236
|
-
|
|
341
|
+
conn = await this.getConn();
|
|
237
342
|
await conn.beginTransaction();
|
|
238
|
-
|
|
343
|
+
|
|
239
344
|
return {
|
|
240
345
|
connection: conn,
|
|
241
346
|
commit: async () => {
|
|
347
|
+
if (transaction_committed || transaction_rolled_back) {
|
|
348
|
+
this.logger('warn', '事务已结束,无需重复提交');
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
242
351
|
await conn.commit();
|
|
243
|
-
|
|
244
|
-
|
|
352
|
+
transaction_committed = true;
|
|
353
|
+
try {
|
|
354
|
+
await this._safeReleaseConnection(conn, this._usePool);
|
|
355
|
+
} catch (release_err) {
|
|
356
|
+
this.logger('error', '提交时释放连接失败', release_err);
|
|
245
357
|
}
|
|
246
358
|
},
|
|
247
359
|
rollback: async () => {
|
|
360
|
+
if (transaction_committed || transaction_rolled_back) {
|
|
361
|
+
this.logger('warn', '事务已结束,无需重复回滚');
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
248
364
|
await conn.rollback();
|
|
249
|
-
|
|
250
|
-
|
|
365
|
+
transaction_rolled_back = true;
|
|
366
|
+
try {
|
|
367
|
+
await this._safeReleaseConnection(conn, this._usePool);
|
|
368
|
+
} catch (release_err) {
|
|
369
|
+
this.logger('error', '回滚时释放连接失败', release_err);
|
|
251
370
|
}
|
|
252
|
-
}
|
|
371
|
+
},
|
|
372
|
+
_is_ended: () => transaction_committed || transaction_rolled_back
|
|
253
373
|
};
|
|
254
374
|
} catch (error) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
375
|
+
// 如果事务开始失败,确保连接被释放
|
|
376
|
+
try {
|
|
377
|
+
await this._safeReleaseConnection(conn, this._usePool);
|
|
378
|
+
} catch (release_err) {
|
|
379
|
+
this.logger('error', '释放连接失败', release_err);
|
|
380
|
+
}
|
|
381
|
+
this.logger('error', '事务开始失败', error);
|
|
258
382
|
throw error;
|
|
259
383
|
}
|
|
260
384
|
};
|
|
261
385
|
|
|
262
386
|
/**
|
|
263
|
-
*
|
|
387
|
+
* 在事务中执行多个操作(改进版)
|
|
264
388
|
* @param {Function} callback - 包含事务操作的回调函数
|
|
265
389
|
* @returns {Promise<*>} 回调函数的返回值
|
|
390
|
+
* @throws {TypeError} 当callback参数无效时
|
|
266
391
|
*/
|
|
267
|
-
Mysql.prototype.transaction = async function(callback) {
|
|
392
|
+
Mysql.prototype.transaction = async function (callback) {
|
|
393
|
+
// 参数校验
|
|
394
|
+
if (typeof callback !== 'function') {
|
|
395
|
+
throw new TypeError('callback must be function');
|
|
396
|
+
}
|
|
397
|
+
|
|
268
398
|
let transaction = null;
|
|
269
|
-
|
|
399
|
+
let transaction_executed = false;
|
|
400
|
+
|
|
270
401
|
try {
|
|
271
402
|
transaction = await this.beginTransaction();
|
|
272
403
|
const result = await callback(transaction);
|
|
273
|
-
|
|
404
|
+
|
|
405
|
+
// 检查事务是否已由用户手动提交/回滚
|
|
406
|
+
if (transaction._is_ended && !transaction._is_ended()) {
|
|
407
|
+
await transaction.commit();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
transaction_executed = true;
|
|
274
411
|
return result;
|
|
275
412
|
} catch (error) {
|
|
276
|
-
if (transaction) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
413
|
+
if (transaction && !transaction_executed) {
|
|
414
|
+
// 只有在事务未成功执行的情况下才回滚
|
|
415
|
+
if (transaction._is_ended && !transaction._is_ended()) {
|
|
416
|
+
await transaction.rollback().catch(err => {
|
|
417
|
+
this.logger('error', '事务回滚失败', err);
|
|
280
418
|
});
|
|
281
|
-
}
|
|
419
|
+
}
|
|
282
420
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
});
|
|
287
|
-
throw error;
|
|
421
|
+
|
|
422
|
+
this.logger('error', '事务执行失败', error);
|
|
423
|
+
throw error; // 重新抛出错误,让调用方知道事务失败
|
|
288
424
|
}
|
|
289
425
|
};
|
|
290
426
|
|
|
291
427
|
/**
|
|
292
|
-
*
|
|
428
|
+
* 读取整张表的数据(改进版)
|
|
293
429
|
* @param {String} table - 表名
|
|
294
430
|
* @param {Object} condition - 查询条件
|
|
295
431
|
* @param {Object} options - 选项(orderBy, limit, offset等)
|
|
296
432
|
* @returns {Promise<Array>}
|
|
433
|
+
* @throws {TypeError} 当table参数无效时
|
|
297
434
|
*/
|
|
298
|
-
Mysql.prototype.read = async function(table, condition = {}, options = {}) {
|
|
435
|
+
Mysql.prototype.read = async function (table, condition = {}, options = {}) {
|
|
436
|
+
// 参数校验
|
|
437
|
+
if (typeof table !== 'string' || table.trim() === '') {
|
|
438
|
+
throw new TypeError('table must be non-empty string');
|
|
439
|
+
}
|
|
440
|
+
if (condition && typeof condition !== 'object') {
|
|
441
|
+
throw new TypeError('condition must be object');
|
|
442
|
+
}
|
|
443
|
+
if (options && typeof options !== 'object') {
|
|
444
|
+
throw new TypeError('options must be object');
|
|
445
|
+
}
|
|
446
|
+
|
|
299
447
|
try {
|
|
300
448
|
let sql = `SELECT * FROM ${table}`;
|
|
301
449
|
const params = [];
|
|
302
|
-
|
|
450
|
+
|
|
303
451
|
// 处理条件
|
|
304
452
|
if (Object.keys(condition).length > 0) {
|
|
305
453
|
const whereClauses = [];
|
|
@@ -309,30 +457,85 @@ Mysql.prototype.read = async function(table, condition = {}, options = {}) {
|
|
|
309
457
|
}
|
|
310
458
|
sql += ` WHERE ${whereClauses.join(' AND ')}`;
|
|
311
459
|
}
|
|
312
|
-
|
|
460
|
+
|
|
313
461
|
// 处理排序
|
|
314
462
|
if (options.orderBy) {
|
|
315
463
|
sql += ` ORDER BY ${options.orderBy}`;
|
|
316
464
|
}
|
|
317
|
-
|
|
465
|
+
|
|
318
466
|
// 处理分页
|
|
319
467
|
if (options.limit) {
|
|
320
468
|
sql += ` LIMIT ${options.limit}`;
|
|
321
469
|
}
|
|
322
|
-
|
|
470
|
+
|
|
323
471
|
if (options.offset) {
|
|
324
472
|
sql += ` OFFSET ${options.offset}`;
|
|
325
473
|
}
|
|
326
|
-
|
|
474
|
+
|
|
327
475
|
// 执行查询
|
|
328
476
|
return await this.run(sql, params);
|
|
329
477
|
} catch (error) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
478
|
+
this.logger('error', '查询失败', error);
|
|
479
|
+
throw error; // 重新抛出错误,让调用方知道查询失败
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* 获取连接池状态信息
|
|
485
|
+
* @returns {Object} 连接池状态对象
|
|
486
|
+
*/
|
|
487
|
+
Mysql.prototype.getPoolStats = function () {
|
|
488
|
+
if (!this._pool) {
|
|
489
|
+
return {
|
|
490
|
+
status: 'not_initialized',
|
|
491
|
+
message: '连接池未初始化'
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// 更新统计信息
|
|
496
|
+
this._pool_stats.last_health_check = new Date();
|
|
497
|
+
|
|
498
|
+
return {
|
|
499
|
+
status: 'healthy',
|
|
500
|
+
...this._pool_stats,
|
|
501
|
+
last_health_check: this._pool_stats.last_health_check
|
|
502
|
+
};
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* 健康检查
|
|
507
|
+
* @returns {Promise<Object>} 健康检查结果
|
|
508
|
+
*/
|
|
509
|
+
Mysql.prototype.healthCheck = async function () {
|
|
510
|
+
try {
|
|
511
|
+
if (!this._pool && !this._connection) {
|
|
512
|
+
return {
|
|
513
|
+
status: 'error',
|
|
514
|
+
message: '数据库连接未初始化'
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// 执行简单的查询来检查连接状态
|
|
519
|
+
const result = await this.run('SELECT 1 as health_check');
|
|
520
|
+
|
|
521
|
+
if (result && result.length > 0 && result[0].health_check === 1) {
|
|
522
|
+
return {
|
|
523
|
+
status: 'healthy',
|
|
524
|
+
message: '数据库连接正常',
|
|
525
|
+
...this.getPoolStats()
|
|
526
|
+
};
|
|
527
|
+
} else {
|
|
528
|
+
return {
|
|
529
|
+
status: 'error',
|
|
530
|
+
message: '健康检查查询失败'
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
} catch (error) {
|
|
534
|
+
this.logger('error', '健康检查失败', error);
|
|
535
|
+
return {
|
|
536
|
+
status: 'error',
|
|
537
|
+
message: `健康检查失败: ${error.message}`
|
|
538
|
+
};
|
|
336
539
|
}
|
|
337
540
|
};
|
|
338
541
|
|
|
@@ -340,7 +543,7 @@ Mysql.prototype.read = async function(table, condition = {}, options = {}) {
|
|
|
340
543
|
* 获取数据库管理器(保持兼容性)
|
|
341
544
|
* @returns {Object} DB实例
|
|
342
545
|
*/
|
|
343
|
-
Mysql.prototype.db = function() {
|
|
546
|
+
Mysql.prototype.db = function () {
|
|
344
547
|
return new DB(this);
|
|
345
548
|
};
|
|
346
549
|
|
|
@@ -348,7 +551,7 @@ Mysql.prototype.db = function() {
|
|
|
348
551
|
* 初始化MySQL服务(保持兼容性)
|
|
349
552
|
* @returns {Promise<void>}
|
|
350
553
|
*/
|
|
351
|
-
Mysql.prototype.init = async function() {
|
|
554
|
+
Mysql.prototype.init = async function () {
|
|
352
555
|
await this.open();
|
|
353
556
|
};
|
|
354
557
|
|
|
@@ -361,28 +564,62 @@ exports.Mysql = Mysql;
|
|
|
361
564
|
* 确保连接池对象存在
|
|
362
565
|
*/
|
|
363
566
|
if (!$.pool) {
|
|
364
|
-
|
|
567
|
+
$.pool = {};
|
|
365
568
|
}
|
|
366
569
|
if (!$.pool.mysql) {
|
|
367
|
-
|
|
570
|
+
$.pool.mysql = {};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* 全局锁对象,用于线程安全
|
|
575
|
+
*/
|
|
576
|
+
if (!$.pool.mysql._locks) {
|
|
577
|
+
$.pool.mysql._locks = {};
|
|
368
578
|
}
|
|
369
579
|
|
|
370
580
|
/**
|
|
371
|
-
*
|
|
581
|
+
* 获取锁
|
|
582
|
+
* @param {string} key - 锁的键名
|
|
583
|
+
* @returns {Promise<Function>} 释放锁的函数
|
|
584
|
+
*/
|
|
585
|
+
function _acquireLock(key) {
|
|
586
|
+
if (!$.pool.mysql._locks[key]) {
|
|
587
|
+
$.pool.mysql._locks[key] = Promise.resolve();
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
let release;
|
|
591
|
+
const lockPromise = $.pool.mysql._locks[key].then(() => {
|
|
592
|
+
return new Promise(resolve => {
|
|
593
|
+
release = resolve;
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
$.pool.mysql._locks[key] = lockPromise;
|
|
598
|
+
return lockPromise.then(() => release);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* @description Mysql管理器,用于创建缓存(线程安全版)
|
|
372
603
|
* @param {String} scope 作用域
|
|
373
604
|
* @param {Object} config 配置参数
|
|
374
605
|
* @return {Object} 返回一个Mysql类实例
|
|
375
606
|
*/
|
|
376
607
|
function mysqlAdmin(scope, config) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
608
|
+
if (!scope) {
|
|
609
|
+
scope = 'sys';
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// 检查是否已存在实例
|
|
613
|
+
if ($.pool.mysql[scope]) {
|
|
614
|
+
return $.pool.mysql[scope];
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// 同步方式创建实例(无需锁,因为Node.js是单线程的)
|
|
618
|
+
if (!$.pool.mysql[scope]) {
|
|
619
|
+
$.pool.mysql[scope] = new Mysql(config);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
return $.pool.mysql[scope];
|
|
386
623
|
}
|
|
387
624
|
|
|
388
625
|
/**
|