@maiyunnet/kebab 7.0.2 → 7.1.1

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/sys/ctr.js CHANGED
@@ -20,54 +20,46 @@ export function clearLocaleData() {
20
20
  localeData = {};
21
21
  }
22
22
  export class Ctr {
23
+ /** --- 路由参数序列数组 --- */
24
+ _param = [];
25
+ /** --- 当前的 action 名 --- */
26
+ _action = '';
27
+ /** --- 请求的 header 列表,key 均为小写 --- */
28
+ _headers = {};
29
+ /** --- GET 数据 --- */
30
+ _get;
31
+ /** --- 原始 POST 数据 --- */
32
+ _rawPost = {};
33
+ /** --- POST 数据 --- */
34
+ _post = {};
35
+ /** --- 原始 input 字符串 */
36
+ _input = '';
37
+ /** --- 上传的文件列表 --- */
38
+ _files = {};
39
+ /** --- Cookie 数组 --- */
40
+ _cookie = {};
41
+ /** --- Session 数组 --- */
42
+ _session = {};
43
+ /** --- Session --- 对象 */
44
+ _sess = null;
45
+ /** --- 页面浏览器客户端缓存 --- */
46
+ _cacheTTL;
47
+ /** --- XSRF TOKEN 值 --- */
48
+ _xsrf = '';
49
+ /** --- 自定义 http code --- */
50
+ _httpCode = 0;
51
+ /** --- 当前语言名 --- */
52
+ _locale = 'en';
53
+ /** --- vhost 的 kebab.json 以及全局常量 --- */
54
+ _config;
55
+ _req;
56
+ _res;
57
+ _socket;
58
+ /** --- 本 ctr 已加载的语言文件列表 --- */
59
+ _localeFiles = [];
60
+ /** --- 本 ctr 的 locale data --- */
61
+ _localeData = {};
23
62
  constructor(config, req, res) {
24
- /** --- 路由参数序列数组 --- */
25
- this._param = [];
26
- /** --- 当前的 action 名 --- */
27
- this._action = '';
28
- /** --- 请求的 header 列表,key 均为小写 --- */
29
- this._headers = {};
30
- /** --- 原始 POST 数据 --- */
31
- this._rawPost = {};
32
- /** --- POST 数据 --- */
33
- this._post = {};
34
- /** --- 原始 input 字符串 */
35
- this._input = '';
36
- /** --- 上传的文件列表 --- */
37
- this._files = {};
38
- /** --- Cookie 数组 --- */
39
- this._cookie = {};
40
- /** --- Session 数组 --- */
41
- this._session = {};
42
- /** --- Session --- 对象 */
43
- this._sess = null;
44
- /** --- XSRF TOKEN 值 --- */
45
- this._xsrf = '';
46
- /** --- 自定义 http code --- */
47
- this._httpCode = 0;
48
- /** --- 当前语言名 --- */
49
- this._locale = 'en';
50
- /** --- 本 ctr 已加载的语言文件列表 --- */
51
- this._localeFiles = [];
52
- /** --- 本 ctr 的 locale data --- */
53
- this._localeData = {};
54
- /** --- 一些需要等待的事项的记录(异步任务、事务) --- */
55
- this._waitInfo = {
56
- 'asyncTask': {
57
- 'count': 0,
58
- 'resolve': () => {
59
- // --- NOTHING ---
60
- },
61
- 'callback': function () {
62
- return new Promise((resolve) => {
63
- this.resolve = resolve;
64
- });
65
- }
66
- },
67
- 'transaction': 0
68
- };
69
- /** --- auth 对象,user, pwd --- */
70
- this._authorization = null;
71
63
  this._config = config;
72
64
  this._req = req;
73
65
  if (res) {
@@ -79,6 +71,8 @@ export class Ctr {
79
71
  get _isAvail() {
80
72
  return this._req.socket.writable;
81
73
  }
74
+ /** --- timeout 的 timer --- */
75
+ _timer;
82
76
  /** --- 获取当前过期时间 --- */
83
77
  get timeout() {
84
78
  return this._timer?.timeout ?? 30_000;
@@ -94,6 +88,21 @@ export class Ctr {
94
88
  clearTimeout(this._timer.timer);
95
89
  this._timer.timer = setTimeout(this._timer.callback, num);
96
90
  }
91
+ /** --- 一些需要等待的事项的记录(异步任务、事务) --- */
92
+ _waitInfo = {
93
+ 'asyncTask': {
94
+ 'count': 0,
95
+ 'resolve': () => {
96
+ // --- NOTHING ---
97
+ },
98
+ 'callback': function () {
99
+ return new Promise((resolve) => {
100
+ this.resolve = resolve;
101
+ });
102
+ }
103
+ },
104
+ 'transaction': 0
105
+ };
97
106
  /**
98
107
  * --- 执行一段跳出堆栈的异步代码,代码执行完成前,热更新不会杀死当面进程 且 ftmp 临时文件不会被清除 ---
99
108
  * @param func 异步代码
@@ -213,6 +222,19 @@ export class Ctr {
213
222
  };
214
223
  return lCore.purify(ejs.render(content, data, {}));
215
224
  }
225
+ /**
226
+ * --- 设置校验错误返回值 ---
227
+ * @param rtn 返回值数组
228
+ * @param lastVal 规则最后一项
229
+ * @param msgSuffix 可选的消息后缀
230
+ */
231
+ _setCheckError(rtn, lastVal, msgSuffix) {
232
+ rtn[0] = lastVal[0];
233
+ rtn[1] = msgSuffix ? lastVal[1] + msgSuffix : lastVal[1];
234
+ if (lastVal[2]) {
235
+ rtn[2] = lastVal[2];
236
+ }
237
+ }
216
238
  /**
217
239
  * --- 检测提交的数据类型 ---
218
240
  * @param input 要校验的输入项
@@ -225,10 +247,7 @@ export class Ctr {
225
247
  for (const key in rule) {
226
248
  const val = rule[key];
227
249
  // --- key 就是上面的 xx ---
228
- if (input[key] === undefined) {
229
- // --- 原值不存在则设定为 null ---
230
- input[key] = null;
231
- }
250
+ input[key] ??= null;
232
251
  // --- 判断是否需要遍历 val ---
233
252
  const c = val.length;
234
253
  if (c === 0) {
@@ -236,20 +255,17 @@ export class Ctr {
236
255
  }
237
256
  // --- ['require', '> 6', [0, 'xx 必须大于 6']] ---
238
257
  const lastK = c - 1;
239
- if ((val[lastK][0] === undefined) || (val[lastK][1] === undefined) || !Number.isInteger(val[lastK][0]) || (typeof val[lastK][1] !== 'string')) {
258
+ const lastVal = val[lastK];
259
+ if ((lastVal[0] === undefined) || (lastVal[1] === undefined) || !Number.isInteger(lastVal[0]) || (typeof lastVal[1] !== 'string')) {
240
260
  rtn[0] = 0;
241
- rtn[1] = 'Param error(' + key + ')';
261
+ rtn[1] = `Param error(${key})`;
242
262
  return false;
243
263
  }
244
264
  for (let k = 0; k < lastK; ++k) {
245
265
  const v = val[k] ?? '';
246
266
  if (Array.isArray(v)) {
247
267
  if (v.length === 0) {
248
- rtn[0] = val[lastK][0];
249
- rtn[1] = val[lastK][1];
250
- if (val[lastK][2]) {
251
- rtn[2] = val[lastK][2];
252
- }
268
+ this._setCheckError(rtn, lastVal);
253
269
  return false;
254
270
  }
255
271
  // --- 判断提交的数据是否在此 array 之内,若没有提交数据,则自动设置为第一个项 ---
@@ -257,23 +273,14 @@ export class Ctr {
257
273
  input[key] = v[0];
258
274
  }
259
275
  else if (!v.includes(input[key])) {
260
- // --- 不在 ---
261
- rtn[0] = val[lastK][0];
262
- rtn[1] = val[lastK][1];
263
- if (val[lastK][2]) {
264
- rtn[2] = val[lastK][2];
265
- }
276
+ this._setCheckError(rtn, lastVal);
266
277
  return false;
267
278
  }
268
279
  }
269
280
  else if (v instanceof RegExp) {
270
281
  // --- 正则 ---
271
282
  if (input[key] !== null && !v.test(input[key])) {
272
- rtn[0] = val[lastK][0];
273
- rtn[1] = val[lastK][1];
274
- if (val[lastK][2]) {
275
- rtn[2] = val[lastK][2];
276
- }
283
+ this._setCheckError(rtn, lastVal);
277
284
  return false;
278
285
  }
279
286
  }
@@ -282,197 +289,111 @@ export class Ctr {
282
289
  if (input[key] !== null) {
283
290
  const r = lCore.checkType(input[key], v.type);
284
291
  if (r) {
285
- rtn[0] = val[lastK][0];
286
- rtn[1] = typeof val[lastK][1] === 'string' ? val[lastK][1] + '(' + r + ')' : val[lastK][1];
287
- if (val[lastK][2]) {
288
- rtn[2] = val[lastK][2];
289
- }
292
+ this._setCheckError(rtn, lastVal, typeof lastVal[1] === 'string' ? `(${r})` : undefined);
290
293
  return false;
291
294
  }
292
295
  }
293
296
  }
294
297
  else {
298
+ /** --- 是否需要返回错误 --- */
299
+ let needReturn = false;
295
300
  switch (v) {
296
301
  case 'require': {
297
- if ((input[key] === null) || (input[key] === '')) {
298
- rtn[0] = val[lastK][0];
299
- rtn[1] = val[lastK][1];
300
- if (val[lastK][2]) {
301
- rtn[2] = val[lastK][2];
302
- }
303
- return false;
304
- }
302
+ needReturn = (input[key] === null) || (input[key] === '');
305
303
  break;
306
304
  }
307
305
  case 'int':
308
306
  case 'integer': {
309
307
  // --- 必须是数字型且是整数 ---
310
- if (input[key] && !Number.isSafeInteger(input[key])) {
311
- rtn[0] = val[lastK][0];
312
- rtn[1] = val[lastK][1];
313
- if (val[lastK][2]) {
314
- rtn[2] = val[lastK][2];
315
- }
316
- return false;
317
- }
308
+ needReturn = input[key] && !Number.isSafeInteger(input[key]);
318
309
  break;
319
310
  }
320
311
  case 'float':
321
312
  case 'double': {
322
313
  // --- 必须是数字型 ---
323
- if (input[key] && (typeof input[key] !== 'number')) {
324
- rtn[0] = val[lastK][0];
325
- rtn[1] = val[lastK][1];
326
- if (val[lastK][2]) {
327
- rtn[2] = val[lastK][2];
328
- }
329
- return false;
330
- }
314
+ needReturn = input[key] && (typeof input[key] !== 'number');
331
315
  break;
332
316
  }
333
317
  case 'num':
334
318
  case 'number': {
335
319
  // --- 可字符串数字 ---
336
- if (input[key] && (typeof input[key] !== 'number') && !/^[0-9]+\.?[0-9]*$/.test(input[key])) {
337
- rtn[0] = val[lastK][0];
338
- rtn[1] = val[lastK][1];
339
- if (val[lastK][2]) {
340
- rtn[2] = val[lastK][2];
341
- }
342
- return false;
343
- }
320
+ needReturn = input[key] && (typeof input[key] !== 'number') && !/^[0-9]+\.?[0-9]*$/.test(input[key]);
344
321
  break;
345
322
  }
346
323
  case 'array': {
347
- if (input[key] !== null && !Array.isArray(input[key])) {
348
- rtn[0] = val[lastK][0];
349
- rtn[1] = val[lastK][1];
350
- if (val[lastK][2]) {
351
- rtn[2] = val[lastK][2];
352
- }
353
- return false;
354
- }
324
+ needReturn = input[key] !== null && !Array.isArray(input[key]);
355
325
  break;
356
326
  }
357
327
  case 'bool':
358
328
  case 'boolean': {
359
- if (input[key] !== null && (typeof input[key] !== 'boolean')) {
360
- // --- 如果不是 bool 直接失败,字符串的 true, false 也会失败 ---
361
- rtn[0] = val[lastK][0];
362
- rtn[1] = val[lastK][1];
363
- if (val[lastK][2]) {
364
- rtn[2] = val[lastK][2];
365
- }
366
- return false;
367
- }
329
+ // --- 如果不是 bool 直接失败,字符串的 true, false 也会失败 ---
330
+ needReturn = input[key] !== null && (typeof input[key] !== 'boolean');
368
331
  break;
369
332
  }
370
333
  case 'string': {
371
- if (input[key] !== null && (typeof input[key] !== 'string')) {
372
- // --- 如果不是 string 直接失败 ---
373
- rtn[0] = val[lastK][0];
374
- rtn[1] = val[lastK][1];
375
- if (val[lastK][2]) {
376
- rtn[2] = val[lastK][2];
377
- }
378
- return false;
379
- }
334
+ // --- 如果不是 string 直接失败 ---
335
+ needReturn = input[key] !== null && (typeof input[key] !== 'string');
380
336
  break;
381
337
  }
382
338
  case 'ascii': {
383
339
  // --- 必须是 ASCII 字符 ---
384
- if (input[key] !== null && !lText.isAscii(input[key])) {
385
- rtn[0] = val[lastK][0];
386
- rtn[1] = val[lastK][1];
387
- if (val[lastK][2]) {
388
- rtn[2] = val[lastK][2];
389
- }
390
- return false;
391
- }
340
+ needReturn = input[key] !== null && !lText.isAscii(input[key]);
392
341
  break;
393
342
  }
394
343
  default: {
395
- let match;
396
- if (input[key] !== null) {
397
- if (v[0] === '/') {
398
- // --- 正则 ---
399
- if (!(new RegExp(v.slice(1, -1))).test(input[key])) {
400
- rtn[0] = val[lastK][0];
401
- rtn[1] = val[lastK][1];
402
- if (val[lastK][2]) {
403
- rtn[2] = val[lastK][2];
404
- }
405
- return false;
406
- }
407
- }
408
- else if ((match = /^([><=]+) *([0-9]+)$/.exec(v))) {
344
+ if (input[key] === null) {
345
+ break;
346
+ }
347
+ if (v[0] === '/') {
348
+ // --- 正则 ---
349
+ needReturn = !(new RegExp(v.slice(1, -1))).test(input[key]);
350
+ }
351
+ else {
352
+ const match = /^([><=]+) *([0-9]+)$/.exec(v);
353
+ if (match) {
409
354
  // --- 判断表达式 ---
410
- let needReturn = false;
411
355
  const inputNum = Number(input[key]);
412
356
  const num = Number(match[2]);
413
357
  switch (match[1]) {
414
358
  case '>': {
415
- if (inputNum <= num) {
416
- needReturn = true;
417
- }
359
+ needReturn = inputNum <= num;
418
360
  break;
419
361
  }
420
362
  case '<': {
421
- if (inputNum >= num) {
422
- needReturn = true;
423
- }
363
+ needReturn = inputNum >= num;
424
364
  break;
425
365
  }
426
366
  case '>=': {
427
- if (inputNum < num) {
428
- needReturn = true;
429
- }
367
+ needReturn = inputNum < num;
430
368
  break;
431
369
  }
432
370
  case '<=': {
433
- if (inputNum > num) {
434
- needReturn = true;
435
- }
371
+ needReturn = inputNum > num;
436
372
  break;
437
373
  }
438
374
  case '=':
439
375
  case '==':
440
376
  case '===': {
441
- if (inputNum !== num) {
442
- needReturn = true;
443
- }
377
+ needReturn = inputNum !== num;
444
378
  break;
445
379
  }
446
380
  case '!=':
447
381
  case '<>': {
448
- if (inputNum === num) {
449
- needReturn = true;
450
- }
382
+ needReturn = inputNum === num;
451
383
  break;
452
384
  }
453
385
  }
454
- if (needReturn) {
455
- rtn[0] = val[lastK][0];
456
- rtn[1] = val[lastK][1];
457
- if (val[lastK][2]) {
458
- rtn[2] = val[lastK][2];
459
- }
460
- return false;
461
- }
462
386
  }
463
387
  else {
464
- if (input[key] !== v) {
465
- rtn[0] = val[lastK][0];
466
- rtn[1] = val[lastK][1];
467
- if (val[lastK][2]) {
468
- rtn[2] = val[lastK][2];
469
- }
470
- return false;
471
- }
388
+ needReturn = input[key] !== v;
472
389
  }
473
390
  }
474
391
  }
475
392
  }
393
+ if (needReturn) {
394
+ this._setCheckError(rtn, lastVal);
395
+ return false;
396
+ }
476
397
  }
477
398
  }
478
399
  }
@@ -532,6 +453,8 @@ export class Ctr {
532
453
  }
533
454
  return 'unknown';
534
455
  }
456
+ /** --- auth 对象,user, pwd --- */
457
+ _authorization = null;
535
458
  /**
536
459
  * --- 通过 header 或 _auth 获取鉴权信息或 JWT 信息(不解析) ---
537
460
  */
package/sys/master.js CHANGED
@@ -138,6 +138,29 @@ function createRpcListener() {
138
138
  lCore.global[msg.key] = msg.data;
139
139
  break;
140
140
  }
141
+ case 'pm2': {
142
+ // --- 执行 PM2 操作 ---
143
+ if (!msg.name || typeof msg.name !== 'string') {
144
+ res.end('Invalid name');
145
+ return;
146
+ }
147
+ // --- 安全过滤,只允许字母、数字、下划线、短横线 ---
148
+ if (!/^[a-zA-Z0-9_-]+$/.test(msg.name)) {
149
+ res.end('Invalid name format');
150
+ return;
151
+ }
152
+ // --- 校验 pm2Action ---
153
+ if (msg.pm2Action !== 'start' && msg.pm2Action !== 'stop' && msg.pm2Action !== 'restart') {
154
+ res.end('Invalid pm2Action');
155
+ return;
156
+ }
157
+ const rtn = await lCore.exec(`pm2 ${msg.pm2Action} ${msg.name}`);
158
+ if (rtn === false) {
159
+ res.end('Exec failed');
160
+ return;
161
+ }
162
+ break;
163
+ }
141
164
  case 'code': {
142
165
  // --- 更新 code 代码包 ---
143
166
  const rtn = await sRoute.getFormData(req);
package/sys/mod.js CHANGED
@@ -9,6 +9,7 @@ import * as lCore from '#kebab/lib/core.js';
9
9
  import * as lText from '#kebab/lib/text.js';
10
10
  /** --- 条数列表 --- */
11
11
  export class Rows {
12
+ _items;
12
13
  constructor(initialItems = []) {
13
14
  this._items = initialItems;
14
15
  }
@@ -46,32 +47,36 @@ export class Rows {
46
47
  */
47
48
  export default class Mod {
48
49
  /** --- 表名 --- */
49
- static { this._$table = ''; }
50
+ static _$table = '';
50
51
  /** --- 主键字段名 --- */
51
- static { this._$primary = 'id'; }
52
+ static _$primary = 'id';
52
53
  /** --- 设置后将由 _keyGenerator 函数生成唯一字段 --- */
53
- static { this._$key = ''; }
54
+ static _$key = '';
54
55
  /** --- 若使用 _$key 并且有多个 unique 索引,这里指定 _$key 的索引名 --- */
55
- static { this._$index = ''; }
56
+ static _$index = '';
57
+ /** --- 前缀,顺序:选项前缀 -> 本前缀 -> 配置文件前缀 --- */
58
+ static _$pre;
59
+ /** --- 要 update 的内容 --- */
60
+ _updates = {};
61
+ /** --- 模型获取的属性 --- */
62
+ _data = {};
63
+ /** --- 当前选择的分表 _ 后缀,多个代表联查 --- */
64
+ _index = null;
65
+ /** --- 必须追加的数据筛选 key 与 values,仅单表模式有效 --- */
66
+ _contain = null;
67
+ /** --- 已算出的 total --- */
68
+ _total = [];
69
+ /** --- 数据库连接对象 --- */
70
+ _db;
71
+ /** --- Sql 对象 --- */
72
+ _sql;
73
+ /** --- ctr 对象 --- */
74
+ _ctr = undefined;
56
75
  /**
57
76
  * --- 构造函数 ---
58
77
  * @param opt 选项
59
78
  */
60
79
  constructor(opt) {
61
- /** --- 要 update 的内容 --- */
62
- this._updates = {};
63
- /** --- 模型获取的属性 --- */
64
- this._data = {};
65
- /** --- 当前选择的分表 _ 后缀,多个代表联查 --- */
66
- this._index = null;
67
- /** --- 必须追加的数据筛选 key 与 values,仅单表模式有效 --- */
68
- this._contain = null;
69
- /** --- 已算出的 total --- */
70
- this._total = [];
71
- /** --- ctr 对象 --- */
72
- this._ctr = undefined;
73
- /** --- 设置的 limit --- */
74
- this._limit = [0, 0];
75
80
  /** --- 导入 ctr 对象 --- */
76
81
  this._ctr = opt.ctr;
77
82
  /** --- 导入数据库连接 --- */
@@ -1303,6 +1308,8 @@ export default class Mod {
1303
1308
  this._sql.group(c);
1304
1309
  return this;
1305
1310
  }
1311
+ /** --- 设置的 limit --- */
1312
+ _limit = [0, 0];
1306
1313
  /**
1307
1314
  * --- LIMIT ---
1308
1315
  * @param a 起始