mm_sqlite 1.3.2 → 1.3.4

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/sql.js ADDED
@@ -0,0 +1,1942 @@
1
+ /**
2
+ * @fileOverview sql语句帮助类函数
3
+ * @author <a href="http://qww.elins.cn">邱文武</a>
4
+ * @version 1.2
5
+ */
6
+ const SqlString = require('sqlstring');
7
+ const { Base } = require('mm_expand');
8
+
9
+ function escape(value, stringifyObjects, timeZone) {
10
+ return SqlString.escape(value, stringifyObjects, timeZone);
11
+ }
12
+
13
+ function escapeId(value, forbidQualified) {
14
+ return SqlString.escapeId(value, forbidQualified);
15
+ }
16
+
17
+ /**
18
+ * @class 数据库语法通用类
19
+ * @property {Function} filter 设置并过滤参数
20
+ */
21
+ class Sql extends Base {
22
+ /**
23
+ * @description 数据库管理器
24
+ * @param {Function} run 查询函数
25
+ * @param {Function} exec 更改函数
26
+ */
27
+ constructor(run, exec) {
28
+ super();
29
+ /**
30
+ * 查询函数
31
+ */
32
+ this.run = run;
33
+ /**
34
+ * 更改函数 用于增删改
35
+ */
36
+ this.exec = exec;
37
+ /**
38
+ * 规避SQL注入函数
39
+ * @param {object} value 值
40
+ * @returns {string} 返回执行结果
41
+ */
42
+ this.escape = function (value) {
43
+ return escape(value);
44
+ };
45
+
46
+ /**
47
+ * 规避排序、SQL注入函数
48
+ * @param {string} key 键
49
+ * @returns {string} 返回执行结果
50
+ */
51
+ this.escapeId = function (key) {
52
+ return escapeId(key);
53
+ };
54
+
55
+ /**
56
+ * sql语句
57
+ */
58
+ this.sql = '';
59
+ /**
60
+ * 错误提示
61
+ */
62
+ this.error;
63
+ /**
64
+ * 查询结果
65
+ */
66
+ this.results = [];
67
+
68
+ /**
69
+ * 表名
70
+ */
71
+ this.table = '';
72
+ /**
73
+ * 显示页
74
+ */
75
+ this.page = 0;
76
+ /**
77
+ * 显示条数
78
+ */
79
+ this.size = 30;
80
+ /**
81
+ * 请求方式 add、del、set、get、import、export等,跟函数名一致
82
+ */
83
+ this.method = '';
84
+
85
+ /**
86
+ * 显示的字段
87
+ */
88
+ this.field = '';
89
+
90
+ /**
91
+ * 排序方式
92
+ */
93
+ this.orderby = '';
94
+
95
+ /**
96
+ * 查询分组
97
+ */
98
+ this.groupby = '';
99
+
100
+ /**
101
+ * 是否统计查询结果数
102
+ */
103
+ this.count_ret = 'false';
104
+
105
+ /**
106
+ * 过滤查询参数字典
107
+ */
108
+ this.config = {
109
+ /**
110
+ * 分隔符 用于查询时的多条件处理
111
+ */
112
+ 'separator': '|',
113
+ /**
114
+ * 过滤
115
+ */
116
+ 'filter': {
117
+ /**
118
+ * 表名
119
+ */
120
+ 'table': 'table',
121
+ /**
122
+ * 查询的页码
123
+ */
124
+ 'page': 'page',
125
+ /**
126
+ * 查询每页条数
127
+ */
128
+ 'size': 'size',
129
+ /**
130
+ * 操作方式: 传入参数method=add, 支持参数 add增、del删、set改、get查,为空则为get
131
+ */
132
+ 'method': 'method',
133
+ /**
134
+ * 排序
135
+ */
136
+ 'orderby': 'orderby',
137
+ /**
138
+ * 查询显示的字段
139
+ */
140
+ 'field': 'field',
141
+ /**
142
+ * 统计结果: 统计符合条件的结果数,只有当page等于1或0时才会统计
143
+ */
144
+ 'count_ret': 'count_ret'
145
+ }
146
+ };
147
+
148
+ this.like = false;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * @description 清空查询条件
154
+ * @returns {object} 返回当前对象
155
+ */
156
+ Sql.prototype.clear = function () {
157
+ this.where = '';
158
+ this.set = '';
159
+ this.order = '';
160
+ this.view = '';
161
+ this.sql = '';
162
+ this.error = '';
163
+ this.like = true;
164
+ this.config = {};
165
+ this.param = {};
166
+ this.page = 0;
167
+ this.size = 0;
168
+ this.run = null;
169
+ this.exec = null;
170
+ return this;
171
+ };
172
+
173
+ /**
174
+ * @description 过滤查询参数
175
+ * @param {object} query 查询参数
176
+ */
177
+ Sql.prototype.filter = function (query) {
178
+ var m = this.config.filter;
179
+ for (var k in m) {
180
+ var key = m[k];
181
+ if (query[key]) {
182
+ this[k] = query[key];
183
+ delete query[key];
184
+ }
185
+ }
186
+ };
187
+
188
+ /**
189
+ * 查询条件拼接
190
+ * @param {string} where 查询条件
191
+ * @param {string} sort 排序
192
+ * @param {string} view 返回的字段
193
+ * @returns {string} 返回查询条件语句
194
+ */
195
+ Sql.prototype.toQuery = function (where, sort, view) {
196
+ if (!view) {
197
+ view = '*';
198
+ }
199
+
200
+ // 使用直接拼接的方式,避免模板替换错误
201
+ var sql = `SELECT ${view} FROM \`${this.table}\``;
202
+
203
+ if (where) {
204
+ sql += ' WHERE ' + where;
205
+ }
206
+ if (sort) {
207
+ sql += ' ORDER BY ' + sort.replace(/;/, '');
208
+ }
209
+ if (this.size && this.page) {
210
+ var start = this.size * (this.page - 1);
211
+ sql += ' limit ' + start + ',' + this.size;
212
+ }
213
+ return sql;
214
+ };
215
+ /* === 传字符串参数 === */
216
+ /**
217
+ * 增加数据
218
+ * @param {string} key 用作增加的键集合
219
+ * @param {string} val 用作增加的值集合
220
+ * @returns {Promise | object} 执行结果
221
+ */
222
+ Sql.prototype.addSql = function (key, val) {
223
+ // 使用直接拼接的方式,避免模板替换错误
224
+ var sql = `INSERT INTO \`${this.table}\` (${key}) VALUES (${val});`;
225
+ return this.exec(sql);
226
+ };/**
227
+ * 删除数据
228
+ * @param {string} where 删除条件
229
+ * @returns {Promise | object} 执行结果
230
+ */
231
+ Sql.prototype.delSql = function (where) {
232
+ // 使用直接拼接的方式,避免模板替换错误
233
+ var sql = `DELETE FROM \`${this.table}\` WHERE ${where};`;
234
+ return this.exec(sql);
235
+ };
236
+ /**
237
+ * 修改数据
238
+ * @param {string} where 查询条件
239
+ * @param {string} set 修改的键值
240
+ * @returns {Promise | object} 执行结果
241
+ */
242
+ Sql.prototype.setSql = function (where, set) {
243
+ // 使用直接拼接的方式,避免模板替换错误
244
+ var sql = `UPDATE \`${this.table}\` SET ${set} WHERE ${where};`;
245
+ return this.exec(sql);
246
+ };
247
+ /**
248
+ * @description 查询数据
249
+ * @param {string} where 查询条件
250
+ * @param {string} sort 排序
251
+ * @param {string} view 显示的字段
252
+ * @returns {Promise|Array} 查询结果数组
253
+ */
254
+ Sql.prototype.getSql = function (where, sort, view) {
255
+ var sql = this.toQuery(where, sort, view);
256
+ return this.run(sql);
257
+ };
258
+
259
+ /**
260
+ * @description 添加或修改
261
+ * @param {string} where 查询条件
262
+ * @param {string} set 修改的键值
263
+ * @returns {Promise | object} 执行结果
264
+ */
265
+ Sql.prototype.addOrSetSql = async function (where, set) {
266
+ if (!where || !set) {
267
+ return -1;
268
+ }
269
+ var count = await this.countSql(where);
270
+ if (count === 0) {
271
+ var arr = set.split(',');
272
+ var key = '';
273
+ var value = '';
274
+ for (var i = 0; i < arr.length; i++) {
275
+ var o = arr[i];
276
+ var ar = o.split('=');
277
+ if (ar.length === 2) {
278
+ key += ',' + ar[0];
279
+ value += ',' + ar[1];
280
+ }
281
+ }
282
+ return await this.addSql(key.replace(',', ''), value.replace(',', ''));
283
+ }
284
+ return await this.setSql(where, set);
285
+ };
286
+
287
+ /**
288
+ * @description 查询符合结果总数
289
+ * @param {string} where 查询条件
290
+ * @returns {Promise | number} 返回结果总数
291
+ */
292
+ Sql.prototype.countSql = async function (where) {
293
+ var sql = 'SELECT count(*) count FROM `' + this.table + '`';
294
+ if (where) {
295
+ sql += ' WHERE ' + where;
296
+ }
297
+ var n = 0;
298
+ var arr = await this.run(sql);
299
+ if (arr.length) {
300
+ n = arr[0].count;
301
+ }
302
+ return n;
303
+ };
304
+
305
+ /**
306
+ * @description 查询数据并返回符合条件总数
307
+ * @param {string} where 查询条件
308
+ * @param {string} sort 排序
309
+ * @param {string} view 返回的字段
310
+ * @returns {Promise | object} 查询到的内容列表和符合条件总数
311
+ */
312
+ Sql.prototype.getCountSql = async function (where, sort, view) {
313
+ var list = [];
314
+ var count = await this.countSql(where);
315
+ if (count > 0) {
316
+ list = await this.getSql(where, sort, view);
317
+ }
318
+ var ret = {
319
+ list: list,
320
+ count: count
321
+ };
322
+ return ret;
323
+ };
324
+
325
+ /**
326
+ * @description 统计学
327
+ * @param {string} where 查询条件
328
+ * @param {string} groupby 分组的字段
329
+ * @param {string} view 返回的字段
330
+ * @param {string} sort 排序方式
331
+ * @param method
332
+ * @returns {Promise | object} 查询到的内容列表和符合条件总数
333
+ */
334
+ Sql.prototype.groupMathSql = async function (where, groupby, view, sort, method) {
335
+ if (!view) {
336
+ view = '*';
337
+ }
338
+ var viewStr = '';
339
+ if (view.indexOf(',') !== -1) {
340
+ var arr = view.split(',');
341
+ for (var i = 0; i < arr.length; i++) {
342
+ var str = escapeId(arr[i]);
343
+ viewStr += ',' + method.toUpperCase() + '(' + str + ') ' + method.toLowerCase() + '_' + str.replace(
344
+ /`/g, '');
345
+ }
346
+ } else {
347
+ viewStr = ',' + method.toUpperCase() + '(' + escapeId(view) + ') ' + method.toLowerCase() + '_' +
348
+ view.replace(/`/g, '');
349
+ }
350
+ var sql = 'SELECT ' + (groupby ? escapeId(groupby) : '') + viewStr + ' FROM `' + this.table + '`';
351
+ if (where) {
352
+ sql += ' WHERE ' + where;
353
+ }
354
+ if (groupby) {
355
+ sql += ' GROUP BY ' + escapeId(groupby);
356
+ }
357
+ if (sort) {
358
+ sql += ' ORDER BY ' + sort;
359
+ }
360
+ if (this.size && this.page) {
361
+ var start = this.size * (this.page - 1);
362
+ sql += ' limit ' + start + ',' + this.size;
363
+ }
364
+ return await this.run(sql);
365
+ };
366
+
367
+
368
+ /**
369
+ * @description 分组求平均值
370
+ * @param {string} where 查询条件
371
+ * @param {string} groupby 分组的字段
372
+ * @param {string} view 返回的字段
373
+ * @param {string} sort 排序方式
374
+ * @returns {Promise | object} 查询到的内容列表和符合条件总数
375
+ */
376
+ Sql.prototype.groupAvgSql = async function (where, groupby, view, sort = '') {
377
+ return await this.groupMathSql(where, groupby, view, sort, 'AVG');
378
+ };
379
+
380
+ /**
381
+ * @description 分组合计数值
382
+ * @param {string} where 查询条件
383
+ * @param {string} groupby 分组的字段
384
+ * @param {string} view 返回的字段
385
+ * @param {string} sort 排序方式
386
+ * @returns {Promise | object} 查询到的内容列表和符合条件总数
387
+ */
388
+ Sql.prototype.groupSumSql = async function (where, groupby, view, sort = '') {
389
+ return await this.groupMathSql(where, groupby, view, sort, 'SUM');
390
+ };
391
+
392
+ /**
393
+ * @description 分组合计不同条数
394
+ * @param {string} where 查询条件
395
+ * @param {string} groupby 分组的字段
396
+ * @param {string} view 返回的字段
397
+ * @param sort
398
+ * @returns {Promise | object} 查询到的内容列表和符合条件总数
399
+ */
400
+ Sql.prototype.groupCountSql = async function (where, groupby, view, sort = '') {
401
+ return await this.groupMathSql(where, groupby, view, sort, 'COUNT');
402
+ };
403
+
404
+
405
+ /**
406
+ * @description 统计学
407
+ * @param {object} query 查询条件
408
+ * @param {string} groupby 分组的字段
409
+ * @param {string} view 返回的字段
410
+ * @param {string} sort 排序方式
411
+ * @param method
412
+ * @returns {Promise | object} 查询到的内容列表和符合条件总数
413
+ */
414
+ Sql.prototype.groupMath = async function (query, groupby, view, sort, method) {
415
+ var where = this.toWhere(query, this.like);
416
+ return await this.groupMathSql(where, groupby, view, sort, method);
417
+ };
418
+
419
+ /**
420
+ * @description 分组求平均值
421
+ * @param {object} query 查询条件
422
+ * @param {string} groupby 分组的字段
423
+ * @param {string} view 返回的字段
424
+ * @param {string} sort 排序方式
425
+ * @returns {Promise | object} 查询到的内容列表和符合条件总数
426
+ */
427
+ Sql.prototype.groupAvg = async function (query, groupby, view, sort) {
428
+ return await this.groupMath(query, groupby, view, sort, 'AVG');
429
+ };
430
+
431
+ /**
432
+ * @description 分组合计数值
433
+ * @param {object} query 查询条件
434
+ * @param {string} groupby 分组的字段
435
+ * @param {string} view 返回的字段
436
+ * @param {string} sort 排序方式
437
+ * @returns {Promise | object} 查询到的内容列表和符合条件总数
438
+ */
439
+ Sql.prototype.groupSum = async function (query, groupby, view, sort) {
440
+ return await this.groupMath(query, groupby, view, sort, 'SUM');
441
+ };
442
+
443
+ /**
444
+ * @description 分组合计不同条数
445
+ * @param {object} query 查询条件
446
+ * @param {string} groupby 分组的字段
447
+ * @param {string} view 返回的字段
448
+ * @param sort
449
+ * @returns {Promise | object} 查询到的内容列表和符合条件总数
450
+ */
451
+ Sql.prototype.groupCount = async function (query, groupby, view, sort) {
452
+ return await this.groupMath(query, groupby, view, sort, 'COUNT');
453
+ };
454
+
455
+ /* === 聚合函数 === */
456
+ /**
457
+ * @description 求和函数
458
+ * @param {string} field 字段名
459
+ * @param {object} query 查询条件
460
+ * @returns {number} 求和结果
461
+ */
462
+ Sql.prototype.sum = async function (field, query) {
463
+ if (!field || typeof field !== 'string') {
464
+ throw new TypeError('字段名必须为字符串');
465
+ }
466
+
467
+ if (!this.table) {
468
+ throw new TypeError('表名未设置');
469
+ }
470
+
471
+ const result = await this.groupSum(query, '', field, '');
472
+ const fieldKey = 'sum_' + field.replace(/`/g, '');
473
+ return result && result.length > 0 ? Number(result[0][fieldKey] || result[0].sum_age) : 0;
474
+ };
475
+
476
+ /**
477
+ * @description 最大值函数
478
+ * @param {string} field 字段名
479
+ * @param {object} query 查询条件
480
+ * @returns {number} 最大值结果
481
+ */
482
+ Sql.prototype.max = async function (field, query) {
483
+ if (!field || typeof field !== 'string') {
484
+ throw new TypeError('字段名必须为字符串');
485
+ }
486
+
487
+ if (!this.table) {
488
+ throw new TypeError('表名未设置');
489
+ }
490
+
491
+ const result = await this.groupMath(query, '', field, '', 'MAX');
492
+ const fieldKey = 'max_' + field.replace(/`/g, '');
493
+ return result && result.length > 0 ? Number(result[0][fieldKey] || result[0].max_age) : 0;
494
+ };
495
+
496
+ /**
497
+ * @description 最小值函数
498
+ * @param {string} field 字段名
499
+ * @param {object} query 查询条件
500
+ * @returns {number} 最小值结果
501
+ */
502
+ Sql.prototype.min = async function (field, query) {
503
+ if (!field || typeof field !== 'string') {
504
+ throw new TypeError('字段名必须为字符串');
505
+ }
506
+
507
+ if (!this.table) {
508
+ throw new TypeError('表名未设置');
509
+ }
510
+
511
+ const result = await this.groupMath(query, '', field, '', 'MIN');
512
+ const fieldKey = 'min_' + field.replace(/`/g, '');
513
+ return result && result.length > 0 ? Number(result[0][fieldKey] || result[0].min_age) : 0;
514
+ };
515
+
516
+ /**
517
+ * @description 平均值函数
518
+ * @param {string} field 字段名
519
+ * @param {object} query 查询条件
520
+ * @returns {number} 平均值结果
521
+ */
522
+ Sql.prototype.avg = async function (field, query) {
523
+ if (!field || typeof field !== 'string') {
524
+ throw new TypeError('字段名必须为字符串');
525
+ }
526
+
527
+ if (!this.table) {
528
+ throw new TypeError('表名未设置');
529
+ }
530
+
531
+ const result = await this.groupMath(query, '', field, '', 'AVG');
532
+ const fieldKey = 'avg_' + field.replace(/`/g, '');
533
+ return result && result.length > 0 ? Number(result[0][fieldKey] || result[0].avg_age) : 0;
534
+ };
535
+
536
+ /* === 字符串操作函数 === */
537
+ /**
538
+ * @description 字符串连接函数
539
+ * @param {...string} args 要连接的字符串参数
540
+ * @returns {string} 连接后的字符串
541
+ */
542
+ Sql.prototype.toStr = function (...args) {
543
+ // 如果没有参数,返回空字符串
544
+ if (args.length === 0) {
545
+ return '';
546
+ }
547
+
548
+ // 将所有参数转换为字符串并用空格连接
549
+ return args.map(arg => String(arg)).join(' ');
550
+ };
551
+
552
+ /* === 备份恢复函数 === */
553
+ /**
554
+ * @description 备份数据库到 SQL 文件
555
+ * @param {string} file 备份文件路径
556
+ * @returns {Promise<boolean>} 备份是否成功
557
+ */
558
+ Sql.prototype.backup = async function (file) {
559
+ if (!file || typeof file !== 'string') {
560
+ throw new TypeError('文件路径必须为字符串');
561
+ }
562
+
563
+ if (!this.table) {
564
+ throw new TypeError('表名未设置');
565
+ }
566
+
567
+ try {
568
+ // 获取所有数据
569
+ const data = await this.getSql('', '', '*');
570
+
571
+ if (!data || !Array.isArray(data)) {
572
+ throw new Error('获取数据失败');
573
+ }
574
+
575
+ // 构建 SQL 语句
576
+ let sqlContent = '';
577
+
578
+ // 添加表结构(简化版,实际应用中可能需要更复杂的表结构导出)
579
+ sqlContent += `-- 备份表: ${this.table}\n`;
580
+ sqlContent += `-- 备份时间: ${new Date().toISOString()}\n\n`;
581
+
582
+ // 添加数据插入语句
583
+ for (const row of data) {
584
+ const keys = Object.keys(row).map(key => this.escapeId(key)).join(', ');
585
+ const values = Object.values(row).map(value => this.escape(value)).join(', ');
586
+ sqlContent += `INSERT INTO \`${this.table}\` (${keys}) VALUES (${values});\n`;
587
+ }
588
+
589
+ // 写入文件(这里需要文件系统模块,实际使用时需要引入 fs)
590
+ const fs = require('fs');
591
+ const path = require('path');
592
+
593
+ // 确保目录存在
594
+ const dir = path.dirname(file);
595
+ if (!fs.existsSync(dir)) {
596
+ fs.mkdirSync(dir, { recursive: true });
597
+ }
598
+
599
+ fs.writeFileSync(file, sqlContent, 'utf8');
600
+
601
+ return true;
602
+ } catch (err) {
603
+ this.error = err.message;
604
+ this.log('error', '备份数据库失败', err);
605
+ return false;
606
+ }
607
+ };
608
+
609
+ /**
610
+ * @description 从 SQL 文件恢复数据库
611
+ * @param {string} file 恢复文件路径
612
+ * @returns {Promise<boolean>} 恢复是否成功
613
+ */
614
+ Sql.prototype.restore = async function (file) {
615
+ if (!file || typeof file !== 'string') {
616
+ throw new TypeError('文件路径必须为字符串');
617
+ }
618
+
619
+ if (!this.table) {
620
+ throw new TypeError('表名未设置');
621
+ }
622
+
623
+ try {
624
+ const fs = require('fs');
625
+
626
+ // 检查文件是否存在
627
+ if (!fs.existsSync(file)) {
628
+ throw new Error('备份文件不存在');
629
+ }
630
+
631
+ // 读取文件内容
632
+ const sqlContent = fs.readFileSync(file, 'utf8');
633
+
634
+ if (!sqlContent) {
635
+ throw new Error('备份文件为空');
636
+ }
637
+
638
+ // 解析 SQL 语句(按分号分割,但需要处理字符串中的分号)
639
+ const statements = this._parseSqlStatements(sqlContent);
640
+
641
+ // 执行所有 SQL 语句
642
+ for (const statement of statements) {
643
+ if (statement.trim()) {
644
+ // 跳过注释行
645
+ if (!statement.trim().startsWith('--')) {
646
+ await this.exec(statement);
647
+ }
648
+ }
649
+ }
650
+
651
+ return true;
652
+ } catch (err) {
653
+ this.error = err.message;
654
+ this.log('error', '恢复数据库失败', err);
655
+ return false;
656
+ }
657
+ };
658
+
659
+ /**
660
+ * @description 解析 SQL 语句,正确处理字符串中的分号
661
+ * @param {string} sql SQL 内容
662
+ * @returns {string[]} SQL 语句数组
663
+ * @private
664
+ */
665
+ Sql.prototype._parseSqlStatements = function (sql) {
666
+ const statements = [];
667
+ let currentStatement = '';
668
+ let inString = false;
669
+ let stringChar = '';
670
+
671
+ for (let i = 0; i < sql.length; i++) {
672
+ const char = sql[i];
673
+
674
+ if (char === "'" || char === '"') {
675
+ if (!inString) {
676
+ inString = true;
677
+ stringChar = char;
678
+ } else if (char === stringChar) {
679
+ // 检查是否是转义字符
680
+ if (i > 0 && sql[i - 1] === '\\') {
681
+ // 转义字符,继续字符串
682
+ } else {
683
+ inString = false;
684
+ stringChar = '';
685
+ }
686
+ }
687
+ }
688
+
689
+ if (!inString && char === ';') {
690
+ statements.push(currentStatement.trim());
691
+ currentStatement = '';
692
+ } else {
693
+ currentStatement += char;
694
+ }
695
+ }
696
+
697
+ // 添加最后一个语句(如果没有分号结尾)
698
+ if (currentStatement.trim()) {
699
+ statements.push(currentStatement.trim());
700
+ }
701
+
702
+ return statements.filter(stmt => stmt.length > 0);
703
+ };
704
+
705
+ /* === sql语句拼接函数 === */
706
+ /**
707
+ * @description 转为where语句
708
+ * @param {object} obj 用作拼接的对象
709
+ * @param {boolean} like 是否使用like匹配, 默认不使用
710
+ * @returns {string} where格式sql语句字符串
711
+ */
712
+ Sql.prototype.toWhere = function (obj, like) {
713
+ var where = '';
714
+ if (like === undefined) {
715
+ like = this.like;
716
+ }
717
+ if (like) {
718
+ for (var k in obj) {
719
+ var val = obj[k];
720
+ val = escape(val);
721
+ if (k.endsWith('_min')) {
722
+ where += ' and ' + escapeId(k.replace('_min', '')) + ' >= ' + val;
723
+ } else if (k.endsWith('_max')) {
724
+ where += ' and ' + escapeId(k.replace('_max', '')) + ' <= ' + val;
725
+ } else if (k.endsWith('_not')) {
726
+ where += ' and ' + escapeId(k.replace('_not', '')) + ' != ' + val;
727
+ } else if (k.endsWith('_has')) {
728
+ var vals = val.trim("'").split(',').map((o) => {
729
+ return escape(o);
730
+ });
731
+ where += ' and ' + escapeId(k.replace('_has', '')) + ' in (' + vals.join(',') + ')';
732
+ } else if (k.endsWith('_like')) {
733
+ where += ' and ' + escapeId(k.replace('_like', '')) + " LIKE '%" + val.trim("'") + "%'";
734
+ } else if (typeof (val) === 'string' && !/^[0-9]+$/.test(val)) {
735
+ where += ' and ' + escapeId(k) + " LIKE '%" + val.trim("'") + "%'";
736
+ } else {
737
+ where += ' and ' + escapeId(k) + ' = ' + val;
738
+ }
739
+ }
740
+ } else {
741
+ for (var k in obj) {
742
+ var val = obj[k];
743
+ val = escape(val);
744
+ if (k.endsWith('_min')) {
745
+ where += ' and ' + escapeId(k.replace('_min', '')) + ' >= ' + val;
746
+ } else if (k.endsWith('_max')) {
747
+ where += ' and ' + escapeId(k.replace('_max', '')) + ' <= ' + val;
748
+ } else if (k.endsWith('_not')) {
749
+ where += ' and ' + escapeId(k.replace('_not', '')) + ' != ' + val;
750
+ } else if (k.endsWith('_has')) {
751
+ var vals = val.trim("'").split(',').map((o) => {
752
+ return escape(o);
753
+ });
754
+ where += ' and ' + escapeId(k.replace('_has', '')) + ' in (' + vals.join(',') + ')';
755
+ } else {
756
+ where += ' and ' + escapeId(k) + ' = ' + val;
757
+ }
758
+ }
759
+ }
760
+ return where.replace(' and ', '');
761
+ };
762
+
763
+ /**
764
+ * @description 转为set语句
765
+ * @param {object} obj 用作拼接的对象
766
+ * @returns {string} set格式sql语句字符串
767
+ */
768
+ Sql.prototype.toSet = function (obj) {
769
+ var set = '';
770
+ for (var k in obj) {
771
+ var val = obj[k];
772
+ if (val === undefined || val === null) continue;
773
+ val = escape(val);
774
+
775
+ if (k.endsWith('_add')) {
776
+ var k2 = escapeId(k.replace('_add', ''));
777
+ set += ',' + k2 + ' = ' + k2 + ' + ' + val;
778
+ } else if (k.endsWith('_del')) {
779
+ var k3 = escapeId(k.replace('_del', ''));
780
+ set += ',' + k3 + ' = ' + k3 + ' - ' + val;
781
+ } else {
782
+ set += ',' + escapeId(k) + ' = ' + val;
783
+ }
784
+ }
785
+ return set.replace(',', '');
786
+ };
787
+
788
+ /**
789
+ * 转添加sql语句
790
+ * @param {object} item 用作添加的键值
791
+ * @returns {string} sql语句
792
+ */
793
+ Sql.prototype.toAddSql = function (item) {
794
+ if (!this.table || !item || typeof item !== 'object') {
795
+ throw new Error('表名或数据未设置');
796
+ }
797
+
798
+ let key = '';
799
+ let value = '';
800
+
801
+ for (const k in item) {
802
+ key += ',' + escapeId(k);
803
+ let val = item[k];
804
+ value += ',' + escape(val);
805
+ }
806
+
807
+ // 使用直接拼接的方式,避免模板替换错误
808
+ const sql = `INSERT INTO \`${this.table}\` (${key.replace(',', '')}) VALUES (${value.replace(',', '')});`;
809
+
810
+ return sql;
811
+ };
812
+
813
+ /**
814
+ * 转删除sql语句
815
+ * @param {object} query 查询键值
816
+ * @param {boolean} like 是否使用like匹配, 为空使用默认方式
817
+ * @returns {string} sql语句
818
+ */
819
+ Sql.prototype.toDelSql = function (query, like) {
820
+ if (!this.table) {
821
+ throw new Error('表名未设置');
822
+ }
823
+ const where = this.toWhere(query, like);
824
+
825
+ // 使用直接拼接的方式,避免模板替换错误
826
+ const sql = `DELETE FROM \`${this.table}\` WHERE ${where};`;
827
+
828
+ return sql;
829
+ };
830
+
831
+ /**
832
+ * 转修改sql语句
833
+ * @param {object} query 查询的键值集合
834
+ * @param {object} item 修改的键值集合
835
+ * @param {boolean} like 是否使用like匹配, 为空使用默认方式
836
+ * @returns {string} sql语句
837
+ */
838
+ Sql.prototype.toSetSql = function (query, item, like) {
839
+ if (!this.table) {
840
+ throw new Error('表名未设置');
841
+ }
842
+ const where = this.toWhere(query, like);
843
+ const set = this.toSet(item);
844
+
845
+ // 使用直接拼接的方式,避免模板替换错误
846
+ const sql = `UPDATE \`${this.table}\` SET ${set} WHERE ${where};`;
847
+
848
+ return sql;
849
+ };
850
+
851
+ /**
852
+ * @description 转查询sql语句
853
+ * @param {object} query 查询键值集合
854
+ * @param {string} sort 排序规则
855
+ * @param {string} view 显示的字段
856
+ * @param {boolean} like 是否使用like匹配, 为空使用默认方式
857
+ * @returns {string} sql语句
858
+ */
859
+ Sql.prototype.toGetSql = function (query, sort, view, like) {
860
+ var where = this.toWhere(query, like);
861
+ return this.toQuery(where, sort, view);
862
+ };
863
+ /* === 传入对象操作 === */
864
+ /**
865
+ * @description 增加数据
866
+ * @param {object} body 添加的对象
867
+ * @returns {Promise | object} 执行结果
868
+ */
869
+ Sql.prototype.add = async function (body) {
870
+ if (!this.table || !body || typeof body !== 'object') {
871
+ throw new Error('表名或数据未设置');
872
+ }
873
+ try {
874
+ var sql = this.toAddSql(body);
875
+ this.sql = sql;
876
+ var bl = await this.exec(sql);
877
+
878
+ return bl;
879
+ } catch (err) {
880
+ this.error = err.message;
881
+ this.log('error', '添加数据失败', err);
882
+ // 返回默认操作结果对象,保持返回值类型一致
883
+ return 0;
884
+ }
885
+ };
886
+ /**
887
+ * @description 删除数据
888
+ * @param {object} query 查询条件集合
889
+ * @param {boolean} like 是否使用like匹配, 为空使用默认方式
890
+ * @returns {Promise | object} 执行结果
891
+ */
892
+ Sql.prototype.del = async function (query, like) {
893
+ if (!this.table) {
894
+ throw new Error('表名未设置');
895
+ }
896
+ try {
897
+ var sql = this.toDelSql(query, like);
898
+ this.sql = sql;
899
+ var bl = await this.exec(sql);
900
+
901
+ return bl;
902
+ } catch (err) {
903
+ this.error = err.message;
904
+ this.log('error', '删除数据失败', err);
905
+ // 返回默认操作结果对象,保持返回值类型一致
906
+ return 0;
907
+ }
908
+ };
909
+
910
+ /**
911
+ * @description 修改数据
912
+ * @param {object} query 查询条件集合
913
+ * @param {object} body 修改的键值集合
914
+ * @param {boolean} like 是否使用like匹配, 为空使用默认方式
915
+ * @returns {Promise | object} 执行结果
916
+ */
917
+ Sql.prototype.set = async function (query, body, like) {
918
+ if (!this.table || !body || typeof body !== 'object') {
919
+ throw new Error('表名或数据未设置');
920
+ }
921
+ try {
922
+ var sql = this.toSetSql(query, body, like);
923
+ this.sql = sql;
924
+ var bl = await this.exec(sql);
925
+ return bl;
926
+ } catch (err) {
927
+ this.error = err.message;
928
+ this.log('error', '修改数据失败', err);
929
+ // 返回默认操作结果对象,保持返回值类型一致
930
+ return 0;
931
+ }
932
+ };
933
+
934
+ /**
935
+ * @description 查询数据
936
+ * @param {object} query 查询条件
937
+ * @param {string} sort 排序
938
+ * @param {string} view 返回的字段
939
+ * @param {boolean} like 是否使用like匹配, 为空使用默认方式
940
+ * @param {number} timeout 超时时间(毫秒),默认30000ms
941
+ * @returns {Promise|Array} 查询结果
942
+ */
943
+ Sql.prototype.get = async function (query, sort, view, like, timeout = 30000) {
944
+ if (!this.table) {
945
+ throw new Error('表名未设置');
946
+ }
947
+
948
+ try {
949
+ // 使用Promise.race实现超时控制
950
+ const timeout_promise = new Promise((unused_resolve, reject) => {
951
+ setTimeout(() => reject(new Error('查询操作超时')), timeout);
952
+ });
953
+
954
+ return await Promise.race([
955
+ (async () => {
956
+ // 生成SQL并执行
957
+ var sql = this.toGetSql(query, sort, view, like);
958
+ var list = await this.run(sql);
959
+ return list;
960
+ })(),
961
+ timeout_promise
962
+ ]);
963
+ } catch (err) {
964
+ this.error = err.message;
965
+ this.log('error', '查询数据失败', err);
966
+ // 返回空数组作为默认值,保持返回值类型一致
967
+ return [];
968
+ }
969
+ };
970
+
971
+ /**
972
+ * 添加或修改数据(存在则修改,不存在则添加)
973
+ * @param {object | string} where 查询条件
974
+ * @param {object | string} set 要设置的数据
975
+ * @param {boolean} like 是否使用like匹配, 为空使用默认方式
976
+ * @returns {Promise<object>} 执行结果
977
+ */
978
+ Sql.prototype.addOrSet = async function (where, set, like) {
979
+ if (!this.table || !where || !set) {
980
+ throw new Error('表名、条件或数据未设置');
981
+ }
982
+ try {
983
+ let where_str;
984
+
985
+ if (typeof where === 'object') {
986
+ where_str = await this.toWhere(where, like);
987
+ } else {
988
+ where_str = where;
989
+ }
990
+
991
+ const count = await this.countSql(whereStr);
992
+
993
+ if (count === 0) {
994
+ let key = '';
995
+ let value = '';
996
+
997
+ if (typeof set === 'string') {
998
+ const arr = set.split(',');
999
+ for (const o of arr) {
1000
+ const ar = o.split('=');
1001
+ if (ar.length === 2) {
1002
+ key += ',' + ar[0];
1003
+ value += ',' + ar[1];
1004
+ }
1005
+ }
1006
+ } else {
1007
+ for (const k in set) {
1008
+ key += ',' + this.escapeId(k);
1009
+ value += ',' + this.escape(set[k]);
1010
+ }
1011
+ }
1012
+
1013
+ const bl = await this.addSql(key.replace(',', ''), value.replace(',', ''));
1014
+
1015
+ return bl;
1016
+ } else {
1017
+ let set_vals = set;
1018
+ if (typeof set === 'object') {
1019
+ set_vals = await this.toSet(set);
1020
+ }
1021
+
1022
+ const bl1 = await this.setSql(where_str, set_vals);
1023
+
1024
+ return bl1;
1025
+ }
1026
+ } catch (err) {
1027
+ this.error = err.message;
1028
+ this.log('error', '添加或修改数据失败', err);
1029
+ // 返回默认操作结果对象,保持返回值类型一致
1030
+ return 0;
1031
+ }
1032
+ };
1033
+
1034
+ /**
1035
+ * 统计记录数
1036
+ * @param {object} query 查询条件
1037
+ * @param {boolean} like 是否使用like匹配, 为空使用默认方式
1038
+ * @param {number} timeout 超时时间(毫秒),默认30000ms
1039
+ * @returns {Promise | number} 记录数
1040
+ */
1041
+ Sql.prototype.count = async function (query, like, timeout = 30000) {
1042
+ if (!this.table) {
1043
+ throw new Error('表名未设置');
1044
+ }
1045
+
1046
+ try {
1047
+ // 添加超时控制
1048
+ const timeout_promise = new Promise((unused_resolve, reject) => {
1049
+ setTimeout(() => reject(new Error('统计操作超时')), timeout);
1050
+ });
1051
+
1052
+ return await Promise.race([
1053
+ (async () => {
1054
+ // 正确生成count SQL
1055
+ const where = typeof query === 'string' ? query : await this.toWhere(query, like);
1056
+ const sql = 'SELECT COUNT(*) as num FROM `' + this.table + '`' + (where ? ' WHERE ' + where : '');
1057
+ this.sql = sql;
1058
+ const list = await this.run(sql);
1059
+ const total = list && list[0] && list[0].num ? parseInt(list[0].num) : 0;
1060
+
1061
+ return total;
1062
+ })(),
1063
+ timeout_promise
1064
+ ]);
1065
+ } catch (err) {
1066
+ this.error = err.message;
1067
+ this.log('error', '统计记录数失败', err);
1068
+ // 返回0作为默认统计值,保持返回值类型一致
1069
+ return 0;
1070
+ }
1071
+ };
1072
+
1073
+ /**
1074
+ * 查询数据并返回符合条件总数
1075
+ * @param {object} query 查询条件
1076
+ * @param {boolean} like 是否使用like匹配, 为空使用默认方式
1077
+ * @param {number} timeout 超时时间(毫秒),默认30000ms
1078
+ * @returns {Promise<number>} 记录数
1079
+ */
1080
+ Sql.prototype.getCount = async function (query, like, timeout = 30000) {
1081
+ return await this.count(query, like, timeout);
1082
+ };
1083
+
1084
+ /* === 传入数组操作 === */
1085
+ /**
1086
+ * 添加多条数据
1087
+ * @param {Array} list 对象数组
1088
+ * @param {number} batch_size 批量大小,默认100
1089
+ * @param {number} timeout 超时时间,默认60000毫秒
1090
+ * @returns {Promise | object} 执行结果
1091
+ */
1092
+ Sql.prototype.addList = async function (list, batch_size = 100, timeout = 60000) {
1093
+ if (!this.table || !Array.isArray(list) || list.length === 0) {
1094
+ throw new Error('表名或数据列表未设置');
1095
+ }
1096
+ try {
1097
+ // 添加整体操作超时控制
1098
+ const timeout_promise = new Promise((unused_resolve, reject) => {
1099
+ setTimeout(() => reject(new Error('批量添加数据操作超时')), timeout);
1100
+ });
1101
+
1102
+ return await Promise.race([
1103
+ (async () => {
1104
+ // 如果数据量较小,直接处理
1105
+ if (list.length <= batch_size) {
1106
+ // 使用批量插入语法,不使用事务包装
1107
+ this.sql = this.toBatchAddSql(list);
1108
+ return await this.exec(this.sql);
1109
+ }
1110
+
1111
+ // 分批处理大数据量
1112
+ const total = Math.ceil(list.length / batch_size);
1113
+ $.log.info(`开始分批添加数据,共${total}批,每批${batch_size}条`);
1114
+
1115
+ // 分批执行,每批一个单独的批量插入语句
1116
+ let final_res = null;
1117
+ for (let i = 0; i < total; i++) {
1118
+ const batch = list.slice(i * batch_size, (i + 1) * batch_size);
1119
+ $.log.debug(`处理第${i + 1}/${total}批数据,${batch.length}条`);
1120
+
1121
+ // 生成批量插入SQL
1122
+ const batch_sql = this.toBatchAddSql(batch);
1123
+ this.sql = batch_sql;
1124
+
1125
+ // 为每批操作添加超时控制
1126
+ final_res = await Promise.race([
1127
+ this.exec(batch_sql),
1128
+ new Promise((unused_resolve, reject) => {
1129
+ setTimeout(() => reject(new Error(`第${i + 1}批数据处理超时`)), timeout / total);
1130
+ })
1131
+ ]);
1132
+ }
1133
+
1134
+ $.log.success(`批量添加数据完成,共${list.length}条`);
1135
+ return final_res;
1136
+ })(),
1137
+ timeout_promise
1138
+ ]);
1139
+ } catch (error) {
1140
+ this.log('error', '批量添加数据失败', error);
1141
+ // 返回默认操作结果对象,保持返回值类型一致
1142
+ return 0;
1143
+ }
1144
+ };
1145
+
1146
+ /**
1147
+ * 生成批量插入SQL语句
1148
+ * @param {Array} list 数据列表
1149
+ * @returns {string} 批量插入SQL语句
1150
+ */
1151
+ Sql.prototype.toBatchAddSql = function (list) {
1152
+ if (!this.table || !Array.isArray(list) || list.length === 0) {
1153
+ throw new Error('表名或数据列表未设置');
1154
+ }
1155
+
1156
+ // 获取第一个对象的键名作为列名
1157
+ const first_obj = list[0];
1158
+ if (!first_obj || typeof first_obj !== 'object') {
1159
+ throw new Error('数据格式错误');
1160
+ }
1161
+
1162
+ let keys = [];
1163
+ for (const k in first_obj) {
1164
+ keys.push(this.escapeId(k));
1165
+ }
1166
+
1167
+ // 构建列名部分
1168
+ const columns = keys.join(',');
1169
+
1170
+ // 构建值部分
1171
+ const values = [];
1172
+ for (const item of list) {
1173
+ const values = [];
1174
+ for (const k of keys) {
1175
+ const key = k.replace(/`/g, '');
1176
+ let val = item[key];
1177
+ values.push(this.escape(val));
1178
+ }
1179
+ values.push(`(${values.join(',')})`);
1180
+ }
1181
+
1182
+ // 生成最终SQL
1183
+ const sql = `INSERT INTO \`${this.table}\` (${columns}) VALUES ${values.join(',')}`;
1184
+ return sql;
1185
+ };
1186
+
1187
+ /**
1188
+ * 删除多条数据
1189
+ * @param {Array} list 对象数组
1190
+ * @param {boolean} like 是否使用like匹配, 为空使用默认方式
1191
+ * @param {number} batch_size 每批处理数量,默认100
1192
+ * @param {number} timeout 超时时间(毫秒),默认60000
1193
+ * @returns {Promise<object>} 执行结果
1194
+ */
1195
+ Sql.prototype.delList = async function (list, like, batch_size = 100, timeout = 60000) {
1196
+ if (!this.table || !Array.isArray(list) || list.length === 0) {
1197
+ throw new Error('表名或数据列表未设置');
1198
+ }
1199
+ try {
1200
+ // 添加整体操作超时控制
1201
+ const timeout_promise = new Promise((unused_resolve, reject) => {
1202
+ setTimeout(() => reject(new Error('批量删除数据操作超时')), timeout);
1203
+ });
1204
+
1205
+ return await Promise.race([
1206
+ (async () => {
1207
+ // 如果数据量较小,直接处理
1208
+ if (list.length <= batch_size) {
1209
+ let sql = '';
1210
+ for (const item of list) {
1211
+ sql += this.toDelSql(item.query, like);
1212
+ }
1213
+ this.sql = sql;
1214
+ return await this.exec(sql);
1215
+ }
1216
+
1217
+ // 分批处理大数据量
1218
+ const total = Math.ceil(list.length / batch_size);
1219
+ $.log.info(`开始分批删除数据,共${total}批,每批${batch_size}条`);
1220
+
1221
+ // 使用事务包装整个操作以保证原子性
1222
+ let final_res = null;
1223
+ try {
1224
+ // 开始事务
1225
+ await this.exec('BEGIN;', 15000);
1226
+
1227
+ // 分批处理
1228
+ for (let i = 0; i < total; i++) {
1229
+ const batch = list.slice(i * batch_size, (i + 1) * batch_size);
1230
+ $.log.debug(`处理第${i + 1}/${total}批数据,${batch.length}条`);
1231
+
1232
+ let batch_sql = '';
1233
+ for (const item of batch) {
1234
+ batch_sql += this.toDelSql(item.query, like);
1235
+ }
1236
+
1237
+ // 为每批操作添加超时控制
1238
+ final_res = await Promise.race([
1239
+ this.exec(batch_sql),
1240
+ new Promise((unused_resolve, reject) => {
1241
+ setTimeout(() => reject(new Error(`第${i + 1}批数据删除超时`)), timeout / total);
1242
+ })
1243
+ ]);
1244
+ }
1245
+
1246
+ // 提交事务
1247
+ await this.exec('COMMIT;', 15000);
1248
+ } catch (error) {
1249
+ // 发生错误时回滚事务
1250
+ try {
1251
+ await this.exec('ROLLBACK;', 15000);
1252
+ this.log('error', '批量删除数据失败,事务已回滚', error);
1253
+ } catch (rollback_err) {
1254
+ this.log('error', '批量删除数据失败,事务回滚也失败', rollback_err);
1255
+ }
1256
+ // 记录错误日志
1257
+ this.log('error', 'SQL语句执行失败', error);
1258
+ }
1259
+
1260
+ $.log.success(`批量删除数据完成,共${list.length}条`);
1261
+ return final_res;
1262
+ })(),
1263
+ timeout_promise
1264
+ ]);
1265
+ } catch (err) {
1266
+ this.log('error', '批量删除数据失败', err);
1267
+ // 返回默认操作结果对象,保持返回值类型一致
1268
+ return 0;
1269
+ }
1270
+ };
1271
+ /**
1272
+ * 更新多条数据
1273
+ * @param {Array} list 对象数组
1274
+ * @param {boolean} like 是否使用like匹配, 为空使用默认方式
1275
+ * @param {number} batch_size 每批处理数量,默认100
1276
+ * @param {number} timeout 超时时间(毫秒),默认60000
1277
+ * @returns {Promise<object>} 执行结果
1278
+ */
1279
+ Sql.prototype.setList = async function (list, like = false, batch_size = 100, timeout = 60000) {
1280
+ if (!this.table || !Array.isArray(list) || list.length === 0) {
1281
+ throw new Error('表名或数据列表未设置');
1282
+ }
1283
+ try {
1284
+ // 添加整体操作超时控制
1285
+ const timeout_promise = new Promise((unused_resolve, reject) => {
1286
+ setTimeout(() => reject(new Error('批量更新数据操作超时')), timeout);
1287
+ });
1288
+
1289
+ return await Promise.race([
1290
+ (async () => {
1291
+ // 如果数据量较小,直接处理
1292
+ if (list.length <= batch_size) {
1293
+ let sql = '';
1294
+ for (const item of list) {
1295
+ sql += this.toSetSql(item.query, item.obj, like);
1296
+ }
1297
+ this.sql = sql;
1298
+ return await this.exec(sql);
1299
+ }
1300
+
1301
+ // 分批处理大数据量
1302
+ const total = Math.ceil(list.length / batch_size);
1303
+ $.log.info(`开始分批更新数据,共${total}批,每批${batch_size}条`);
1304
+
1305
+ // 使用事务包装整个操作以保证原子性
1306
+ let final_res = null;
1307
+ try {
1308
+ // 开始事务
1309
+ await this.exec('BEGIN;', 15000);
1310
+
1311
+ // 分批处理
1312
+ for (let i = 0; i < total; i++) {
1313
+ const batch = list.slice(i * batch_size, (i + 1) * batch_size);
1314
+ $.log.debug(`处理第${i + 1}/${total}批数据,${batch.length}条`);
1315
+
1316
+ let batch_sql = '';
1317
+ for (const item of batch) {
1318
+ batch_sql += this.toSetSql(item.query, item.obj, like);
1319
+ }
1320
+
1321
+ // 为每批操作添加超时控制
1322
+ final_res = await Promise.race([
1323
+ this.exec(batch_sql),
1324
+ new Promise((unused_resolve, reject) => {
1325
+ setTimeout(() => reject(new Error(`第${i + 1}批数据更新超时`)), timeout / total);
1326
+ })
1327
+ ]);
1328
+ }
1329
+
1330
+ // 提交事务
1331
+ await this.exec('COMMIT;', 15000);
1332
+ } catch (error) {
1333
+ // 发生错误时回滚事务
1334
+ try {
1335
+ await this.exec('ROLLBACK;', 15000);
1336
+ this.log('error', '批量更新数据失败,事务已回滚', error);
1337
+ } catch (rollback_err) {
1338
+ this.log('error', '批量更新数据失败,事务回滚也失败', rollback_err);
1339
+ }
1340
+ }
1341
+
1342
+ $.log.success(`批量更新数据完成,共${list.length}条`);
1343
+ return final_res;
1344
+ })(),
1345
+ timeout_promise
1346
+ ]);
1347
+ } catch (err) {
1348
+ this.log('error', '批量更新数据失败', err);
1349
+ // 返回默认操作结果对象,保持返回值类型一致
1350
+ return 0;
1351
+ }
1352
+ };
1353
+
1354
+ /* 辅助类 */
1355
+ /**
1356
+ * 判断SQL模板是否包含某些参数
1357
+ * @param {object} param_dt 参数集合
1358
+ * @param {object} sql_dt sql模板集合
1359
+ * @returns {boolean} 有则返回true,没有则返回false
1360
+ */
1361
+ Sql.prototype.has = function (param_dt, sql_dt) {
1362
+ var bl = false;
1363
+ for (var key in sql_dt) {
1364
+ var value = param_dt[key];
1365
+ if (value !== undefined && value !== null && value !== '') {
1366
+ bl = true;
1367
+ break;
1368
+ }
1369
+ }
1370
+ return bl;
1371
+ };
1372
+
1373
+ /**
1374
+ * 判断某些参数是否没有SQL模板
1375
+ * @param {object} param_dt 参数集合
1376
+ * @param {object} sql_dt sql模板集合
1377
+ * @returns {string|undefined} 没有模板则返回名称,都有则返回undefined
1378
+ */
1379
+ Sql.prototype.noPrm = function (param_dt, sql_dt) {
1380
+ var name;
1381
+ for (var key in param_dt) {
1382
+ if (!sql_dt[key]) {
1383
+ name = key;
1384
+ break;
1385
+ }
1386
+ }
1387
+ return name;
1388
+ };
1389
+
1390
+ /**
1391
+ * 过滤参数,仅保留没有sql模板的参数
1392
+ * @param {object} param_dt 参数集合
1393
+ * @param {object} sql_dt sql模板集合
1394
+ * @returns {object} 返回过滤后的参数集合
1395
+ */
1396
+ Sql.prototype.filtPrm = function (param_dt, sql_dt) {
1397
+ var dt = [];
1398
+ for (var key in param_dt) {
1399
+ if (!sql_dt[key]) {
1400
+ dt.Add(key, o.Value);
1401
+ }
1402
+ }
1403
+ return dt;
1404
+ };
1405
+
1406
+ /**
1407
+ * 通过模板拼接查询参数
1408
+ * @param {object} param_dt 参数集合
1409
+ * @param {object} sql_dt 模板集合
1410
+ * @returns {string} 返回拼接的查询参数
1411
+ */
1412
+ Sql.prototype.tplQuery = function (param_dt, sql_dt) {
1413
+ var sql = '';
1414
+ if (sql_dt) {
1415
+ var l = this.config.separator;
1416
+ if (l) {
1417
+ for (var key in param_dt) {
1418
+ var value = param_dt[key] + '';
1419
+ var arr = value.split(l);
1420
+ var tpl = sql_dt[key];
1421
+ if (tpl) {
1422
+ if (arr.length > 1) {
1423
+ // 如果数量大于0,则增加多条件
1424
+ var sl = '(';
1425
+ var len = arr.length;
1426
+ for (var i = 0; i < len; i++) {
1427
+ // 使用直接替换方式,避免模板替换错误
1428
+ const escaped_val = this.escape(arr[i]);
1429
+ sl += ' || ' + tpl.replaceAll('{0}', escaped_val.trim("'"));
1430
+ }
1431
+ sl = sl.replace(' || ', '') + ')';
1432
+ sql += ' && ' + sl;
1433
+ } else {
1434
+ // 使用直接替换方式,避免模板替换错误
1435
+ const escaped_val = this.escape(value);
1436
+ sql += ' && ' + tpl.replaceAll('{0}', escaped_val.trim("'"));
1437
+ }
1438
+ } else {
1439
+ if (arr.length > 1) {
1440
+ // 如果数量大于0,则增加多条件
1441
+ var sl = '(';
1442
+ var len = arr.length;
1443
+ for (var i = 0; i < len; i++) {
1444
+ sl += ' || ' + this.escapeId(key) + ' = ' + this.escape(arr[i]);
1445
+ }
1446
+ sl = sl.replace(' || ', '') + ')';
1447
+ sql += ' && ' + sl;
1448
+ } else {
1449
+ sql += ' && ' + this.escapeId(key) + ' = ' + this.escape(value);
1450
+ }
1451
+ }
1452
+ }
1453
+ } else {
1454
+ for (var key in param_dt) {
1455
+ var value = this.escape(param_dt[key]);
1456
+ if (sql_dt[key]) {
1457
+ // 使用直接替换方式,避免模板替换错误
1458
+ sql += ' && ' + sql_dt[key].replaceAll('{0}', value.trim("'"));
1459
+ } else {
1460
+ sql += ' && ' + this.escapeId(key) + ' = ' + value;
1461
+ }
1462
+ }
1463
+ }
1464
+ } else {
1465
+ // 如果没有模板,则直接拼接参数
1466
+ var l = this.config.separator;
1467
+ if (l) {
1468
+ // 使用分隔数组拼接
1469
+ for (var key in param_dt) {
1470
+ var value = param_dt[key];
1471
+ var arr = value.split(l);
1472
+ if (arr.length > 1) {
1473
+ // 如果数量大于0,则增加多条件
1474
+ var sl = '(';
1475
+ var len = arr.length;
1476
+ for (var i = 0; i < len; i++) {
1477
+ sl += ' || ' + this.escapeId(key) + ' = ' + this.escape(arr[i]);
1478
+ }
1479
+ sl = sl.replace(' || ', '') + ')';
1480
+ sql += ' && ' + sl;
1481
+ } else {
1482
+ sql += ' && ' + this.escapeId(key) + ' = ' + this.escape(value);
1483
+ }
1484
+ }
1485
+ } else {
1486
+ // 直接拼接
1487
+ for (var key in param_dt) {
1488
+ sql += ' && ' + this.escapeId(key) + ' = ' + this.escape(param_dt[key]);
1489
+ }
1490
+ }
1491
+ }
1492
+ return sql.replace(' && ', '');
1493
+ };
1494
+
1495
+ /**
1496
+ * 通过模板拼接修改参数
1497
+ * @param {object} param_dt 参数集合
1498
+ * @param {object} sql_dt 模板集合
1499
+ * @returns {string} 返回拼接的查询参数
1500
+ */
1501
+ Sql.prototype.tplBody = function (param_dt, sql_dt) {
1502
+ var sql = '';
1503
+ if (!sql_dt || sql_dt.length === 0) {
1504
+ for (var key in param_dt) {
1505
+ sql += ' , ' + this.escapeId(key) + ' = ' + this.escape(val[key]);
1506
+ }
1507
+ } else {
1508
+ for (var key in param_dt) {
1509
+ var value = this.escape(param_dt[key]);
1510
+ if (sql_dt[key]) {
1511
+ // 使用直接替换方式,避免模板替换错误
1512
+ const replaced = sql_dt[key].replaceAll('{0}', value.trim("'"));
1513
+ sql += ' , ' + replaced.replace('+ -', '- ').replace('- -', '+ ');
1514
+ } else {
1515
+ sql += ' , ' + this.escapeId(key) + ' = ' + value;
1516
+ }
1517
+ }
1518
+ }
1519
+ return sql.replace(' , ', '');
1520
+ };
1521
+
1522
+ /**
1523
+ * 构建实体模型
1524
+ * @param {object} model 模型对象
1525
+ * @returns {object} 返回监听操作的对象
1526
+ */
1527
+ Sql.prototype.model = function (model) {
1528
+ var self = this;
1529
+ return new Proxy(model, {
1530
+ set: function (obj, prop, value) {
1531
+ var modified_obj = { ...obj };
1532
+ if (typeof (value) === 'number') {
1533
+ var n = obj[prop];
1534
+ var cha = value - n;
1535
+ if (cha > 0) {
1536
+ self.setSql('`' + self.key + '`=' + obj[self.key], '`' + prop + '`=`' +
1537
+ prop + '` + ' + cha);
1538
+ } else if (cha < 0) {
1539
+ self.setSql('`' + self.key + '`=' + obj[self.key], '`' + prop + '`=`' +
1540
+ prop + '` - ' + (-cha));
1541
+ } else {
1542
+ self.setSql('`' + self.key + '`=' + obj[self.key], '`' + prop + '`=' +
1543
+ value);
1544
+ }
1545
+ } else {
1546
+ var query = {};
1547
+ query[self.key] = obj[self.key];
1548
+ var set = {};
1549
+ set[prop] = value;
1550
+ self.set(query, set);
1551
+ }
1552
+ modified_obj[prop] = value;
1553
+ return modified_obj;
1554
+ }
1555
+ });
1556
+ };
1557
+
1558
+ /**
1559
+ * 查询单条数据
1560
+ * @param {object} query 查询条件
1561
+ * @param {string} sort 排序
1562
+ * @param {string} view 返回的字段
1563
+ * @param {boolean} like 是否使用like匹配, 为空使用默认方式
1564
+ * @param {number} timeout 超时时间(毫秒),默认30000ms
1565
+ * @returns {Promise | object | null} 查询结果
1566
+ */
1567
+ Sql.prototype.getObj = async function (query, sort, view, like, timeout = 30000) {
1568
+ try {
1569
+ // 保存当前分页设置
1570
+ const old_page = this.page;
1571
+ const size = this.size;
1572
+
1573
+ // 设置为只查询一条
1574
+ this.page = 1;
1575
+ this.size = 1;
1576
+
1577
+ // 使用优化后的get方法
1578
+ const list = await this.get(query, sort, view, like, timeout);
1579
+
1580
+ // 恢复分页设置
1581
+ this.page = old_page;
1582
+ this.size = size;
1583
+
1584
+ var obj = null;
1585
+ if (list.length > 0) {
1586
+ obj = list[0];
1587
+ if (this.key) {
1588
+ obj = this.model(obj);
1589
+ }
1590
+ }
1591
+
1592
+ return obj;
1593
+ } catch (err) {
1594
+ this.error = err.message;
1595
+ this.log('error', '查询单条数据失败', err);
1596
+ }
1597
+ return null;
1598
+ };
1599
+
1600
+ /**
1601
+ * 从SQL文件加载数据库
1602
+ * @param {string} file - SQL文件路径
1603
+ * @returns {Promise<boolean>} 是否加载成功
1604
+ */
1605
+ Sql.prototype.load = async function (file) {
1606
+ try {
1607
+ // 记录操作日志
1608
+ if (this.config && this.config.debug) {
1609
+ this.log('debug', `开始从文件加载数据库`, {
1610
+ file: file
1611
+ });
1612
+ }
1613
+
1614
+ // 检查文件是否存在
1615
+ if (!file.hasFile()) {
1616
+ throw new Error(`SQL文件不存在: ${file}`);
1617
+ }
1618
+
1619
+ // 读取SQL文件内容
1620
+ const sql_content = await $.file.readText(file);
1621
+
1622
+ if (!sql_content || sql_content.trim() === '') {
1623
+ throw new Error(`SQL文件内容为空: ${file}`);
1624
+ }
1625
+
1626
+ // 分割SQL语句(按分号分割,但需要注意处理字符串中的分号)
1627
+ const sql_stmts = this._splitSqlStatements(sql_content);
1628
+
1629
+ // 开始事务执行SQL语句
1630
+ await this.exec('BEGIN TRANSACTION');
1631
+
1632
+ try {
1633
+ // 逐个执行SQL语句
1634
+ for (const sql of sql_stmts) {
1635
+ const trim_sql = sql.trim();
1636
+ if (trim_sql) {
1637
+ await this.exec(trim_sql);
1638
+ }
1639
+ }
1640
+
1641
+ // 提交事务
1642
+ await this.exec('COMMIT');
1643
+
1644
+ if (this.config && this.config.debug) {
1645
+ this.log('info', `数据库加载成功`, {
1646
+ file: file,
1647
+ stmt_num: sql_stmts.length
1648
+ });
1649
+ }
1650
+
1651
+ return true;
1652
+ } catch (err) {
1653
+ // 回滚事务
1654
+ await this.exec('ROLLBACK').catch(rollback_err => {
1655
+ this.log('error', '事务回滚失败', rollback_err);
1656
+ });
1657
+
1658
+ // 记录错误日志
1659
+ this.log('error', 'SQL语句执行失败', err);
1660
+ }
1661
+ } catch (error) {
1662
+ // 记录错误日志
1663
+ this.log('error', '数据库加载失败', error);
1664
+ }
1665
+ };
1666
+
1667
+ /**
1668
+ * 分割SQL语句
1669
+ * @private
1670
+ * @param {string} sql_content - SQL内容
1671
+ * @returns {Array} SQL语句数组
1672
+ */
1673
+ Sql.prototype._splitSqlStatements = function (sql_content) {
1674
+ const stmts = [];
1675
+ let in_str = false;
1676
+ let str_char = '';
1677
+ let in_cmt = false;
1678
+ let cur_stmt = '';
1679
+
1680
+ for (let i = 0; i < sql_content.length; i++) {
1681
+ const char = sql_content[i];
1682
+ const next_char = i + 1 < sql_content.length ? sql_content[i + 1] : '';
1683
+
1684
+ // 处理注释
1685
+ if (!in_str && !in_cmt && char === '-' && next_char === '-') {
1686
+ in_cmt = true;
1687
+ i++; // 跳过第二个'-'
1688
+ continue;
1689
+ }
1690
+
1691
+ if (in_cmt && char === '\n') {
1692
+ in_cmt = false;
1693
+ continue;
1694
+ }
1695
+
1696
+ if (in_cmt) {
1697
+ continue;
1698
+ }
1699
+
1700
+ // 处理多行注释
1701
+ if (!in_str && !in_cmt && char === '/' && next_char === '*') {
1702
+ in_cmt = true;
1703
+ i++; // 跳过'*'
1704
+ continue;
1705
+ }
1706
+
1707
+ if (in_cmt && char === '*' && next_char === '/') {
1708
+ in_cmt = false;
1709
+ i++; // 跳过'/'
1710
+ continue;
1711
+ }
1712
+
1713
+ // 处理字符串
1714
+ if (!in_cmt && (char === "'" || char === '"') && (!in_str || str_char === char)) {
1715
+ // 检查是否是转义的引号
1716
+ let escaped = false;
1717
+ for (let j = i - 1; j >= 0; j--) {
1718
+ if (sql_content[j] === '\\') {
1719
+ escaped = !escaped;
1720
+ } else {
1721
+ break;
1722
+ }
1723
+ }
1724
+
1725
+ if (!escaped) {
1726
+ if (in_str && str_char === char) {
1727
+ in_str = false;
1728
+ str_char = '';
1729
+ } else if (!in_str) {
1730
+ in_str = true;
1731
+ str_char = char;
1732
+ }
1733
+ }
1734
+ }
1735
+
1736
+ // 分割语句
1737
+ if (!in_str && !in_cmt && char === ';') {
1738
+ stmts.push(cur_stmt.trim());
1739
+ cur_stmt = '';
1740
+ } else {
1741
+ cur_stmt += char;
1742
+ }
1743
+ }
1744
+
1745
+ // 添加最后一个语句(如果有)
1746
+ if (cur_stmt.trim()) {
1747
+ stmts.push(cur_stmt.trim());
1748
+ }
1749
+
1750
+ return stmts;
1751
+ };
1752
+
1753
+ /**
1754
+ * @description 获取创建表的 SQL 语句
1755
+ * @returns {Promise<string>} 创建表的 SQL 语句
1756
+ * @private
1757
+ */
1758
+ Sql.prototype._getCreateTableSql = async function () {
1759
+ try {
1760
+ // 查询表结构信息(SQLite 使用 sqlite_master 表)
1761
+ const sql = `SELECT sql FROM sqlite_master WHERE type='table' AND name='${this.table}'`;
1762
+ const result = await this.run(sql);
1763
+
1764
+ if (result && result.length > 0 && result[0].sql) {
1765
+ // 在创建表语句中添加 IF NOT EXISTS 条件
1766
+ let create_sql = result[0].sql;
1767
+ if (create_sql.toUpperCase().startsWith('CREATE TABLE')) {
1768
+ create_sql = create_sql.replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS');
1769
+ }
1770
+ return create_sql + ';';
1771
+ }
1772
+
1773
+ return '';
1774
+ } catch (err) {
1775
+ this.log('warn', '获取表结构失败,将仅备份数据', err);
1776
+ return '';
1777
+ }
1778
+ };
1779
+
1780
+ /**
1781
+ * @description 备份数据库到 SQL 文件
1782
+ * @param {string} file 备份文件路径
1783
+ * @param {boolean} create 是否导出创建表语句,默认为false
1784
+ * @returns {Promise<boolean>} 备份是否成功
1785
+ */
1786
+ Sql.prototype.backup = async function (file, create = false) {
1787
+ if (!file || typeof file !== 'string') {
1788
+ throw new TypeError('文件路径必须为字符串');
1789
+ }
1790
+
1791
+ if (!this.table) {
1792
+ throw new TypeError('表名未设置');
1793
+ }
1794
+
1795
+ try {
1796
+ // 获取所有数据
1797
+ const data = await this.getSql('', '', '*');
1798
+
1799
+ if (!data || !Array.isArray(data)) {
1800
+ throw new Error('获取数据失败');
1801
+ }
1802
+
1803
+ // 构建 SQL 语句
1804
+ let sql_content = '';
1805
+
1806
+ // 添加表结构(简化版,实际应用中可能需要更复杂的表结构导出)
1807
+ sql_content += `-- 备份表: ${this.table}\n`;
1808
+ sql_content += `-- 备份时间: ${new Date().toISOString()}\n\n`;
1809
+
1810
+ // 如果create为true,添加创建表语句
1811
+ if (create) {
1812
+ const create_table_sql = await this._getCreateTableSql();
1813
+ if (create_table_sql) {
1814
+ sql_content += create_table_sql + '\n\n';
1815
+ }
1816
+ }
1817
+
1818
+ // 添加数据插入语句
1819
+ for (const row of data) {
1820
+ const keys = Object.keys(row).map(key => this.escapeId(key)).join(', ');
1821
+ const values = Object.values(row).map(value => this.escape(value)).join(', ');
1822
+ sql_content += `INSERT INTO \`${this.table}\` (${keys}) VALUES (${values});\n`;
1823
+ }
1824
+
1825
+ // 写入文件(这里需要文件系统模块,实际使用时需要引入 fs)
1826
+ const fs = require('fs');
1827
+ const path = require('path');
1828
+
1829
+ // 确保目录存在
1830
+ const dir = path.dirname(file);
1831
+ if (!fs.existsSync(dir)) {
1832
+ fs.mkdirSync(dir, { recursive: true });
1833
+ }
1834
+
1835
+ fs.writeFileSync(file, sql_content, 'utf8');
1836
+
1837
+ return true;
1838
+ } catch (err) {
1839
+ this.error = err.message;
1840
+ this.log('error', '备份数据库失败', err);
1841
+ return false;
1842
+ }
1843
+ };
1844
+
1845
+ /**
1846
+ * @description 从 SQL 文件恢复数据库
1847
+ * @param {string} file 备份文件路径
1848
+ * @returns {Promise<boolean>} 恢复是否成功
1849
+ */
1850
+ Sql.prototype.restore = async function (file) {
1851
+ if (!file || typeof file !== 'string') {
1852
+ throw new TypeError('文件路径必须为字符串');
1853
+ }
1854
+
1855
+ if (!this.table) {
1856
+ throw new TypeError('表名未设置');
1857
+ }
1858
+
1859
+ try {
1860
+ const fs = require('fs');
1861
+
1862
+ if (!fs.existsSync(file)) {
1863
+ throw new Error('备份文件不存在');
1864
+ }
1865
+
1866
+ // 读取 SQL 文件
1867
+ const sql_content = fs.readFileSync(file, 'utf8');
1868
+
1869
+ // 解析 SQL 语句
1870
+ const stmts = this._parseSqlStatements(sql_content);
1871
+
1872
+ // 执行每个 SQL 语句
1873
+ for (const stmt of stmts) {
1874
+ if (stmt.trim() && !stmt.startsWith('--')) {
1875
+ await this.exec(stmt);
1876
+ }
1877
+ }
1878
+
1879
+ return true;
1880
+ } catch (err) {
1881
+ this.error = err.message;
1882
+ this.log('error', '恢复数据库失败', err);
1883
+ return false;
1884
+ }
1885
+ };
1886
+
1887
+ /**
1888
+ * @description 解析 SQL 语句
1889
+ * @param {string} sql SQL 内容
1890
+ * @returns {string[]} SQL 语句数组
1891
+ * @private
1892
+ */
1893
+ Sql.prototype._parseSqlStatements = function (sql) {
1894
+ const stmts = [];
1895
+ let cur_stmt = '';
1896
+ let in_str = false;
1897
+ let in_cmt = false;
1898
+ let quote_char = '';
1899
+
1900
+ for (let i = 0; i < sql.length; i++) {
1901
+ const char = sql[i];
1902
+ const next_char = sql[i + 1] || '';
1903
+
1904
+ // 处理字符串
1905
+ if (!in_cmt && (char === "'" || char === '"') && (i === 0 || sql[i - 1] !== '\\')) {
1906
+ if (!in_str) {
1907
+ in_str = true;
1908
+ quote_char = char;
1909
+ } else if (char === quote_char) {
1910
+ in_str = false;
1911
+ quote_char = '';
1912
+ }
1913
+ }
1914
+
1915
+ // 处理注释
1916
+ if (!in_str && char === '-' && next_char === '-') {
1917
+ in_cmt = true;
1918
+ }
1919
+ if (in_cmt && char === '\n') {
1920
+ in_cmt = false;
1921
+ }
1922
+
1923
+ // 分割语句
1924
+ if (!in_str && !in_cmt && char === ';') {
1925
+ stmts.push(cur_stmt.trim());
1926
+ cur_stmt = '';
1927
+ } else {
1928
+ cur_stmt += char;
1929
+ }
1930
+ }
1931
+
1932
+ // 添加最后一个语句(如果有)
1933
+ if (cur_stmt.trim()) {
1934
+ stmts.push(cur_stmt.trim());
1935
+ }
1936
+
1937
+ return stmts;
1938
+ };
1939
+
1940
+ module.exports = {
1941
+ Sql
1942
+ };