befly 3.24.19 → 3.25.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.
Files changed (68) 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/dict/_dict.js +24 -0
  9. package/apis/dict/all.js +6 -4
  10. package/apis/dict/detail.js +9 -5
  11. package/apis/dict/insert.js +4 -11
  12. package/apis/dict/items.js +5 -6
  13. package/apis/dict/select.js +13 -9
  14. package/apis/dict/update.js +9 -13
  15. package/apis/dictType/select.js +4 -4
  16. package/apis/email/config.js +9 -11
  17. package/apis/email/logList.js +14 -4
  18. package/apis/email/send.js +10 -3
  19. package/apis/email/verify.js +9 -7
  20. package/apis/loginLog/select.js +11 -4
  21. package/apis/menu/all.js +13 -19
  22. package/apis/menu/select.js +19 -14
  23. package/apis/operateLog/select.js +13 -4
  24. package/apis/role/_role.js +21 -0
  25. package/apis/role/all.js +1 -3
  26. package/apis/role/apiSave.js +8 -15
  27. package/apis/role/apis.js +4 -10
  28. package/apis/role/delete.js +28 -36
  29. package/apis/role/detail.js +4 -10
  30. package/apis/role/insert.js +12 -10
  31. package/apis/role/menuSave.js +9 -15
  32. package/apis/role/menus.js +4 -10
  33. package/apis/role/save.js +19 -23
  34. package/apis/role/select.js +12 -9
  35. package/apis/role/update.js +14 -15
  36. package/apis/source/imageList.js +3 -3
  37. package/apis/sysConfig/get.js +11 -23
  38. package/apis/sysConfig/insert.js +22 -27
  39. package/apis/sysConfig/select.js +11 -5
  40. package/apis/sysConfig/update.js +33 -38
  41. package/apis/tongJi/_tongJi.js +41 -0
  42. package/apis/tongJi/cacheHealth.js +8 -30
  43. package/apis/tongJi/errorList.js +26 -27
  44. package/apis/tongJi/errorReport.js +26 -43
  45. package/apis/tongJi/errorStats.js +17 -48
  46. package/apis/tongJi/fallbackReset.js +7 -15
  47. package/apis/tongJi/infoReport.js +20 -32
  48. package/apis/tongJi/infoStats.js +5 -17
  49. package/apis/tongJi/onlineReport.js +50 -56
  50. package/apis/tongJi/onlineStats.js +97 -111
  51. package/checks/config.js +44 -1
  52. package/configs/beflyConfig.json +10 -1
  53. package/index.js +25 -0
  54. package/lib/dbHelper.js +1 -1
  55. package/lib/dbParse.js +61 -99
  56. package/lib/dbUtil.js +101 -21
  57. package/lib/redisHelper.js +25 -0
  58. package/lib/sqlBuilder.js +6 -0
  59. package/package.json +1 -1
  60. package/plugins/email.js +3 -6
  61. package/router/api.js +0 -7
  62. package/utils/email.js +3 -0
  63. package/apis/admin/cacheRefresh.js +0 -122
  64. package/apis/dashboard/configStatus.js +0 -39
  65. package/apis/dashboard/performanceMetrics.js +0 -23
  66. package/apis/dashboard/permissionStats.js +0 -27
  67. package/apis/dashboard/systemInfo.js +0 -19
  68. package/lib/requestMetrics.js +0 -203
package/checks/config.js CHANGED
@@ -10,6 +10,47 @@ z.config(z.locales.zhCN());
10
10
  const boolIntSchema = z.union([z.literal(0), z.literal(1), z.literal(true), z.literal(false)]);
11
11
  const noTrimString = z.string().refine(isNoTrimStringAllowEmpty, "不允许首尾空格");
12
12
  const beflyModeSchema = z.union([z.literal("manual"), z.literal("auto")]);
13
+ const projectListCodeSchema = z.string().regex(/^[a-z][a-zA-Z0-9]*$/, "必须是小驼峰命名");
14
+ const projectListItemSchema = z
15
+ .object({
16
+ code: projectListCodeSchema,
17
+ name: noTrimString.min(1),
18
+ productName: noTrimString.min(1),
19
+ productCode: noTrimString.min(1),
20
+ productVersion: noTrimString.optional()
21
+ })
22
+ .strict();
23
+ const projectListsSchema = z.array(projectListItemSchema).superRefine((projectLists, ctx) => {
24
+ const codeSet = new Set();
25
+ const productCodeSet = new Set();
26
+
27
+ for (let index = 0; index < projectLists.length; index += 1) {
28
+ const code = projectLists[index].code;
29
+ const productCode = projectLists[index].productCode;
30
+
31
+ if (codeSet.has(code)) {
32
+ ctx.addIssue({
33
+ code: z.ZodIssueCode.custom,
34
+ message: `projectLists[${index}].code 重复`,
35
+ path: [index, "code"]
36
+ });
37
+ continue;
38
+ }
39
+
40
+ codeSet.add(code);
41
+
42
+ if (productCodeSet.has(productCode)) {
43
+ ctx.addIssue({
44
+ code: z.ZodIssueCode.custom,
45
+ message: `projectLists[${index}].productCode 重复`,
46
+ path: [index, "productCode"]
47
+ });
48
+ continue;
49
+ }
50
+
51
+ productCodeSet.add(productCode);
52
+ }
53
+ });
13
54
 
14
55
  const configSchema = z
15
56
  .object({
@@ -98,7 +139,9 @@ const configSchema = z
98
139
  skipRoutes: z.array(noTrimString),
99
140
  rules: z.array(z.object({}).passthrough())
100
141
  })
