befly 3.76.6 → 3.77.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 (55) hide show
  1. package/Befly.js +99 -31
  2. package/apis/admin/delete.js +1 -1
  3. package/apis/admin/detail.js +1 -0
  4. package/apis/admin/insert.js +1 -1
  5. package/apis/admin/update.js +1 -1
  6. package/apis/auth/login.js +3 -2
  7. package/apis/dashboard/systemResources.js +12 -1
  8. package/apis/dict/detail.js +1 -0
  9. package/apis/dictType/detail.js +1 -0
  10. package/apis/email/config.js +1 -1
  11. package/apis/role/apiSave.js +14 -1
  12. package/apis/role/detail.js +1 -0
  13. package/apis/role/menuSave.js +1 -1
  14. package/apis/tongJi/_tongJi.js +16 -0
  15. package/apis/tongJi/dailyReport.js +5 -1
  16. package/apis/tongJi/dailyStatsDistribution.js +5 -4
  17. package/apis/tongJi/errorReport.js +8 -2
  18. package/apis/tongJi/todayOnline.js +2 -5
  19. package/checks/api.js +2 -6
  20. package/checks/field.js +30 -6
  21. package/checks/menu.js +2 -1
  22. package/configs/beflyConfig.json +3 -2
  23. package/exports.js +1 -0
  24. package/hooks/auth.js +7 -1
  25. package/hooks/permission.js +22 -1
  26. package/hooks/rateLimit.js +36 -5
  27. package/hooks/validator.js +5 -16
  28. package/index.js +44 -38
  29. package/libs/cacheHelper.js +13 -18
  30. package/libs/logger/logger.js +30 -12
  31. package/libs/memCache.js +53 -0
  32. package/libs/mysql/dbHelper.js +39 -71
  33. package/libs/mysql/dbParse.js +5 -1
  34. package/libs/mysql/sql/sqlBuilder.js +9 -0
  35. package/libs/redis/redis.js +44 -57
  36. package/libs/smtpText.js +41 -22
  37. package/libs/validator/compiler.js +1 -0
  38. package/libs/validator/parser.js +10 -4
  39. package/libs/validator/util.js +1 -5
  40. package/package.json +1 -1
  41. package/paths.js +23 -10
  42. package/router/static.js +13 -6
  43. package/schemas/api.json +4 -0
  44. package/schemas/config.json +20 -0
  45. package/sync/api.js +15 -7
  46. package/sync/dev.js +27 -3
  47. package/sync/menu.js +3 -3
  48. package/sync/syncUtil.js +12 -6
  49. package/tables/api.json +12 -0
  50. package/tables/emailLog.json +6 -2
  51. package/tables/menu.json +3 -0
  52. package/utils/is.js +7 -0
  53. package/utils/scanFiles.js +49 -46
  54. package/utils/scanSources.js +17 -0
  55. package/utils/util.js +5 -1
@@ -48,14 +48,18 @@
48
48
  "maxValue": 500,
49
49
  "paramType": "string",
50
50
  "fieldType": "varchar",
51
- "fieldDefault": ""
51
+ "fieldDefault": "",
52
+ "pattern": "^(|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}([;,][a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})*)$",
53
+ "minValue": 0
52
54
  },
53
55
  "bccEmail": {
54
56
  "name": "密送邮箱",
55
57
  "maxValue": 500,
56
58
  "paramType": "string",
57
59
  "fieldType": "varchar",
58
- "fieldDefault": ""
60
+ "fieldDefault": "",
61
+ "pattern": "^(|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}([;,][a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})*)$",
62
+ "minValue": 0
59
63
  },
