befly 3.24.18 → 3.24.20

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.
Files changed (71) hide show
  1. package/apis/_apis.js +20 -0
  2. package/apis/admin/delete.js +3 -1
  3. package/apis/admin/detail.js +1 -3
  4. package/apis/admin/select.js +9 -7
  5. package/apis/admin/update.js +2 -2
  6. package/apis/api/all.js +6 -11
  7. package/apis/api/select.js +18 -19
  8. package/apis/dashboard/environmentInfo.js +6 -1
  9. package/apis/dashboard/serviceStatus.js +78 -60
  10. package/apis/dict/_dict.js +24 -0
  11. package/apis/dict/all.js +6 -4
  12. package/apis/dict/detail.js +9 -5
  13. package/apis/dict/insert.js +4 -11
  14. package/apis/dict/items.js +5 -6
  15. package/apis/dict/select.js +13 -9
  16. package/apis/dict/update.js +9 -13
  17. package/apis/dictType/select.js +4 -4
  18. package/apis/email/config.js +9 -11
  19. package/apis/email/logList.js +14 -4
  20. package/apis/email/send.js +10 -3
  21. package/apis/email/verify.js +9 -7
  22. package/apis/loginLog/select.js +11 -4
  23. package/apis/menu/all.js +13 -19
  24. package/apis/menu/select.js +19 -14
  25. package/apis/operateLog/select.js +13 -4
  26. package/apis/role/_role.js +21 -0
  27. package/apis/role/all.js +1 -3
  28. package/apis/role/apiSave.js +8 -15
  29. package/apis/role/apis.js +4 -10
  30. package/apis/role/delete.js +28 -36
  31. package/apis/role/detail.js +4 -10
  32. package/apis/role/insert.js +12 -10
  33. package/apis/role/menuSave.js +9 -15
  34. package/apis/role/menus.js +4 -10
  35. package/apis/role/save.js +19 -23
  36. package/apis/role/select.js +12 -9
  37. package/apis/role/update.js +14 -15
  38. package/apis/source/imageList.js +3 -3
  39. package/apis/sysConfig/get.js +11 -23
  40. package/apis/sysConfig/insert.js +22 -27
  41. package/apis/sysConfig/select.js +11 -5
  42. package/apis/sysConfig/update.js +33 -38
  43. package/apis/tongJi/_tongJi.js +41 -0
  44. package/apis/tongJi/cacheHealth.js +192 -0
  45. package/apis/tongJi/errorList.js +26 -27
  46. package/apis/tongJi/errorReport.js +72 -18
  47. package/apis/tongJi/errorStats.js +154 -30
  48. package/apis/tongJi/fallbackReset.js +61 -0
  49. package/apis/tongJi/infoReport.js +112 -24
  50. package/apis/tongJi/infoStats.js +164 -84
  51. package/apis/tongJi/onlineReport.js +58 -73
  52. package/apis/tongJi/onlineStats.js +140 -151
  53. package/checks/config.js +44 -1
  54. package/configs/beflyConfig.json +10 -1
  55. package/hooks/permission.js +6 -2
  56. package/hooks/rateLimit.js +245 -0
  57. package/index.js +25 -0
  58. package/lib/cacheHelper.js +105 -60
  59. package/lib/dbHelper.js +1 -1
  60. package/lib/dbParse.js +61 -99
  61. package/lib/dbUtil.js +101 -21
  62. package/lib/redisHelper.js +93 -0
  63. package/lib/sqlBuilder.js +6 -0
  64. package/package.json +2 -2
  65. package/plugins/email.js +4 -3
  66. package/utils/email.js +3 -0
  67. package/apis/admin/cacheRefresh.js +0 -122
  68. package/apis/dashboard/configStatus.js +0 -39
  69. package/apis/dashboard/performanceMetrics.js +0 -20
  70. package/apis/dashboard/permissionStats.js +0 -27
  71. package/apis/dashboard/systemInfo.js +0 -19
package/lib/dbParse.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { isNonEmptyString, isNullable, isPlainObject, isString } from "../utils/is.js";
2
2
  import { snakeCase } from "../utils/util.js";
