mm_dbs 0.0.0-stage → 1.0.0

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/lib/drive.js ADDED
@@ -0,0 +1,2347 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const Item = require('mm_corejs').Base;
4
+ const { string, array, file } = require('mm_helper');
5
+
6
+ /** 关键词搜索默认SQL模板(用户表关联查询) */
7
+ const KEYWORD_DEFAULT =
8
+ "`user_id` in (SELECT `user_id` FROM `user_account` WHERE `nickname` LIKE '%{0}%' OR `phone` LIKE '%{0}%' OR `wallet_address` LIKE '%{0}%')";
9
+
10
+ /** 关键词搜索默认提示 */
11
+ const KEYWORD_DEFAULT_TIP = '昵称、手机号、钱包地址';
12
+
13
+ /**
14
+ * 按目标类型转换源值(复刻 mm_expand 对象合并的类型转换规则)
15
+ * @param {*} target_val 目标值
16
+ * @param {*} source_val 源值
17
+ * @returns {*} 转换后的值
18
+ */
19
+ function _convertValue(target_val, source_val) {
20
+ var target_type = typeof target_val;
21
+ if (target_type === 'number') {
22
+ var num = Number(source_val);
23
+ return isNaN(num) ? 0 : num;
24
+ } else if (target_type === 'boolean') {
25
+ if (source_val === '0' || source_val === 'false' || source_val === 'False') {
26
+ return false;
27
+ } else if (source_val === '1' || source_val === 'true' || source_val === 'True') {
28
+ return true;
29
+ }
30
+ return Boolean(source_val);
31
+ } else if (target_type === 'string') {
32
+ return String(source_val);
33
+ }
34
+ return source_val;
35
+ }
36
+
37
+ /**
38
+ * 深度合并对象属性(替代原全局 push 深合并)
39
+ * @param {object} target 被添加的对象
40
+ * @param {object} source 用作添加的对象
41
+ * @param {boolean} supplement 是否补充目标没有的属性
42
+ * @returns {object} 修改后的目标对象
43
+ */
44
+ function _push(target, source, supplement) {
45
+ if (!target || !source || typeof target !== 'object' || typeof source !== 'object') {
46
+ return target;
47
+ }
48
+ for (var key in source) {
49
+ if (target.hasOwnProperty(key)) {
50
+ var target_val = target[key];
51
+ var source_val = source[key];
52
+ if (typeof target_val === 'object' && typeof source_val === 'object') {
53
+ _push(target_val, source_val, supplement);
54
+ } else if (typeof target_val === typeof source_val) {
55
+ target[key] = _convertValue(target_val, source_val);
56
+ }
57
+ } else if (supplement) {
58
+ target[key] = source[key];
59
+ }
60
+ }
61
+ return target;
62
+ }
63
+
64
+ /**
65
+ * 补全文件路径(相对路径基于当前工作目录或指定基准目录)
66
+ * @param {string} file 文件路径
67
+ * @param {string} dir 基准目录(可选)
68
+ * @returns {string} 完整路径
69
+ */
70
+ function _resolvePath(file, dir) {
71
+ if (!file || typeof file !== 'string') {
72
+ return process.cwd();
73
+ }
74
+ if (path.isAbsolute(file)) {
75
+ return file;
76
+ }
77
+ return path.resolve(dir || process.cwd(), file);
78
+ }
79
+
80
+ /**
81
+ * 对LIKE查询中的关键词进行转义, 防止通配符注入和性能攻击
82
+ * @param {string} keyword 原始关键词
83
+ * @returns {string} 转义后的安全关键词
84
+ */
85
+ var escapeLikeKeyword = function (keyword) {
86
+ if (typeof keyword !== 'string') {
87
+ return '';
88
+ }
89
+ return keyword
90
+ .replace(/\\/g, '\\\\')
91
+ .replace(/%/g, '\\%')
92
+ .replace(/_/g, '\\_');
93
+ };
94
+
95
+ /**
96
+ * db数据库开发驱动类
97
+ * @augments {Item}
98
+ * @class
99
+ */
100
+ class Drive extends Item {
101
+ static config = {
102
+ // 库表名称(唯一标识)
103
+ name: '',
104
+ // 标题(中文名)
105
+ title: '',
106
+ // 表名
107
+ table: '',
108
+ // 主键,用于实体模型
109
+ key: '',
110
+ // 字段
111
+ fields: [/* */],
112
+ index: [],
113
+ // 主程序文件 - 默认为空
114
+ main: '',
115
+ // 驱动目录(方案B:手动配置,替代目录扫描)
116
+ dir: '',
117
+ // 用户ID字段名(替代原全局字典中的 user_id 配置)
118
+ user_id: 'user_id',
119
+ // 事件接口管理器获取函数(可选注入,替代原全局 admin 事件查找)
120
+ get_event_api: null,
121
+ };
122
+
123
+ /**
124
+ * 构造函数
125
+ * @param {object} config 配置参数
126
+ * @param {object} parent 父对象(管理器实例)
127
+ * @class
128
+ */
129
+ constructor(config, parent) {
130
+ super({ ...Drive.config, ...config });
131
+
132
+ // 父级管理器引用(用于配置回退与插件查找)
133
+ this.parent = parent || null;
134
+
135
+ // 当前配置文件路径
136
+ this.config_file = '';
137
+
138
+ // 当前驱动目录
139
+ this._dir = '';
140
+
141
+ // 事件接口缺失告警去重标记
142
+ this._event_api_warned = false;
143
+
144
+ // 初始化查询字段配置(原由旧基类 setConfig 触发)
145
+ this._preset();
146
+ }
147
+ }
148
+
149
+ /**
150
+ * 重置配置参数
151
+ */
152
+ Drive.prototype._preset = function () {
153
+ this.query_string = [
154
+ 'name',
155
+ 'title',
156
+ 'keywords',
157
+ 'tag',
158
+ 'description',
159
+ 'content',
160
+ ];
161
+
162
+ // 是否设置数值类型为可查询
163
+ this.query_number = ['state', 'uin'];
164
+
165
+ // 是否设置数值类型为关键词可查
166
+ this.query_keyword = ['name', 'title', 'keywords', 'tag', 'description'];
167
+
168
+ // 是否设置以下字段为get查列表SQL时不可见
169
+ this.get_not = ['password', 'salt', 'content'];
170
+
171
+ // 是否设置以下字段为getObj查对象SQL时不可见
172
+ this.get_obj_not = ['password', 'salt', 'display'];
173
+
174
+ // 是否设置默认该表仅用户可访问
175
+ this.query_default_table = ['user'];
176
+ };
177
+
178
+ /**
179
+ * 解析字段类型
180
+ * @param {string} type 字段类型
181
+ * @returns {object} 解析结果
182
+ */
183
+ Drive.prototype._parseFieldType = function (type) {
184
+ var tp = string.left(type, '(', true);
185
+ var len = string.between(type, '(', ')');
186
+ var max = 0;
187
+ var decimal = 0;
188
+ if (len) {
189
+ max = Number(string.left(len, ',', true));
190
+ var d = string.right(len, ',');
191
+ if (d) {
192
+ decimal = Number(d);
193
+ }
194
+ }
195
+ return { tp, max, decimal };
196
+ };
197
+
198
+ /**
199
+ * 解析字段备注
200
+ * @param {string} note 字段备注
201
+ * @param {string} tp 字段类型
202
+ * @param {number} max_length 最大长度
203
+ * @returns {object} 解析结果
204
+ */
205
+ Drive.prototype._parseFieldNote = function (note, tp, max_length) {
206
+ var proc_note = note.replace(':', ':').replace('(', '(').replace(')', ')');
207
+ var title = string.left(proc_note, ':', true).trim();
208
+ var desc = string.right(proc_note, ':');
209
+ var description = '';
210
+ var map = '';
211
+ var min = 0;
212
+ var max = 0;
213
+ var local_max_length = max_length;
214
+
215
+ if (desc) {
216
+ var range = string.between(desc, '[', ']');
217
+ if (range) {
218
+ var min_str = string.left(range, ',', true);
219
+ if (min_str) {
220
+ min = Number(min_str);
221
+ }
222
+ var max_str = string.right(range, ',');
223
+ if (max_str) {
224
+ var n = Number(max_str);
225
+ if (tp === 'varchar') {
226
+ if (n < local_max_length) {
227
+ local_max_length = n;
228
+ }
229
+ } else {
230
+ max = n;
231
+ }
232
+ }
233
+ }
234
+ map = string.between(desc, '(', ')');
235
+ description = string.right(desc, ']', true).replace(`(${map})`, '').trim();
236
+ }
237
+ let min_length = 0;
238
+ if (min > 0) {
239
+ min_length = min;
240
+ }
241
+ return {
242
+ title,
243
+ description,
244
+ map,
245
+ min,
246
+ max,
247
+ min_length,
248
+ max_length: local_max_length,
249
+ };
250
+ };
251
+
252
+ /**
253
+ * 计算字段最大值
254
+ * @param {string} tp 字段类型
255
+ * @param {number} max 当前最大值
256
+ * @param {number} max_length 最大长度
257
+ * @returns {number} 计算后的最大值
258
+ */
259
+ Drive.prototype._calcMaxValue = function (tp, max, max_length) {
260
+ var local_max = max;
261
+ if (local_max === 0) {
262
+ switch (tp) {
263
+ case 'tinyint':
264
+ local_max = 1;
265
+ break;
266
+ case 'smallint':
267
+ local_max = 32767;
268
+ break;
269
+ case 'mediumint':
270
+ local_max = 8388607;
271
+ break;
272
+ case 'int':
273
+ local_max = 2147483647;
274
+ break;
275
+ case 'bigint':
276
+ local_max = 0;
277
+ break;
278
+ default:
279
+ break;
280
+ }
281
+ if (max_length) {
282
+ var num = Math.pow(10, max_length) - 1;
283
+ if (local_max > num) {
284
+ local_max = num;
285
+ }
286
+ }
287
+ }
288
+ return local_max;
289
+ };
290
+
291
+ /**
292
+ * 处理字段默认值
293
+ * @param {string} tp 字段类型
294
+ * @param {boolean} not_null 是否非空
295
+ * @param {*} default_val 默认值
296
+ * @returns {*} 处理后的默认值
297
+ */
298
+ Drive.prototype._handleDefaultValue = function (tp, not_null, default_val) {
299
+ if (not_null && !default_val) {
300
+ if (tp === 'varchar' || tp === 'text') {
301
+ return '';
302
+ } else if (tp === 'datetime' || tp === 'timestamp') {
303
+ return 'CURRENT_TIMESTAMP';
304
+ } else {
305
+ return '0';
306
+ }
307
+ }
308
+ return default_val;
309
+ };
310
+
311
+ /**
312
+ * 创建字段模型对象
313
+ * @param {string} name 字段名
314
+ * @param {string} title 字段标题
315
+ * @param {string} description 字段描述
316
+ * @param {string} tp 字段类型
317
+ * @param {boolean} pk 是否主键
318
+ * @param {boolean} auto 是否自动
319
+ * @param {boolean} not_null 是否非空
320
+ * @param {number} min_length 最小长度
321
+ * @param {number} max_length 最大长度
322
+ * @param {number} min 最小值
323
+ * @param {number} max 最大值
324
+ * @param {number} decimal 小数位
325
+ * @param {*} default_val 默认值
326
+ * @param {object} map 转换编排
327
+ * @returns {object} 字段模型对象
328
+ * @private
329
+ */
330
+ Drive.prototype._createFieldModel = function (
331
+ name,
332
+ title,
333
+ description,
334
+ tp,
335
+ pk,
336
+ auto,
337
+ not_null,
338
+ min_length,
339
+ max_length,
340
+ min,
341
+ max,
342
+ decimal,
343
+ default_val,
344
+ map,
345
+ ) {
346
+ return {
347
+ // 字段名
348
+ name: name,
349
+ // 字段标题
350
+ title: title,
351
+ // 字段描述
352
+ description: description,
353
+ // 字段类型 smallint短整数、mediumint中长整数、int整数、float浮点数、double双精度、tinyint二进制(0和1的布尔)、text文本、varchar字符串、datetime日期时间、date日期、time时间、timestamp时间戳
354
+ type: tp,
355
+ // 是否主键
356
+ key: pk,
357
+ // 自动
358
+ auto: auto,
359
+ // 是否含符号
360
+ symbol: tp === 'float' || tp === 'decimal',
361
+ // 是否填充零,用于数字类型
362
+ fill_zero: false,
363
+ // 非空
364
+ not_null: not_null,
365
+ // 最小长度
366
+ min_length: min_length,
367
+ // 最大长度
368
+ max_length: max_length,
369
+ // 最小值
370
+ min: min,
371
+ // 最大值
372
+ max: max,
373
+ // 小数位
374
+ decimal: decimal,
375
+ // 默认值
376
+ default: default_val,
377
+ // 转换编排
378
+ map: map,
379
+ };
380
+ };
381
+
382
+ /**
383
+ * 创建字段模型
384
+ * @param {object} fields 字段对象
385
+ * @param {string} fields.name 字段名称
386
+ * @param {object} fields.default 默认值
387
+ * @param {object} fields.notnull 是否非空
388
+ * @param {object} fields.type 字段类型
389
+ * @param {object} fields.pk 是否主键
390
+ * @param {object} fields.auto 是否自动增长
391
+ * @param {object} fields.note 字段备注
392
+ * @returns {object} 返回字段模型
393
+ */
394
+ Drive.prototype.model = function (fields) {
395
+ var { name, notnull, type, pk, note, auto } = fields;
396
+
397
+ var { tp, max: max_length, decimal } = this._parseFieldType(type);
398
+ var {
399
+ title,
400
+ description,
401
+ map,
402
+ min,
403
+ max: parsedMax,
404
+ min_length,
405
+ max_length: parsedMaxLength,
406
+ } = this._parseFieldNote(note, tp, max_length);
407
+
408
+ var max = this._calcMaxValue(tp, parsedMax, parsedMaxLength);
409
+
410
+ if (name.indexOf('_id') !== -1 || name == 'id') {
411
+ min = 0;
412
+ }
413
+
414
+ var not_null = notnull | pk | (tp !== 'varchar' && tp !== 'text');
415
+
416
+ var config = this.config;
417
+ if (pk && !config.key) {
418
+ config.key = name;
419
+ }
420
+
421
+ var default_value = this._handleDefaultValue(tp, not_null, fields.default);
422
+
423
+ return this._createFieldModel(
424
+ name,
425
+ title,
426
+ description,
427
+ tp,
428
+ pk,
429
+ auto,
430
+ not_null,
431
+ min_length,
432
+ parsedMaxLength,
433
+ min,
434
+ max,
435
+ decimal,
436
+ default_value,
437
+ map,
438
+ );
439
+ };
440
+
441
+ /**
442
+ * 创建索引字段模型
443
+ * @param {object} o 索引对象
444
+ * @property {string} o.Key_name 索引名称
445
+ * @property {string} o.Non_unique 是否唯一索引
446
+ * @property {string} o.Column_name 索引字段
447
+ * @property {string} o.Manager_comment 索引备注
448
+ * @returns {object} 返回索引模型
449
+ */
450
+ Drive.prototype.newIndexModel = function (o) {
451
+ return {
452
+ name: o.Key_name,
453
+ type: o.Non_unique ? 'unique' : 'index',
454
+ fields: o.Column_name.replace(/`/g, '').split(','),
455
+ comment: o.Manager_comment,
456
+ };
457
+ };
458
+
459
+ /**
460
+ * 从数据库更新索引配置
461
+ * @param {object} db 数据库管理器
462
+ * @returns {Array} 索引列表
463
+ */
464
+ Drive.prototype._updateFile = async function (db) {
465
+ var sql = 'SHOW INDEX FROM `' + this.config.table + '`';
466
+ var rows = await db.run(sql);
467
+ var dict = {};
468
+ for (var i = 0; i < rows.length; i++) {
469
+ var o = rows[i];
470
+ if (o.Key_name !== 'PRIMARY') {
471
+ if (!dict[o.Key_name]) {
472
+ dict[o.Key_name] = {
473
+ columnName: [],
474
+ };
475
+ }
476
+ dict[o.Key_name].Column_name.push(o.Column_name);
477
+ }
478
+ }
479
+ var list = [];
480
+ for (var k in dict) {
481
+ var m = this.newIndexModel(dict[k]);
482
+ list.push(m);
483
+ }
484
+
485
+ return list;
486
+ };
487
+
488
+ /**
489
+ * 从数据库更新配置
490
+ * @param {object} db 数据库管理器
491
+ * @param {boolean} cover 是否覆盖文件
492
+ */
493
+ Drive.prototype.updateFile = async function (db, cover) {
494
+ var cg = this.config;
495
+ var list = [];
496
+
497
+ // 查询表注释并修改
498
+ var db_name = db.database().replace(/[^a-zA-Z0-9_]/g, '');
499
+ var sql =
500
+ "SELECT TABLE_NAME, TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '" +
501
+ db_name +
502
+ "' && TABLE_NAME = '" +
503
+ cg.table +
504
+ "';";
505
+
506
+ var lt = await db.run(sql);
507
+ if (lt && lt.length > 0) {
508
+ var commit = lt[0].TABLE_COMMENT;
509
+ var arr = commit.replace(':', ':').split(':');
510
+ cg.title = arr[0];
511
+ if (arr.length > 1) {
512
+ cg.description = arr[1];
513
+ }
514
+ }
515
+
516
+ // 设置表名
517
+ db.table = cg.table + '';
518
+
519
+ // 获取所有字段
520
+ var fields = await db.fields();
521
+ for (var i = 0; i < fields.length; i++) {
522
+ var field = this.model(fields[i]);
523
+ list.push(field);
524
+ }
525
+ cg.fields = list;
526
+ cg.index = await this._updateFile(db);
527
+ if (!cg.name) {
528
+ cg.name = cg.table;
529
+ }
530
+ await this.updateApp(cover);
531
+ };
532
+
533
+ /**
534
+ * 更新索引
535
+ * @param {object} db 数据库管理器
536
+ */
537
+ Drive.prototype._updateDb = async function (db) {
538
+ var cg = this.config;
539
+ let index = cg.index || [];
540
+ // 更新索引
541
+ if (index) {
542
+ var len = index.length;
543
+ for (var i = 0; i < len; i++) {
544
+ var o = index[i];
545
+ var sql_start = '';
546
+ if (o.type === 'unique') {
547
+ sql_start = 'CREATE UNIQUE INDEX `';
548
+ } else {
549
+ sql_start = 'CREATE INDEX `';
550
+ }
551
+ await db.exec(
552
+ sql_start + o.name + '` ON `' + cg.table + '` (' + o.fields + ');',
553
+ );
554
+ }
555
+ }
556
+ };
557
+
558
+ /**
559
+ * 初始化数据表
560
+ * @param {object} db 数据库管理器
561
+ * @param {object} cg 配置对象
562
+ * @param {Array} list 字段列表
563
+ * @returns {Array} 字段列表
564
+ */
565
+ Drive.prototype._initTable = async function (db, cg, list) {
566
+ var fields = await db.fields();
567
+ if (fields.length === 0) {
568
+ var k = cg.key;
569
+ var len = list.length;
570
+ for (var i = 0; i < len; i++) {
571
+ var o = list[i];
572
+ if (k === o.name) {
573
+ await db.addTable(
574
+ cg.table,
575
+ o.name,
576
+ o.type,
577
+ o.auto,
578
+ cg.title + ':' + cg.description,
579
+ );
580
+ fields.push({
581
+ name: o.name,
582
+ });
583
+ break;
584
+ }
585
+ }
586
+ } else {
587
+ var commit = cg.title + ':' + cg.description;
588
+ var sql = "alter table `{0}` comment '{1}';"
589
+ .replace('{0}', cg.table)
590
+ .replace('{1}', commit);
591
+ await db.exec(sql);
592
+ }
593
+ return fields;
594
+ };
595
+
596
+ /**
597
+ * 删除配置中没有的字段
598
+ * @param {object} db 数据库管理器
599
+ * @param {Array} fields 数据库字段列表
600
+ * @param {Array} list 配置字段列表
601
+ */
602
+ Drive.prototype._deleteUnusedFields = async function (db, fields, list) {
603
+ for (var i = 0; i < fields.length; i++) {
604
+ var o = fields[i];
605
+ var obj = array.getObj(list, {
606
+ name: o.name,
607
+ });
608
+
609
+ if (!obj) {
610
+ await db.fieldDel(o.name);
611
+ }
612
+ }
613
+ };
614
+
615
+ /**
616
+ * 生成字段SQL
617
+ * @param {object} o 字段对象
618
+ * @returns {string} 字段SQL
619
+ */
620
+ Drive.prototype._generateFieldSql = function (o) {
621
+ var type = this._getTypeSql(o);
622
+ var notnull = this._getNotNullSql(o);
623
+ var value = this._getValueSql(o);
624
+ var note = this._getNoteSql(o);
625
+ return this._buildFieldSql(o.name, type, notnull, value, note);
626
+ };
627
+
628
+ /**
629
+ * 获取类型SQL
630
+ * @param {object} o 字段对象
631
+ * @returns {string} 类型SQL
632
+ * @private
633
+ */
634
+ Drive.prototype._getTypeSql = function (o) {
635
+ var type = o.type;
636
+ var max_len = o.max_length || 0;
637
+
638
+ if (max_len > 0) {
639
+ // decimal仅对浮点类型有效(int/smallint/bigint等使用decimal=0时不追加)
640
+ var is_float_type =
641
+ o.type.indexOf('float') !== -1 ||
642
+ o.type.indexOf('double') !== -1 ||
643
+ o.type.indexOf('decimal') !== -1;
644
+ if (is_float_type && o.decimal !== undefined && o.decimal !== null) {
645
+ type += '(' + max_len + ',' + o.decimal + ')';
646
+ } else {
647
+ type += '(' + max_len + ')';
648
+ }
649
+ }
650
+
651
+ // 类型未包含unsigned且为非字符/日期类型时追加UNSIGNED
652
+ if (
653
+ !o.symbol &&
654
+ type.indexOf('unsigned') === -1 &&
655
+ o.type !== 'varchar' &&
656
+ o.type !== 'longtext' &&
657
+ o.type !== 'text' &&
658
+ o.type !== 'date' &&
659
+ o.type !== 'time' &&
660
+ o.type !== 'datetime' &&
661
+ o.type !== 'timestamp'
662
+ ) {
663
+ type += ' UNSIGNED';
664
+ }
665
+
666
+ return type;
667
+ };
668
+
669
+ /**
670
+ * 获取NOT NULL SQL
671
+ * @param {object} o 字段对象
672
+ * @returns {string} NOT NULL SQL
673
+ * @private
674
+ */
675
+ Drive.prototype._getNotNullSql = function (o) {
676
+ var notnull = '';
677
+
678
+ if (this._isNotNullRequired(o)) {
679
+ notnull = 'NOT NULL';
680
+ }
681
+
682
+ if (this._isAutoInc(o)) {
683
+ notnull += ' AUTO_INCREMENT';
684
+ }
685
+
686
+ return notnull;
687
+ };
688
+
689
+ /**
690
+ * 检查是否需要NOT NULL
691
+ * @param {object} o 字段对象
692
+ * @returns {boolean} 是否需要NOT NULL
693
+ * @private
694
+ */
695
+ Drive.prototype._isNotNullRequired = function (o) {
696
+ return (
697
+ o.not_null || this._isDateTimeType(o.type) || this._isNumericType(o.type)
698
+ );
699
+ };
700
+
701
+ /**
702
+ * 检查是否为日期时间类型
703
+ * @param {string} type 类型
704
+ * @returns {boolean} 是否为日期时间类型
705
+ * @private
706
+ */
707
+ Drive.prototype._isDateTimeType = function (type) {
708
+ return (
709
+ type === 'date' ||
710
+ type === 'time' ||
711
+ type === 'datetime' ||
712
+ type === 'timestamp'
713
+ );
714
+ };
715
+
716
+ /**
717
+ * 检查是否为数字类型
718
+ * @param {string} type 类型
719
+ * @returns {boolean} 是否为数字类型
720
+ * @private
721
+ */
722
+ Drive.prototype._isNumericType = function (type) {
723
+ return (
724
+ type !== 'varchar' &&
725
+ type !== 'longtext' &&
726
+ type !== 'text' &&
727
+ !this._isDateTimeType(type)
728
+ );
729
+ };
730
+
731
+ /**
732
+ * 检查是否需要AUTO_INCREMENT
733
+ * @param {object} o 字段对象
734
+ * @returns {boolean} 是否需要AUTO_INCREMENT
735
+ * @private
736
+ */
737
+ Drive.prototype._isAutoInc = function (o) {
738
+ return !!o.auto && !this._isDateTimeType(o.type);
739
+ };
740
+
741
+ /**
742
+ * 获取默认值SQL
743
+ * @param {object} o 字段对象
744
+ * @returns {string} 默认值SQL
745
+ * @private
746
+ */
747
+ Drive.prototype._getValueSql = function (o) {
748
+ if (this._isAutoTime(o)) {
749
+ return 'DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP';
750
+ }
751
+
752
+ if (this._isAutoInc(o)) {
753
+ return '';
754
+ }
755
+
756
+ if (o.default === null) {
757
+ // NOT NULL 列不允许 DEFAULT NULL(MySQL 语法错误),此时省略默认值
758
+ return this._isNotNullRequired(o) ? '' : 'DEFAULT NULL';
759
+ }
760
+
761
+ if (o.default !== undefined) {
762
+ return this._getDefaultValueSql(o);
763
+ }
764
+
765
+ return this._getDefaultEmptySql(o);
766
+ };
767
+
768
+ /**
769
+ * 检查是否为自动时间
770
+ * @param {object} o 字段对象
771
+ * @returns {boolean} 是否为自动时间
772
+ * @private
773
+ */
774
+ Drive.prototype._isAutoTime = function (o) {
775
+ return !!o.auto && this._isDateTimeType(o.type);
776
+ };
777
+
778
+ /**
779
+ * 获取默认值SQL
780
+ * @param {object} o 字段对象
781
+ * @returns {string} 默认值SQL
782
+ * @private
783
+ */
784
+ Drive.prototype._getDefaultValueSql = function (o) {
785
+ if (this._isCurrentDefault(o)) {
786
+ return 'DEFAULT ' + o.default;
787
+ } else if (this._isQuotableType(o.type)) {
788
+ return "DEFAULT '" + o.default + "'";
789
+ } else {
790
+ return 'DEFAULT ' + o.default;
791
+ }
792
+ };
793
+
794
+ /**
795
+ * 检查是否为当前时间默认值
796
+ * @param {object} o 字段对象
797
+ * @returns {boolean} 是否为当前时间默认值
798
+ * @private
799
+ */
800
+ Drive.prototype._isCurrentDefault = function (o) {
801
+ return (
802
+ this._isDateTimeType(o.type) &&
803
+ o.default &&
804
+ o.default.indexOf('CURR') !== -1
805
+ );
806
+ };
807
+
808
+ /**
809
+ * 检查默认值是否需要引号包裹(字符类型与日期时间类型)
810
+ * @param {string} type 类型
811
+ * @returns {boolean} 需要引号返回true,否则返回false
812
+ * @private
813
+ */
814
+ Drive.prototype._isQuotableType = function (type) {
815
+ return (
816
+ type === 'varchar' ||
817
+ type === 'longtext' ||
818
+ type === 'text' ||
819
+ type === 'date' ||
820
+ type === 'time' ||
821
+ type === 'datetime' ||
822
+ type === 'timestamp'
823
+ );
824
+ };
825
+
826
+ /**
827
+ * 获取空默认值SQL
828
+ * @param {object} o 字段对象
829
+ * @returns {string} 空默认值SQL
830
+ * @private
831
+ */
832
+ Drive.prototype._getDefaultEmptySql = function (o) {
833
+ if (this._isStringType(o.type)) {
834
+ return "DEFAULT ''";
835
+ } else {
836
+ return 'DEFAULT 0';
837
+ }
838
+ };
839
+
840
+ /**
841
+ * 获取注释SQL
842
+ * @param {object} o 字段对象
843
+ * @returns {string} 注释SQL
844
+ * @private
845
+ */
846
+ Drive.prototype._getNoteSql = function (o) {
847
+ var note = (o.title || '') + ':';
848
+
849
+ if (o.type === 'varchar' || o.type === 'text' || o.type === 'longtext') {
850
+ if (o.max_length) {
851
+ note += '[' + (o.min_length || '0') + ',' + o.max_length + ']';
852
+ } else if (o.min_length) {
853
+ note += '[' + o.min_length + ']';
854
+ }
855
+ } else {
856
+ if (o.max) {
857
+ note += '[' + (o.min || '0') + ',' + o.max + ']';
858
+ } else if (o.min) {
859
+ note += '[' + o.min + ']';
860
+ }
861
+ }
862
+
863
+ note += o.description || '';
864
+ if (o.map) {
865
+ note += '(' + o.map + ')';
866
+ }
867
+
868
+ return note;
869
+ };
870
+
871
+ /**
872
+ * 构建字段SQL
873
+ * @param {string} name 字段名
874
+ * @param {string} type 类型
875
+ * @param {string} notnull NOT NULL
876
+ * @param {string} value 默认值
877
+ * @param {string} note 注释
878
+ * @returns {string} 字段SQL
879
+ * @private
880
+ */
881
+ Drive.prototype._buildFieldSql = function (name, type, notnull, value, note) {
882
+ var sql = "`{1}` {2} {3} {4} COMMENT '{5}'";
883
+ return sql
884
+ .replace('{1}', name)
885
+ .replace('{2}', type)
886
+ .replace('{3}', notnull)
887
+ .replace('{4}', value)
888
+ .replace('{5}', note);
889
+ };
890
+
891
+ /**
892
+ * 添加或修改字段
893
+ * @param {object} db 数据库管理器
894
+ * @param {string} table 表名
895
+ * @param {object} o 字段对象
896
+ * @param {Array} fields 数据库字段列表
897
+ */
898
+ Drive.prototype._addOrUpdateField = async function (db, table, o, fields) {
899
+ var arr = fields.filter((f) => {
900
+ return f.name === o.name;
901
+ });
902
+ var sql = this._generateFieldSql(o);
903
+
904
+ if (arr.length === 0) {
905
+ // 如果没有则添加
906
+ await db.exec('alter table `{0}` add '.replace('{0}', table) + sql);
907
+ } else {
908
+ // 如果有则修改
909
+ await db.exec(
910
+ 'alter table `{0}` change `{1}` '
911
+ .replace('{0}', table)
912
+ .replace('{1}', o.name) + sql,
913
+ );
914
+ }
915
+ };
916
+
917
+ /**
918
+ * 通过配置更新数据库
919
+ * @param {object} db 数据库管理器
920
+ * @returns {string} 更新成功返回空,否则返回错误提示
921
+ */
922
+ Drive.prototype.updateDb = async function (db) {
923
+ var cg = this.config;
924
+ var table = cg.table + '';
925
+ var list = cg.fields;
926
+ db.table = table;
927
+ var fields = await this._initTable(db, cg, list);
928
+
929
+ if (fields.length > 0) {
930
+ // 删除配置中没有的字段
931
+ await this._deleteUnusedFields(db, fields, list);
932
+
933
+ // 添加或修改配置
934
+ var len = list.length;
935
+ for (var i = 0; i < len; i++) {
936
+ var o = list[i];
937
+ await this._addOrUpdateField(db, table, o, fields);
938
+ }
939
+ } else {
940
+ return '数据表更新失败';
941
+ }
942
+ };
943
+
944
+ /**
945
+ * 确定目录结构
946
+ * @param {object} cg 配置对象
947
+ * @returns {object} 目录信息
948
+ * @private
949
+ */
950
+ Drive.prototype._deteDir = function (cg) {
951
+ var arr = cg.table.split('_');
952
+ var scope = arr[0];
953
+ var p = _resolvePath('./app/');
954
+ var dir = _resolvePath('./' + scope, p);
955
+ var dir_api = _resolvePath('./plugin/server', dir);
956
+ var db_dir = _resolvePath('./db', dir_api);
957
+ var config_file = _resolvePath(
958
+ './' + cg.table.replace(scope + '_', '') + '.db.json',
959
+ db_dir,
960
+ );
961
+
962
+ return {
963
+ scope,
964
+ dir,
965
+ dir_api,
966
+ db_dir,
967
+ config_file,
968
+ };
969
+ };
970
+
971
+ /**
972
+ * 确保目录存在
973
+ * @param {string} dir 目录路径
974
+ * @private
975
+ */
976
+ Drive.prototype._ensureDir = function (dir) {
977
+ if (!fs.existsSync(dir)) {
978
+ fs.mkdirSync(dir);
979
+ }
980
+ };
981
+
982
+ /**
983
+ * 保存当前配置到配置文件(替代旧基类 save)
984
+ * @returns {void}
985
+ */
986
+ Drive.prototype.save = function () {
987
+ if (!this.config_file) {
988
+ return;
989
+ }
990
+ try {
991
+ var file = this.config_file;
992
+ if (file.hasFile(file)) {
993
+ var data = file.loadJson(file);
994
+ if (Array.isArray(data)) {
995
+ // 数组格式配置:查找并更新现有配置项,不存在则追加
996
+ var index = data.findIndex((item) => item.name === this.config.name);
997
+ if (index !== -1) {
998
+ data[index] = this.config;
999
+ } else {
1000
+ data.push(this.config);
1001
+ }
1002
+ file.saveText(file, JSON.stringify(data, null, 2));
1003
+ return;
1004
+ }
1005
+ }
1006
+ // 单对象格式配置,直接保存
1007
+ file.saveText(file, JSON.stringify(this.config, null, 2));
1008
+ } catch (err) {
1009
+ this.log('error', '保存配置失败', err);
1010
+ }
1011
+ };
1012
+
1013
+ /**
1014
+ * 获取当前驱动目录(替代旧基类 getDir)
1015
+ * @returns {string} 目录路径
1016
+ */
1017
+ Drive.prototype.getDir = function () {
1018
+ if (this._dir) {
1019
+ return this._dir;
1020
+ }
1021
+ if (this.config_file) {
1022
+ return path.dirname(this.config_file);
1023
+ }
1024
+ return '';
1025
+ };
1026
+
1027
+ /**
1028
+ * 获取事件接口管理器(方案B:可选注入,替代原全局 admin 事件查找)
1029
+ * @returns {object|null} 事件接口管理器实例,未配置返回null
1030
+ * @private
1031
+ */
1032
+ Drive.prototype._getEventApi = function () {
1033
+ var getter = this.config.get_event_api;
1034
+ if (typeof getter !== 'function' && this.parent) {
1035
+ getter = this.parent.config.get_event_api;
1036
+ }
1037
+ if (typeof getter === 'function') {
1038
+ return getter();
1039
+ }
1040
+ if (!this._event_api_warned) {
1041
+ this._event_api_warned = true;
1042
+ this.log('warn', '未配置 get_event_api,API目录判断使用默认路径');
1043
+ }
1044
+ return null;
1045
+ };
1046
+
1047
+ /**
1048
+ * 处理配置文件
1049
+ * @param {string} f 文件路径
1050
+ * @param {boolean} cover 是否覆盖文件
1051
+ * @private
1052
+ */
1053
+ Drive.prototype._handleConfigFile = function (f, cover) {
1054
+ if (!file.hasFile(f)) {
1055
+ this.save();
1056
+ } else if (cover) {
1057
+ var jobj = file.loadJson(f);
1058
+ var o = { ...this.config };
1059
+ if (!o.title) {
1060
+ delete o.title;
1061
+ }
1062
+ if (!o.description) {
1063
+ delete o.description;
1064
+ }
1065
+ _push(jobj, o, true);
1066
+ file.saveText(f, JSON.stringify(jobj, null, 2));
1067
+ }
1068
+ };
1069
+
1070
+ /**
1071
+ * 更新应用,根据表生成目录结构和文件
1072
+ * @param {boolean} cover 是否覆盖文件
1073
+ */
1074
+ Drive.prototype.updateApp = async function (cover) {
1075
+ var cg = this.config;
1076
+ var f;
1077
+ var dir_api;
1078
+
1079
+ if (!this.config_file) {
1080
+ var dirs = this._deteDir(cg);
1081
+ this._ensureDir(dirs.dir);
1082
+ this._ensureDir(dirs.dir + '/plugin');
1083
+ this._ensureDir(dirs.dir_api);
1084
+ this._ensureDir(dirs.db_dir);
1085
+ this._dir = dirs.db_dir;
1086
+ this.config_file = dirs.config_file;
1087
+ f = dirs.config_file;
1088
+ dir_api = dirs.dir_api;
1089
+ } else {
1090
+ if (this.config_file.endsWith('.db.json')) {
1091
+ f = this.config_file;
1092
+ } else {
1093
+ f = this.config_file + '.db.json';
1094
+ }
1095
+ }
1096
+
1097
+ if (f) {
1098
+ file.addDir(f);
1099
+ if (!dir_api) {
1100
+ dir_api = path.dirname(path.dirname(f));
1101
+ }
1102
+ // 处理db配置文件,生成xxx.db.json文件
1103
+ this._handleConfigFile(f, cover);
1104
+ // 更新API及相关配置文件
1105
+ this.updateApi(dir_api, cover);
1106
+ }
1107
+ };
1108
+
1109
+ /**
1110
+ * 确保API目录存在
1111
+ * @param {string} dir 目录路径
1112
+ * @private
1113
+ */
1114
+ Drive.prototype._ensureApiDir = function (dir) {
1115
+ if (!fs.existsSync(dir)) {
1116
+ fs.mkdirSync(dir);
1117
+ }
1118
+ };
1119
+
1120
+ /**
1121
+ * 确定API目录路径
1122
+ * @param {string} dir 基础目录
1123
+ * @param {string} app 应用名称
1124
+ * @param {string} type 类型 (client 或 manage)
1125
+ * @param {string} name 目录名称
1126
+ * @returns {string} 完整目录路径
1127
+ * @private
1128
+ */
1129
+ Drive.prototype._getApiDir = function (dir, app, type, name) {
1130
+ var l = path.sep;
1131
+ var event_api = this._getEventApi();
1132
+ var o =
1133
+ event_api && typeof event_api.get === 'function'
1134
+ ? event_api.get(app + '_' + type)
1135
+ : undefined;
1136
+ var api_dir;
1137
+
1138
+ if (o) {
1139
+ api_dir = dir + l + 'api_' + app + '_' + type;
1140
+ } else {
1141
+ api_dir = dir + l + 'api_' + type;
1142
+ }
1143
+
1144
+ this._ensureApiDir(api_dir);
1145
+ api_dir += '/' + name;
1146
+ this._ensureApiDir(api_dir);
1147
+
1148
+ return api_dir;
1149
+ };
1150
+
1151
+ /**
1152
+ * 更新API及相关配置文件
1153
+ * @param {string} dir API存放目录
1154
+ * @param {boolean} cover 是否覆盖文件
1155
+ */
1156
+ Drive.prototype.updateApi = async function (dir, cover) {
1157
+ var cg = this.config;
1158
+ var arr = cg.table.split('_');
1159
+
1160
+ var name = 'root';
1161
+ if (arr.length > 1) {
1162
+ name = string.trim(cg.table.replace(arr[0], ''), '_');
1163
+ }
1164
+ var app = arr[0];
1165
+
1166
+ var client = this._getApiDir(dir, app, 'client', name);
1167
+ var manage = this._getApiDir(dir, app, 'manage', name);
1168
+
1169
+ await this.newSql(client, manage, cover);
1170
+ await this.newParam(client, manage, cover);
1171
+ this.newApi(client, manage, cover);
1172
+ };
1173
+
1174
+ /**
1175
+ * 新建event配置文件和文件
1176
+ * @param {string} dir 保存的路径
1177
+ * @param {string} path 检索路径
1178
+ * @param {string} scope 接口域
1179
+ */
1180
+ Drive.prototype.newEvent = async function (dir, path, scope) {
1181
+ var f = dir + '/main.js';
1182
+ if (!file.hasFile(f)) {
1183
+ var code = file.loadText(__dirname + '/event_script.js');
1184
+ code = string.replaceAll(string.replaceAll(code, '{0}', path), '{1}', scope);
1185
+ file.saveText(f, code);
1186
+ }
1187
+ };
1188
+
1189
+ /**
1190
+ * 处理带逗号的列表映射
1191
+ * @param {Array} list 列表
1192
+ * @returns {Array} 处理后的列表
1193
+ * @private
1194
+ */
1195
+ Drive.prototype._handleCommaList = function (list) {
1196
+ return list.map((o) => {
1197
+ var arr = o.split(',');
1198
+ var value = arr[0];
1199
+ if (arr.length > 0) {
1200
+ return {
1201
+ name: arr[1],
1202
+ value: value,
1203
+ };
1204
+ } else {
1205
+ return {
1206
+ name: value,
1207
+ value,
1208
+ };
1209
+ }
1210
+ });
1211
+ };
1212
+
1213
+ /**
1214
+ * 处理数字列表映射
1215
+ * @param {Array} list 列表
1216
+ * @param {string} map 映射字符串
1217
+ * @returns {Array} 处理后的列表
1218
+ * @private
1219
+ */
1220
+ Drive.prototype._handleNumberList = function (list, map) {
1221
+ if (map.indexOf('0') !== 0) {
1222
+ list.unshift('');
1223
+ }
1224
+ return list.map((value) => {
1225
+ return value.replace(/[0-9]+/, '');
1226
+ });
1227
+ };
1228
+
1229
+ /**
1230
+ * 处理简单列表映射
1231
+ * @param {Array} list 列表
1232
+ * @returns {Array} 处理后的列表
1233
+ * @private
1234
+ */
1235
+ Drive.prototype._handleSimpleList = function (list) {
1236
+ return list.map((value) => {
1237
+ return {
1238
+ name: value,
1239
+ value,
1240
+ };
1241
+ });
1242
+ };
1243
+
1244
+ /**
1245
+ * 处理点分隔的映射
1246
+ * @param {object} obj 字段对象
1247
+ * @param {string} map 映射字符串
1248
+ * @returns {object} 处理后的格式对象
1249
+ * @private
1250
+ */
1251
+ Drive.prototype._handleDotMap = function (obj, map) {
1252
+ var arr = map.split('.');
1253
+ var format = {
1254
+ table: arr[0],
1255
+ name: arr[1],
1256
+ };
1257
+ if (arr.length > 2) {
1258
+ format.id = arr[2];
1259
+ } else {
1260
+ format.id = obj.name;
1261
+ }
1262
+ return format;
1263
+ };
1264
+
1265
+ /**
1266
+ * 处理简单映射
1267
+ * @param {object} obj 字段对象
1268
+ * @param {string} map 映射字符串
1269
+ * @returns {object} 处理后的格式对象
1270
+ * @private
1271
+ */
1272
+ Drive.prototype._handleSimpleMap = function (obj, map) {
1273
+ return {
1274
+ table: map,
1275
+ id: obj.name,
1276
+ name: 'name',
1277
+ };
1278
+ };
1279
+
1280
+ /**
1281
+ * 获取格式
1282
+ * @param {object} obj 字段对象
1283
+ * @returns {object} 返回格式
1284
+ */
1285
+ Drive.prototype.getFormat = async function (obj) {
1286
+ var map = obj.map;
1287
+ var format = {
1288
+ key: obj.name,
1289
+ title: obj.title.replace('ID', '').replace('id', ''),
1290
+ };
1291
+
1292
+ if (map.indexOf('|') !== -1) {
1293
+ var list = map.split('|');
1294
+ if (map.indexOf(',') !== -1) {
1295
+ format.list = this._handleCommaList(list);
1296
+ } else if (/^[0-9]+/.test(map)) {
1297
+ format.list = this._handleNumberList(list, map);
1298
+ } else {
1299
+ format.list = this._handleSimpleList(list);
1300
+ }
1301
+ } else {
1302
+ if (map.indexOf('.') !== -1) {
1303
+ Object.assign(format, this._handleDotMap(obj, map));
1304
+ } else {
1305
+ Object.assign(format, this._handleSimpleMap(obj, map));
1306
+ }
1307
+ }
1308
+
1309
+ return format;
1310
+ };
1311
+
1312
+ /**
1313
+ * 处理字段选择
1314
+ * @param {string} field_name 字段名
1315
+ * @param {string} field_type 字段类型
1316
+ * @returns {object} 处理结果
1317
+ * @private
1318
+ */
1319
+ Drive.prototype._procFieldSel = function (field_name, field_type) {
1320
+ var res = {
1321
+ field: '',
1322
+ field_obj: '',
1323
+ };
1324
+
1325
+ if (this.isCan(field_name, this.get_not, field_type)) {
1326
+ res.field += ',`' + field_name + '`';
1327
+ }
1328
+ if (this.isCan(field_name, this.get_obj_not)) {
1329
+ res.field_obj += ',`' + field_name + '`';
1330
+ }
1331
+ return res;
1332
+ };
1333
+
1334
+ /**
1335
+ * 处理字符串类型字段
1336
+ * @param {object} query 查询对象
1337
+ * @param {string} field_name 字段名
1338
+ * @param {string} keyword 关键词
1339
+ * @returns {string} 更新后的关键词
1340
+ * @private
1341
+ */
1342
+ Drive.prototype._procStringQuery = function (query, field_name, keyword) {
1343
+ var new_keyword = keyword;
1344
+ query[field_name] = '`' + field_name + "` like '%{0}%'";
1345
+ if (this.isSet(field_name, this.query_keyword)) {
1346
+ new_keyword += ' || `' + field_name + "` like '%{0}%'";
1347
+ }
1348
+ return new_keyword;
1349
+ };
1350
+
1351
+ /**
1352
+ * 处理日期时间类型字段
1353
+ * @param {object} query 查询对象
1354
+ * @param {string} field_name 字段名
1355
+ * @private
1356
+ */
1357
+ Drive.prototype._procDateTimeQuery = function (query, field_name) {
1358
+ query[field_name + '_min'] = '`' + field_name + "` >= '{0}'";
1359
+ query[field_name + '_max'] = '`' + field_name + "` <= '{0}'";
1360
+ };
1361
+
1362
+ /**
1363
+ * 处理数字类型字段
1364
+ * @param {object} query 查询对象
1365
+ * @param {object} update_obj 更新对象
1366
+ * @param {string} field_name 字段名
1367
+ * @param {string} uid 用户ID
1368
+ * @param {boolean} query_default_user 是否默认用户查询
1369
+ * @param {string} orderby 排序
1370
+ * @returns {object} 处理结果
1371
+ * @private
1372
+ */
1373
+ Drive.prototype._procNumberQuery = function (
1374
+ query,
1375
+ update_obj,
1376
+ field_name,
1377
+ uid,
1378
+ query_default_user,
1379
+ orderby,
1380
+ ) {
1381
+ var res = {
1382
+ orderby: orderby,
1383
+ update: { ...update_obj },
1384
+ };
1385
+
1386
+ if (!field_name.endsWith('id')) {
1387
+ query[field_name + '_min'] = '`' + field_name + '` >= {0}';
1388
+ query[field_name + '_max'] = '`' + field_name + '` <= {0}';
1389
+ res.update[field_name + '_add'] =
1390
+ '`' + field_name + '` = `' + field_name + '` + {0}';
1391
+
1392
+ if (
1393
+ field_name === 'sort' ||
1394
+ field_name === 'display' ||
1395
+ field_name === 'orderby'
1396
+ ) {
1397
+ res.orderby = '`' + field_name + '` asc';
1398
+ }
1399
+ } else if (field_name === uid && query_default_user) {
1400
+ res.query_default = '`' + field_name + '` = {' + uid + '}';
1401
+ } else if (field_name === 'available' || field_name === 'show') {
1402
+ res.query_default = '`' + field_name + '` = 1';
1403
+ }
1404
+
1405
+ return res;
1406
+ };
1407
+
1408
+ /**
1409
+ * 处理字段类型
1410
+ * @param {string} type 字段类型
1411
+ * @param {string} field_name 字段名
1412
+ * @param {object} query 查询对象
1413
+ * @param {object} update_obj 更新对象
1414
+ * @param {string} uid 用户ID
1415
+ * @param {boolean} query_default_user 是否默认用户查询
1416
+ * @param {string} orderby 排序
1417
+ * @param {string} keyword 关键词
1418
+ * @returns {object} 处理结果
1419
+ * @private
1420
+ */
1421
+ Drive.prototype._procFieldType = function (
1422
+ type,
1423
+ field_name,
1424
+ query,
1425
+ update_obj,
1426
+ uid,
1427
+ query_default_user,
1428
+ orderby,
1429
+ keyword,
1430
+ ) {
1431
+ var result = {
1432
+ keyword: keyword,
1433
+ orderby: orderby,
1434
+ update_obj: update_obj,
1435
+ query_default: {},
1436
+ };
1437
+
1438
+ if (type === 'varchar' || type === 'text' || type === 'longtext') {
1439
+ result.keyword = this._procStringQuery(query, field_name, keyword);
1440
+ } else if (
1441
+ type === 'date' ||
1442
+ type === 'time' ||
1443
+ type === 'datetime' ||
1444
+ type === 'timestamp'
1445
+ ) {
1446
+ this._procDateTimeQuery(query, field_name);
1447
+ } else if (type !== 'tinyint') {
1448
+ var num_res = this._procNumberQuery(
1449
+ query,
1450
+ update_obj,
1451
+ field_name,
1452
+ uid,
1453
+ query_default_user,
1454
+ orderby,
1455
+ );
1456
+ result.orderby = num_res.orderby;
1457
+ result.update_obj = num_res.update;
1458
+ if (num_res.query_default) {
1459
+ result.query_default[field_name] = num_res.query_default;
1460
+ }
1461
+ }
1462
+
1463
+ return result;
1464
+ };
1465
+
1466
+ /**
1467
+ * 处理单个字段
1468
+ * @param {object} o 字段对象
1469
+ * @param {object} query 查询对象
1470
+ * @param {object} update_obj 更新对象
1471
+ * @param {string} uid 用户ID
1472
+ * @param {boolean} query_default_user 是否默认用户查询
1473
+ * @param {string} orderby 排序
1474
+ * @param {string} keyword 关键词
1475
+ * @returns {object} 处理结果
1476
+ * @private
1477
+ */
1478
+ Drive.prototype._procSingleField = async function (
1479
+ o,
1480
+ query,
1481
+ update_obj,
1482
+ uid,
1483
+ query_default_user,
1484
+ orderby,
1485
+ keyword,
1486
+ ) {
1487
+ var { type, name } = o;
1488
+ var result = {
1489
+ field: '*',
1490
+ field_obj: '*',
1491
+ keyword: keyword,
1492
+ orderby: orderby,
1493
+ update_obj: update_obj,
1494
+ query_default: {},
1495
+ format: [],
1496
+ };
1497
+
1498
+ var field_sel = this._procFieldSel(name, type);
1499
+ result.field = field_sel.field;
1500
+ result.field_obj = field_sel.field_obj;
1501
+
1502
+ var type_info = this._procFieldType(
1503
+ type,
1504
+ name,
1505
+ query,
1506
+ update_obj,
1507
+ uid,
1508
+ query_default_user,
1509
+ orderby,
1510
+ keyword,
1511
+ );
1512
+ result.keyword = type_info.keyword;
1513
+ result.orderby = type_info.orderby;
1514
+ result.update_obj = type_info.update_obj;
1515
+ result.query_default = type_info.query_default;
1516
+
1517
+ if (o.map) {
1518
+ var fmt = await this.getFormat(o);
1519
+ if (fmt) {
1520
+ result.format.push(fmt);
1521
+ }
1522
+ }
1523
+
1524
+ return result;
1525
+ };
1526
+
1527
+ /**
1528
+ * 处理字段和查询条件
1529
+ * @param {Array} fields 字段列表
1530
+ * @param {string} table 表名
1531
+ * @returns {object} 处理结果
1532
+ * @private
1533
+ */
1534
+ Drive.prototype._procFields = async function (fields, table) {
1535
+ var query = {};
1536
+ var update_obj = {};
1537
+ var field = '';
1538
+ var field_obj = '';
1539
+ var query_default = {};
1540
+ var format = [];
1541
+ var orderby = '';
1542
+ var uid = this.config.user_id;
1543
+ var keyword = '';
1544
+ var query_default_user = this.isSet(table, this.query_default_table);
1545
+
1546
+ for (var i = 0; i < fields.length; i++) {
1547
+ var field_info = await this._procSingleField(
1548
+ fields[i],
1549
+ query,
1550
+ update_obj,
1551
+ uid,
1552
+ query_default_user,
1553
+ orderby,
1554
+ keyword,
1555
+ );
1556
+
1557
+ field += field_info.field;
1558
+ field_obj += field_info.field_obj;
1559
+ keyword = field_info.keyword;
1560
+ orderby = field_info.orderby;
1561
+ update_obj = field_info.update_obj;
1562
+
1563
+ // 合并查询默认值
1564
+ for (var key in field_info.query_default) {
1565
+ query_default[key] = field_info.query_default[key];
1566
+ }
1567
+
1568
+ // 合并格式
1569
+ format = format.concat(field_info.format);
1570
+ }
1571
+
1572
+ return {
1573
+ query,
1574
+ update: update_obj,
1575
+ field: string.trim(field, ','),
1576
+ field_obj: string.trim(field_obj, ','),
1577
+ query_default,
1578
+ format,
1579
+ orderby,
1580
+ keyword,
1581
+ };
1582
+ };
1583
+
1584
+ /**
1585
+ * 处理关键字搜索
1586
+ * @param {string} keyword 关键字
1587
+ * @param {string} table 表名
1588
+ * @returns {object} 处理后的查询对象
1589
+ * @private
1590
+ */
1591
+ Drive.prototype._procKeyword = function (keyword, table) {
1592
+ var safe_keyword = escapeLikeKeyword(keyword);
1593
+ var new_keyword = safe_keyword;
1594
+ if (table.indexOf('user_') !== -1) {
1595
+ new_keyword += ' || ' + KEYWORD_DEFAULT;
1596
+ }
1597
+
1598
+ var query = {};
1599
+ if (new_keyword) {
1600
+ query['keyword'] = '(' + new_keyword.replace(' || ', '') + ')';
1601
+ }
1602
+
1603
+ return query;
1604
+ };
1605
+
1606
+ /**
1607
+ * 创建基础模型
1608
+ * @param {object} cg 配置对象
1609
+ * @param {object} processed 处理结果
1610
+ * @param {object} keyword_query 关键字查询
1611
+ * @returns {object} 基础模型
1612
+ * @private
1613
+ */
1614
+ Drive.prototype._createSqlConfig = function (cg, processed, keyword_query) {
1615
+ return {
1616
+ name: cg.table,
1617
+ title: cg.title,
1618
+ table: cg.table,
1619
+ key: cg.key,
1620
+ orderby_default: '`' + cg.key + '` desc',
1621
+ field_obj: processed.field_obj,
1622
+ field_default: processed.field,
1623
+ method: 'get get_obj avg sum count',
1624
+ query: { ...processed.query, ...keyword_query },
1625
+ query_default: processed.query_default,
1626
+ update: processed.update,
1627
+ format: processed.format,
1628
+ };
1629
+ };
1630
+
1631
+ /**
1632
+ * 保存客户端配置
1633
+ * @param {string} client 客户端配置保存路径
1634
+ * @param {object} base_model 基础模型
1635
+ * @param {boolean} cover 是否覆盖文件
1636
+ * @param {string} orderby 排序
1637
+ * @param {string} uid 用户ID
1638
+ * @param {string} table 表名
1639
+ * @private
1640
+ */
1641
+ Drive.prototype._saveClientConfig = function (
1642
+ client,
1643
+ base_model,
1644
+ cover,
1645
+ orderby,
1646
+ uid,
1647
+ table,
1648
+ ) {
1649
+ var oj = { ...base_model };
1650
+ if (orderby) {
1651
+ oj.orderby_default = orderby;
1652
+ }
1653
+ if (
1654
+ oj.orderby_default &&
1655
+ oj.query_default[uid] &&
1656
+ table.indexOf('user_') !== -1
1657
+ ) {
1658
+ oj.filter = {
1659
+ table: 'table',
1660
+ page: 'page',
1661
+ size: 'size',
1662
+ method: 'method',
1663
+ orderby: 'orderby',
1664
+ field: 'field',
1665
+ count_ret: 'count_ret',
1666
+ };
1667
+ oj.filter[uid] = uid;
1668
+ }
1669
+ this.saveFile(client + '/sql.json', oj, cover);
1670
+ };
1671
+
1672
+ /**
1673
+ * 保存管理端配置
1674
+ * @param {string} manage 管理端配置保存路径
1675
+ * @param {object} base_model 基础模型
1676
+ * @param {boolean} cover 是否覆盖文件
1677
+ * @private
1678
+ */
1679
+ Drive.prototype._saveManageConfig = function (manage, base_model, cover) {
1680
+ var m = { ...base_model };
1681
+ delete m.method;
1682
+ m.field_hide = [];
1683
+ m.name += 2;
1684
+ m.field_obj = (m.field_obj || '*')
1685
+ .replace(',`time_create`', '')
1686
+ .replace(',`time_update`', '')
1687
+ .replace(',`create_time`', '')
1688
+ .replace(',`update_time`', '');
1689
+ delete m.query_default;
1690
+ this.saveFile(manage + '/sql.json', m, cover);
1691
+ };
1692
+
1693
+ /**
1694
+ * 新建sql配置文件
1695
+ * @param {string} client 客户端配置保存路径
1696
+ * @param {string} manage 管理端配置保存路径
1697
+ * @param {boolean} cover 是否覆盖文件
1698
+ */
1699
+ Drive.prototype.newSql = async function (client, manage, cover) {
1700
+ var cg = this.config;
1701
+ var uid = this.config.user_id;
1702
+
1703
+ var processed = await this._procFields(cg.fields, cg.table);
1704
+ var keyword_query = this._procKeyword(processed.keyword, cg.table);
1705
+ var base_model = this._createSqlConfig(cg, processed, keyword_query);
1706
+
1707
+ if (client) {
1708
+ this._saveClientConfig(
1709
+ client,
1710
+ base_model,
1711
+ cover,
1712
+ processed.orderby,
1713
+ uid,
1714
+ cg.table,
1715
+ );
1716
+ }
1717
+
1718
+ if (manage) {
1719
+ this._saveManageConfig(manage, base_model, cover);
1720
+ }
1721
+ };
1722
+
1723
+ /**
1724
+ * 保存sql配置
1725
+ * @param {string} file 文件名
1726
+ * @param {object} model 配置模型
1727
+ * @param {boolean} cover 是否覆盖文件
1728
+ */
1729
+ Drive.prototype.saveFile = function (file, model, cover) {
1730
+ if (!file.hasFile(file)) {
1731
+ file.saveText(file, JSON.stringify(model, null, 2));
1732
+ } else if (cover) {
1733
+ var jobj = file.loadJson(file);
1734
+ for (var k in model) {
1735
+ var val = model[k];
1736
+ if (!val) {
1737
+ model[k] = jobj[k];
1738
+ }
1739
+ }
1740
+ _push(jobj, model, true);
1741
+ file.saveText(file, JSON.stringify(jobj, null, 2));
1742
+ }
1743
+ };
1744
+
1745
+ /**
1746
+ * 新建param配置文件
1747
+ * @param {string} client 客户端配置保存路径
1748
+ * @param {string} manage 管理端配置保存路径
1749
+ * @param {boolean} cover 是否覆盖文件
1750
+ */
1751
+ Drive.prototype.newParam = async function (client, manage, cover) {
1752
+ var cg = this.config;
1753
+ var lt = cg.fields;
1754
+ var cm = this._initParamConfig(cg);
1755
+ var keyword = '';
1756
+
1757
+ for (var i = 0; i < lt.length; i++) {
1758
+ var o = lt[i];
1759
+ var p = o.type;
1760
+ var n = o.name;
1761
+ var m = this._createParamModel(o, p, n);
1762
+
1763
+ if (this._isStringType(p)) {
1764
+ keyword = this._processStringField(cm, m, o, n, keyword);
1765
+ } else if (this._isDateTimeType(p)) {
1766
+ this._processDateTimeField(cm, m, o, n);
1767
+ } else {
1768
+ this._processNumberField(cm, m, o, n, cg);
1769
+ }
1770
+ }
1771
+
1772
+ this._addKeywordConfig(cm, keyword, cg);
1773
+ this._saveParamFiles(cm, client, manage, cover);
1774
+ };
1775
+
1776
+ /**
1777
+ * 初始化参数配置对象
1778
+ * @param {object} cg 配置对象
1779
+ * @returns {object} 参数配置对象
1780
+ * @private
1781
+ */
1782
+ Drive.prototype._initParamConfig = function (cg) {
1783
+ return {
1784
+ name: cg.table,
1785
+ title: cg.title,
1786
+ add: {
1787
+ body: [],
1788
+ body_required: [],
1789
+ },
1790
+ del: {
1791
+ query: [],
1792
+ query_required: [],
1793
+ },
1794
+ set: {
1795
+ query: [],
1796
+ query_required: [],
1797
+ body: [],
1798
+ body_required: [],
1799
+ body_not: [],
1800
+ },
1801
+ get: {
1802
+ query: [],
1803
+ query_required: [],
1804
+ },
1805
+ get_obj: {
1806
+ query_required: [],
1807
+ },
1808
+ list: [],
1809
+ };
1810
+ };
1811
+
1812
+ /**
1813
+ * 创建参数模型
1814
+ * @param {object} field 字段对象
1815
+ * @param {string} type 字段类型
1816
+ * @param {string} name 字段名
1817
+ * @returns {object} 参数模型对象
1818
+ * @private
1819
+ */
1820
+ Drive.prototype._createParamModel = function (field, type, name) {
1821
+ return {
1822
+ name: name,
1823
+ title: field.title,
1824
+ description: field.description + (field.map ? '(' + field.map + ')' : ''),
1825
+ key: field.pk,
1826
+ type: '',
1827
+ dataType: type,
1828
+ };
1829
+ };
1830
+
1831
+ /**
1832
+ * 检查是否为字符串类型
1833
+ * @param {string} type 字段类型
1834
+ * @returns {boolean} 是否为字符串类型
1835
+ * @private
1836
+ */
1837
+ Drive.prototype._isStringType = function (type) {
1838
+ return type === 'varchar' || type === 'text' || type === 'longtext';
1839
+ };
1840
+
1841
+ /**
1842
+ * 处理字符串类型字段
1843
+ * @param {object} cm 参数配置对象
1844
+ * @param {object} m 模型对象
1845
+ * @param {object} o 字段对象
1846
+ * @param {string} n 字段名
1847
+ * @param {string} keyword 关键词
1848
+ * @returns {string} 更新后的关键词
1849
+ * @private
1850
+ */
1851
+ Drive.prototype._processStringField = function (cm, m, o, n, keyword) {
1852
+ var new_keyword = keyword;
1853
+ m.type = 'string';
1854
+ m.default = o.default;
1855
+ m.string = {};
1856
+
1857
+ this._addStringRange(m, o);
1858
+ this._addStringFormat(m, n);
1859
+
1860
+ if (o.not_null) {
1861
+ m.string.not_empty = !!o.not_null;
1862
+ cm.add.body_required.push(n);
1863
+ } else {
1864
+ cm.add.body.push(n);
1865
+ }
1866
+
1867
+ if (this.isSet(n, this.query_string)) {
1868
+ cm.get.query.push(n);
1869
+ cm.set.query.push(n);
1870
+ new_keyword += '、' + o.title + '(' + n + ')';
1871
+ }
1872
+
1873
+ cm.set.body.push(n);
1874
+ cm.list.push(m);
1875
+ return new_keyword;
1876
+ };
1877
+
1878
+ /**
1879
+ * 添加字符串范围验证
1880
+ * @param {object} m 模型对象
1881
+ * @param {object} o 字段对象
1882
+ * @private
1883
+ */
1884
+ Drive.prototype._addStringRange = function (m, o) {
1885
+ var range = o.min_length && o.max_length ? [o.min_length, o.max_length] : [];
1886
+ if (range.length > 0) {
1887
+ m.string.range = range;
1888
+ } else if (o.min_length) {
1889
+ m.string.min = o.min_length;
1890
+ } else if (o.max_length) {
1891
+ m.string.max = o.max_length;
1892
+ }
1893
+ };
1894
+
1895
+ /**
1896
+ * 检查字段名是否包含指定关键词
1897
+ * @param {string} field_name 字段名
1898
+ * @param {string} keyword 关键词
1899
+ * @returns {boolean} 是否包含
1900
+ * @private
1901
+ */
1902
+ Drive.prototype._hasKeyword = function (field_name, keyword) {
1903
+ return string.has(field_name, keyword);
1904
+ };
1905
+
1906
+ /**
1907
+ * 检查字段名是否等于指定值
1908
+ * @param {string} field_name 字段名
1909
+ * @param {Array} values 值数组
1910
+ * @returns {boolean} 是否等于
1911
+ * @private
1912
+ */
1913
+ Drive.prototype._isEqual = function (field_name, values) {
1914
+ return values.includes(field_name);
1915
+ };
1916
+
1917
+ /**
1918
+ * 添加字符串格式验证
1919
+ * @param {object} m 模型对象
1920
+ * @param {string} n 字段名
1921
+ * @private
1922
+ */
1923
+ Drive.prototype._addStringFormat = function (m, n) {
1924
+ if (this._hasKeyword(n, 'phone') || this._isEqual(n, ['tel'])) {
1925
+ m.string.format = 'phone';
1926
+ } else if (
1927
+ this._hasKeyword(n, 'url') ||
1928
+ this._isEqual(n, ['src', 'source'])
1929
+ ) {
1930
+ m.string.format = 'url';
1931
+ } else if (this._hasKeyword(n, 'date')) {
1932
+ m.string.format = 'date';
1933
+ } else if (this._isEqual(n, ['num', 'number', 'count'])) {
1934
+ m.string.format = 'digits';
1935
+ } else if (this._isEqual(n, ['money', 'coin'])) {
1936
+ m.string.format = 'number';
1937
+ } else if (this._hasKeyword(n, 'email')) {
1938
+ m.string.format = 'email';
1939
+ }
1940
+ };
1941
+
1942
+ /**
1943
+ * 处理日期时间类型字段
1944
+ * @param {object} cm 参数配置对象
1945
+ * @param {object} m 模型对象
1946
+ * @param {object} o 字段对象
1947
+ * @param {string} n 字段名
1948
+ * @private
1949
+ */
1950
+ Drive.prototype._processDateTimeField = function (cm, m, o, n) {
1951
+ var format = o.type === 'timestamp' ? 'datetime' : o.type;
1952
+ m.type = 'string';
1953
+ m.default = '';
1954
+ m.string = {
1955
+ not_empty: true,
1956
+ format: format,
1957
+ };
1958
+
1959
+ cm.list.push(m);
1960
+ this._addDateTimeRange(cm, m, n);
1961
+
1962
+ if (n.indexOf('create') !== -1 && n.indexOf('update') !== -1) {
1963
+ cm.add.body.push(n);
1964
+ cm.set.body.push(n);
1965
+ }
1966
+
1967
+ cm.get.query.push(n + '_min');
1968
+ cm.get.query.push(n + '_max');
1969
+ };
1970
+
1971
+ /**
1972
+ * 添加日期时间范围模型
1973
+ * @param {object} cm 参数配置对象
1974
+ * @param {object} m 模型对象
1975
+ * @param {string} n 字段名
1976
+ * @private
1977
+ */
1978
+ Drive.prototype._addDateTimeRange = function (cm, m, n) {
1979
+ var m_min = { ...m };
1980
+ m_min.name = n + '_min';
1981
+ m_min.title += '——开始时间';
1982
+ cm.list.push(m_min);
1983
+
1984
+ var m_max = { ...m };
1985
+ m_max.name = n + '_max';
1986
+ m_max.title += '——结束时间';
1987
+ cm.list.push(m_max);
1988
+ };
1989
+
1990
+ /**
1991
+ * 处理数字类型字段
1992
+ * @param {object} cm 参数配置对象
1993
+ * @param {object} m 模型对象
1994
+ * @param {object} o 字段对象
1995
+ * @param {string} n 字段名
1996
+ * @param {object} cg 配置对象
1997
+ * @private
1998
+ */
1999
+ Drive.prototype._processNumberField = function (cm, m, o, n, cg) {
2000
+ m.type = 'number';
2001
+ m.default = Number(o.default) + '';
2002
+ m.number = {};
2003
+
2004
+ this._addNumberRange(m, o);
2005
+
2006
+ if (o.name === cg.key) {
2007
+ this._processPrimaryKey(cm, m, n);
2008
+ } else {
2009
+ this._processNonPrimaryKey(cm, m, n);
2010
+ }
2011
+ };
2012
+
2013
+ /**
2014
+ * 添加数字范围验证
2015
+ * @param {object} m 模型对象
2016
+ * @param {object} o 字段对象
2017
+ * @private
2018
+ */
2019
+ Drive.prototype._addNumberRange = function (m, o) {
2020
+ var range = o.min && o.max ? [o.min, o.max] : [];
2021
+ if (range.length > 0) {
2022
+ m.number.range = range;
2023
+ } else if (o.min) {
2024
+ m.number.min = o.min;
2025
+ } else if (o.max) {
2026
+ m.number.max = o.max;
2027
+ }
2028
+ };
2029
+
2030
+ /**
2031
+ * 处理主键字段
2032
+ * @param {object} cm 参数配置对象
2033
+ * @param {object} m 模型对象
2034
+ * @param {string} n 字段名
2035
+ * @private
2036
+ */
2037
+ Drive.prototype._processPrimaryKey = function (cm, m, n) {
2038
+ cm.del.query_required.push(n);
2039
+ cm.set.query.push(n);
2040
+ cm.get.query.push(n);
2041
+ cm.get_obj.query_required.push(n);
2042
+ cm.list.push(m);
2043
+ };
2044
+
2045
+ /**
2046
+ * 处理非主键字段
2047
+ * @param {object} cm 参数配置对象
2048
+ * @param {object} m 模型对象
2049
+ * @param {string} n 字段名
2050
+ * @private
2051
+ */
2052
+ Drive.prototype._processNonPrimaryKey = function (cm, m, n) {
2053
+ cm.add.body.push(n);
2054
+ cm.set.body.push(n);
2055
+ cm.list.push(m);
2056
+
2057
+ if (m.dataType !== 'tinyint') {
2058
+ this._processNonTinyInt(cm, m, n);
2059
+ } else {
2060
+ cm.set.query.push(n);
2061
+ cm.get.query.push(n);
2062
+ }
2063
+ };
2064
+
2065
+ /**
2066
+ * 处理非tinyint字段
2067
+ * @param {object} cm 参数配置对象
2068
+ * @param {object} m 模型对象
2069
+ * @param {string} n 字段名
2070
+ * @private
2071
+ */
2072
+ Drive.prototype._processNonTinyInt = function (cm, m, n) {
2073
+ var ne = n;
2074
+ if (!ne.endsWith('id')) {
2075
+ this._addNumberRangeFields(cm, m, n);
2076
+ } else {
2077
+ cm.get.query.push(n);
2078
+ if (this.isSet(n, this.query_number)) {
2079
+ cm.set.query.push(n);
2080
+ }
2081
+ }
2082
+ };
2083
+
2084
+ /**
2085
+ * 添加数字范围字段
2086
+ * @param {object} cm 参数配置对象
2087
+ * @param {object} m 模型对象
2088
+ * @param {string} n 字段名
2089
+ * @private
2090
+ */
2091
+ Drive.prototype._addNumberRangeFields = function (cm, m, n) {
2092
+ cm.get.query.push(n + '_min');
2093
+ cm.get.query.push(n + '_max');
2094
+ cm.set.query.push(n + '_min');
2095
+ cm.set.query.push(n + '_max');
2096
+ cm.set.body.push(n + '_add');
2097
+
2098
+ var m_min = { ...m };
2099
+ m_min.name = n + '_min';
2100
+ m_min.title += '——最小值';
2101
+ cm.list.push(m_min);
2102
+
2103
+ var m_max = { ...m };
2104
+ m_max.name = n + '_max';
2105
+ m_max.title += '——最大值';
2106
+ cm.list.push(m_max);
2107
+ };
2108
+
2109
+ /**
2110
+ * 添加关键词配置
2111
+ * @param {object} cm 参数配置对象
2112
+ * @param {string} keyword 关键词
2113
+ * @param {object} cg 配置对象
2114
+ * @private
2115
+ */
2116
+ Drive.prototype._addKeywordConfig = function (cm, keyword, cg) {
2117
+ if (keyword) {
2118
+ cm.get.query.push('keyword');
2119
+ cm.set.query.push('keyword');
2120
+ var new_keyword = keyword;
2121
+ if (cg.table.indexOf('user_') !== -1) {
2122
+ new_keyword += '、' + KEYWORD_DEFAULT_TIP;
2123
+ }
2124
+ var m_k = {
2125
+ name: 'keyword',
2126
+ title: '关键词',
2127
+ description: '用于搜索' + new_keyword.replace('、', ''),
2128
+ type: 'string',
2129
+ string: {},
2130
+ };
2131
+ cm.list.push(m_k);
2132
+ }
2133
+ };
2134
+
2135
+ /**
2136
+ * 保存参数配置文件
2137
+ * @param {object} cm 参数配置对象
2138
+ * @param {string} client 客户端配置保存路径
2139
+ * @param {string} manage 管理端配置保存路径
2140
+ * @param {boolean} cover 是否覆盖文件
2141
+ * @private
2142
+ */
2143
+ Drive.prototype._saveParamFiles = function (cm, client, manage, cover) {
2144
+ if (client) {
2145
+ this.saveFile(client + '/param.json', cm, cover);
2146
+ }
2147
+ if (manage) {
2148
+ var manage_cm = { ...cm };
2149
+ delete manage_cm.method;
2150
+ manage_cm.name += 2;
2151
+ this.saveFile(manage + '/param.json', manage_cm, cover);
2152
+ }
2153
+ };
2154
+
2155
+ /**
2156
+ * 是否设置
2157
+ * @param {string} name 名称
2158
+ * @param {Array} arr 匹配的对象
2159
+ * @returns {boolean} 是否设置
2160
+ */
2161
+ Drive.prototype.isSet = function (name, arr) {
2162
+ if (!arr) {
2163
+ return false;
2164
+ }
2165
+ var bl = false;
2166
+ for (var i = 0; i < arr.length; i++) {
2167
+ if (name.indexOf(arr[i]) !== -1) {
2168
+ bl = true;
2169
+ break;
2170
+ }
2171
+ }
2172
+ return bl;
2173
+ };
2174
+
2175
+ /**
2176
+ * 是否排除
2177
+ * @param {string} name 名称
2178
+ * @param {Array} arr 匹配的对象
2179
+ * @param {string} type 数据类型
2180
+ * @returns {boolean} 是否可以
2181
+ */
2182
+ Drive.prototype.isCan = function (name, arr, type) {
2183
+ if (type === 'longtext') {
2184
+ return false;
2185
+ }
2186
+ if (!arr) {
2187
+ return true;
2188
+ }
2189
+ var bl = true;
2190
+ for (var i = 0; i < arr.length; i++) {
2191
+ if (name.indexOf(arr[i]) !== -1) {
2192
+ bl = false;
2193
+ break;
2194
+ }
2195
+ }
2196
+ return bl;
2197
+ };
2198
+
2199
+ /**
2200
+ * 确定API路径
2201
+ * @param {object} cg 配置对象
2202
+ * @returns {string} API路径
2203
+ * @private
2204
+ */
2205
+ Drive.prototype._deteApiPath = function (cg) {
2206
+ var arr = cg.table.split('_');
2207
+ var p = '/api/';
2208
+ if (arr.length > 1) {
2209
+ p += cg.table.replace('_', '/');
2210
+ } else {
2211
+ p += arr[0];
2212
+ }
2213
+ return p;
2214
+ };
2215
+
2216
+ /**
2217
+ * 创建基础API配置
2218
+ * @param {object} cg 配置对象
2219
+ * @param {string} path API路径
2220
+ * @returns {object} API配置
2221
+ * @private
2222
+ */
2223
+ Drive.prototype._createApiConfig = function (cg, path) {
2224
+ return {
2225
+ name: cg.table,
2226
+ title: cg.title,
2227
+ description: cg.description,
2228
+ path: path,
2229
+ method: 'ALL',
2230
+ cache: 0,
2231
+ client_cache: false,
2232
+ param_path: './param.json',
2233
+ sql_path: './sql.json',
2234
+ check_param: true,
2235
+ };
2236
+ };
2237
+
2238
+ /**
2239
+ * 检查是否包含用户ID字段
2240
+ * @param {Array} fields 字段列表
2241
+ * @returns {boolean} 是否包含用户ID字段
2242
+ * @private
2243
+ */
2244
+ Drive.prototype._hasUserIdField = function (fields) {
2245
+ for (var i = 0, item; (item = fields[i++]);) {
2246
+ var name = item.name;
2247
+ if (
2248
+ name === this.config.user_id ||
2249
+ name === 'uid' ||
2250
+ name === 'user_id' ||
2251
+ name === 'userid'
2252
+ ) {
2253
+ return true;
2254
+ }
2255
+ }
2256
+ return false;
2257
+ };
2258
+
2259
+ /**
2260
+ * 保存客户端API配置
2261
+ * @param {string} client 客户端配置保存路径
2262
+ * @param {object} base_config 基础配置
2263
+ * @param {object} cg 配置对象
2264
+ * @param {boolean} cover 是否覆盖文件
2265
+ * @private
2266
+ */
2267
+ Drive.prototype._saveClientApiConfig = function (
2268
+ client,
2269
+ base_config,
2270
+ cg,
2271
+ cover,
2272
+ ) {
2273
+ var o = { ...base_config };
2274
+
2275
+ if (cg.title.indexOf('user') !== -1) {
2276
+ if (this._hasUserIdField(cg.fields)) {
2277
+ o.oauth = {
2278
+ scope: true,
2279
+ sign_in: true,
2280
+ vip: 0,
2281
+ user_group: [],
2282
+ };
2283
+ }
2284
+ }
2285
+ o.method = 'GET';
2286
+ this.saveFile(client + '/api.json', o, cover);
2287
+ };
2288
+
2289
+ /**
2290
+ * 保存管理端API配置
2291
+ * @param {string} manage 管理端配置保存路径
2292
+ * @param {object} base_config 基础配置
2293
+ * @param {boolean} cover 是否覆盖文件
2294
+ * @private
2295
+ */
2296
+ Drive.prototype._saveManageApiConfig = function (manage, base_config, cover) {
2297
+ var o = { ...base_config };
2298
+ o.oauth = {
2299
+ scope: true,
2300
+ sign_in: true,
2301
+ gm: 2,
2302
+ user_admin: [],
2303
+ };
2304
+ o.path = o.path.replace('/api/', '/apis/');
2305
+ o.name += '_manage';
2306
+ this.saveFile(manage + '/api.json', o, cover);
2307
+ };
2308
+
2309
+ /**
2310
+ * 新建api配置文件
2311
+ * @param {string} client 客户端配置保存路径
2312
+ * @param {string} manage 管理端配置保存路径
2313
+ * @param {boolean} cover 是否覆盖文件
2314
+ */
2315
+ Drive.prototype.newApi = async function (client, manage, cover) {
2316
+ var cg = this.config;
2317
+ var path = this._deteApiPath(cg);
2318
+ var base_config = this._createApiConfig(cg, path);
2319
+
2320
+ if (client) {
2321
+ this._saveClientApiConfig(client, base_config, cg, cover);
2322
+ }
2323
+
2324
+ if (manage) {
2325
+ this._saveManageApiConfig(manage, base_config, cover);
2326
+ }
2327
+ };
2328
+
2329
+ /**
2330
+ * 获取模型
2331
+ * @param {string} type 模型类型
2332
+ * @returns {object} 返回获取到的模型
2333
+ */
2334
+ Drive.prototype.getModel = function (type) {
2335
+ let model = { ...this.config };
2336
+ let dir = this.getDir();
2337
+ let l = path.sep;
2338
+ let app_name = string.between(dir, 'app' + l, l);
2339
+ let plugin_name = string.between(dir, 'plugin' + l, l);
2340
+ let name = path.basename(dir);
2341
+ model.app = app_name;
2342
+ model.plugin = plugin_name;
2343
+ model.name = model.name || name;
2344
+ return model;
2345
+ };
2346
+
2347
+ module.exports = Drive;