mm_mysql 2.0.4 → 2.0.6
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/db.js +1 -1
- package/index.js +358 -1071
- package/package.json +1 -1
- package/test.js +518 -0
package/index.js
CHANGED
|
@@ -1,1072 +1,359 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Mysql extends BaseService {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
this._connection.end(err => {
|
|
360
|
-
if (err) {
|
|
361
|
-
$.log.error('关闭连接失败', { error: err.message });
|
|
362
|
-
} else {
|
|
363
|
-
$.log.info('连接已关闭');
|
|
364
|
-
}
|
|
365
|
-
this._connection = null;
|
|
366
|
-
this._status = 'closed';
|
|
367
|
-
resolve(!err);
|
|
368
|
-
});
|
|
369
|
-
} else {
|
|
370
|
-
this._status = 'closed';
|
|
371
|
-
resolve(true);
|
|
372
|
-
}
|
|
373
|
-
} catch (err) {
|
|
374
|
-
$.log.error('关闭数据库连接时发生异常', { error: err.message });
|
|
375
|
-
this._status = 'closed';
|
|
376
|
-
resolve(false);
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* 获取数据库连接
|
|
383
|
-
* @param {Number} timeout - 超时时间(毫秒)
|
|
384
|
-
* @returns {Promise<Object>}
|
|
385
|
-
*/
|
|
386
|
-
Mysql.prototype.getConn = async function (timeout = null) {
|
|
387
|
-
// 确定最终超时时间,优先使用传入参数,其次是配置,最后是默认值
|
|
388
|
-
timeout = timeout || this.config.acquireTimeout || this.config.connectTimeout || 20000;
|
|
389
|
-
|
|
390
|
-
// 记录实际使用的超时时间
|
|
391
|
-
$.log.debug(`[${this.constructor.name}] [getConn] 使用超时时间: ${timeout}ms`);
|
|
392
|
-
|
|
393
|
-
if (this._status !== 'connected') {
|
|
394
|
-
throw new Error('数据库连接未建立');
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
try {
|
|
398
|
-
// 先检查连接池状态(如果存在)
|
|
399
|
-
if (this._pool && typeof this._pool._closed !== 'undefined' && this._pool._closed) {
|
|
400
|
-
$.log.warn('连接池已关闭,尝试重新连接');
|
|
401
|
-
await this.close();
|
|
402
|
-
await this.open();
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// 直接在方法内部实现超时控制
|
|
406
|
-
const connectionPromise = this._getConnectionInternal();
|
|
407
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
408
|
-
setTimeout(() => {
|
|
409
|
-
reject(new Error(`获取数据库连接超时(${timeout}ms)`));
|
|
410
|
-
}, timeout);
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
const conn = await Promise.race([connectionPromise, timeoutPromise]);
|
|
414
|
-
|
|
415
|
-
$.log.debug(`[${this.constructor.name}] [getConn] 成功获取数据库连接`);
|
|
416
|
-
|
|
417
|
-
// 处理连接错误
|
|
418
|
-
conn.on('error', (err) => {
|
|
419
|
-
if (err.code && (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT' || err.code === 'PROTOCOL_CONNECTION_LOST' ||
|
|
420
|
-
err.code === 'POOL_CLOSED' || err.code === 'CONNECTION_TIMED_OUT')) {
|
|
421
|
-
this._handleConnectionError(err);
|
|
422
|
-
}
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
return conn;
|
|
426
|
-
} catch (error) {
|
|
427
|
-
$.log.error(`[${this.constructor.name}] [getConn] 获取连接失败`, {
|
|
428
|
-
error: error.message
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
// 处理连接错误
|
|
432
|
-
if (error.code && (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT' || error.code === 'PROTOCOL_CONNECTION_LOST' ||
|
|
433
|
-
error.code === 'POOL_CLOSED' || error.code === 'CONNECTION_TIMED_OUT')) {
|
|
434
|
-
this._handleConnectionError(error);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
throw error;
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* 内部获取连接方法
|
|
443
|
-
* @private
|
|
444
|
-
* @returns {Promise<Object>}
|
|
445
|
-
*/
|
|
446
|
-
Mysql.prototype._getConnectionInternal = function () {
|
|
447
|
-
return new Promise((resolve, reject) => {
|
|
448
|
-
if (this._pool) {
|
|
449
|
-
// 从连接池获取连接
|
|
450
|
-
this._pool.getConnection()
|
|
451
|
-
.then(conn => {
|
|
452
|
-
// 设置连接的超时处理
|
|
453
|
-
conn.on('error', (err) => {
|
|
454
|
-
$.log.error('数据库连接发生错误', { error: err.message });
|
|
455
|
-
if (this.config.enableReconnect) {
|
|
456
|
-
this._handleConnectionError(err);
|
|
457
|
-
}
|
|
458
|
-
});
|
|
459
|
-
resolve(conn);
|
|
460
|
-
})
|
|
461
|
-
.catch(err => {
|
|
462
|
-
reject(err);
|
|
463
|
-
});
|
|
464
|
-
} else if (this._connection) {
|
|
465
|
-
// 返回单个连接
|
|
466
|
-
resolve(this._connection);
|
|
467
|
-
} else {
|
|
468
|
-
reject(new Error('无可用连接'));
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* 处理连接错误
|
|
475
|
-
* @private
|
|
476
|
-
* @param {Error} err - 错误对象
|
|
477
|
-
*/
|
|
478
|
-
Mysql.prototype._handleConnectionError = function (err) {
|
|
479
|
-
if (this._reconnecting) {
|
|
480
|
-
$.log.debug('重连已在进行中');
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (this.config.enableReconnect) {
|
|
485
|
-
$.log.info('开始自动重连...');
|
|
486
|
-
this._reconnect(this.config.maxReconnectAttempts || 3)
|
|
487
|
-
.catch(reconnectErr => {
|
|
488
|
-
$.log.error('重连失败', { error: reconnectErr.message });
|
|
489
|
-
this._status = 'closed';
|
|
490
|
-
});
|
|
491
|
-
} else {
|
|
492
|
-
$.log.warn('自动重连已禁用');
|
|
493
|
-
this._status = 'closed';
|
|
494
|
-
}
|
|
495
|
-
};
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* 重新连接数据库
|
|
499
|
-
* @private
|
|
500
|
-
* @param {Number} maxRetries - 最大重试次数
|
|
501
|
-
* @returns {Promise<boolean>}
|
|
502
|
-
*/
|
|
503
|
-
Mysql.prototype._reconnect = async function (maxRetries = 3) {
|
|
504
|
-
if (this._reconnecting) {
|
|
505
|
-
$.log.warn('重连已在进行中');
|
|
506
|
-
return false;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
this._reconnecting = true;
|
|
510
|
-
let retryCount = 0;
|
|
511
|
-
|
|
512
|
-
try {
|
|
513
|
-
// 先关闭现有连接
|
|
514
|
-
if (this._status === 'connected') {
|
|
515
|
-
await this.close();
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
while (retryCount < maxRetries) {
|
|
519
|
-
retryCount++;
|
|
520
|
-
$.log.info(`第${retryCount}/${maxRetries}次尝试重连`);
|
|
521
|
-
|
|
522
|
-
try {
|
|
523
|
-
// 使用配置中的超时时间,优先使用connectTimeout,其次是acquireTimeout,最后是默认值20000ms
|
|
524
|
-
const reconnectTimeout = this.config.connectTimeout || this.config.acquireTimeout || 20000;
|
|
525
|
-
$.log.debug(`重连超时设置为: ${reconnectTimeout}ms`);
|
|
526
|
-
await this.open(reconnectTimeout);
|
|
527
|
-
$.log.info('重连成功');
|
|
528
|
-
return true;
|
|
529
|
-
} catch (err) {
|
|
530
|
-
$.log.error(`重连失败(${retryCount}/${maxRetries})`, { error: err.message });
|
|
531
|
-
|
|
532
|
-
if (retryCount < maxRetries) {
|
|
533
|
-
// 等待一段时间后重试
|
|
534
|
-
const waitTime = this.config.reconnectInterval || 1000;
|
|
535
|
-
$.log.debug(`等待${waitTime}ms后重试`);
|
|
536
|
-
await this._sleep(waitTime);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
$.log.error(`达到最大重试次数(${maxRetries}),重连失败`);
|
|
542
|
-
throw new Error(`重连失败:达到最大重试次数(${maxRetries})`);
|
|
543
|
-
} finally {
|
|
544
|
-
this._reconnecting = false;
|
|
545
|
-
}
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
/**
|
|
549
|
-
* 执行SQL查询
|
|
550
|
-
* @param {String} sql - SQL语句
|
|
551
|
-
* @param {Array} params - 参数数组
|
|
552
|
-
* @param {Number} timeout - 超时时间(毫秒)
|
|
553
|
-
* @returns {Promise<Object>}
|
|
554
|
-
*/
|
|
555
|
-
Mysql.prototype.run = async function (sql, params = [], timeout = null) {
|
|
556
|
-
let conn = null;
|
|
557
|
-
let isPoolConn = false;
|
|
558
|
-
timeout = timeout || this.config.queryTimeout || 30000;
|
|
559
|
-
|
|
560
|
-
try {
|
|
561
|
-
// 获取连接
|
|
562
|
-
conn = await this.getConn(timeout);
|
|
563
|
-
isPoolConn = !!this._pool;
|
|
564
|
-
|
|
565
|
-
// 直接在方法内部实现超时控制
|
|
566
|
-
const queryPromise = conn.query(sql, params);
|
|
567
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
568
|
-
setTimeout(() => {
|
|
569
|
-
reject(new Error(`SQL查询超时: ${timeout}ms`));
|
|
570
|
-
}, timeout);
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
const [rows] = await Promise.race([queryPromise, timeoutPromise]);
|
|
574
|
-
|
|
575
|
-
// 返回查询结果的第一行或整个结果集,与之前版本兼容
|
|
576
|
-
return rows;
|
|
577
|
-
} catch (err) {
|
|
578
|
-
$.log.error('[Mysql] [run]', 'SQL执行失败', {
|
|
579
|
-
error: err.message,
|
|
580
|
-
sql: typeof sql === 'string' ? sql.substring(0, 200) : sql,
|
|
581
|
-
params: params
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
// 处理连接错误,触发重连
|
|
585
|
-
if (err.code && (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT' || err.code === 'PROTOCOL_CONNECTION_LOST')) {
|
|
586
|
-
this._handleConnectionError(err);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
throw err;
|
|
590
|
-
} finally {
|
|
591
|
-
// 释放连接
|
|
592
|
-
if (conn && isPoolConn) {
|
|
593
|
-
try {
|
|
594
|
-
conn.release();
|
|
595
|
-
} catch (releaseErr) {
|
|
596
|
-
$.log.error('释放连接失败', { error: releaseErr.message });
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* 执行SQL语句(用于执行非查询语句如INSERT/UPDATE/DELETE)
|
|
604
|
-
* @param {String} sql - SQL语句
|
|
605
|
-
* @param {Array} params - 参数数组
|
|
606
|
-
* @param {Number} timeout - 超时时间(毫秒)
|
|
607
|
-
* @returns {Promise<Object>}
|
|
608
|
-
*/
|
|
609
|
-
Mysql.prototype.exec = async function (sql, params = [], timeout = null) {
|
|
610
|
-
let conn = null;
|
|
611
|
-
let isPoolConn = false;
|
|
612
|
-
timeout = timeout || this.config.queryTimeout || 30000;
|
|
613
|
-
|
|
614
|
-
try {
|
|
615
|
-
// 获取连接
|
|
616
|
-
conn = await this.getConn(timeout);
|
|
617
|
-
isPoolConn = !!this._pool;
|
|
618
|
-
|
|
619
|
-
// 直接在方法内部实现超时控制
|
|
620
|
-
const queryPromise = conn.query(sql, params);
|
|
621
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
622
|
-
setTimeout(() => {
|
|
623
|
-
reject(new Error(`SQL执行超时: ${timeout}ms`));
|
|
624
|
-
}, timeout);
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
const [rows] = await Promise.race([queryPromise, timeoutPromise]);
|
|
628
|
-
|
|
629
|
-
// 返回与mm_sqlite兼容的格式
|
|
630
|
-
return {
|
|
631
|
-
affectedRows: rows.affectedRows || 0,
|
|
632
|
-
insertId: rows.insertId || 0,
|
|
633
|
-
changedRows: rows.changedRows || 0
|
|
634
|
-
};
|
|
635
|
-
} catch (err) {
|
|
636
|
-
$.log.error('[Mysql] [exec]', 'SQL执行失败', {
|
|
637
|
-
error: err.message,
|
|
638
|
-
sql: sql.substring(0, 200),
|
|
639
|
-
params: params
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
// 处理连接错误,触发重连
|
|
643
|
-
if (err.code && (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT' || err.code === 'PROTOCOL_CONNECTION_LOST')) {
|
|
644
|
-
this._handleConnectionError(err);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
throw err;
|
|
648
|
-
} finally {
|
|
649
|
-
// 释放连接(如果是从连接池获取的)
|
|
650
|
-
if (conn && isPoolConn) {
|
|
651
|
-
try {
|
|
652
|
-
conn.release();
|
|
653
|
-
} catch (releaseErr) {
|
|
654
|
-
$.log.error('释放连接失败', { error: releaseErr.message });
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
};
|
|
659
|
-
|
|
660
|
-
/**
|
|
661
|
-
* 读取整张表的数据
|
|
662
|
-
* @param {String} table - 表名
|
|
663
|
-
* @param {Object} condition - 查询条件
|
|
664
|
-
* @param {Object} options - 选项(orderBy, limit, offset等)
|
|
665
|
-
* @returns {Promise<Array>}
|
|
666
|
-
*/
|
|
667
|
-
Mysql.prototype.read = async function (table, condition = {}, options = {}) {
|
|
668
|
-
try {
|
|
669
|
-
// 检查连接状态
|
|
670
|
-
if (this._status !== 'connected') {
|
|
671
|
-
throw new Error('数据库连接未建立');
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// 构建基础SQL查询
|
|
675
|
-
let sql = `SELECT * FROM ${table}`;
|
|
676
|
-
const params = [];
|
|
677
|
-
|
|
678
|
-
// 处理条件
|
|
679
|
-
if (Object.keys(condition).length > 0) {
|
|
680
|
-
const whereClauses = [];
|
|
681
|
-
for (const [field, value] of Object.entries(condition)) {
|
|
682
|
-
whereClauses.push(`${field} = ?`);
|
|
683
|
-
params.push(value);
|
|
684
|
-
}
|
|
685
|
-
sql += ` WHERE ${whereClauses.join(' AND ')}`;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
// 处理排序
|
|
689
|
-
if (options.orderBy) {
|
|
690
|
-
sql += ` ORDER BY ${options.orderBy}`;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// 处理分页
|
|
694
|
-
if (options.limit) {
|
|
695
|
-
sql += ` LIMIT ${options.limit}`;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
if (options.offset) {
|
|
699
|
-
sql += ` OFFSET ${options.offset}`;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
// 记录查询日志
|
|
703
|
-
if (this.config.debug) {
|
|
704
|
-
$.log.debug(`[${this.constructor.name}] [read] 查询数据`, { sql, params });
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// 执行查询,使用run方法获取数组格式的结果
|
|
708
|
-
const results = await this.run(sql, params);
|
|
709
|
-
|
|
710
|
-
if (this.config.debug) {
|
|
711
|
-
$.log.info(`[${this.constructor.name}] [read] 查询成功`, { count: results.length });
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
return results;
|
|
715
|
-
} catch (error) {
|
|
716
|
-
// 记录错误日志
|
|
717
|
-
$.log.error(`[${this.constructor.name}] [read] 查询失败`, {
|
|
718
|
-
error: error.message,
|
|
719
|
-
table: table,
|
|
720
|
-
condition: condition
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
// 抛出错误
|
|
724
|
-
throw error;
|
|
725
|
-
}
|
|
726
|
-
};
|
|
727
|
-
|
|
728
|
-
/**
|
|
729
|
-
* 保存数据库结构到SQL文件
|
|
730
|
-
* @param {String} file - 输出文件路径
|
|
731
|
-
* @param {Array} tables - 要保存的表名数组,空数组表示保存所有表
|
|
732
|
-
* @returns {Promise<boolean>}
|
|
733
|
-
*/
|
|
734
|
-
Mysql.prototype.save = async function (file, tables = []) {
|
|
735
|
-
try {
|
|
736
|
-
let sqlContent = '';
|
|
737
|
-
|
|
738
|
-
// 获取所有表名
|
|
739
|
-
const allTablesResult = await this.run('SHOW TABLES');
|
|
740
|
-
let tableNames = [];
|
|
741
|
-
|
|
742
|
-
if (allTablesResult && allTablesResult.length > 0) {
|
|
743
|
-
const tableKey = Object.keys(allTablesResult[0])[0];
|
|
744
|
-
tableNames = allTablesResult.map(row => row[tableKey]);
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// 过滤要保存的表
|
|
748
|
-
const tablesToSave = tables.length > 0
|
|
749
|
-
? tableNames.filter(name => tables.includes(name))
|
|
750
|
-
: tableNames;
|
|
751
|
-
|
|
752
|
-
// 导出每个表的结构
|
|
753
|
-
for (const tableName of tablesToSave) {
|
|
754
|
-
const createSqlResult = await this.run(`SHOW CREATE TABLE \`${tableName}\``);
|
|
755
|
-
if (createSqlResult && createSqlResult.length > 0) {
|
|
756
|
-
const createSql = createSqlResult[0]['Create Table'];
|
|
757
|
-
sqlContent += `\n\n-- Table structure for \`${tableName}\`\n`;
|
|
758
|
-
sqlContent += createSql + ';\n';
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
// 确保目录存在
|
|
763
|
-
file.addDir();
|
|
764
|
-
file.saveText(sqlContent);
|
|
765
|
-
$.log.info(`成功导出数据库结构到: ${file}`);
|
|
766
|
-
return true;
|
|
767
|
-
} catch (err) {
|
|
768
|
-
$.log.error(`导出数据库结构失败: ${file}`, { error: err.message });
|
|
769
|
-
throw err;
|
|
770
|
-
}
|
|
771
|
-
};
|
|
772
|
-
|
|
773
|
-
/**
|
|
774
|
-
* 从SQL文件加载数据库结构和数据
|
|
775
|
-
* @param {String} file - SQL文件路径
|
|
776
|
-
* @returns {Promise<boolean>} 是否加载成功
|
|
777
|
-
*/
|
|
778
|
-
Mysql.prototype.load = async function (file) {
|
|
779
|
-
try {
|
|
780
|
-
// 记录操作日志
|
|
781
|
-
if (this.config.debug) {
|
|
782
|
-
$.log.debug(`[${this.constructor.name}] [load] 开始从文件加载数据库`, {
|
|
783
|
-
file: file
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// 检查文件是否存在
|
|
788
|
-
if (!file.hasFile()) {
|
|
789
|
-
throw new Error(`SQL文件不存在: ${file}`);
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
// 读取SQL文件内容
|
|
793
|
-
const sqlContent = file.readText();
|
|
794
|
-
|
|
795
|
-
if (!sqlContent || sqlContent.trim() === '') {
|
|
796
|
-
throw new Error(`SQL文件内容为空: ${file}`);
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// 分割SQL语句(按分号分割,但需要注意处理字符串中的分号)
|
|
800
|
-
const sqlStatements = this._splitSqlStatements(sqlContent);
|
|
801
|
-
|
|
802
|
-
// 开始事务执行SQL语句
|
|
803
|
-
await this.exec('START TRANSACTION');
|
|
804
|
-
|
|
805
|
-
try {
|
|
806
|
-
// 逐个执行SQL语句
|
|
807
|
-
for (const sql of sqlStatements) {
|
|
808
|
-
const trimmedSql = sql.trim();
|
|
809
|
-
if (trimmedSql) {
|
|
810
|
-
await this.exec(trimmedSql);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
// 提交事务
|
|
815
|
-
await this.exec('COMMIT');
|
|
816
|
-
|
|
817
|
-
if (this.config.debug) {
|
|
818
|
-
$.log.info(`[${this.constructor.name}] [load] 数据库加载成功`, {
|
|
819
|
-
file: file,
|
|
820
|
-
statementCount: sqlStatements.length
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
return true;
|
|
825
|
-
} catch (err) {
|
|
826
|
-
// 回滚事务
|
|
827
|
-
await this.exec('ROLLBACK').catch(rollbackErr => {
|
|
828
|
-
$.log.error(`[${this.constructor.name}] [load] 事务回滚失败`, {
|
|
829
|
-
error: rollbackErr.message
|
|
830
|
-
});
|
|
831
|
-
});
|
|
832
|
-
|
|
833
|
-
throw err;
|
|
834
|
-
}
|
|
835
|
-
} catch (error) {
|
|
836
|
-
// 记录错误日志
|
|
837
|
-
$.log.error(`[${this.constructor.name}] [load] 数据库加载失败`, {
|
|
838
|
-
error: error.message,
|
|
839
|
-
file: file
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
// 抛出错误
|
|
843
|
-
throw error;
|
|
844
|
-
}
|
|
845
|
-
};
|
|
846
|
-
|
|
847
|
-
/**
|
|
848
|
-
* 分割SQL语句
|
|
849
|
-
* @private
|
|
850
|
-
* @param {String} sqlContent - SQL内容
|
|
851
|
-
* @returns {Array} SQL语句数组
|
|
852
|
-
*/
|
|
853
|
-
Mysql.prototype._splitSqlStatements = function(sqlContent) {
|
|
854
|
-
const statements = [];
|
|
855
|
-
let inString = false;
|
|
856
|
-
let stringChar = '';
|
|
857
|
-
let inComment = false;
|
|
858
|
-
let currentStatement = '';
|
|
859
|
-
|
|
860
|
-
for (let i = 0; i < sqlContent.length; i++) {
|
|
861
|
-
const char = sqlContent[i];
|
|
862
|
-
const nextChar = i + 1 < sqlContent.length ? sqlContent[i + 1] : '';
|
|
863
|
-
|
|
864
|
-
// 处理注释
|
|
865
|
-
if (!inString && !inComment && char === '-' && nextChar === '-') {
|
|
866
|
-
inComment = true;
|
|
867
|
-
i++; // 跳过第二个'-'
|
|
868
|
-
continue;
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
if (inComment && char === '\n') {
|
|
872
|
-
inComment = false;
|
|
873
|
-
continue;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
if (inComment) {
|
|
877
|
-
continue;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
// 处理多行注释
|
|
881
|
-
if (!inString && !inComment && char === '/' && nextChar === '*') {
|
|
882
|
-
inComment = true;
|
|
883
|
-
i++; // 跳过'*'
|
|
884
|
-
continue;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
if (inComment && char === '*' && nextChar === '/') {
|
|
888
|
-
inComment = false;
|
|
889
|
-
i++; // 跳过'/'
|
|
890
|
-
continue;
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
// 处理字符串
|
|
894
|
-
if (!inComment && (char === "'" || char === '"') && (!inString || stringChar === char)) {
|
|
895
|
-
// 检查是否是转义的引号
|
|
896
|
-
let escaped = false;
|
|
897
|
-
for (let j = i - 1; j >= 0; j--) {
|
|
898
|
-
if (sqlContent[j] === '\\') {
|
|
899
|
-
escaped = !escaped;
|
|
900
|
-
} else {
|
|
901
|
-
break;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
if (!escaped) {
|
|
906
|
-
if (inString && stringChar === char) {
|
|
907
|
-
inString = false;
|
|
908
|
-
stringChar = '';
|
|
909
|
-
} else if (!inString) {
|
|
910
|
-
inString = true;
|
|
911
|
-
stringChar = char;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
// 分割语句
|
|
917
|
-
if (!inString && !inComment && char === ';') {
|
|
918
|
-
statements.push(currentStatement.trim());
|
|
919
|
-
currentStatement = '';
|
|
920
|
-
} else {
|
|
921
|
-
currentStatement += char;
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
// 添加最后一个语句(如果有)
|
|
926
|
-
if (currentStatement.trim()) {
|
|
927
|
-
statements.push(currentStatement.trim());
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
return statements;
|
|
931
|
-
};
|
|
932
|
-
|
|
933
|
-
/**
|
|
934
|
-
* 获取DB实例(用于高级操作)
|
|
935
|
-
* @returns {Object} 包含exec、add、set、get、del等方法的数据库操作对象
|
|
936
|
-
*/
|
|
937
|
-
Mysql.prototype.db = function () {
|
|
938
|
-
return new DB(this);
|
|
939
|
-
};
|
|
940
|
-
|
|
941
|
-
/**
|
|
942
|
-
* 睡眠指定时间(工具方法)
|
|
943
|
-
* @private
|
|
944
|
-
* @param {Number} ms - 毫秒数
|
|
945
|
-
* @returns {Promise<void>}
|
|
946
|
-
*/
|
|
947
|
-
Mysql.prototype._sleep = function (ms) {
|
|
948
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
949
|
-
};
|
|
950
|
-
|
|
951
|
-
/**
|
|
952
|
-
* @description 销毁MySQL服务,关闭连接池
|
|
953
|
-
* @async
|
|
954
|
-
* @returns {Promise<void>}
|
|
955
|
-
*/
|
|
956
|
-
Mysql.prototype.destroy = async function () {
|
|
957
|
-
if (this._isDestroyed) {
|
|
958
|
-
$.log.warn('MySQL服务已销毁');
|
|
959
|
-
return;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
try {
|
|
963
|
-
$.log.debug('[Mysql] [destroy]', '开始销毁MySQL服务');
|
|
964
|
-
|
|
965
|
-
// 关闭数据库连接
|
|
966
|
-
if (this._status === 'connected') {
|
|
967
|
-
await this.close();
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
// 重置状态
|
|
971
|
-
this._isDestroyed = true;
|
|
972
|
-
this._isInited = false;
|
|
973
|
-
this._connection = null;
|
|
974
|
-
this._pool = null;
|
|
975
|
-
this._status = 'closed';
|
|
976
|
-
|
|
977
|
-
$.log.debug('[Mysql] [destroy]', 'MySQL服务销毁完成');
|
|
978
|
-
} catch (error) {
|
|
979
|
-
$.log.error('[Mysql] [destroy]', 'MySQL服务销毁失败', { error: error.message });
|
|
980
|
-
throw error;
|
|
981
|
-
}
|
|
982
|
-
};
|
|
983
|
-
|
|
984
|
-
/**
|
|
985
|
-
* 睡眠指定时间(工具方法)
|
|
986
|
-
* @private
|
|
987
|
-
* @param {Number} ms - 毫秒数
|
|
988
|
-
* @returns {Promise<void>}
|
|
989
|
-
*/
|
|
990
|
-
Mysql.prototype._sleep = function (ms) {
|
|
991
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
992
|
-
};
|
|
993
|
-
|
|
994
|
-
/**
|
|
995
|
-
* SQL语法转换方法(与mm_sqlite保持兼容)
|
|
996
|
-
* @param {String} sql - 原始SQL语句
|
|
997
|
-
* @returns {String} 转换后的SQL语句
|
|
998
|
-
*/
|
|
999
|
-
Mysql.prototype._convertSqlSyntax = function (sql) {
|
|
1000
|
-
// MySQL不需要特殊的语法转换,直接返回原SQL
|
|
1001
|
-
// 此方法主要用于保持与mm_sqlite的接口兼容性
|
|
1002
|
-
return sql;
|
|
1003
|
-
};
|
|
1004
|
-
|
|
1005
|
-
/**
|
|
1006
|
-
* 获取数据库路径(与mm_sqlite保持兼容)
|
|
1007
|
-
* @returns {String} 数据库连接信息
|
|
1008
|
-
*/
|
|
1009
|
-
Mysql.prototype._getDatabasePath = function () {
|
|
1010
|
-
// MySQL不需要文件路径,返回连接信息用于兼容性
|
|
1011
|
-
return `${this.config.host}:${this.config.port}/${this.config.database}`;
|
|
1012
|
-
};
|
|
1013
|
-
|
|
1014
|
-
/**
|
|
1015
|
-
* 初始化MySQL服务
|
|
1016
|
-
* @async
|
|
1017
|
-
* @returns {Promise<void>}
|
|
1018
|
-
*/
|
|
1019
|
-
Mysql.prototype.init = async function () {
|
|
1020
|
-
if (this._isInited) {
|
|
1021
|
-
$.log.warn('MySQL服务已初始化');
|
|
1022
|
-
return;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
try {
|
|
1026
|
-
$.log.debug('[Mysql] [init]', '初始化MySQL服务');
|
|
1027
|
-
await this._initService();
|
|
1028
|
-
await this.open();
|
|
1029
|
-
$.log.debug('[Mysql] [init]', 'MySQL服务初始化完成');
|
|
1030
|
-
} catch (error) {
|
|
1031
|
-
$.log.error('[Mysql] [init]', 'MySQL服务初始化失败', { error: error.message });
|
|
1032
|
-
throw error;
|
|
1033
|
-
}
|
|
1034
|
-
};
|
|
1035
|
-
|
|
1036
|
-
/**
|
|
1037
|
-
* 导出模块
|
|
1038
|
-
*/
|
|
1039
|
-
exports.Mysql = Mysql;
|
|
1040
|
-
|
|
1041
|
-
/**
|
|
1042
|
-
* 确保连接池对象存在
|
|
1043
|
-
*/
|
|
1044
|
-
if (!$.pool) {
|
|
1045
|
-
$.pool = {};
|
|
1046
|
-
}
|
|
1047
|
-
if (!$.pool.mysql) {
|
|
1048
|
-
$.pool.mysql = {};
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
/**
|
|
1052
|
-
* @description Mysql管理器,用于创建缓存
|
|
1053
|
-
* @param {String} scope 作用域
|
|
1054
|
-
* @param {Object} config 配置参数
|
|
1055
|
-
* @return {Object} 返回一个Mysql类实例
|
|
1056
|
-
*/
|
|
1057
|
-
function mysql_admin(scope, config) {
|
|
1058
|
-
if (!scope) {
|
|
1059
|
-
scope = 'sys';
|
|
1060
|
-
}
|
|
1061
|
-
var obj = $.pool.mysql[scope];
|
|
1062
|
-
if (!obj) {
|
|
1063
|
-
$.pool.mysql[scope] = new Mysql(config);
|
|
1064
|
-
obj = $.pool.mysql[scope];
|
|
1065
|
-
}
|
|
1066
|
-
return obj;
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
/**
|
|
1070
|
-
* @module 导出Mysql管理器
|
|
1071
|
-
*/
|
|
1
|
+
const mysql = require('mysql2/promise');
|
|
2
|
+
const { BaseService } = require('mm_base_service');
|
|
3
|
+
const { DB } = require('./db');
|
|
4
|
+
/**
|
|
5
|
+
* 优化版MySQL数据库操作类
|
|
6
|
+
* 保持必要功能,简化过度封装,直接使用mysql2模块
|
|
7
|
+
* @class Mysql
|
|
8
|
+
* @extends BaseService
|
|
9
|
+
*/
|
|
10
|
+
class Mysql extends BaseService {
|
|
11
|
+
/**
|
|
12
|
+
* 默认配置
|
|
13
|
+
*/
|
|
14
|
+
static default_config = {
|
|
15
|
+
host: '127.0.0.1',
|
|
16
|
+
port: 3306,
|
|
17
|
+
user: 'root',
|
|
18
|
+
password: '',
|
|
19
|
+
database: '',
|
|
20
|
+
charset: 'utf8mb4',
|
|
21
|
+
timezone: '+08:00',
|
|
22
|
+
connectTimeout: 20000,
|
|
23
|
+
connectionLimit: 10,
|
|
24
|
+
queueLimit: 0
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 构造函数
|
|
29
|
+
* @param {Object} config - 配置对象
|
|
30
|
+
*/
|
|
31
|
+
constructor(config = {}) {
|
|
32
|
+
const mergedConfig = Object.assign({}, Mysql.default_config, config);
|
|
33
|
+
super(mergedConfig);
|
|
34
|
+
|
|
35
|
+
this.config = mergedConfig;
|
|
36
|
+
this._pool = null;
|
|
37
|
+
this._connection = null;
|
|
38
|
+
this._usePool = this.config.connectionLimit > 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 打开数据库连接
|
|
44
|
+
* @returns {Promise<boolean>}
|
|
45
|
+
*/
|
|
46
|
+
Mysql.prototype.open = async function() {
|
|
47
|
+
try {
|
|
48
|
+
if (this._usePool) {
|
|
49
|
+
this._pool = mysql.createPool(this.config);
|
|
50
|
+
// 测试连接池连接
|
|
51
|
+
const conn = await this._pool.getConnection();
|
|
52
|
+
conn.release();
|
|
53
|
+
} else {
|
|
54
|
+
this._connection = await mysql.createConnection(this.config);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
$.log.info(`[${this.constructor.name}] [open] 数据库连接成功`);
|
|
58
|
+
return true;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
$.log.error(`[${this.constructor.name}] [open] 数据库连接失败`, {
|
|
61
|
+
error: error.message
|
|
62
|
+
});
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 关闭数据库连接
|
|
69
|
+
* @returns {Promise<boolean>}
|
|
70
|
+
*/
|
|
71
|
+
Mysql.prototype.close = async function() {
|
|
72
|
+
try {
|
|
73
|
+
if (this._pool) {
|
|
74
|
+
await this._pool.end();
|
|
75
|
+
this._pool = null;
|
|
76
|
+
}
|
|
77
|
+
if (this._connection) {
|
|
78
|
+
await this._connection.end();
|
|
79
|
+
this._connection = null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
$.log.info(`[${this.constructor.name}] [close] 数据库连接已关闭`);
|
|
83
|
+
return true;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
$.log.error(`[${this.constructor.name}] [close] 关闭连接失败`, {
|
|
86
|
+
error: error.message
|
|
87
|
+
});
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 获取数据库连接(保持兼容性)
|
|
94
|
+
* @param {Number} timeout - 超时时间(毫秒)
|
|
95
|
+
* @returns {Promise<Object>}
|
|
96
|
+
*/
|
|
97
|
+
Mysql.prototype.getConn = async function(timeout = null) {
|
|
98
|
+
try {
|
|
99
|
+
if (this._usePool) {
|
|
100
|
+
return await this._pool.getConnection();
|
|
101
|
+
} else {
|
|
102
|
+
return this._connection;
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
$.log.error(`[${this.constructor.name}] [getConn] 获取连接失败`, {
|
|
106
|
+
error: error.message
|
|
107
|
+
});
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 执行SQL查询(保持兼容性)
|
|
114
|
+
* @param {String} sql - SQL语句
|
|
115
|
+
* @param {Array} params - 参数数组
|
|
116
|
+
* @param {Number} timeout - 超时时间(毫秒)
|
|
117
|
+
* @returns {Promise<Object>}
|
|
118
|
+
*/
|
|
119
|
+
Mysql.prototype.run = async function(sql, params = [], timeout = null) {
|
|
120
|
+
let conn = null;
|
|
121
|
+
let isPoolConn = false;
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
// 获取连接
|
|
125
|
+
conn = await this.getConn();
|
|
126
|
+
isPoolConn = this._usePool;
|
|
127
|
+
|
|
128
|
+
// 直接使用mysql2的query方法
|
|
129
|
+
const [rows] = await conn.query(sql, params);
|
|
130
|
+
return rows;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
$.log.error(`[${this.constructor.name}] [run] SQL执行失败`, {
|
|
133
|
+
error: error.message,
|
|
134
|
+
sql: typeof sql === 'string' ? sql.substring(0, 200) : sql,
|
|
135
|
+
params: params
|
|
136
|
+
});
|
|
137
|
+
throw error;
|
|
138
|
+
} finally {
|
|
139
|
+
// 释放连接
|
|
140
|
+
if (conn && isPoolConn) {
|
|
141
|
+
try {
|
|
142
|
+
conn.release();
|
|
143
|
+
} catch (releaseErr) {
|
|
144
|
+
$.log.error('释放连接失败', {
|
|
145
|
+
error: releaseErr.message
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 执行SQL语句(保持兼容性)
|
|
154
|
+
* @param {String} sql - SQL语句
|
|
155
|
+
* @param {Array} params - 参数数组
|
|
156
|
+
* @param {Number} timeout - 超时时间(毫秒)
|
|
157
|
+
* @returns {Promise<Object>}
|
|
158
|
+
*/
|
|
159
|
+
Mysql.prototype.exec = async function(sql, params = [], timeout = null) {
|
|
160
|
+
let conn = null;
|
|
161
|
+
let isPoolConn = false;
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
// 获取连接
|
|
165
|
+
conn = await this.getConn();
|
|
166
|
+
isPoolConn = this._usePool;
|
|
167
|
+
|
|
168
|
+
// 直接使用mysql2的execute方法
|
|
169
|
+
const [result] = await conn.execute(sql, params);
|
|
170
|
+
|
|
171
|
+
// 返回与mm_sqlite兼容的格式
|
|
172
|
+
return {
|
|
173
|
+
affectedRows: result.affectedRows || 0,
|
|
174
|
+
insertId: result.insertId || 0,
|
|
175
|
+
changedRows: result.changedRows || 0
|
|
176
|
+
};
|
|
177
|
+
} catch (error) {
|
|
178
|
+
$.log.error(`[${this.constructor.name}] [exec] SQL执行失败`, {
|
|
179
|
+
error: error.message,
|
|
180
|
+
sql: sql.substring(0, 200),
|
|
181
|
+
params: params
|
|
182
|
+
});
|
|
183
|
+
throw error;
|
|
184
|
+
} finally {
|
|
185
|
+
// 释放连接
|
|
186
|
+
if (conn && isPoolConn) {
|
|
187
|
+
try {
|
|
188
|
+
conn.release();
|
|
189
|
+
} catch (releaseErr) {
|
|
190
|
+
$.log.error('释放连接失败', {
|
|
191
|
+
error: releaseErr.message
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 开始事务(保持兼容性)
|
|
200
|
+
* @returns {Promise<Object>} 事务连接对象
|
|
201
|
+
*/
|
|
202
|
+
Mysql.prototype.beginTransaction = async function() {
|
|
203
|
+
try {
|
|
204
|
+
const conn = await this.getConn();
|
|
205
|
+
await conn.beginTransaction();
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
connection: conn,
|
|
209
|
+
commit: async () => {
|
|
210
|
+
await conn.commit();
|
|
211
|
+
if (this._usePool) {
|
|
212
|
+
conn.release();
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
rollback: async () => {
|
|
216
|
+
await conn.rollback();
|
|
217
|
+
if (this._usePool) {
|
|
218
|
+
conn.release();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
} catch (error) {
|
|
223
|
+
$.log.error(`[${this.constructor.name}] [beginTransaction] 事务开始失败`, {
|
|
224
|
+
error: error.message
|
|
225
|
+
});
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 在事务中执行多个操作(保持兼容性)
|
|
232
|
+
* @param {Function} callback - 包含事务操作的回调函数
|
|
233
|
+
* @returns {Promise<*>} 回调函数的返回值
|
|
234
|
+
*/
|
|
235
|
+
Mysql.prototype.transaction = async function(callback) {
|
|
236
|
+
let transaction = null;
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
transaction = await this.beginTransaction();
|
|
240
|
+
const result = await callback(transaction);
|
|
241
|
+
await transaction.commit();
|
|
242
|
+
return result;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (transaction) {
|
|
245
|
+
await transaction.rollback().catch(err => {
|
|
246
|
+
$.log.error(`[${this.constructor.name}] [transaction] 事务回滚失败`, {
|
|
247
|
+
error: err.message
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
$.log.error(`[${this.constructor.name}] [transaction] 事务执行失败`, {
|
|
253
|
+
error: error.message
|
|
254
|
+
});
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 读取整张表的数据(保持兼容性)
|
|
261
|
+
* @param {String} table - 表名
|
|
262
|
+
* @param {Object} condition - 查询条件
|
|
263
|
+
* @param {Object} options - 选项(orderBy, limit, offset等)
|
|
264
|
+
* @returns {Promise<Array>}
|
|
265
|
+
*/
|
|
266
|
+
Mysql.prototype.read = async function(table, condition = {}, options = {}) {
|
|
267
|
+
try {
|
|
268
|
+
let sql = `SELECT * FROM ${table}`;
|
|
269
|
+
const params = [];
|
|
270
|
+
|
|
271
|
+
// 处理条件
|
|
272
|
+
if (Object.keys(condition).length > 0) {
|
|
273
|
+
const whereClauses = [];
|
|
274
|
+
for (const [field, value] of Object.entries(condition)) {
|
|
275
|
+
whereClauses.push(`${field} = ?`);
|
|
276
|
+
params.push(value);
|
|
277
|
+
}
|
|
278
|
+
sql += ` WHERE ${whereClauses.join(' AND ')}`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 处理排序
|
|
282
|
+
if (options.orderBy) {
|
|
283
|
+
sql += ` ORDER BY ${options.orderBy}`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 处理分页
|
|
287
|
+
if (options.limit) {
|
|
288
|
+
sql += ` LIMIT ${options.limit}`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (options.offset) {
|
|
292
|
+
sql += ` OFFSET ${options.offset}`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 执行查询
|
|
296
|
+
return await this.run(sql, params);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
$.log.error(`[${this.constructor.name}] [read] 查询失败`, {
|
|
299
|
+
error: error.message,
|
|
300
|
+
table: table,
|
|
301
|
+
condition: condition
|
|
302
|
+
});
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* 获取数据库管理器(保持兼容性)
|
|
309
|
+
* @returns {Object} DB实例
|
|
310
|
+
*/
|
|
311
|
+
Mysql.prototype.db = function() {
|
|
312
|
+
return new DB(this);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* 初始化MySQL服务(保持兼容性)
|
|
317
|
+
* @returns {Promise<void>}
|
|
318
|
+
*/
|
|
319
|
+
Mysql.prototype.init = async function() {
|
|
320
|
+
await this.open();
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 导出模块
|
|
325
|
+
*/
|
|
326
|
+
exports.Mysql = Mysql;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* 确保连接池对象存在
|
|
330
|
+
*/
|
|
331
|
+
if (!$.pool) {
|
|
332
|
+
$.pool = {};
|
|
333
|
+
}
|
|
334
|
+
if (!$.pool.mysql) {
|
|
335
|
+
$.pool.mysql = {};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* @description Mysql管理器,用于创建缓存
|
|
340
|
+
* @param {String} scope 作用域
|
|
341
|
+
* @param {Object} config 配置参数
|
|
342
|
+
* @return {Object} 返回一个Mysql类实例
|
|
343
|
+
*/
|
|
344
|
+
function mysql_admin(scope, config) {
|
|
345
|
+
if (!scope) {
|
|
346
|
+
scope = 'sys';
|
|
347
|
+
}
|
|
348
|
+
var obj = $.pool.mysql[scope];
|
|
349
|
+
if (!obj) {
|
|
350
|
+
$.pool.mysql[scope] = new Mysql(config);
|
|
351
|
+
obj = $.pool.mysql[scope];
|
|
352
|
+
}
|
|
353
|
+
return obj;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* @module 导出Mysql管理器
|
|
358
|
+
*/
|
|
1072
359
|
exports.mysql_admin = mysql_admin;
|