101
- .strict()
142
+ .strict(),
143
+
144
+ projectLists: projectListsSchema
102
145
  })
103
146
  .strict();
104
147
 
@@ -68,5 +68,14 @@
68
68
  "key": "ip",
69
69
  "skipRoutes": [],
70
70
  "rules": []
71
- }
71
+ },
72
+ "projectLists": [
73
+ {
74
+ "code": "beflyCore",
75
+ "name": "后台管理",
76
+ "productName": "后台管理",
77
+ "productCode": "beflyCore",
78
+ "productVersion": "1.0.0"
79
+ }
80
+ ]
72
81
  }
package/index.js CHANGED
@@ -55,6 +55,30 @@ function prefixMenuPaths(menus, prefix) {
55
55
  });
56
56
  }
57
57
 
58
+ function mergeProjectListsByCode(projectLists) {
59
+ if (!Array.isArray(projectLists)) {
60
+ return [];
61
+ }
62
+
63
+ const itemMap = new Map();
64
+
65
+ for (const item of projectLists) {
66
+ const code = String(item?.code || "");
67
+
68
+ if (!code) {
69
+ continue;
70
+ }
71
+
72
+ if (itemMap.has(code)) {
73
+ itemMap.delete(code);
74
+ }
75
+
76
+ itemMap.set(code, item);
77
+ }
78
+
79
+ return Array.from(itemMap.values());
80
+ }
81
+
58
82
  async function ensureSyncPrerequisites(ctx) {
59
83
  const missingCtxKeys = ["ctx.redis", "ctx.mysql", "ctx.cache"].filter((key) => !ctx[key.slice(4)]);
60
84
  if (missingCtxKeys.length > 0) {
@@ -90,6 +114,7 @@ async function ensureSyncPrerequisites(ctx) {
90
114
  export async function createBefly(env = {}, config = {}, menus = []) {
91
115
  const mergedConfig = deepMerge(beflyConfig, config);
92
116
  const mergedMenus = deepMerge(prefixMenuPaths(beflyMenus, "core"), menus);
117
+ mergedConfig.projectLists = mergeProjectListsByCode(mergedConfig.projectLists);
93
118
 
94
119
  const configHasError = await checkConfig(mergedConfig);
95
120
  const { apis, tables, plugins, hooks } = await scanSources();
package/lib/dbHelper.js CHANGED
@@ -436,7 +436,7 @@ class DbHelper {
436
436
  }
437
437
 
438
438
  prepareWriteInputData(data) {
439
- return serializeArrayFields(keysToSnake(fieldClear(data, { excludeValues: [null, undefined] })));
439
+ return serializeArrayFields(keysToSnake(fieldClear(data, { excludeValues: [undefined] })));
440
440
  }
441
441
 
442
442
  prepareWriteUserData(data, allowState) {
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
 
@@ -448,6 +448,31 @@ export class RedisHelper {
448
448
  }
449
449
  }
450
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
+
451
476
  /**
452
477
  * 删除键
453
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.19",
3
+ "version": "3.25.0",
4
4
  "gitHead": "49c39d36695036e85fc64083cc43c1652fff96cb",
5
5
  "private": false,
6
6
  "description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
package/plugins/email.js CHANGED
@@ -5,11 +5,8 @@
5
5
 
6
6
  import nodemailer from "nodemailer";
7
7
 
8
- import { Logger } from "../lib/logger.js";
9
-
10
- function canCreateTransport(config) {
11
- return Boolean(String(config?.host || "").trim() && String(config?.user || "").trim() && String(config?.pass || "").trim());
12
- }
8
+ import { Logger } from "#root/lib/logger.js";
9
+ import { hasEmailConfig } from "#root/utils/email.js";
13
10
 
14
11
  export default {
15
12
  order: 7,
@@ -17,7 +14,7 @@ export default {
17
14
  const config = befly?.config?.email || {};
18
15
  let transporter = null;
19
16
 
20
- if (canCreateTransport(config)) {
17
+ if (hasEmailConfig(config)) {
21
18
  try {
22
19
  transporter = nodemailer.createTransport({
23
20
  host: config.host,
package/router/api.js CHANGED
@@ -6,7 +6,6 @@
6
6
  import picomatch from "picomatch";
7
7
 
8
8
  import { Logger } from "../lib/logger.js";
9
- import { requestMetrics } from "../lib/requestMetrics.js";
10
9
  // 相对导入
11
10
  import { setCorsOptions } from "../utils/cors.js";
12
11
  import { getClientIp } from "../utils/getClientIp.js";
@@ -95,9 +94,6 @@ export function apiHandler(apis, hooks, context) {
95
94
  apiRequired: apiData.required,
96
95
  apiFile: apiData.filePath
97
96
  };
98
- const requestStartTime = Date.now();
99
- let requestHasError = false;
100
- requestMetrics.onRequestStart();
101
97
 
102
98
  try {
103
99
  // 4. 串联执行所有钩子
@@ -147,7 +143,6 @@ export function apiHandler(apis, hooks, context) {
147
143
  // 7. 返回响应(自动处理 response/result/日志)
148
144
  return FinalResponse(ctx);
149
145
  } catch (err) {
150
- requestHasError = true;
151
146
  // 全局错误处理
152
147
  Logger.error("请求错误", err, {
153
148
  path: ctx.apiPath,
@@ -167,8 +162,6 @@ export function apiHandler(apis, hooks, context) {
167
162
  msg: "内部服务错误"
168
163
  };
169
164
  return FinalResponse(ctx);
170
- } finally {
171
- requestMetrics.onRequestEnd(ctx.apiPath, Date.now() - requestStartTime, requestHasError);
172
165
  }
173
166
  };
174
167
  }
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
+ }