3
- import { assertNoExprField, clearDeep, fieldsToSnake, normalizeQualifierField, orderByToSnake, parseFieldAliasParts, parseTableRef, processJoinField, processJoinOn, processJoinOrderBy, processJoinWhere, whereKeysToSnake } from "./dbUtil.js";
3
+ import { assertNoExprField, clearDeep, fieldsToSnake, normalizeQualifierField, orderByToSnake, parseFieldAliasParts, parseFlatWhereKey, parseTableRef, processJoinField, processJoinOn, processJoinOrderBy, processJoinWhere, whereKeysToSnake } from "./dbUtil.js";
4
4
 
5
5
  function assertNonEmptyString(value, label) {
6
6
  if (!isNonEmptyString(value)) {
@@ -38,48 +38,47 @@ function validateWhereObject(where, label, required = false) {
38
38
  });
39
39
  }
40
40
 
41
- const visitWhereMeta = (currentWhere, currentLabel) => {
42
- for (const [key, value] of Object.entries(currentWhere)) {
43
- if (key === "$exclude") {
44
- if (isNullable(value)) {
45
- continue;
46
- }
41
+ for (const [key, value] of Object.entries(where)) {
42
+ if (key === "$exclude") {
43
+ if (isNullable(value)) {
44
+ continue;
45
+ }
47
46
 
48
- if (!isPlainObject(value)) {
49
- throw new Error(`${currentLabel}.$exclude 必须是对象`, {
47
+ if (!isPlainObject(value)) {
48
+ throw new Error(`${label}.$exclude 必须是对象`, {
49
+ cause: null,
50
+ code: "validation"
51
+ });
52
+ }
53
+
54
+ for (const [excludeKey, excludeValue] of Object.entries(value)) {
55
+ assertNonEmptyString(excludeKey, `${label}.$exclude 的字段名`);
56
+ if (!Array.isArray(excludeValue)) {
57
+ throw new Error(`${label}.$exclude.${excludeKey} 必须是数组`, {
50
58
  cause: null,
51
59
  code: "validation"
52
60
  });
53
61
  }
54
-
55
- for (const [excludeKey, excludeValue] of Object.entries(value)) {
56
- assertNonEmptyString(excludeKey, `${currentLabel}.$exclude 的字段名`);
57
- if (!Array.isArray(excludeValue)) {
58
- throw new Error(`${currentLabel}.$exclude.${excludeKey} 必须是数组`, {
59
- cause: null,
60
- code: "validation"
61
- });
62
- }
63
- }
64
- continue;
65
- }
66
-
67
- if ((key === "$or" || key === "$and") && Array.isArray(value)) {
68
- for (let i = 0; i < value.length; i++) {
69
- if (isPlainObject(value[i])) {
70
- visitWhereMeta(value[i], `${currentLabel}.${key}[${i}]`);
71
- }
72
- }
73
- continue;
74
62
  }
63
+ continue;
64
+ }
75
65
 
76
- if (isPlainObject(value)) {
77
- visitWhereMeta(value, `${currentLabel}.${key}`);
78
- }
66
+ if (key === "$or" || key === "$and") {
67
+ throw new Error(`${label} 不再支持 ${key} 嵌套,请改用 field$op$or 扁平写法`, {
68
+ cause: null,
69
+ code: "validation"
70
+ });
79
71
  }
80
- };
81
72
 
82
- visitWhereMeta(where, label);
73
+ parseFlatWhereKey(key);
74
+
75
+ if (isPlainObject(value)) {
76
+ throw new Error(`${label}.${key} 不再支持嵌套对象操作符,请改用 field$op 扁平写法`, {
77
+ cause: null,
78
+ code: "validation"
79
+ });
80
+ }
81
+ }
83
82
  }
84
83
 
85
84
  function validateOrderByItems(orderBy, label) {
@@ -563,6 +562,14 @@ function appendWhereNode(root, node) {
563
562
  }
564
563
  }
565
564
 
565
+ function normalizeOrGroupKey(orGroup) {
566
+ if (orGroup === "or") {
567
+ return "or1";
568
+ }
569
+
570
+ return orGroup;
571
+ }
572
+
566
573
  function buildArrayOperatorNode(fieldName, operator, value, errorFactory, emptyMessage) {
567
574
  if (!Array.isArray(value)) {
568
575
  throw new Error(errorFactory(operator), {
@@ -620,69 +627,33 @@ function buildOperatorNode(fieldName, operator, value) {
620
627
  }
621
628
  }
622
629
 
623
- function appendFieldCondition(group, key, value) {
624
- if (key.includes("$")) {
625
- const lastDollarIndex = key.lastIndexOf("$");
626
- appendWhereNode(group, buildOperatorNode(key.substring(0, lastDollarIndex), `$${key.substring(lastDollarIndex + 1)}`, value));
627
- return;
628
- }
630
+ function appendFieldCondition(group, orGroups, key, value) {
631
+ const parsed = parseFlatWhereKey(key);
632
+ const node = buildOperatorNode(parsed.field, parsed.operator, value);
629
633
 
630
- if (value && typeof value === "object" && !Array.isArray(value)) {
631
- for (const [operator, operatorValue] of Object.entries(value)) {
632
- appendWhereNode(group, buildOperatorNode(key, operator, operatorValue));
633
- }
634
- return;
635
- }
636
-
637
- appendWhereNode(group, buildOperatorNode(key, "=", value));
638
- }
639
-
640
- function appendAndConditions(group, value) {
641
- if (!Array.isArray(value)) {
634
+ if (!node) {
642
635
  return;
643
636
  }
644
637
 
645
- for (const condition of value) {
646
- if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
647
- continue;
648
- }
649
-
650
- const sub = parseWhereObject(condition);
651
- if (sub.items.length === 0) {
652
- continue;
653
- }
654
-
655
- if (sub.join === "AND") {
656
- for (const item of sub.items) {
657
- group.items.push(item);
658
- }
659
- continue;
660
- }
661
-
662
- group.items.push(sub);
663
- }
664
- }
665
-
666
- function appendOrConditions(group, value) {
667
- if (!Array.isArray(value)) {
638
+ if (!parsed.orGroup) {
639
+ appendWhereNode(group, node);
668
640
  return;
669
641
  }
670
642
 
671
- const orGroup = createWhereRoot("OR");
672
- for (const condition of value) {
673
- if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
674
- continue;
675
- }
643
+ const orGroupKey = normalizeOrGroupKey(parsed.orGroup);
644
+ let orGroup = orGroups.get(orGroupKey);
676
645
 
677
- const sub = parseWhereObject(condition);
678
- if (sub.items.length > 0) {
679
- orGroup.items.push(sub);
680
- }
646
+ if (!orGroup) {
647
+ orGroup = createWhereRoot("OR");
648
+ orGroups.set(orGroupKey, orGroup);
649
+ appendWhereNode(group, orGroup);
681
650
  }
682
651
 
683
- if (orGroup.items.length > 0) {
684
- group.items.push(orGroup);
685
- }
652
+ appendWhereNode(orGroup, {
653
+ type: "group",
654
+ join: "AND",
655
+ items: [node]
656
+ });
686
657
  }
687
658
 
688
659
  function parseWhereObject(whereObj) {
@@ -691,23 +662,14 @@ function parseWhereObject(whereObj) {
691
662
  }
692
663
 
693
664
  const group = createWhereRoot();
665
+ const orGroups = new Map();
694
666
 
695
667
  for (const [key, value] of Object.entries(whereObj)) {
696
- if (value === undefined) {
697
- continue;
698
- }
699
-
700
- if (key === "$and") {
701
- appendAndConditions(group, value);
702
- continue;
703
- }
704
-
705
- if (key === "$or") {
706
- appendOrConditions(group, value);
668
+ if (value === undefined || key === "$exclude") {
707
669
  continue;
708
670
  }
709
671
 
710
- appendFieldCondition(group, key, value);
672
+ appendFieldCondition(group, orGroups, key, value);
711
673
  }
712
674
 
713
675
  return group;
package/lib/dbUtil.js CHANGED
@@ -252,7 +252,17 @@ function shouldExcludeFieldValue(field, value, excludeValueMap) {
252
252
  return false;
253
253
  }
254
254
 
255
- const candidateValues = excludeValueMap.get(field);
255
+ let normalizedField = field;
256
+
257
+ if (isString(field) && field !== "$or" && field !== "$and" && field !== "$exclude") {
258
+ try {
259
+ normalizedField = parseFlatWhereKey(field).field;
260
+ } catch {
261
+ normalizedField = field;
262
+ }
263
+ }
264
+
265
+ const candidateValues = excludeValueMap.get(normalizedField) || excludeValueMap.get(field);
256
266
  if (!candidateValues || candidateValues.length === 0) {
257
267
  return false;
258
268
  }
@@ -320,7 +330,7 @@ export function clearDeep(value, options) {
320
330
  continue;
321
331
  }
322
332
 
323
- if (isNullable(item)) {
333
+ if (item === undefined) {
324
334
  continue;
325
335
  }
326
336
 
@@ -354,7 +364,7 @@ export function clearDeep(value, options) {
354
364
  continue;
355
365
  }
356
366
 
357
- if (typeof item === "object" && !Array.isArray(item)) {
367
+ if (item !== null && typeof item === "object" && !Array.isArray(item)) {
358
368
  if (!canRecurse) {
359
369
  result[key] = item;
360
370
  continue;
@@ -437,6 +447,89 @@ function normalizeOrderBy(orderBy, mapField) {
437
447
  });
438
448
  }
439
449
 
450
+ const flatWhereOperatorTokens = new Set(["not", "in", "notIn", "like", "leftLike", "rightLike", "notLike", "gt", "gte", "lt", "lte", "between", "notBetween", "null", "notNull"]);
451
+
452
+ function isFlatWhereOrGroupToken(token) {
453
+ return token === "or" || /^or\d+$/.test(token);
454
+ }
455
+
456
+ function buildFlatWhereKey(field, operator, orGroup) {
457
+ let key = field;
458
+
459
+ if (operator && operator !== "=") {
460
+ key += operator;
461
+ }
462
+
463
+ if (orGroup) {
464
+ key += `$${orGroup}`;
465
+ }
466
+
467
+ return key;
468
+ }
469
+
470
+ export function parseFlatWhereKey(key) {
471
+ if (!isNonEmptyString(key)) {
472
+ throw new Error("where 字段名不能为空", {
473
+ cause: null,
474
+ code: "validation"
475
+ });
476
+ }
477
+
478
+ assertNoExprField(key);
479
+
480
+ const rawParts = key.split("$").map((item) => item.trim());
481
+ const field = rawParts[0];
482
+
483
+ if (!isNonEmptyString(field)) {
484
+ throw new Error(`where 字段名不能为空 (field: ${key})`, {
485
+ cause: null,
486
+ code: "validation"
487
+ });
488
+ }
489
+
490
+ const suffixParts = rawParts.slice(1);
491
+ let orGroup = null;
492
+
493
+ if (suffixParts.length > 0 && isFlatWhereOrGroupToken(suffixParts[suffixParts.length - 1])) {
494
+ orGroup = suffixParts.pop();
495
+ }
496
+
497
+ if (suffixParts.length > 1) {
498
+ throw new Error(`where 字段后缀格式无效,请使用 field、field$op、field$or、field$op$orN (field: ${key})`, {
499
+ cause: null,
500
+ code: "validation"
501
+ });
502
+ }
503
+
504
+ if (suffixParts.length === 0) {
505
+ return {
506
+ field: field,
507
+ operator: "=",
508
+ orGroup: orGroup
509
+ };
510
+ }
511
+
512
+ const operatorToken = suffixParts[0];
513
+
514
+ if (!flatWhereOperatorTokens.has(operatorToken)) {
515
+ throw new Error(`where 操作符无效,请使用受支持的扁平操作符 (field: ${key}, operator: ${operatorToken})`, {
516
+ cause: null,
517
+ code: "validation"
518
+ });
519
+ }
520
+
521
+ return {
522
+ field: field,
523
+ operator: `$${operatorToken}`,
524
+ orGroup: orGroup
525
+ };
526
+ }
527
+
528
+ function normalizeFlatWhereKey(key, mapField) {
529
+ const parsed = parseFlatWhereKey(key);
530
+ return buildFlatWhereKey(mapField(parsed.field), parsed.operator, parsed.orGroup);
531
+ }
532
+
440
533
  function mapWhereKeys(where, mapKey) {
441
534
  if (!where || typeof where !== "object") {
442
535
  return where;
@@ -483,20 +576,11 @@ export function processJoinField(field) {
483
576
  }
484
577
 
485
578
  function processJoinWhereKey(key) {
486
- if (key === "$or" || key === "$and") {
579
+ if (key === "$or" || key === "$and" || key === "$exclude") {
487
580
  return key;
488
581
  }
489
582
 
490
- assertNoExprField(key);
491
- if (key.includes("$")) {
492
- const lastDollarIndex = key.lastIndexOf("$");
493
- const fieldPart = key.substring(0, lastDollarIndex);
494
- const operator = key.substring(lastDollarIndex);
495
-
496
- return `${normalizeQualifierField(fieldPart)}${operator}`;
497
- }
498
-
499
- return normalizeQualifierField(key);
583
+ return normalizeFlatWhereKey(key, normalizeQualifierField);
500
584
  }
501
585
 
502
586
  export function processJoinWhere(where) {
@@ -565,15 +649,11 @@ export function processJoinOn(on) {
565
649
 
566
650
  export function whereKeysToSnake(where) {
567
651
  return mapWhereKeys(where, (key) => {
568
- assertNoExprField(key);
569
- if (key.includes("$")) {
570
- const lastDollarIndex = key.lastIndexOf("$");
571
- const fieldName = key.substring(0, lastDollarIndex);
572
- const operator = key.substring(lastDollarIndex);
573
- return `${snakeCase(fieldName)}${operator}`;
652
+ if (key === "$or" || key === "$and" || key === "$exclude") {
653
+ return key;
574
654
  }
575
655
 
576
- return snakeCase(key);
656
+ return normalizeFlatWhereKey(key, (field) => snakeCase(field));
577
657
  });
578
658
  }
579
659
 
@@ -380,6 +380,99 @@ export class RedisHelper {
380
380
  }
381
381
  }
382
382
 
383
+ /**
384
+ * 向有序集合添加成员
385
+ * @param key - 键名
386
+ * @param items - [{ score, member }] 数组
387
+ * @returns 成功添加的成员数量
388
+ */
389
+ async zadd(key, items) {
390
+ try {
391
+ if (items.length === 0) {
392
+ return 0;
393
+ }
394
+
395
+ const pkey = `${this.prefix}${key}`;
396
+ const args = [pkey];
397
+
398
+ for (const item of items) {
399
+ const score = Number(item.score);
400
+ const member = String(item.member);
401
+
402
+ if (!Number.isFinite(score) || member.length === 0) {
403
+ continue;
404
+ }
405
+
406
+ args.push(score);
407
+ args.push(member);
408
+ }
409
+
410
+ if (args.length <= 1) {
411
+ return 0;
412
+ }
413
+
414
+ return await this.client.zadd.apply(this.client, args);
415
+ } catch (error) {
416
+ Logger.error("Redis zadd 错误", error);
417
+ return 0;
418
+ }
419
+ }
420
+
421
+ /**
422
+ * 获取有序集合成员数量
423
+ * @param key - 键名
424
+ */
425
+ async zcard(key) {
426
+ try {
427
+ const pkey = `${this.prefix}${key}`;
428
+ return await this.client.zcard(pkey);
429
+ } catch (error) {
430
+ Logger.error("Redis zcard 错误", error);
431
+ return 0;
432
+ }
433
+ }
434
+
435
+ /**
436
+ * 按 score 区间删除有序集合成员
437
+ * @param key - 键名
438
+ * @param min - 最小分值
439
+ * @param max - 最大分值
440
+ */
441
+ async zremrangebyscore(key, min, max) {
442
+ try {
443
+ const pkey = `${this.prefix}${key}`;
444
+ return await this.client.zremrangebyscore(pkey, min, max);
445
+ } catch (error) {
446
+ Logger.error("Redis zremrangebyscore 错误", error);
447
+ return 0;
448
+ }
449
+ }
450
+
451
+ /**
452
+ * 从有序集合删除一个或多个成员
453
+ * @param key - 键名
454
+ * @param members - 成员数组
455
+ */
456
+ async zrem(key, members) {
457
+ try {
458
+ if (members.length === 0) {
459
+ return 0;
460
+ }
461
+
462
+ const pkey = `${this.prefix}${key}`;
463
+ const args = [pkey];
464
+
465
+ for (const member of members) {
466
+ args.push(String(member));
467
+ }
468
+
469
+ return await this.client.zrem.apply(this.client, args);
470
+ } catch (error) {
471
+ Logger.error("Redis zrem 错误", error);
472
+ return 0;
473
+ }
474
+ }
475
+
383
476
  /**
384
477
  * 删除键
385
478
  * @param key - 键名
package/lib/sqlBuilder.js CHANGED
@@ -47,6 +47,9 @@ export class SqlBuilder {
47
47
 
48
48
  switch (node.operator) {
49
49
  case "$not":
50
+ if (node.value === null) {
51
+ return { sql: `${escapedField} IS NOT NULL`, params: [] };
52
+ }
50
53
  return { sql: `${escapedField} != ?`, params: [node.value] };
51
54
  case "$in":
52
55
  return {
@@ -83,6 +86,9 @@ export class SqlBuilder {
83
86
  case "$notNull":
84
87
  return { sql: `${escapedField} IS NOT NULL`, params: [] };
85
88
  default:
89
+ if (node.value === null) {
90
+ return { sql: `${escapedField} IS NULL`, params: [] };
91
+ }
86
92
  return { sql: `${escapedField} = ?`, params: [node.value] };
87
93
  }
88
94
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.24.18",
3
+ "version": "3.24.20",
4
4
  "gitHead": "49c39d36695036e85fc64083cc43c1652fff96cb",
5
5
  "private": false,
6
6
  "description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "fast-xml-parser": "^5.8.0",
59
- "nodemailer": "^8.0.7",
59
+ "nodemailer": "^8.0.9",
60
60
  "pathe": "^2.0.3",
61
61
  "picomatch": "^4.0.4",
62
62
  "ua-parser-js": "^2.0.10",
package/plugins/email.js CHANGED
@@ -5,7 +5,8 @@
5
5
 
6
6
  import nodemailer from "nodemailer";
7
7
 
8
- import { Logger } from "../lib/logger.js";
8
+ import { Logger } from "#root/lib/logger.js";
9
+ import { hasEmailConfig } from "#root/utils/email.js";
9
10
 
10
11
  export default {
11
12
  order: 7,
@@ -13,12 +14,12 @@ export default {
13
14
  const config = befly?.config?.email || {};
14
15
  let transporter = null;
15
16
 
16
- if (config && config.host && config.user) {
17
+ if (hasEmailConfig(config)) {
17
18
  try {
18
19
  transporter = nodemailer.createTransport({
19
20
  host: config.host,
20
21
  port: config.port || 25,
21
- secure: config.ssl,
22
+ secure: config.secure ?? config.ssl,
22
23
  auth: {
23
24
  user: config.user,
24
25
  pass: config.pass
package/utils/email.js ADDED
@@ -0,0 +1,3 @@
1
+ export function hasEmailConfig(config) {
2
+ return Boolean(String(config?.host || "").trim() && String(config?.user || "").trim() && String(config?.pass || "").trim());
3
+ }
@@ -1,122 +0,0 @@
1
- /**
2
- * 刷新全部缓存接口
3
- *
4
- * 功能:
5
- * 1. 刷新接口缓存(apis:all)
6
- * 2. 刷新菜单缓存(menus:all)
7
- * 3. 刷新角色缓存(role:info:{code})
8
- * 4. 重建角色接口权限缓存(role:apis:{code},Set)
9
- * 5. 重建角色菜单权限缓存(role:menus:{code},Set)
10
- *
11
- * 使用场景:
12
- * - 执行数据库同步后
13
- * - 手动修改配置需要立即生效
14
- * - 缓存出现异常需要重建
15
- */
16
-
17
- import { isNonEmptyString } from "#root/utils/is.js";
18
-
19
- export default {
20
- name: "刷新全部缓存",
21
- method: "POST",
22
- body: "none",
23
- auth: true,
24
- fields: {},
25
- required: [],
26
- handler: async (befly) => {
27
- try {
28
- const results = {
29
- apis: { success: false, count: 0 },
30
- menus: { success: false, count: 0 },
31
- roles: { success: false, count: 0 },
32
- roleApiPermissions: { success: false },
33
- roleMenuPermissions: { success: false }
34
- };
35
-
36
- // 1. 刷新接口缓存
37
- try {
38
- const apis = await befly.mysql.getAll({
39
- table: "beflyApi"
40
- });
41
-
42
- await befly.redis.setObject("apis:all", apis.data.lists);
43
- results["apis"] = { success: true, count: apis.data.lists.length };
44
- } catch (error) {
45
- befly.logger.error("刷新接口缓存失败", error);
46
- results["apis"] = { success: false, error: error.message };
47
- }
48
-
49
- // 2. 刷新菜单缓存
50
- try {
51
- const menus = await befly.mysql.getAll({
52
- table: "beflyMenu"
53
- });
54
-
55
- await befly.redis.setObject("menus:all", menus.data.lists);
56
-
57
- const parentCount = menus.data.lists.filter((m) => !isNonEmptyString(m.parentPath)).length;
58
- const childCount = menus.data.lists.filter((m) => isNonEmptyString(m.parentPath)).length;
59
-
60
- results["menus"] = {
61
- success: true,
62
- count: menus.data.lists.length,
63
- parentCount: parentCount,
64
- childCount: childCount
65
- };
66
- } catch (error) {
67
- befly.logger.error("刷新菜单缓存失败", error);
68
- results["menus"] = { success: false, error: error.message };
69
- }
70
-
71
- // 3. 刷新角色权限缓存
72
- try {
73
- const roles = await befly.mysql.getAll({
74
- table: "beflyRole"
75
- });
76
-
77
- // 使用 setBatch 批量缓存所有角色
78
- const count = await befly.redis.setBatch(
79
- roles.data.lists.map((role) => ({
80
- key: `role:info:${role.code}`,
81
- value: role
82
- }))
83
- );
84
-
85
- results["roles"] = { success: true, count: count };
86
- } catch (error) {
87
- befly.logger.error("刷新角色缓存失败", error);
88
- results["roles"] = { success: false, error: error.message };
89
- }
90
-
91
- // 4. 重建角色接口权限缓存(版本化 + 原子切换)
92
- try {
93
- await befly.cache.cacheRoleApis();
94
- results["roleApiPermissions"] = { success: true };
95
- } catch (error) {
96
- befly.logger.error("重建角色接口权限缓存失败", error);
97
- results["roleApiPermissions"] = { success: false, error: error.message };
98
- }
99
-
100
- // 5. 重建角色菜单权限缓存
101
- try {
102
- await befly.cache.cacheRoleMenus();
103
- results["roleMenuPermissions"] = { success: true };
104
- } catch (error) {
105
- befly.logger.error("重建角色菜单权限缓存失败", error);
106
- results["roleMenuPermissions"] = { success: false, error: error.message };
107
- }
108
-
109
- // 检查是否全部成功
110
- const allSuccess = results["apis"].success && results["menus"].success && results["roles"].success && results["roleApiPermissions"].success && results["roleMenuPermissions"].success;
111
-
112
- if (allSuccess) {
113
- return befly.tool.Yes("全部缓存刷新成功", results);
114
- } else {
115
- return befly.tool.No("部分缓存刷新失败", results);
116
- }
117
- } catch (error) {
118
- befly.logger.error("刷新全部缓存失败", error);
119
- return befly.tool.No("刷新全部缓存失败", { error: error.message });
120
- }
121
- }
122
- };