mm_sqlite 1.1.3 → 1.1.5
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/LICENSE +21 -201
- package/README.md +192 -95
- package/db.js +128 -112
- package/index.js +751 -542
- package/package.json +2 -2
- package/sql.js +245 -372
- package/config.json +0 -8
- package/db/db/test_db_methods.db.db +0 -0
- package/db/mm.db +0 -0
- package/sql_builder.js +0 -375
- package/test.js +0 -81
- package/test_all_methods.js +0 -115
- package/test_db_methods.js +0 -262
package/index.js
CHANGED
|
@@ -1,56 +1,71 @@
|
|
|
1
1
|
const sqlite3 = require('sqlite3').verbose();
|
|
2
|
-
const
|
|
3
|
-
|
|
2
|
+
const {
|
|
3
|
+
DB
|
|
4
|
+
} = require('./db');
|
|
5
|
+
const { Base } = require('mm_expand');
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* SQLite数据库操作类
|
|
7
9
|
* @class Sqlite
|
|
8
|
-
* @extends
|
|
10
|
+
* @extends Base
|
|
9
11
|
*/
|
|
10
|
-
class Sqlite extends
|
|
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
|
-
|
|
12
|
+
class Sqlite extends Base {
|
|
13
|
+
/**
|
|
14
|
+
* 默认配置
|
|
15
|
+
*/
|
|
16
|
+
static default_config = {
|
|
17
|
+
dir: "./db/".fullname(),
|
|
18
|
+
user: "root",
|
|
19
|
+
password: "",
|
|
20
|
+
database: "mm",
|
|
21
|
+
charset: "utf8mb4",
|
|
22
|
+
timezone: "+08:00",
|
|
23
|
+
connect_timeout: 20000,
|
|
24
|
+
acquire_timeout: 20000,
|
|
25
|
+
query_timeout: 20000,
|
|
26
|
+
connection_limit: 1,
|
|
27
|
+
queue_limit: 0,
|
|
28
|
+
enable_keep_alive: true,
|
|
29
|
+
keep_alive_initial_delay: 10000,
|
|
30
|
+
enable_reconnect: true,
|
|
31
|
+
reconnect_interval: 1000,
|
|
32
|
+
max_reconnect_attempts: 5,
|
|
33
|
+
wait_for_connections: true,
|
|
34
|
+
// 连接池相关配置
|
|
35
|
+
pool_min: 1,
|
|
36
|
+
pool_max: 5,
|
|
37
|
+
pool_acquire_timeout: 30000,
|
|
38
|
+
pool_idle_timeout: 60000,
|
|
39
|
+
pool_reap_interval: 1000
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 构造函数
|
|
44
|
+
* @param {Object} config - 配置对象
|
|
45
|
+
*/
|
|
46
|
+
constructor(config) {
|
|
47
|
+
const merged_config = Object.assign({}, Sqlite.default_config, config || {});
|
|
48
|
+
super(merged_config);
|
|
49
|
+
|
|
50
|
+
this.config = merged_config;
|
|
51
|
+
this._connection = null;
|
|
52
|
+
this._pool = null;
|
|
53
|
+
this._status = 'closed';
|
|
54
|
+
this._last_connect_time = 0;
|
|
55
|
+
this._reconnecting = false;
|
|
56
|
+
this._is_inited = false;
|
|
57
|
+
this._is_destroyed = false;
|
|
58
|
+
this._db = null;
|
|
59
|
+
this._open = false;
|
|
60
|
+
this._conn_retry_count = 0;
|
|
61
|
+
|
|
62
|
+
// 连接池相关属性
|
|
63
|
+
this._use_pool = this.config.connection_limit > 1;
|
|
64
|
+
this._pool_connections = [];
|
|
65
|
+
this._pool_available = [];
|
|
66
|
+
this._pool_waiting = [];
|
|
67
|
+
this._pool_reaper = null;
|
|
68
|
+
}
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
/**
|
|
@@ -58,123 +73,112 @@ class Sqlite extends BaseService {
|
|
|
58
73
|
* @returns {Promise<void>}
|
|
59
74
|
*/
|
|
60
75
|
Sqlite.prototype._initService = async function () {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
76
|
+
if (this._is_inited) {
|
|
77
|
+
this.logger('warn', 'SQLite服务已初始化');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
this.logger('debug', '初始化SQLite服务', { config: this.config });
|
|
83
|
+
this._is_inited = true;
|
|
84
|
+
this.logger('debug', 'SQLite服务初始化完成');
|
|
85
|
+
} catch (error) {
|
|
86
|
+
this.logger('error', 'SQLite服务初始化失败', error);
|
|
87
|
+
}
|
|
74
88
|
};
|
|
75
89
|
|
|
76
90
|
/**
|
|
77
|
-
*
|
|
78
|
-
* @
|
|
79
|
-
* @returns {Promise<boolean>}
|
|
91
|
+
* 获取数据库文件名
|
|
92
|
+
* @returns {string} 数据库文件名
|
|
80
93
|
*/
|
|
81
|
-
Sqlite.prototype.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
timeout = timeout || this.config.connect_timeout || 10000;
|
|
88
|
-
this._status = 'connecting';
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
// 创建连接
|
|
92
|
-
const connection_promise = this._openInternal();
|
|
93
|
-
const timeout_promise = new Promise((_, reject) => {
|
|
94
|
-
setTimeout(() => {
|
|
95
|
-
reject(new Error(`数据库连接超时(${timeout}ms)`));
|
|
96
|
-
}, timeout);
|
|
97
|
-
});
|
|
94
|
+
Sqlite.prototype._getDbFilename = function () {
|
|
95
|
+
var file = this.config.database;
|
|
96
|
+
if (file.indexOf(".") === -1) {
|
|
97
|
+
file += ".db";
|
|
98
|
+
}
|
|
98
99
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this._last_connect_time = Date.now();
|
|
103
|
-
this._status = 'connected';
|
|
104
|
-
$.log.info(`[${this.constructor.name}] [open]`, '数据库连接成功', {
|
|
105
|
-
dir: this.config.dir,
|
|
106
|
-
database: this.config.database
|
|
107
|
-
});
|
|
108
|
-
return true;
|
|
109
|
-
} catch (connection_err) {
|
|
110
|
-
$.log.error(`[${this.constructor.name}] [open]`, '连接过程错误详情', {
|
|
111
|
-
error: connection_err.message,
|
|
112
|
-
code: connection_err.code,
|
|
113
|
-
syscall: connection_err.syscall,
|
|
114
|
-
address: connection_err.address,
|
|
115
|
-
dir: connection_err.dir,
|
|
116
|
-
stack: connection_err.stack
|
|
117
|
-
});
|
|
118
|
-
throw connection_err;
|
|
119
|
-
}
|
|
120
|
-
} catch (err) {
|
|
121
|
-
this._status = 'closed';
|
|
122
|
-
$.log.error(`[${this.constructor.name}] [open]`, '数据库连接失败', {
|
|
123
|
-
error: err.message,
|
|
124
|
-
dir: this.config.dir,
|
|
125
|
-
database: this.config.database
|
|
126
|
-
});
|
|
127
|
-
throw err;
|
|
128
|
-
}
|
|
129
|
-
};
|
|
100
|
+
return file.fullname(this.config.dir);
|
|
101
|
+
}
|
|
130
102
|
|
|
131
103
|
/**
|
|
132
|
-
*
|
|
104
|
+
* 内部打开数据库连接
|
|
133
105
|
* @private
|
|
134
106
|
* @returns {Promise<void>}
|
|
135
107
|
*/
|
|
136
|
-
Sqlite.prototype._openInternal = function () {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (!fs.existsSync(dir)) {
|
|
147
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// 创建数据库连接
|
|
151
|
-
this._db = new sqlite3.Database(db_path);
|
|
152
|
-
this._open = true;
|
|
153
|
-
this._conn_retry_count = 0;
|
|
154
|
-
|
|
155
|
-
// 设置连接错误处理
|
|
156
|
-
this._db.on('error', (err) => {
|
|
157
|
-
$.log.error('SQLite数据库连接错误:', err);
|
|
158
|
-
this._handleConnectionError(err);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
$.log.debug(`SQLite数据库已打开: ${db_path}`);
|
|
162
|
-
resolve();
|
|
163
|
-
} catch (error) {
|
|
164
|
-
reject(error);
|
|
165
|
-
}
|
|
166
|
-
});
|
|
108
|
+
Sqlite.prototype._openInternal = async function () {
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
this._db = new sqlite3.Database(this._getDbFilename(), (err) => {
|
|
111
|
+
if (err) {
|
|
112
|
+
reject(err);
|
|
113
|
+
} else {
|
|
114
|
+
resolve();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
167
118
|
};
|
|
168
119
|
|
|
169
120
|
/**
|
|
170
|
-
*
|
|
171
|
-
* @
|
|
172
|
-
* @returns {
|
|
121
|
+
* 打开数据库连接
|
|
122
|
+
* @param {Number} timeout - 超时时间(毫秒)
|
|
123
|
+
* @returns {Promise<boolean>}
|
|
124
|
+
* @throws {TypeError} 当timeout参数无效时
|
|
173
125
|
*/
|
|
174
|
-
Sqlite.prototype.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
126
|
+
Sqlite.prototype.open = async function (timeout) {
|
|
127
|
+
// 参数校验
|
|
128
|
+
if (timeout !== undefined && typeof timeout !== 'number') {
|
|
129
|
+
throw new TypeError('timeout must be number');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (this._status === 'connected' || this._status === 'connecting') {
|
|
133
|
+
this.logger('warn', '数据库连接已存在或正在连接中');
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
timeout = timeout || this.config.connect_timeout || 10000;
|
|
138
|
+
this._status = 'connecting';
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
this.config.dir.addDir();
|
|
142
|
+
if (this._use_pool) {
|
|
143
|
+
// 使用连接池模式
|
|
144
|
+
await this._initPool();
|
|
145
|
+
this._last_connect_time = Date.now();
|
|
146
|
+
this._status = 'connected';
|
|
147
|
+
this.logger('info', '数据库连接池初始化成功', {
|
|
148
|
+
dir: this.config.dir,
|
|
149
|
+
database: this.config.database,
|
|
150
|
+
pool_min: this.config.pool_min,
|
|
151
|
+
pool_max: this.config.pool_max
|
|
152
|
+
});
|
|
153
|
+
return true;
|
|
154
|
+
} else {
|
|
155
|
+
// 使用单连接模式
|
|
156
|
+
const connection_promise = this._openInternal();
|
|
157
|
+
const timeout_promise = new Promise((_, reject) => {
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
reject(new Error(`数据库连接超时(${timeout}ms)`));
|
|
160
|
+
}, timeout);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
await Promise.race([connection_promise, timeout_promise]);
|
|
165
|
+
|
|
166
|
+
this._last_connect_time = Date.now();
|
|
167
|
+
this._status = 'connected';
|
|
168
|
+
this.logger('info', '数据库连接成功', {
|
|
169
|
+
dir: this.config.dir,
|
|
170
|
+
database: this.config.database
|
|
171
|
+
});
|
|
172
|
+
return true;
|
|
173
|
+
} catch (connection_err) {
|
|
174
|
+
this.logger('error', '连接过程错误详情', connection_err);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch (err) {
|
|
178
|
+
this._status = 'closed';
|
|
179
|
+
this.logger('error', '数据库连接失败', err);
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
178
182
|
};
|
|
179
183
|
|
|
180
184
|
/**
|
|
@@ -182,168 +186,91 @@ Sqlite.prototype._getDatabasePath = function () {
|
|
|
182
186
|
* @returns {Promise<boolean>}
|
|
183
187
|
*/
|
|
184
188
|
Sqlite.prototype.close = function () {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return new Promise((resolve) => {
|
|
191
|
-
try {
|
|
192
|
-
if (this._db) {
|
|
193
|
-
$.log.debug('关闭数据库连接');
|
|
194
|
-
this._db.close((err) => {
|
|
195
|
-
if (err) {
|
|
196
|
-
$.log.error('关闭数据库失败', { error: err.message });
|
|
197
|
-
} else {
|
|
198
|
-
$.log.info('数据库已关闭');
|
|
199
|
-
}
|
|
200
|
-
this._db = null;
|
|
201
|
-
this._open = false;
|
|
202
|
-
this._status = 'closed';
|
|
203
|
-
resolve(!err);
|
|
204
|
-
});
|
|
205
|
-
} else {
|
|
206
|
-
this._status = 'closed';
|
|
207
|
-
resolve(true);
|
|
208
|
-
}
|
|
209
|
-
} catch (err) {
|
|
210
|
-
$.log.error('关闭数据库连接时发生异常', { error: err.message });
|
|
211
|
-
this._status = 'closed';
|
|
212
|
-
resolve(false);
|
|
189
|
+
if (this._status !== 'connected') {
|
|
190
|
+
this.logger('warn', '数据库连接未建立');
|
|
191
|
+
return Promise.resolve(false);
|
|
213
192
|
}
|
|
214
|
-
|
|
193
|
+
|
|
194
|
+
return new Promise((resolve) => {
|
|
195
|
+
try {
|
|
196
|
+
if (this._use_pool) {
|
|
197
|
+
// 关闭连接池
|
|
198
|
+
this._closePool();
|
|
199
|
+
this._db = null;
|
|
200
|
+
this._open = false;
|
|
201
|
+
this._status = 'closed';
|
|
202
|
+
this.logger('info', '数据库连接池已关闭');
|
|
203
|
+
resolve(true);
|
|
204
|
+
} else if (this._db) {
|
|
205
|
+
// 单连接模式
|
|
206
|
+
this.logger('debug', '关闭数据库连接');
|
|
207
|
+
this._db.close((err) => {
|
|
208
|
+
if (err) {
|
|
209
|
+
this.logger('error', '关闭数据库失败', err);
|
|
210
|
+
} else {
|
|
211
|
+
this.logger('info', '数据库已关闭');
|
|
212
|
+
}
|
|
213
|
+
this._db = null;
|
|
214
|
+
this._open = false;
|
|
215
|
+
this._status = 'closed';
|
|
216
|
+
resolve(!err);
|
|
217
|
+
});
|
|
218
|
+
} else {
|
|
219
|
+
this._status = 'closed';
|
|
220
|
+
resolve(true);
|
|
221
|
+
}
|
|
222
|
+
} catch (err) {
|
|
223
|
+
this.logger('error', '关闭数据库连接时发生异常', err);
|
|
224
|
+
this._status = 'closed';
|
|
225
|
+
resolve(false);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
215
228
|
};
|
|
216
229
|
|
|
217
230
|
/**
|
|
218
231
|
* 获取数据库连接
|
|
219
232
|
* @param {Number} timeout - 超时时间(毫秒)
|
|
220
233
|
* @returns {Promise<Object>}
|
|
234
|
+
* @throws {TypeError} 当timeout参数无效时
|
|
221
235
|
*/
|
|
222
236
|
Sqlite.prototype.getConn = async function (timeout) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (this._status !== 'connected') {
|
|
228
|
-
throw new Error('数据库连接未建立');
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
try {
|
|
232
|
-
const connection_promise = new Promise((resolve) => {
|
|
233
|
-
resolve(this._db);
|
|
234
|
-
});
|
|
235
|
-
const timeout_promise = new Promise((_, reject) => {
|
|
236
|
-
setTimeout(() => {
|
|
237
|
-
reject(new Error(`获取数据库连接超时(${timeout}ms)`));
|
|
238
|
-
}, timeout);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
const conn = await Promise.race([connection_promise, timeout_promise]);
|
|
242
|
-
|
|
243
|
-
// $.log.debug(`[${this.constructor.name}] [getConn] 成功获取数据库连接`);
|
|
244
|
-
|
|
245
|
-
// 处理连接错误
|
|
246
|
-
conn.on('error', (err) => {
|
|
247
|
-
if (err.code && (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT')) {
|
|
248
|
-
this._handleConnectionError(err);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
return conn;
|
|
253
|
-
} catch (error) {
|
|
254
|
-
$.log.error(`[${this.constructor.name}] [getConn] 获取连接失败`, {
|
|
255
|
-
error: error.message
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
if (error.code && (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT')) {
|
|
259
|
-
this._handleConnectionError(error);
|
|
237
|
+
// 参数校验
|
|
238
|
+
if (timeout !== undefined && typeof timeout !== 'number') {
|
|
239
|
+
throw new TypeError('timeout must be number');
|
|
260
240
|
}
|
|
261
241
|
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
};
|
|
242
|
+
timeout = timeout || this.config.acquire_timeout || this.config.connect_timeout || 20000;
|
|
265
243
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
* @param {Error} err - 错误对象
|
|
270
|
-
*/
|
|
271
|
-
Sqlite.prototype._handleConnectionError = function (err) {
|
|
272
|
-
if (this._reconnecting) {
|
|
273
|
-
$.log.debug('重连已在进行中');
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (this.config.enable_reconnect) {
|
|
278
|
-
$.log.info('开始自动重连...');
|
|
279
|
-
this._reconnect(this.config.max_reconnect_attempts || 3)
|
|
280
|
-
.catch(reconnect_err => {
|
|
281
|
-
$.log.error('重连失败', { error: reconnect_err.message });
|
|
282
|
-
this._status = 'closed';
|
|
283
|
-
});
|
|
284
|
-
} else {
|
|
285
|
-
$.log.warn('自动重连已禁用');
|
|
286
|
-
this._status = 'closed';
|
|
287
|
-
}
|
|
288
|
-
};
|
|
244
|
+
if (this._status !== 'connected') {
|
|
245
|
+
throw new Error('数据库连接未建立');
|
|
246
|
+
}
|
|
289
247
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
while (retry_count < max_retries) {
|
|
312
|
-
retry_count++;
|
|
313
|
-
$.log.info(`第${retry_count}/${max_retries}次尝试重连`);
|
|
314
|
-
|
|
315
|
-
try {
|
|
316
|
-
const reconnect_timeout = this.config.connect_timeout || this.config.acquire_timeout || 20000;
|
|
317
|
-
$.log.debug(`重连超时设置为: ${reconnect_timeout}ms`);
|
|
318
|
-
await this.open(reconnect_timeout);
|
|
319
|
-
$.log.info('重连成功');
|
|
320
|
-
return true;
|
|
321
|
-
} catch (err) {
|
|
322
|
-
$.log.error(`重连失败(${retry_count}/${max_retries})`, { error: err.message });
|
|
248
|
+
try {
|
|
249
|
+
if (this._use_pool) {
|
|
250
|
+
// 从连接池获取连接
|
|
251
|
+
return await this._getPoolConnection();
|
|
252
|
+
} else {
|
|
253
|
+
// 单连接模式
|
|
254
|
+
const connection_promise = new Promise((resolve) => {
|
|
255
|
+
resolve(this._db);
|
|
256
|
+
});
|
|
257
|
+
const timeout_promise = new Promise((_, reject) => {
|
|
258
|
+
setTimeout(() => {
|
|
259
|
+
reject(new Error(`获取数据库连接超时(${timeout}ms)`));
|
|
260
|
+
}, timeout);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const conn = await Promise.race([connection_promise, timeout_promise]);
|
|
264
|
+
return conn;
|
|
265
|
+
}
|
|
266
|
+
} catch (error) {
|
|
267
|
+
this.logger('error', '获取连接失败', error);
|
|
323
268
|
|
|
324
|
-
if (
|
|
325
|
-
|
|
326
|
-
$.log.debug(`等待${wait_time}ms后重试`);
|
|
327
|
-
await this._sleep(wait_time);
|
|
269
|
+
if (error.code && (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT')) {
|
|
270
|
+
this._handleConnectionError(error);
|
|
328
271
|
}
|
|
329
|
-
|
|
272
|
+
return null;
|
|
330
273
|
}
|
|
331
|
-
|
|
332
|
-
$.log.error(`达到最大重试次数(${max_retries}),重连失败`);
|
|
333
|
-
throw new Error(`重连失败:达到最大重试次数(${max_retries})`);
|
|
334
|
-
} finally {
|
|
335
|
-
this._reconnecting = false;
|
|
336
|
-
}
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* 休眠函数
|
|
341
|
-
* @private
|
|
342
|
-
* @param {Number} ms - 休眠时间(毫秒)
|
|
343
|
-
* @returns {Promise<void>}
|
|
344
|
-
*/
|
|
345
|
-
Sqlite.prototype._sleep = function (ms) {
|
|
346
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
347
274
|
};
|
|
348
275
|
|
|
349
276
|
/**
|
|
@@ -352,50 +279,82 @@ Sqlite.prototype._sleep = function (ms) {
|
|
|
352
279
|
* @param {Array} params - 参数数组
|
|
353
280
|
* @param {Number} timeout - 超时时间(毫秒)
|
|
354
281
|
* @returns {Promise<Object>}
|
|
282
|
+
* @throws {TypeError} 当sql参数无效时
|
|
355
283
|
*/
|
|
356
284
|
Sqlite.prototype.run = async function (sql, params, timeout) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (error) {
|
|
368
|
-
reject(error);
|
|
369
|
-
} else {
|
|
370
|
-
// 保持与MySQL兼容的返回值格式,始终返回数组
|
|
371
|
-
resolve(rows);
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
});
|
|
285
|
+
// 参数校验
|
|
286
|
+
if (typeof sql !== 'string' || sql.trim() === '') {
|
|
287
|
+
throw new TypeError('sql must be non-empty string');
|
|
288
|
+
}
|
|
289
|
+
if (params !== undefined && !Array.isArray(params)) {
|
|
290
|
+
throw new TypeError('params must be array');
|
|
291
|
+
}
|
|
292
|
+
if (timeout !== undefined && typeof timeout !== 'number') {
|
|
293
|
+
throw new TypeError('timeout must be number');
|
|
294
|
+
}
|
|
375
295
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
296
|
+
this.error = null;
|
|
297
|
+
let conn = null;
|
|
298
|
+
timeout = timeout || this.config.query_timeout || 30000;
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
// 获取连接
|
|
302
|
+
conn = await this.getConn(timeout);
|
|
303
|
+
|
|
304
|
+
var _this = this;
|
|
305
|
+
// 直接在方法内部实现超时控制
|
|
306
|
+
const query_promise = new Promise((resolve, reject) => {
|
|
307
|
+
conn.all(sql, params || [], (error, rows) => {
|
|
308
|
+
if (error) {
|
|
309
|
+
_this.sql = sql;
|
|
310
|
+
_this.error = {
|
|
311
|
+
code: error.errno,
|
|
312
|
+
message: error.message
|
|
313
|
+
}
|
|
314
|
+
reject(error);
|
|
315
|
+
} else {
|
|
316
|
+
// 保持与MySQL兼容的返回值格式,始终返回数组
|
|
317
|
+
resolve(rows);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
});
|
|
381
321
|
|
|
382
|
-
|
|
322
|
+
const timeout_promise = new Promise((_, reject) => {
|
|
323
|
+
setTimeout(() => {
|
|
324
|
+
reject(new Error(`SQL查询超时: ${timeout}ms`));
|
|
325
|
+
}, timeout);
|
|
326
|
+
});
|
|
383
327
|
|
|
384
|
-
|
|
385
|
-
} catch (err) {
|
|
386
|
-
$.log.error('[Sqlite] [run]', 'SQL执行失败', {
|
|
387
|
-
error: err.message,
|
|
388
|
-
sql: typeof sql === 'string' ? sql.substring(0, 200) : sql,
|
|
389
|
-
params: params
|
|
390
|
-
});
|
|
328
|
+
const result = await Promise.race([query_promise, timeout_promise]);
|
|
391
329
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
330
|
+
// 连接池模式下释放连接
|
|
331
|
+
if (this._use_pool && conn) {
|
|
332
|
+
this._releasePoolConnection(conn);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return result;
|
|
336
|
+
} catch (err) {
|
|
337
|
+
this.logger('error', 'SQL执行失败', err);
|
|
338
|
+
|
|
339
|
+
// 连接池模式下释放连接
|
|
340
|
+
if (this._use_pool && conn) {
|
|
341
|
+
this._releasePoolConnection(conn);
|
|
342
|
+
}
|
|
396
343
|
|
|
397
|
-
|
|
398
|
-
|
|
344
|
+
// 处理连接错误,触发重连
|
|
345
|
+
if (err.code && (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT')) {
|
|
346
|
+
this._handleConnectionError(err);
|
|
347
|
+
}
|
|
348
|
+
this.sql = sql;
|
|
349
|
+
// 记录错误信息
|
|
350
|
+
this.error = {
|
|
351
|
+
code: err.code,
|
|
352
|
+
message: err.message
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// 返回空数组作为默认值,保持返回值类型一致
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
399
358
|
};
|
|
400
359
|
|
|
401
360
|
/**
|
|
@@ -404,54 +363,82 @@ Sqlite.prototype.run = async function (sql, params, timeout) {
|
|
|
404
363
|
* @param {Array} params - 参数数组
|
|
405
364
|
* @param {Number} timeout - 超时时间(毫秒)
|
|
406
365
|
* @returns {Promise<Object>}
|
|
366
|
+
* @throws {TypeError} 当sql参数无效时
|
|
407
367
|
*/
|
|
408
368
|
Sqlite.prototype.exec = async function (sql, params, timeout) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if (error) {
|
|
420
|
-
reject(error);
|
|
421
|
-
} else {
|
|
422
|
-
// 保持与MySQL兼容的返回值格式
|
|
423
|
-
resolve({
|
|
424
|
-
affected_rows: this.changes || 0,
|
|
425
|
-
insert_id: this.lastID || 0,
|
|
426
|
-
changed_rows: this.changes || 0
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
});
|
|
369
|
+
// 参数校验
|
|
370
|
+
if (typeof sql !== 'string' || sql.trim() === '') {
|
|
371
|
+
throw new TypeError('sql must be non-empty string');
|
|
372
|
+
}
|
|
373
|
+
if (params !== undefined && !Array.isArray(params)) {
|
|
374
|
+
throw new TypeError('params must be array');
|
|
375
|
+
}
|
|
376
|
+
if (timeout !== undefined && typeof timeout !== 'number') {
|
|
377
|
+
throw new TypeError('timeout must be number');
|
|
378
|
+
}
|
|
431
379
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}, timeout);
|
|
436
|
-
});
|
|
380
|
+
this.error = null;
|
|
381
|
+
let conn = null;
|
|
382
|
+
timeout = timeout || this.config.query_timeout || 30000;
|
|
437
383
|
|
|
438
|
-
|
|
384
|
+
try {
|
|
385
|
+
// 获取连接
|
|
386
|
+
conn = await this.getConn(timeout);
|
|
387
|
+
var _this = this;
|
|
388
|
+
// 直接在方法内部实现超时控制
|
|
389
|
+
const query_promise = new Promise((resolve, reject) => {
|
|
390
|
+
conn.run(sql, params || [], function (error, rows) {
|
|
391
|
+
if (error) {
|
|
392
|
+
_this.sql = error.sql;
|
|
393
|
+
_this.error = {
|
|
394
|
+
code: error.errno,
|
|
395
|
+
message: error.message
|
|
396
|
+
}
|
|
397
|
+
reject(error);
|
|
398
|
+
resolve(0);
|
|
399
|
+
} else {
|
|
400
|
+
// 保持与MySQL兼容的返回值格式
|
|
401
|
+
resolve(this.lastID || this.changes || 1);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
});
|
|
439
405
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
params: params
|
|
446
|
-
});
|
|
406
|
+
const timeout_promise = new Promise((_, reject) => {
|
|
407
|
+
setTimeout(() => {
|
|
408
|
+
reject(new Error(`SQL执行超时: ${timeout}ms`));
|
|
409
|
+
}, timeout);
|
|
410
|
+
});
|
|
447
411
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
412
|
+
const result = await Promise.race([query_promise, timeout_promise]);
|
|
413
|
+
|
|
414
|
+
// 连接池模式下释放连接
|
|
415
|
+
if (this._use_pool && conn) {
|
|
416
|
+
this._releasePoolConnection(conn);
|
|
417
|
+
}
|
|
452
418
|
|
|
453
|
-
|
|
454
|
-
|
|
419
|
+
return result;
|
|
420
|
+
} catch (err) {
|
|
421
|
+
this.logger('error', 'SQL执行失败', err);
|
|
422
|
+
|
|
423
|
+
// 连接池模式下释放连接
|
|
424
|
+
if (this._use_pool && conn) {
|
|
425
|
+
this._releasePoolConnection(conn);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// 处理连接错误,触发重连
|
|
429
|
+
if (err.code && (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT')) {
|
|
430
|
+
this._handleConnectionError(err);
|
|
431
|
+
}
|
|
432
|
+
this.sql = sql;
|
|
433
|
+
// 记录错误信息
|
|
434
|
+
this.error = {
|
|
435
|
+
code: err.code,
|
|
436
|
+
message: err.message
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
// 返回默认操作结果对象,保持返回值类型一致
|
|
440
|
+
return 0;
|
|
441
|
+
}
|
|
455
442
|
};
|
|
456
443
|
|
|
457
444
|
/**
|
|
@@ -460,101 +447,105 @@ Sqlite.prototype.exec = async function (sql, params, timeout) {
|
|
|
460
447
|
* @param {Object} condition - 查询条件
|
|
461
448
|
* @param {Object} options - 选项(order_by, limit, offset等)
|
|
462
449
|
* @returns {Promise<Array>}
|
|
450
|
+
* @throws {TypeError} 当table参数无效时
|
|
463
451
|
*/
|
|
464
452
|
Sqlite.prototype.read = async function (table, condition, options) {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
453
|
+
// 参数校验
|
|
454
|
+
if (typeof table !== 'string' || table.trim() === '') {
|
|
455
|
+
throw new TypeError('table must be non-empty string');
|
|
456
|
+
}
|
|
457
|
+
if (condition !== undefined && (typeof condition !== 'object' || Array.isArray(condition))) {
|
|
458
|
+
throw new TypeError('condition must be object');
|
|
459
|
+
}
|
|
460
|
+
if (options !== undefined && (typeof options !== 'object' || Array.isArray(options))) {
|
|
461
|
+
throw new TypeError('options must be object');
|
|
469
462
|
}
|
|
470
463
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
464
|
+
try {
|
|
465
|
+
// 检查连接状态
|
|
466
|
+
if (this._status !== 'connected') {
|
|
467
|
+
throw new Error('数据库连接未建立');
|
|
468
|
+
}
|
|
474
469
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
for (const [field, value] of Object.entries(condition)) {
|
|
479
|
-
where_clauses.push(`${field} = ?`);
|
|
480
|
-
params.push(value);
|
|
481
|
-
}
|
|
482
|
-
sql += ` WHERE ${where_clauses.join(' AND ')}`;
|
|
483
|
-
}
|
|
470
|
+
// 构建基础SQL查询
|
|
471
|
+
let sql = `SELECT * FROM ${table}`;
|
|
472
|
+
const params = [];
|
|
484
473
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
474
|
+
// 处理条件
|
|
475
|
+
if (condition && Object.keys(condition).length > 0) {
|
|
476
|
+
const where_clauses = [];
|
|
477
|
+
for (const [field, value] of Object.entries(condition)) {
|
|
478
|
+
where_clauses.push(`${field} = ?`);
|
|
479
|
+
params.push(value);
|
|
480
|
+
}
|
|
481
|
+
sql += ` WHERE ${where_clauses.join(' AND ')}`;
|
|
482
|
+
}
|
|
489
483
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
484
|
+
// 处理排序
|
|
485
|
+
if (options && options.order_by) {
|
|
486
|
+
sql += ` ORDER BY ${options.order_by}`;
|
|
487
|
+
}
|
|
494
488
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
489
|
+
// 处理分页
|
|
490
|
+
if (options && options.limit) {
|
|
491
|
+
sql += ` LIMIT ${options.limit}`;
|
|
492
|
+
}
|
|
498
493
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
494
|
+
if (options && options.offset) {
|
|
495
|
+
sql += ` OFFSET ${options.offset}`;
|
|
496
|
+
}
|
|
503
497
|
|
|
504
|
-
|
|
505
|
-
|
|
498
|
+
// 记录查询日志
|
|
499
|
+
if (this.config.debug) {
|
|
500
|
+
this.logger('debug', '查询数据', { sql, params });
|
|
501
|
+
}
|
|
506
502
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
503
|
+
// 执行查询
|
|
504
|
+
const results = await this.run(sql, params);
|
|
510
505
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
// 记录错误日志
|
|
515
|
-
$.log.error(`[${this.constructor.name}] [read] 查询失败`, {
|
|
516
|
-
error: error.message,
|
|
517
|
-
table: table,
|
|
518
|
-
condition: condition
|
|
519
|
-
});
|
|
506
|
+
if (this.config.debug) {
|
|
507
|
+
this.logger('info', '查询成功', { count: Array.isArray(results) ? results.length : 1 });
|
|
508
|
+
}
|
|
520
509
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
510
|
+
// 确保返回数组格式
|
|
511
|
+
return Array.isArray(results) ? results : [results];
|
|
512
|
+
} catch (error) {
|
|
513
|
+
this.logger('error', '查询失败', error);
|
|
514
|
+
// 返回空数组作为默认值,保持返回值类型一致
|
|
515
|
+
return [];
|
|
516
|
+
}
|
|
524
517
|
};
|
|
525
518
|
|
|
526
519
|
/**
|
|
527
520
|
* 获取数据库操作实例
|
|
528
521
|
* @param {String} database - 数据库名称
|
|
529
522
|
* @returns {Object} 数据库操作实例
|
|
523
|
+
* @throws {TypeError} 当database参数无效时
|
|
530
524
|
*/
|
|
531
525
|
Sqlite.prototype.db = function (database) {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
526
|
+
// 参数校验
|
|
527
|
+
if (database !== undefined && typeof database !== 'string') {
|
|
528
|
+
throw new TypeError('database must be string');
|
|
529
|
+
}
|
|
535
530
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
531
|
+
if (this._status !== 'connected') {
|
|
532
|
+
throw new Error('数据库连接未建立');
|
|
533
|
+
}
|
|
539
534
|
|
|
540
|
-
//
|
|
541
|
-
this._db_instance
|
|
542
|
-
|
|
543
|
-
}
|
|
535
|
+
// 优化:缓存DB实例,避免重复创建
|
|
536
|
+
if (!this._db_instance) {
|
|
537
|
+
this._db_instance = new DB(this);
|
|
544
538
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
539
|
+
// 为DB实例添加必要的方法,确保与MySQL接口兼容
|
|
540
|
+
this._db_instance.run = this.run.bind(this);
|
|
541
|
+
this._db_instance.exec = this.exec.bind(this);
|
|
542
|
+
}
|
|
548
543
|
|
|
549
|
-
|
|
550
|
-
|
|
544
|
+
if (database) {
|
|
545
|
+
this._db_instance.database = database;
|
|
546
|
+
}
|
|
551
547
|
|
|
552
|
-
|
|
553
|
-
* 获取数据库管理器模型
|
|
554
|
-
* @returns {Object} 数据库管理器模型
|
|
555
|
-
*/
|
|
556
|
-
Sqlite.prototype.dbs = function () {
|
|
557
|
-
return this.db();
|
|
548
|
+
return this._db_instance;
|
|
558
549
|
};
|
|
559
550
|
|
|
560
551
|
/**
|
|
@@ -562,126 +553,170 @@ Sqlite.prototype.dbs = function () {
|
|
|
562
553
|
* @returns {Promise<Object>} 事务连接对象
|
|
563
554
|
*/
|
|
564
555
|
Sqlite.prototype.beginTransaction = async function () {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// 获取连接
|
|
572
|
-
const connection = await this.getConn();
|
|
573
|
-
|
|
574
|
-
// 开始事务
|
|
575
|
-
await new Promise((resolve, reject) => {
|
|
576
|
-
connection.run('BEGIN TRANSACTION', (error) => {
|
|
577
|
-
if (error) {
|
|
578
|
-
reject(error);
|
|
579
|
-
} else {
|
|
580
|
-
resolve();
|
|
556
|
+
try {
|
|
557
|
+
// 检查连接状态
|
|
558
|
+
if (this._status !== 'connected') {
|
|
559
|
+
throw new Error('数据库连接未建立');
|
|
581
560
|
}
|
|
582
|
-
});
|
|
583
|
-
});
|
|
584
561
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
562
|
+
// 获取连接
|
|
563
|
+
const connection = await this.getConn();
|
|
588
564
|
|
|
589
|
-
|
|
590
|
-
return {
|
|
591
|
-
connection,
|
|
592
|
-
commit: async () => {
|
|
565
|
+
// 开始事务
|
|
593
566
|
await new Promise((resolve, reject) => {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
});
|
|
602
|
-
if (this.config.debug) {
|
|
603
|
-
$.log.debug(`[${this.constructor.name}] [beginTransaction] 事务提交`);
|
|
604
|
-
}
|
|
605
|
-
},
|
|
606
|
-
rollback: async () => {
|
|
607
|
-
await new Promise((resolve, reject) => {
|
|
608
|
-
connection.run('ROLLBACK', (error) => {
|
|
609
|
-
if (error) {
|
|
610
|
-
reject(error);
|
|
611
|
-
} else {
|
|
612
|
-
resolve();
|
|
613
|
-
}
|
|
614
|
-
});
|
|
567
|
+
connection.run('BEGIN TRANSACTION', (error) => {
|
|
568
|
+
if (error) {
|
|
569
|
+
reject(error);
|
|
570
|
+
} else {
|
|
571
|
+
resolve();
|
|
572
|
+
}
|
|
573
|
+
});
|
|
615
574
|
});
|
|
575
|
+
|
|
616
576
|
if (this.config.debug) {
|
|
617
|
-
|
|
577
|
+
this.logger('debug', '事务开始');
|
|
618
578
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
579
|
+
|
|
580
|
+
// 返回事务连接对象,包含提交和回滚方法
|
|
581
|
+
return {
|
|
582
|
+
connection,
|
|
583
|
+
commit: async () => {
|
|
584
|
+
await new Promise((resolve, reject) => {
|
|
585
|
+
connection.run('COMMIT', (error) => {
|
|
586
|
+
if (error) {
|
|
587
|
+
reject(error);
|
|
588
|
+
} else {
|
|
589
|
+
resolve();
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
if (this.config.debug) {
|
|
594
|
+
this.logger('debug', '事务提交');
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// 连接池模式下释放连接
|
|
598
|
+
if (this._use_pool) {
|
|
599
|
+
this._releasePoolConnection(connection);
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
rollback: async () => {
|
|
603
|
+
await new Promise((resolve, reject) => {
|
|
604
|
+
connection.run('ROLLBACK', (error) => {
|
|
605
|
+
if (error) {
|
|
606
|
+
reject(error);
|
|
607
|
+
} else {
|
|
608
|
+
resolve();
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
if (this.config.debug) {
|
|
613
|
+
this.logger('debug', '事务回滚');
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// 连接池模式下释放连接
|
|
617
|
+
if (this._use_pool) {
|
|
618
|
+
this._releasePoolConnection(connection);
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
exec: async (sql, params) => {
|
|
622
|
+
// 在事务中执行SQL
|
|
623
|
+
return new Promise((resolve, reject) => {
|
|
624
|
+
connection.run(sql, params || [], function (error) {
|
|
625
|
+
if (error) {
|
|
626
|
+
this.sql = error.sql;
|
|
627
|
+
this.error = {
|
|
628
|
+
code: error.errno,
|
|
629
|
+
message: error.message
|
|
630
|
+
}
|
|
631
|
+
reject(error);
|
|
632
|
+
resolve(0);
|
|
633
|
+
} else {
|
|
634
|
+
resolve(this.lastID || this.changes || 1);
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
});
|
|
634
638
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
throw error;
|
|
642
|
-
}
|
|
639
|
+
};
|
|
640
|
+
} catch (error) {
|
|
641
|
+
this.logger('error', '事务开始失败', error);
|
|
642
|
+
// 重新抛出异常,因为调用方需要事务对象
|
|
643
|
+
throw error;
|
|
644
|
+
}
|
|
643
645
|
};
|
|
644
646
|
|
|
645
647
|
/**
|
|
646
648
|
* 在事务中执行多个操作
|
|
647
649
|
* @param {Function} callback - 包含事务操作的回调函数
|
|
648
650
|
* @returns {Promise<*>} 回调函数的返回值
|
|
651
|
+
* @throws {TypeError} 当callback参数无效时
|
|
649
652
|
*/
|
|
650
653
|
Sqlite.prototype.transaction = async function (callback) {
|
|
651
|
-
|
|
654
|
+
// 参数校验
|
|
655
|
+
if (typeof callback !== 'function') {
|
|
656
|
+
throw new TypeError('callback must be function');
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
let transaction = null;
|
|
660
|
+
|
|
661
|
+
try {
|
|
662
|
+
// 开始事务
|
|
663
|
+
transaction = await this.beginTransaction();
|
|
664
|
+
|
|
665
|
+
// 执行回调函数,传入事务对象
|
|
666
|
+
const result = await callback(transaction);
|
|
652
667
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
transaction = await this.beginTransaction();
|
|
668
|
+
// 提交事务
|
|
669
|
+
await transaction.commit();
|
|
656
670
|
|
|
657
|
-
|
|
658
|
-
|
|
671
|
+
return result;
|
|
672
|
+
} catch (error) {
|
|
673
|
+
// 如果有事务,回滚
|
|
674
|
+
if (transaction) {
|
|
675
|
+
await transaction.rollback().catch(err => {
|
|
676
|
+
this.logger('error', '事务回滚失败', err);
|
|
677
|
+
});
|
|
678
|
+
}
|
|
659
679
|
|
|
660
|
-
|
|
661
|
-
|
|
680
|
+
this.logger('error', '事务执行失败', error);
|
|
681
|
+
// 重新抛出异常,因为调用方需要事务执行结果
|
|
682
|
+
throw error;
|
|
683
|
+
}
|
|
684
|
+
};
|
|
662
685
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
686
|
+
/**
|
|
687
|
+
* 设置当前操作的表名
|
|
688
|
+
* @param {String} name - 表名
|
|
689
|
+
* @param {String} key - 主键
|
|
690
|
+
* @returns {Object} 数据库操作实例
|
|
691
|
+
* @throws {TypeError} 当name参数无效时
|
|
692
|
+
*/
|
|
693
|
+
Sqlite.prototype.table = function (name, key) {
|
|
694
|
+
// 参数校验
|
|
695
|
+
if (typeof name !== 'string' || name.trim() === '') {
|
|
696
|
+
throw new TypeError('name must be non-empty string');
|
|
697
|
+
}
|
|
698
|
+
if (key !== undefined && typeof key !== 'string') {
|
|
699
|
+
throw new TypeError('key must be string');
|
|
670
700
|
}
|
|
671
701
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
702
|
+
var db = this.db();
|
|
703
|
+
db.table = name;
|
|
704
|
+
if (key) {
|
|
705
|
+
db.key = key;
|
|
706
|
+
}
|
|
707
|
+
return db;
|
|
675
708
|
};
|
|
676
709
|
|
|
710
|
+
// close 方法已在文件前面部分实现,这里不再重复
|
|
711
|
+
|
|
677
712
|
/**
|
|
678
713
|
* 确保连接池对象存在
|
|
679
714
|
*/
|
|
680
715
|
if (!$.pool) {
|
|
681
|
-
|
|
716
|
+
$.pool = {};
|
|
682
717
|
}
|
|
683
718
|
if (!$.pool.sqlite) {
|
|
684
|
-
|
|
719
|
+
$.pool.sqlite = {};
|
|
685
720
|
}
|
|
686
721
|
|
|
687
722
|
/**
|
|
@@ -691,17 +726,191 @@ if (!$.pool.sqlite) {
|
|
|
691
726
|
* @return {Object} 返回一个Sqlite类实例
|
|
692
727
|
*/
|
|
693
728
|
function sqliteAdmin(scope, config) {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
729
|
+
if (!scope) {
|
|
730
|
+
scope = 'sys';
|
|
731
|
+
}
|
|
732
|
+
var obj = $.pool.sqlite[scope];
|
|
733
|
+
if (!obj) {
|
|
734
|
+
$.pool.sqlite[scope] = new Sqlite(config);
|
|
735
|
+
obj = $.pool.sqlite[scope];
|
|
736
|
+
}
|
|
737
|
+
return obj;
|
|
703
738
|
}
|
|
704
739
|
|
|
740
|
+
/**
|
|
741
|
+
* 创建SQLite连接
|
|
742
|
+
* @private
|
|
743
|
+
* @returns {Promise<Object>} 数据库连接对象
|
|
744
|
+
*/
|
|
745
|
+
Sqlite.prototype._createConnection = function () {
|
|
746
|
+
return new Promise((resolve, reject) => {
|
|
747
|
+
const db = new sqlite3.Database(this._getDbFilename(), (err) => {
|
|
748
|
+
if (err) {
|
|
749
|
+
reject(err);
|
|
750
|
+
} else {
|
|
751
|
+
resolve(db);
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* 初始化连接池
|
|
759
|
+
* @private
|
|
760
|
+
* @returns {Promise<void>}
|
|
761
|
+
*/
|
|
762
|
+
Sqlite.prototype._initPool = async function () {
|
|
763
|
+
if (this._pool_reaper) {
|
|
764
|
+
return; // 连接池已初始化
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
try {
|
|
768
|
+
// 创建最小连接数
|
|
769
|
+
for (let i = 0; i < this.config.pool_min; i++) {
|
|
770
|
+
const conn = await this._createConnection();
|
|
771
|
+
this._pool_connections.push(conn);
|
|
772
|
+
this._pool_available.push(conn);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// 启动连接回收器
|
|
776
|
+
this._pool_reaper = setInterval(() => {
|
|
777
|
+
this._reapIdleConnections();
|
|
778
|
+
}, this.config.pool_reap_interval);
|
|
779
|
+
|
|
780
|
+
this.logger('debug', '连接池初始化完成', {
|
|
781
|
+
min: this.config.pool_min,
|
|
782
|
+
max: this.config.pool_max,
|
|
783
|
+
current: this._pool_connections.length
|
|
784
|
+
});
|
|
785
|
+
} catch (error) {
|
|
786
|
+
this.logger('error', '连接池初始化失败', error);
|
|
787
|
+
throw error;
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* 回收空闲连接
|
|
793
|
+
* @private
|
|
794
|
+
*/
|
|
795
|
+
Sqlite.prototype._reapIdleConnections = function () {
|
|
796
|
+
const now = Date.now();
|
|
797
|
+
const idle_timeout = this.config.pool_idle_timeout;
|
|
798
|
+
|
|
799
|
+
for (let i = this._pool_available.length - 1; i >= 0; i--) {
|
|
800
|
+
const conn = this._pool_available[i];
|
|
801
|
+
if (conn._last_used && now - conn._last_used > idle_timeout) {
|
|
802
|
+
// 关闭并移除空闲连接
|
|
803
|
+
conn.close((err) => {
|
|
804
|
+
if (err) {
|
|
805
|
+
this.logger('error', '关闭空闲连接失败', err);
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// 从连接池中移除
|
|
810
|
+
const conn_index = this._pool_connections.indexOf(conn);
|
|
811
|
+
if (conn_index > -1) {
|
|
812
|
+
this._pool_connections.splice(conn_index, 1);
|
|
813
|
+
}
|
|
814
|
+
this._pool_available.splice(i, 1);
|
|
815
|
+
|
|
816
|
+
this.logger('debug', '回收空闲连接');
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* 从连接池获取连接
|
|
823
|
+
* @private
|
|
824
|
+
* @returns {Promise<Object>} 数据库连接
|
|
825
|
+
*/
|
|
826
|
+
Sqlite.prototype._getPoolConnection = async function () {
|
|
827
|
+
const acquire_timeout = this.config.pool_acquire_timeout;
|
|
828
|
+
const start_time = Date.now();
|
|
829
|
+
|
|
830
|
+
return new Promise((resolve, reject) => {
|
|
831
|
+
const tryGetConnection = () => {
|
|
832
|
+
// 检查超时
|
|
833
|
+
if (Date.now() - start_time > acquire_timeout) {
|
|
834
|
+
reject(new Error(`获取连接超时(${acquire_timeout}ms)`));
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// 检查是否有可用连接
|
|
839
|
+
if (this._pool_available.length > 0) {
|
|
840
|
+
const conn = this._pool_available.shift();
|
|
841
|
+
conn._last_used = Date.now();
|
|
842
|
+
resolve(conn);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// 检查是否可以创建新连接
|
|
847
|
+
if (this._pool_connections.length < this.config.pool_max) {
|
|
848
|
+
this._createConnection()
|
|
849
|
+
.then(conn => {
|
|
850
|
+
this._pool_connections.push(conn);
|
|
851
|
+
conn._last_used = Date.now();
|
|
852
|
+
resolve(conn);
|
|
853
|
+
})
|
|
854
|
+
.catch(reject);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// 没有可用连接,等待一段时间后重试
|
|
859
|
+
setTimeout(tryGetConnection, 100);
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
tryGetConnection();
|
|
863
|
+
});
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* 释放连接回连接池
|
|
868
|
+
* @private
|
|
869
|
+
* @param {Object} conn - 数据库连接
|
|
870
|
+
*/
|
|
871
|
+
Sqlite.prototype._releasePoolConnection = function (conn) {
|
|
872
|
+
if (this._pool_connections.includes(conn)) {
|
|
873
|
+
conn._last_used = Date.now();
|
|
874
|
+
this._pool_available.push(conn);
|
|
875
|
+
|
|
876
|
+
// 处理等待队列
|
|
877
|
+
if (this._pool_waiting.length > 0) {
|
|
878
|
+
const waiting = this._pool_waiting.shift();
|
|
879
|
+
waiting.resolve(conn);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* 关闭连接池
|
|
886
|
+
* @private
|
|
887
|
+
*/
|
|
888
|
+
Sqlite.prototype._closePool = function () {
|
|
889
|
+
if (this._pool_reaper) {
|
|
890
|
+
clearInterval(this._pool_reaper);
|
|
891
|
+
this._pool_reaper = null;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// 关闭所有连接
|
|
895
|
+
this._pool_connections.forEach(conn => {
|
|
896
|
+
try {
|
|
897
|
+
conn.close((err) => {
|
|
898
|
+
if (err) {
|
|
899
|
+
this.logger('error', '关闭连接池连接失败', err);
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
} catch (error) {
|
|
903
|
+
this.logger('error', '关闭连接池连接异常', error);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
this._pool_connections = [];
|
|
908
|
+
this._pool_available = [];
|
|
909
|
+
this._pool_waiting = [];
|
|
910
|
+
|
|
911
|
+
this.logger('debug', '连接池已关闭');
|
|
912
|
+
};
|
|
913
|
+
|
|
705
914
|
// 模块导出
|
|
706
915
|
exports.Sqlite = Sqlite;
|
|
707
916
|
exports.sqliteAdmin = sqliteAdmin;
|