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.
- package/apis/_apis.js +20 -0
- package/apis/admin/delete.js +3 -1
- package/apis/admin/detail.js +1 -3
- package/apis/admin/select.js +9 -7
- package/apis/admin/update.js +2 -2
- package/apis/api/all.js +6 -11
- package/apis/api/select.js +18 -19
- package/apis/dashboard/environmentInfo.js +6 -1
- package/apis/dashboard/serviceStatus.js +78 -60
- package/apis/dict/_dict.js +24 -0
- package/apis/dict/all.js +6 -4
- package/apis/dict/detail.js +9 -5
- package/apis/dict/insert.js +4 -11
- package/apis/dict/items.js +5 -6
- package/apis/dict/select.js +13 -9
- package/apis/dict/update.js +9 -13
- package/apis/dictType/select.js +4 -4
- package/apis/email/config.js +9 -11
- package/apis/email/logList.js +14 -4
- package/apis/email/send.js +10 -3
- package/apis/email/verify.js +9 -7
- package/apis/loginLog/select.js +11 -4
- package/apis/menu/all.js +13 -19
- package/apis/menu/select.js +19 -14
- package/apis/operateLog/select.js +13 -4
- package/apis/role/_role.js +21 -0
- package/apis/role/all.js +1 -3
- package/apis/role/apiSave.js +8 -15
- package/apis/role/apis.js +4 -10
- package/apis/role/delete.js +28 -36
- package/apis/role/detail.js +4 -10
- package/apis/role/insert.js +12 -10
- package/apis/role/menuSave.js +9 -15
- package/apis/role/menus.js +4 -10
- package/apis/role/save.js +19 -23
- package/apis/role/select.js +12 -9
- package/apis/role/update.js +14 -15
- package/apis/source/imageList.js +3 -3
- package/apis/sysConfig/get.js +11 -23
- package/apis/sysConfig/insert.js +22 -27
- package/apis/sysConfig/select.js +11 -5
- package/apis/sysConfig/update.js +33 -38
- package/apis/tongJi/_tongJi.js +41 -0
- package/apis/tongJi/cacheHealth.js +192 -0
- package/apis/tongJi/errorList.js +26 -27
- package/apis/tongJi/errorReport.js +72 -18
- package/apis/tongJi/errorStats.js +154 -30
- package/apis/tongJi/fallbackReset.js +61 -0
- package/apis/tongJi/infoReport.js +112 -24
- package/apis/tongJi/infoStats.js +164 -84
- package/apis/tongJi/onlineReport.js +58 -73
- package/apis/tongJi/onlineStats.js +140 -151
- package/checks/config.js +44 -1
- package/configs/beflyConfig.json +10 -1
- package/hooks/permission.js +6 -2
- package/hooks/rateLimit.js +245 -0
- package/index.js +25 -0
- package/lib/cacheHelper.js +105 -60
- package/lib/dbHelper.js +1 -1
- package/lib/dbParse.js +61 -99
- package/lib/dbUtil.js +101 -21
- package/lib/redisHelper.js +93 -0
- package/lib/sqlBuilder.js +6 -0
- package/package.json +2 -2
- package/plugins/email.js +4 -3
- package/utils/email.js +3 -0
- package/apis/admin/cacheRefresh.js +0 -122
- package/apis/dashboard/configStatus.js +0 -39
- package/apis/dashboard/performanceMetrics.js +0 -20
- package/apis/dashboard/permissionStats.js +0 -27
- 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
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
41
|
+
for (const [key, value] of Object.entries(where)) {
|
|
42
|
+
if (key === "$exclude") {
|
|
43
|
+
if (isNullable(value)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
625
|
-
|
|
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 (
|
|
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
|
-
|
|
646
|
-
|
|
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
|
|
672
|
-
|
|
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
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
646
|
+
if (!orGroup) {
|
|
647
|
+
orGroup = createWhereRoot("OR");
|
|
648
|
+
orGroups.set(orGroupKey, orGroup);
|
|
649
|
+
appendWhereNode(group, orGroup);
|
|
681
650
|
}
|
|
682
651
|
|
|
683
|
-
|
|
684
|
-
group
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
569
|
-
|
|
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
|
|
656
|
+
return normalizeFlatWhereKey(key, (field) => snakeCase(field));
|
|
577
657
|
});
|
|
578
658
|
}
|
|
579
659
|
|
package/lib/redisHelper.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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 "
|
|
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
|
|
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
|
@@ -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
|
-
};
|