befly 3.17.0 → 3.17.3

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 (150) hide show
  1. package/README.md +6 -0
  2. package/apis/admin/cacheRefresh.js +122 -0
  3. package/apis/admin/del.js +34 -0
  4. package/apis/admin/detail.js +23 -0
  5. package/apis/admin/ins.js +69 -0
  6. package/apis/admin/list.js +28 -0
  7. package/apis/admin/upd.js +95 -0
  8. package/apis/api/all.js +24 -0
  9. package/apis/api/list.js +31 -0
  10. package/apis/auth/login.js +123 -0
  11. package/apis/auth/sendSmsCode.js +24 -0
  12. package/apis/dashboard/configStatus.js +39 -0
  13. package/apis/dashboard/environmentInfo.js +43 -0
  14. package/apis/dashboard/performanceMetrics.js +20 -0
  15. package/apis/dashboard/permissionStats.js +27 -0
  16. package/apis/dashboard/serviceStatus.js +75 -0
  17. package/apis/dashboard/systemInfo.js +19 -0
  18. package/apis/dashboard/systemOverview.js +30 -0
  19. package/apis/dashboard/systemResources.js +106 -0
  20. package/apis/dict/all.js +23 -0
  21. package/apis/dict/del.js +16 -0
  22. package/apis/dict/detail.js +27 -0
  23. package/apis/dict/ins.js +51 -0
  24. package/apis/dict/items.js +30 -0
  25. package/apis/dict/list.js +36 -0
  26. package/apis/dict/upd.js +74 -0
  27. package/apis/dictType/all.js +16 -0
  28. package/apis/dictType/del.js +38 -0
  29. package/apis/dictType/detail.js +20 -0
  30. package/apis/dictType/ins.js +37 -0
  31. package/apis/dictType/list.js +26 -0
  32. package/apis/dictType/upd.js +51 -0
  33. package/apis/email/config.js +25 -0
  34. package/apis/email/logList.js +23 -0
  35. package/apis/email/send.js +66 -0
  36. package/apis/email/verify.js +21 -0
  37. package/apis/loginLog/list.js +23 -0
  38. package/apis/menu/all.js +41 -0
  39. package/apis/menu/list.js +25 -0
  40. package/apis/operateLog/list.js +23 -0
  41. package/apis/role/all.js +21 -0
  42. package/apis/role/apiSave.js +43 -0
  43. package/apis/role/apis.js +22 -0
  44. package/apis/role/del.js +49 -0
  45. package/apis/role/detail.js +32 -0
  46. package/apis/role/ins.js +46 -0
  47. package/apis/role/list.js +27 -0
  48. package/apis/role/menuSave.js +42 -0
  49. package/apis/role/menus.js +22 -0
  50. package/apis/role/save.js +40 -0
  51. package/apis/role/upd.js +50 -0
  52. package/apis/sysConfig/all.js +16 -0
  53. package/apis/sysConfig/del.js +36 -0
  54. package/apis/sysConfig/get.js +49 -0
  55. package/apis/sysConfig/ins.js +50 -0
  56. package/apis/sysConfig/list.js +24 -0
  57. package/apis/sysConfig/upd.js +62 -0
  58. package/checks/api.js +55 -0
  59. package/checks/config.js +107 -0
  60. package/checks/hook.js +38 -0
  61. package/checks/menu.js +58 -0
  62. package/checks/plugin.js +38 -0
  63. package/checks/table.js +78 -0
  64. package/configs/beflyConfig.json +61 -0
  65. package/configs/beflyMenus.json +85 -0
  66. package/configs/constConfig.js +34 -0
  67. package/configs/regexpAlias.json +55 -0
  68. package/hooks/auth.js +34 -0
  69. package/hooks/cors.js +39 -0
  70. package/hooks/parser.js +90 -0
  71. package/hooks/permission.js +71 -0
  72. package/hooks/validator.js +43 -0
  73. package/index.js +334 -0
  74. package/lib/cacheHelper.js +483 -0
  75. package/lib/cacheKeys.js +42 -0
  76. package/lib/connect.js +120 -0
  77. package/lib/dbHelper/builders.js +698 -0
  78. package/lib/dbHelper/context.js +131 -0
  79. package/lib/dbHelper/dataOps.js +505 -0
  80. package/lib/dbHelper/execute.js +65 -0
  81. package/lib/dbHelper/index.js +27 -0
  82. package/lib/dbHelper/transaction.js +43 -0
  83. package/lib/dbHelper/util.js +58 -0
  84. package/lib/dbHelper/validate.js +549 -0
  85. package/lib/emailHelper.js +110 -0
  86. package/lib/logger.js +604 -0
  87. package/lib/redisHelper.js +684 -0
  88. package/lib/sqlBuilder/batch.js +113 -0
  89. package/lib/sqlBuilder/check.js +150 -0
  90. package/lib/sqlBuilder/compiler.js +347 -0
  91. package/lib/sqlBuilder/errors.js +60 -0
  92. package/lib/sqlBuilder/index.js +218 -0
  93. package/lib/sqlBuilder/parser.js +296 -0
  94. package/lib/sqlBuilder/util.js +260 -0
  95. package/lib/validator.js +303 -0
  96. package/package.json +19 -12
  97. package/paths.js +112 -0
  98. package/plugins/cache.js +16 -0
  99. package/plugins/config.js +11 -0
  100. package/plugins/email.js +27 -0
  101. package/plugins/logger.js +20 -0
  102. package/plugins/mysql.js +36 -0
  103. package/plugins/redis.js +34 -0
  104. package/plugins/tool.js +155 -0
  105. package/router/api.js +140 -0
  106. package/router/static.js +71 -0
  107. package/sql/admin.sql +18 -0
  108. package/sql/api.sql +12 -0
  109. package/sql/dict.sql +13 -0
  110. package/sql/dictType.sql +12 -0
  111. package/sql/emailLog.sql +20 -0
  112. package/sql/loginLog.sql +25 -0
  113. package/sql/menu.sql +12 -0
  114. package/sql/operateLog.sql +22 -0
  115. package/sql/role.sql +14 -0
  116. package/sql/sysConfig.sql +16 -0
  117. package/sync/api.js +93 -0
  118. package/sync/cache.js +13 -0
  119. package/sync/dev.js +171 -0
  120. package/sync/menu.js +99 -0
  121. package/tables/admin.json +56 -0
  122. package/tables/api.json +26 -0
  123. package/tables/dict.json +30 -0
  124. package/tables/dictType.json +24 -0
  125. package/tables/emailLog.json +61 -0
  126. package/tables/loginLog.json +86 -0
  127. package/tables/menu.json +24 -0
  128. package/tables/operateLog.json +68 -0
  129. package/tables/role.json +32 -0
  130. package/tables/sysConfig.json +43 -0
  131. package/utils/calcPerfTime.js +13 -0
  132. package/utils/cors.js +17 -0
  133. package/utils/deepMerge.js +78 -0
  134. package/utils/fieldClear.js +65 -0
  135. package/utils/formatYmdHms.js +23 -0
  136. package/utils/formatZodIssues.js +109 -0
  137. package/utils/getClientIp.js +47 -0
  138. package/utils/importDefault.js +51 -0
  139. package/utils/is.js +462 -0
  140. package/utils/loggerUtils.js +185 -0
  141. package/utils/processInfo.js +39 -0
  142. package/utils/regexpUtil.js +52 -0
  143. package/utils/response.js +114 -0
  144. package/utils/scanFiles.js +124 -0
  145. package/utils/scanSources.js +68 -0
  146. package/utils/sortModules.js +75 -0
  147. package/utils/toSessionTtlSeconds.js +14 -0
  148. package/utils/util.js +374 -0
  149. package/befly.js +0 -16413
  150. package/befly.min.js +0 -72