60
64
  "sendTime": {
61
65
  "name": "发送时间",
package/tables/menu.json CHANGED
@@ -1,5 +1,8 @@
1
1
  {
2
2
  "$tableName": "菜单表",
3
+ "$tableIndexes": {
4
+ "uk_befly_menu_path": "unique#path"
5
+ },
3
6
  "name": {
4
7
  "name": "菜单名称",
5
8
  "minValue": 2,
package/utils/is.js CHANGED
@@ -28,3 +28,10 @@ export function isPrimaryProcess() {
28
28
  const instance = Bun.env.BM2_APP_INSTANCE;
29
29
  return instance === "0" || instance === 0 || instance === undefined;
30
30
  }
31
+
32
+ // 超级管理员判定(auth: "dev" 接口的准入口径):
33
+ // 必须是管理员表账号(roleType=admin)且角色代号为 dev 或 admin;
34
+ // 用户表账号 roleType 固定为 user,即使 roleCode=admin 也不具备超管接口权限
35
+ export function isSuperAdminContext(ctx) {
36
+ return ctx?.roleType === "admin" && (ctx?.roleCode === "dev" || ctx?.roleCode === "admin");
37
+ }
@@ -24,58 +24,61 @@ export async function scanFiles(dir, source, type, pattern) {
24
24
  const glob = new Bun.Glob(pattern);
25
25
  if (!existsSync(dir)) return [];
26
26
 
27
- const results = [];
27
+ const files = await Array.fromAsync(
28
+ glob.scan({
29
+ cwd: dir,
30
+ onlyFiles: true,
31
+ absolute: true,
32
+ followSymlinks: true
33
+ })
34
+ );
28
35
 
29
- const files = await glob.scan({
30
- cwd: dir,
31
- onlyFiles: true,
32
- absolute: true,
33
- followSymlinks: true
34
- });
36
+ // 并行导入源文件,Promise.all 保持与扫描顺序一致的结果顺序
37
+ const results = await Promise.all(
38
+ files.map(async (file) => {
39
+ const filePath = normalize(file);
40
+ const parsedFile = parse(filePath);
35
41
 
36
- for await (const file of files) {
37
- const filePath = normalize(file);
38
- const parsedFile = parse(filePath);
42
+ // 获取文件名(去除扩展名)
43
+ const fileName = parsedFile.name;
39
44
 
40
- // 获取文件名(去除扩展名)
41
- const fileName = parsedFile.name;
45
+ // 计算相对路径(去除扩展名)
46
+ const parsedRelativePath = parse(relative(dir, filePath));
47
+ const relativePath = (parsedRelativePath.dir ? join(parsedRelativePath.dir, parsedRelativePath.name) : parsedRelativePath.name).replaceAll("\\", "/");
42
48
 
43
- // 计算相对路径(去除扩展名)
44
- const parsedRelativePath = parse(relative(dir, filePath));
45
- const relativePath = (parsedRelativePath.dir ? join(parsedRelativePath.dir, parsedRelativePath.name) : parsedRelativePath.name).replaceAll("\\", "/");
49
+ // 固定默认过滤(不可关闭):忽略下划线开头的文件/目录
50
+ if (relativePath.split("/").some((part) => part.startsWith("_"))) return null;
51
+ const content = await importDefault(filePath, {});
52
+ const contentObj = isPlainObject(content) ? content : {};
46
53
 
47
- // 固定默认过滤(不可关闭):忽略下划线开头的文件/目录
48
- if (relativePath.split("/").some((part) => part.startsWith("_"))) continue;
49
- const content = await importDefault(filePath, {});
50
- const contentObj = isPlainObject(content) ? content : {};
51
-
52
- const base = {
53
- source: source,
54
- type: type,
55
- filePath: filePath,
56
- relativePath: relativePath,
57
- fileName: fileName,
58
- apiPath: source === "core" ? `/api/${source}/${relativePath}` : `/api/${relativePath}`
59
- };
60
- if (type === "table") {
61
- base["fieldsDef"] = contentObj;
62
- }
63
- if (type === "api") {
64
- base["name"] = "";
65
- }
66
- if (type !== "table") {
67
- for (const [key, value] of Object.entries(contentObj)) {
68
- if (base[key]) continue;
69
- base[key] = value;
54
+ const base = {
55
+ source: source,
56
+ type: type,
57
+ filePath: filePath,
58
+ relativePath: relativePath,
59
+ fileName: fileName,
60
+ apiPath: source === "core" ? `/api/${source}/${relativePath}` : `/api/${relativePath}`
61
+ };
62
+ if (type === "table") {
63
+ base["fieldsDef"] = contentObj;
64
+ }
65
+ if (type === "api") {
66
+ base["name"] = "";
67
+ }
68
+ if (type !== "table") {
69
+ for (const [key, value] of Object.entries(contentObj)) {
70
+ if (base[key]) continue;
71
+ base[key] = value;
72
+ }
73
+ }
74
+ if (type === "api") {
75
+ base.category ||= "other";
76
+ if (base.category === "select") base.fields = { ...selectFields, ...base.fields };
70
77
  }
71
- }
72
- if (type === "api") {
73
- base.category ||= "other";
74
- if (base.category === "select") base.fields = { ...selectFields, ...base.fields };
75
- }
76
- results.push(base);
77
- }
78
- return results;
78
+ return base;
79
+ })
80
+ );
81
+ return results.filter((item) => item !== null);
79
82
  } catch (error) {
80
83
  throw createError(`扫描失败: source=${source} type=${type} dir=${dir} pattern=${pattern}`, { cause: error, code: "runtime" });
81
84
  }
@@ -1,3 +1,6 @@
1
+ import { existsSync } from "node:fs";
2
+
3
+ import { Logger } from "../libs/logger/index.js";
1
4
  import { buildTableModel } from "../libs/tableModel.js";
2
5
  import {
3
6
  //
@@ -15,6 +18,18 @@ import {
15
18
  import { createError } from "./error.js";
16
19
  import { scanFiles } from "./scanFiles.js";
17
20
 
21
+ function warnWhenAppSourcesMissing() {
22
+ // 全部 app 源码目录都不存在时显式告警,避免"项目源码静默消失、以纯 core 形态运行"的配置事故
23
+ const appDirs = [appApiDir, appTableDir, appPluginDir, appHookDir, appCornDir];
24
+ if (appDirs.every((dir) => !existsSync(dir))) {
25
+ Logger.warn("项目源码目录(apis/tables/plugins/hooks/corns)均不存在,请检查启动目录是否为项目根目录", {
26
+ subsystem: "scan",
27
+ operation: "scanSources",
28
+ appDir: appDirs[0]
29
+ });
30
+ }
31
+ }
32
+
18
33
  export function assertUnique(items, key, label) {
19
34
  const values = new Map();
20
35
  for (const item of items) {
@@ -72,6 +87,8 @@ export const scanSources = async ({ beflyMode = "auto" } = {}) => {
72
87
  const tables = await scanTables({ beflyMode: beflyMode });
73
88
  const corns = [];
74
89
 
90
+ warnWhenAppSourcesMissing();
91
+
75
92
  // 处理插件
76
93
  const allCorePlugins = await scanFiles(corePluginDir, "core", "plugin", "*.js");
77
94
  for (const item of allCorePlugins) {
package/utils/util.js CHANGED
@@ -37,7 +37,11 @@ export function getRunMode() {
37
37
  }
38
38
 
39
39
  export function toSessionTtlSeconds(ttlDays) {
40
- return ttlDays * 24 * 60 * 60;
40
+ const parsed = typeof ttlDays === "number" ? ttlDays : Number(String(ttlDays ?? "").trim());
41
+ if (Number.isFinite(parsed) && parsed > 0) {
42
+ return Math.floor(parsed * 24 * 60 * 60);
43
+ }
44
+ return 7 * 24 * 60 * 60;
41
45
  }
42
46
 
43
47
  export function genShortId() {