package/README.md CHANGED
@@ -3,3 +3,9 @@
3
3
  ![野蜂飞舞](https://static.yicode.tech/befly.svg)
4
4
 
5
5
  > 道生一,一生二,二生三,三生万物
6
+
7
+ ```js
8
+ "bundle": "bun build ./index.js --outfile ./befly.js --target bun --format esm --packages bundle",
9
+ "bundle:min": "bun build ./index.js --outfile ./befly.min.js --target bun --format esm --packages bundle --minify",
10
+ "build": "rimraf befly.js befly.min.js && bun run bundle && bun run bundle:min"
11
+ ```
@@ -0,0 +1,122 @@
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 "../../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, _ctx) => {
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
+ };
@@ -0,0 +1,34 @@
1
+ export default {
2
+ name: "删除管理员",
3
+ method: "POST",
4
+ body: "none",
5
+ auth: true,
6
+ fields: {
7
+ id: { name: "ID", input: "integer", min: 1, max: null }
8
+ },
9
+ required: ["id"],
10
+ handler: async (befly, ctx) => {
11
+ // 检查管理员是否存在
12
+ const adminData = await befly.mysql.getOne({
13
+ table: "beflyAdmin",
14
+ where: { id: ctx.body.id }
15
+ });
16
+
17
+ if (!adminData.data?.id) {
18
+ return befly.tool.No("管理员不存在");
19
+ }
20
+
21
+ // 不能删除 dev 角色的管理员
22
+ if (adminData.data.roleCode === "dev") {
23
+ return befly.tool.No("不能删除开发管理员");
24
+ }
25
+
26
+ // 删除管理员
27
+ await befly.mysql.delData({
28
+ table: "beflyAdmin",
29
+ where: { id: ctx.body.id }
30
+ });
31
+
32
+ return befly.tool.Yes("删除成功");
33
+ }
34
+ };
@@ -0,0 +1,23 @@
1
+ export default {
2
+ name: "获取管理员信息",
3
+ method: "POST",
4
+ body: "none",
5
+ auth: true,
6
+ fields: {
7
+ id: { name: "ID", input: "integer", min: 1, max: null }
8
+ },
9
+ required: [],
10
+ handler: async (befly, ctx) => {
11
+ const adminData = await befly.mysql.getOne({
12
+ table: "beflyAdmin",
13
+ fields: ["!password"],
14
+ where: { id: ctx.userId }
15
+ });
16
+
17
+ if (!adminData.data?.id) {
18
+ return befly.tool.No("管理员不存在");
19
+ }
20
+
21
+ return befly.tool.Yes("查询成功", adminData.data);
22
+ }
23
+ };
@@ -0,0 +1,69 @@
1
+ import adminTable from "../../tables/admin.json";
2
+ import { isString } from "../../utils/is.js";
3
+
4
+ export default {
5
+ name: "添加管理员",
6
+ method: "POST",
7
+ body: "none",
8
+ auth: true,
9
+ fields: {
10
+ username: adminTable.username,
11
+ password: adminTable.password,
12
+ nickname: adminTable.nickname,
13
+ roleCode: adminTable.roleCode
14
+ },
15
+ required: ["username", "password", "roleCode"],
16
+ handler: async (befly, ctx) => {
17
+ // 检查用户名是否已存在
18
+ const existingByUsername = await befly.mysql.getOne({
19
+ table: "beflyAdmin",
20
+ where: { username: ctx.body.username }
21
+ });
22
+
23
+ if (existingByUsername.data?.id) {
24
+ return befly.tool.No("用户名已被使用");
25
+ }
26
+
27
+ // 检查昵称是否已存在
28
+ if (ctx.body.nickname) {
29
+ const existingByNickname = await befly.mysql.getOne({
30
+ table: "beflyAdmin",
31
+ where: { nickname: ctx.body.nickname }
32
+ });
33
+
34
+ if (existingByNickname.data?.id) {
35
+ return befly.tool.No("昵称已被使用");
36
+ }
37
+ }
38
+
39
+ // 查询角色信息
40
+ const roleData = await befly.mysql.getOne({
41
+ table: "beflyRole",
42
+ where: { code: ctx.body.roleCode }
43
+ });
44
+
45
+ if (!roleData.data?.id) {
46
+ return befly.tool.No("角色不存在");
47
+ }
48
+
49
+ // 加密密码
50
+ const incoming = isString(ctx.body.password) ? ctx.body.password.toLowerCase() : "";
51
+ const isSha256Hex = /^[a-f0-9]{64}$/.test(incoming);
52
+ const hashedPassword = isSha256Hex ? befly.tool.sha256(incoming) : befly.tool.sha256(befly.tool.sha256(ctx.body.password));
53
+
54
+ // 创建管理员
55
+ const adminId = await befly.mysql.insData({
56
+ table: "beflyAdmin",
57
+ data: {
58
+ username: ctx.body.username,
59
+ password: hashedPassword,
60
+ nickname: ctx.body.nickname,
61
+ roleCode: ctx.body.roleCode
62
+ }
63
+ });
64
+
65
+ return befly.tool.Yes("添加成功", {
66
+ id: adminId.data
67
+ });
68
+ }
69
+ };
@@ -0,0 +1,28 @@
1
+ export default {
2
+ name: "查询管理员列表",
3
+ method: "POST",
4
+ body: "none",
5
+ auth: true,
6
+ fields: {
7
+ page: { name: "页码", input: "integer", min: 1, max: 9999 },
8
+ limit: { name: "每页数量", input: "integer", min: 1, max: 100 },
9
+ keyword: { name: "关键词", input: "string", min: 0, max: 50 },
10
+ state: { name: "状态", input: "integer", min: 0, max: 2 }
11
+ },
12
+ required: [],
13
+ handler: async (befly, ctx) => {
14
+ const result = await befly.mysql.getList({
15
+ table: "beflyAdmin",
16
+ page: ctx.body.page,
17
+ limit: ctx.body.limit,
18
+ where: {
19
+ roleCode: {
20
+ $ne: "dev"
21
+ }
22
+ },
23
+ orderBy: ["createdAt#DESC"]
24
+ });
25
+
26
+ return befly.tool.Yes("获取成功", result.data);
27
+ }
28
+ };
@@ -0,0 +1,95 @@
1
+ import adminTable from "../../tables/admin.json";
2
+
3
+ export default {
4
+ name: "更新管理员",
5
+ method: "POST",
6
+ body: "none",
7
+ auth: true,
8
+ fields: {
9
+ id: { name: "ID", input: "integer", min: 1, max: null },
10
+ username: adminTable.username,
11
+ nickname: adminTable.nickname,
12
+ roleCode: adminTable.roleCode,
13
+ avatar: adminTable.avatar,
14
+ email: adminTable.email,
15
+ lastLoginIp: adminTable.lastLoginIp,
16
+ lastLoginTime: adminTable.lastLoginTime,
17
+ password: adminTable.password,
18
+ phone: adminTable.phone,
19
+ roleType: adminTable.roleType
20
+ },
21
+ required: ["id"],
22
+ handler: async (befly, ctx) => {
23
+ const id = ctx.body.id;
24
+ const username = ctx.body.username;
25
+ const nickname = ctx.body.nickname;
26
+ const roleCode = ctx.body.roleCode;
27
+
28
+ // 检查管理员是否存在
29
+ const admin = await befly.mysql.getOne({
30
+ table: "beflyAdmin",
31
+ where: { id: id }
32
+ });
33
+
34
+ if (!admin.data?.id) {
35
+ return befly.tool.No("管理员不存在");
36
+ }
37
+
38
+ // 检查用户名是否已被其他管理员使用
39
+ if (username && username !== admin.data.username) {
40
+ const existingUsername = await befly.mysql.getOne({
41
+ table: "beflyAdmin",
42
+ where: { username: username, id: { $ne: id } }
43
+ });
44
+ if (existingUsername.data?.id) {
45
+ return befly.tool.No("用户名已被使用");
46
+ }
47
+ }
48
+
49
+ // 检查昵称是否已被其他管理员使用
50
+ if (nickname && nickname !== admin.data.nickname) {
51
+ const existingNickname = await befly.mysql.getOne({
52
+ table: "beflyAdmin",
53
+ where: { nickname: nickname, id: { $ne: id } }
54
+ });
55
+ if (existingNickname.data?.id) {
56
+ return befly.tool.No("昵称已被使用");
57
+ }
58
+ }
59
+
60
+ // 检查角色是否存在
61
+ if (roleCode && roleCode !== admin.data.roleCode) {
62
+ const role = await befly.mysql.getOne({
63
+ table: "beflyRole",
64
+ where: { code: roleCode }
65
+ });
66
+ if (!role.data?.id) {
67
+ return befly.tool.No("角色不存在");
68
+ }
69
+ }
70
+
71
+ // 构建更新数据
72
+ const dataToUpdate = {};
73
+
74
+ if (ctx.body.avatar !== undefined) dataToUpdate["avatar"] = ctx.body.avatar;
75
+ if (ctx.body.email !== undefined) dataToUpdate["email"] = ctx.body.email;
76
+ if (ctx.body.lastLoginIp !== undefined) dataToUpdate["lastLoginIp"] = ctx.body.lastLoginIp;
77
+ if (ctx.body.lastLoginTime !== undefined) dataToUpdate["lastLoginTime"] = ctx.body.lastLoginTime;
78
+ if (ctx.body.password !== undefined) dataToUpdate["password"] = ctx.body.password;
79
+ if (ctx.body.phone !== undefined) dataToUpdate["phone"] = ctx.body.phone;
80
+ if (ctx.body.roleType !== undefined) dataToUpdate["roleType"] = ctx.body.roleType;
81
+
82
+ if (username !== undefined) dataToUpdate["username"] = username;
83
+ if (nickname !== undefined) dataToUpdate["nickname"] = nickname;
84
+ if (roleCode !== undefined) dataToUpdate["roleCode"] = roleCode;
85
+
86
+ // 更新管理员信息
87
+ await befly.mysql.updData({
88
+ table: "beflyAdmin",
89
+ data: dataToUpdate,
90
+ where: { id: id }
91
+ });
92
+
93
+ return befly.tool.Yes("更新成功");
94
+ }
95
+ };
@@ -0,0 +1,24 @@
1
+ export default {
2
+ name: "获取所有接口",
3
+ method: "POST",
4
+ body: "none",
5
+ auth: true,
6
+ fields: {},
7
+ required: [],
8
+ handler: async (befly) => {
9
+ try {
10
+ const allApis = await befly.cache.getApis();
11
+
12
+ if (allApis.length === 0) {
13
+ return befly.tool.No("接口缓存不存在,请刷新缓存", { lists: [] });
14
+ }
15
+
16
+ const lists = allApis.filter((api) => api && typeof api === "object");
17
+
18
+ return befly.tool.Yes("操作成功", { lists: lists });
19
+ } catch (error) {
20
+ befly.logger.error("获取接口列表失败", error);
21
+ return befly.tool.No("获取接口列表失败");
22
+ }
23
+ }
24
+ };
@@ -0,0 +1,31 @@
1
+ export default {
2
+ name: "获取接口列表",
3
+ method: "POST",
4
+ body: "none",
5
+ auth: true,
6
+ fields: {
7
+ page: { name: "页码", input: "integer", min: 1, max: 9999 },
8
+ limit: { name: "每页数量", input: "integer", min: 1, max: 100 },
9
+ keyword: { name: "关键词", input: "string", min: 0, max: 50 },
10
+ state: { name: "状态", input: "integer", min: 0, max: 2 }
11
+ },
12
+ required: [],
13
+ handler: async (befly, ctx) => {
14
+ try {
15
+ const result = await befly.mysql.getList({
16
+ table: "beflyApi",
17
+ where: {
18
+ $or: [{ name$like: `%${ctx.body.keyword}%` }, { path$like: `%${ctx.body.keyword}%` }]
19
+ },
20
+ orderBy: ["id#ASC"],
21
+ page: ctx.body.page,
22
+ limit: ctx.body.limit
23
+ });
24
+
25
+ return befly.tool.Yes("操作成功", result.data);
26
+ } catch (error) {
27
+ befly.logger.error("获取接口列表失败", error);
28
+ return befly.tool.No("获取接口列表失败");
29
+ }
30
+ }
31
+ };
@@ -0,0 +1,123 @@
1
+ import { UAParser } from "ua-parser-js";
2
+
3
+ import adminTable from "../../tables/admin.json";
4
+ import { toSessionTtlSeconds } from "../../utils/toSessionTtlSeconds.js";
5
+ import { isString } from "../../utils/is.js";
6
+
7
+ export default {
8
+ name: "管理员登录",
9
+ method: "POST",
10
+ body: "none",
11
+ auth: false,
12
+ fields: {
13
+ account: {
14
+ name: "账号",
15
+ input: "string",
16
+ min: 3,
17
+ max: 100
18
+ },
19
+ password: adminTable.password,
20
+ loginType: {
21
+ name: "登录类型",
22
+ input: "/^(username|email|phone)$/",
23
+ min: null,
24
+ max: null
25
+ }
26
+ },
27
+ required: ["account", "password", "loginType"],
28
+ handler: async (befly, ctx) => {
29
+ const userAgent = ctx.req.headers.get("user-agent") || "";
30
+ const uaResult = UAParser(userAgent);
31
+
32
+ const logData = {
33
+ adminId: 0,
34
+ username: ctx.body.account,
35
+ nickname: "",
36
+ ip: ctx.ip || "",
37
+ userAgent: userAgent.substring(0, 500),
38
+ browserName: uaResult.browser.name || "",
39
+ browserVersion: uaResult.browser.version || "",
40
+ osName: uaResult.os.name || "",
41
+ osVersion: uaResult.os.version || "",
42
+ deviceType: uaResult.device.type || "desktop",
43
+ deviceVendor: uaResult.device.vendor || "",
44
+ deviceModel: uaResult.device.model || "",
45
+ engineName: uaResult.engine.name || "",
46
+ cpuArchitecture: uaResult.cpu.architecture || "",
47
+ loginTime: Date.now(),
48
+ loginResult: 0,
49
+ failReason: ""
50
+ };
51
+
52
+ const whereCondition = {};
53
+ if (ctx.body.loginType === "username") {
54
+ whereCondition["username"] = ctx.body.account;
55
+ } else if (ctx.body.loginType === "email") {
56
+ whereCondition["email"] = ctx.body.account;
57
+ } else if (ctx.body.loginType === "phone") {
58
+ whereCondition["phone"] = ctx.body.account;
59
+ }
60
+
61
+ const admin = await befly.mysql.getOne({
62
+ table: "beflyAdmin",
63
+ where: whereCondition
64
+ });
65
+
66
+ if (!admin.data?.id) {
67
+ logData.failReason = "账号不存在";
68
+ await befly.mysql.insData({ table: "beflyLoginLog", data: logData });
69
+ return befly.tool.No("账号或密码错误");
70
+ }
71
+
72
+ logData.adminId = admin.data.id;
73
+ logData.username = admin.data.username;
74
+ logData.nickname = admin.data.nickname;
75
+
76
+ if (befly.tool.sha256(ctx.body.password) !== admin.data.password) {
77
+ logData.failReason = "密码错误";
78
+ await befly.mysql.insData({ table: "beflyLoginLog", data: logData });
79
+ return befly.tool.No("账号或密码错误");
80
+ }
81
+
82
+ if (admin.data.state === 2) {
83
+ logData.failReason = "账号已被禁用";
84
+ await befly.mysql.insData({ table: "beflyLoginLog", data: logData });
85
+ return befly.tool.No("账号已被禁用");
86
+ }
87
+
88
+ logData.loginResult = 1;
89
+ await befly.mysql.insData({ table: "beflyLoginLog", data: logData });
90
+
91
+ const ttlSeconds = toSessionTtlSeconds(befly.config?.session?.expireDays);
92
+ const sid = Bun.randomUUIDv7();
93
+ const sessionKey = `session:${sid}`;
94
+
95
+ const sessionData = {
96
+ sid: sid,
97
+ id: admin.data.id,
98
+ username: admin.data.username,
99
+ nickname: admin.data.nickname,
100
+ state: admin.data.state,
101
+ roleCode: admin.data.roleCode,
102
+ roleType: admin.data.roleType,
103
+ loginAt: Date.now()
104
+ };
105
+
106
+ const sessionResult = await befly.redis.setObject(sessionKey, sessionData, ttlSeconds);
107
+ if (!sessionResult) {
108
+ return befly.tool.No("登录失败,请稍后重试");
109
+ }
110
+
111
+ return befly.tool.Yes("登录成功", {
112
+ token: sid,
113
+ userInfo: {
114
+ id: sessionData.id,
115
+ username: sessionData.username,
116
+ nickname: sessionData.nickname,
117
+ state: sessionData.state,
118
+ roleCode: sessionData.roleCode,
119
+ roleType: sessionData.roleType
120
+ }
121
+ });
122
+ }
123
+ };
@@ -0,0 +1,24 @@
1
+ import adminTable from "../../tables/admin.json";
2
+
3
+ export default {
4
+ name: "发送短信验证码",
5
+ method: "POST",
6
+ body: "none",
7
+ auth: false,
8
+ fields: {
9
+ phone: adminTable.phone
10
+ },
11
+ required: ["phone"],
12
+ handler: async (befly, ctx) => {
13
+ const code = Math.floor(100000 + Math.random() * 900000).toString();
14
+
15
+ if (befly.redis) {
16
+ const key = `sms_code:${ctx.body.phone}`;
17
+ await befly.redis.setString(key, code, 300);
18
+ }
19
+
20
+ const isDev = befly.config ? befly.config.runMode === "development" : false;
21
+
22
+ return befly.tool.Yes("验证码已发送", isDev ? { code: code } : null);
23
+ }
24
+ };
@@ -0,0 +1,39 @@
1
+ export default {
2
+ name: "获取配置状态",
3
+ method: "POST",
4
+ body: "none",
5
+ auth: true,
6
+ fields: {},
7
+ required: [],
8
+ handler: async (befly) => {
9
+ const status = {
10
+ database: { status: "ok", latency: 0, message: "" },
11
+ redis: { status: "ok", latency: 0, message: "" },
12
+ fileSystem: { status: "ok" },
13
+ email: { status: "warning", message: "未配置" },
14
+ oss: { status: "warning", message: "未配置" }
15
+ };
16
+
17
+ try {
18
+ const startTime = Date.now();
19
+ await befly.mysql.execute("SELECT 1");
20
+ status.database.latency = Date.now() - startTime;
21
+ status.database.status = "ok";
22
+ } catch {
23
+ status.database.status = "error";
24
+ status.database.message = "连接失败";
25
+ }
26
+
27
+ try {
28
+ const startTime = Date.now();
29
+ await befly.redis.ping();
30
+ status.redis.latency = Date.now() - startTime;
31
+ status.redis.status = "ok";
32
+ } catch {
33
+ status.redis.status = "error";
34
+ status.redis.message = "连接失败";
35
+ }
36
+
37
+ return befly.tool.Yes("获取成功", status);
38
+ }
39
+ };
@@ -0,0 +1,43 @@
1
+ import os from "node:os";
2
+ import { isString } from "../../utils/is.js";
3
+
4
+ export default {
5
+ name: "获取运行环境信息",
6
+ method: "POST",
7
+ body: "none",
8
+ auth: true,
9
+ fields: {},
10
+ required: [],
11
+ handler: async (befly) => {
12
+ let databaseVersion = "Unknown";
13
+ try {
14
+ const versionResult = await befly.mysql.execute("SELECT VERSION() as version");
15
+ const rawVersion = versionResult.data && versionResult.data[0] ? versionResult.data[0].version : undefined;
16
+ databaseVersion = isString(rawVersion) ? rawVersion : String(rawVersion ?? "Unknown");
17
+ } catch {
18
+ // 忽略错误
19
+ }
20
+
21
+ let redisVersion = "未配置";
22
+ if (befly.redis) {
23
+ try {
24
+ const info = await befly.redis.info("server");
25
+ const match = info.match(/redis_version:([^\r\n]+)/);
26
+ if (match) {
27
+ redisVersion = match[1];
28
+ }
29
+ } catch {
30
+ redisVersion = "未知";
31
+ }
32
+ }
33
+
34
+ return befly.tool.Yes("获取成功", {
35
+ os: `${os.type()} ${os.arch()}`,
36
+ server: `${os.platform()} ${os.release()}`,
37
+ nodeVersion: process.version,
38
+ database: `MySQL ${databaseVersion}`,
39
+ cache: `Redis ${redisVersion}`,
40
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
41
+ });
42
+ }
43
+ };