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
@@ -1,23 +1,26 @@
1
+ import { queryFields } from "#root/apis/_apis.js";
2
+ import roleTable from "#root/tables/role.json";
3
+
1
4
  export default {
2
5
  name: "获取角色列表",
3
6
  method: "POST",
4
7
  body: "none",
5
8
  auth: true,
6
9
  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 }
10
+ ...queryFields,
11
+ state: roleTable.state
11
12
  },
12
13
  required: [],
13
- handler: async (befly) => {
14
+ handler: async (befly, ctx) => {
14
15
  const roles = await befly.mysql.getList({
15
- limit: 30,
16
16
  table: "beflyRole",
17
+ page: ctx.body.page,
18
+ limit: ctx.body.limit,
17
19
  where: {
18
- code: {
19
- $not: "dev"
20
- }
20
+ name$like$or: ctx.body.keyword,
21
+ code$like$or: ctx.body.keyword,
22
+ state: ctx.body.state,
23
+ code$not: "dev"
21
24
  },
22
25
  orderBy: ["sort#ASC", "id#ASC"]
23
26
  });
@@ -1,4 +1,6 @@
1
- import adminRoleTable from "#root/tables/role.json";
1
+ import roleTable from "#root/tables/role.json";
2
+
3
+ import { getRoleById } from "./_role.js";
2
4
 
3
5
  export default {
4
6
  name: "更新角色",
@@ -6,20 +8,17 @@ export default {
6
8
  body: "none",
7
9
  auth: true,
8
10
  fields: {
9
- id: { name: "ID", input: "integer", min: 1, max: null },
10
- name: adminRoleTable.name,
11
- code: adminRoleTable.code,
12
- description: adminRoleTable.description,
13
- menus: adminRoleTable.menus,
14
- apis: adminRoleTable.apis,
15
- sort: adminRoleTable.sort
11
+ id: roleTable.id,
12
+ name: roleTable.name,
13
+ code: roleTable.code,
14
+ description: roleTable.description,
15
+ menus: roleTable.menus,
16
+ apis: roleTable.apis,
17
+ sort: roleTable.sort
16
18
  },
17
19
  required: ["id"],
18
20
  handler: async (befly, ctx) => {
19
- const role = await befly.mysql.getOne({
20
- table: "beflyRole",
21
- where: { id: ctx.body.id }
22
- });
21
+ const role = await getRoleById(befly, ctx.body.id);
23
22
 
24
23
  if (!role.data?.id) {
25
24
  return befly.tool.No("角色不存在");
@@ -39,9 +38,9 @@ export default {
39
38
  }
40
39
  }
41
40
 
42
- const roleCode = ctx.body.code !== undefined ? ctx.body.code : role.data.code;
43
- const apiPaths = ctx.body.apis !== undefined ? ctx.body.apis : role.data.apis || [];
44
- const menuPaths = ctx.body.menus !== undefined ? ctx.body.menus : role.data.menus || [];
41
+ const roleCode = ctx.body.code === undefined ? role.data.code : ctx.body.code;
42
+ const apiPaths = ctx.body.apis === undefined ? role.data.apis || [] : ctx.body.apis;
43
+ const menuPaths = ctx.body.menus === undefined ? role.data.menus || [] : ctx.body.menus;
45
44
 
46
45
  await befly.mysql.updData({
47
46
  table: "beflyRole",
@@ -1,12 +1,12 @@
1
+ import { queryFields } from "#root/apis/_apis.js";
2
+
1
3
  export default {
2
4
  name: "获取图库列表",
3
5
  method: "POST",
4
6
  body: "none",
5
7
  auth: true,
6
8
  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: 100 }
9
+ ...queryFields
10
10
  },
11
11
  required: [],
12
12
  handler: async (befly, ctx) => {
@@ -1,5 +1,4 @@
1
1
  import sysConfigTable from "#root/tables/sysConfig.json";
2
- import { isString } from "#root/utils/is.js";
3
2
 
4
3
  export default {
5
4
  name: "根据代码获取配置值",
@@ -9,41 +8,30 @@ export default {
9
8
  fields: {
10
9
  code: sysConfigTable.code
11
10
  },
12
- required: [],
11
+ required: ["code"],
13
12
  handler: async (befly, ctx) => {
14
13
  const config = await befly.mysql.getOne({
15
14
  table: "beflySysConfig",
16
15
  where: { code: ctx.body.code }
17
16
  });
18
17
 
19
- if (!config.data || !config.data.id) {
18
+ if (!config.data?.id) {
20
19
  return befly.tool.No("配置不存在");
21
20
  }
22
21
 
23
- const valueType = config.data.valueType;
24
- const rawValue = config.data.value;
25
- if (!isString(valueType) || !isString(rawValue)) {
26
- return befly.tool.No("配置数据不完整");
27
- }
28
-
29
- const code = config.data.code;
30
- if (!isString(code)) {
31
- return befly.tool.No("配置数据不完整");
32
- }
33
-
34
- let value = rawValue;
35
- if (valueType === "number") {
36
- value = Number(rawValue);
37
- } else if (valueType === "boolean") {
38
- value = rawValue === "true" || rawValue === "1";
39
- } else if (valueType === "json") {
22
+ let value = config.data.value;
23
+ if (config.data.valueType === "number") {
24
+ value = Number(config.data.value);
25
+ } else if (config.data.valueType === "boolean") {
26
+ value = config.data.value === "true" || config.data.value === "1";
27
+ } else if (config.data.valueType === "json") {
40
28
  try {
41
- value = JSON.parse(rawValue);
29
+ value = JSON.parse(config.data.value);
42
30
  } catch {
43
- value = rawValue;
31
+ value = config.data.value;
44
32
  }
45
33
  }
46
34
 
47
- return befly.tool.Yes("操作成功", { code: code, value: value });
35
+ return befly.tool.Yes("操作成功", { code: config.data.code, value: value });
48
36
  }
49
37
  };
@@ -15,36 +15,31 @@ export default {
15
15
  isSystem: sysConfigTable.isSystem,
16
16
  description: sysConfigTable.description
17
17
  },
18
- required: [],
18
+ required: ["name", "code", "value"],
19
19
  handler: async (befly, ctx) => {
20
- try {
21
- const existing = await befly.mysql.getOne({
22
- table: "beflySysConfig",
23
- where: { code: ctx.body.code }
24
- });
20
+ const existing = await befly.mysql.getOne({
21
+ table: "beflySysConfig",
22
+ where: { code: ctx.body.code }
23
+ });
25
24
 
26
- if (existing.data?.id) {
27
- return befly.tool.No("配置代码已存在");
28
- }
25
+ if (existing.data?.id) {
26
+ return befly.tool.No("配置代码已存在");
27
+ }
29
28
 
30
- const configId = await befly.mysql.insData({
31
- table: "beflySysConfig",
32
- data: {
33
- name: ctx.body.name,
34
- code: ctx.body.code,
35
- value: ctx.body.value,
36
- valueType: ctx.body.valueType || "string",
37
- group: ctx.body.group || "",
38
- sort: ctx.body.sort || 0,
39
- isSystem: ctx.body.isSystem || 0,
40
- description: ctx.body.description || ""
41
- }
42
- });
29
+ const configId = await befly.mysql.insData({
30
+ table: "beflySysConfig",
31
+ data: {
32
+ name: ctx.body.name,
33
+ code: ctx.body.code,
34
+ value: ctx.body.value,
35
+ valueType: ctx.body.valueType === undefined ? "string" : ctx.body.valueType,
36
+ group: ctx.body.group === undefined ? "" : ctx.body.group,
37
+ sort: ctx.body.sort === undefined ? 0 : ctx.body.sort,
38
+ isSystem: ctx.body.isSystem === undefined ? 0 : ctx.body.isSystem,
39
+ description: ctx.body.description === undefined ? "" : ctx.body.description
40
+ }
41
+ });
43
42
 
44
- return befly.tool.Yes("操作成功", { id: configId.data });
45
- } catch (error) {
46
- befly.logger.error("添加系统配置失败", error);
47
- return befly.tool.No("操作失败");
48
- }
43
+ return befly.tool.Yes("操作成功", { id: configId.data });
49
44
  }
50
45
  };
@@ -1,19 +1,25 @@
1
+ import { queryFields } from "#root/apis/_apis.js";
2
+ import sysConfigTable from "#root/tables/sysConfig.json";
3
+
1
4
  export default {
2
5
  name: "获取系统配置列表",
3
6
  method: "POST",
4
7
  body: "none",
5
8
  auth: true,
6
9
  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 }
10
+ ...queryFields,
11
+ state: sysConfigTable.state
11
12
  },
12
13
  required: [],
13
14
  handler: async (befly, ctx) => {
14
15
  const result = await befly.mysql.getList({
15
16
  table: "beflySysConfig",
16
- fields: ["id", "name", "code", "value", "valueType", "group", "sort", "isSystem", "description", "state", "createdAt", "updatedAt"],
17
+ where: {
18
+ name$like$or: ctx.body.keyword,
19
+ code$like$or: ctx.body.keyword,
20
+ group$like$or: ctx.body.keyword,
21
+ state: ctx.body.state
22
+ },
17
23
  page: ctx.body.page,
18
24
  limit: ctx.body.limit,
19
25
  orderBy: ["group#ASC", "sort#ASC", "id#ASC"]
@@ -6,7 +6,7 @@ export default {
6
6
  body: "none",
7
7
  auth: true,
8
8
  fields: {
9
- id: { name: "ID", input: "integer", min: 1, max: null },
9
+ id: sysConfigTable.id,
10
10
  name: sysConfigTable.name,
11
11
  code: sysConfigTable.code,
12
12
  value: sysConfigTable.value,
@@ -14,49 +14,44 @@ export default {
14
14
  group: sysConfigTable.group,
15
15
  sort: sysConfigTable.sort,
16
16
  description: sysConfigTable.description,
17
- state: { name: "状态", input: "integer", min: 0, max: 2 }
17
+ state: sysConfigTable.state
18
18
  },
19
19
  required: ["id"],
20
20
  handler: async (befly, ctx) => {
21
- try {
22
- const config = await befly.mysql.getOne({
21
+ const config = await befly.mysql.getOne({
22
+ table: "beflySysConfig",
23
+ where: { id: ctx.body.id }
24
+ });
25
+
26
+ if (!config.data?.id) {
27
+ return befly.tool.No("配置不存在");
28
+ }
29
+
30
+ if (config.data.isSystem === 1) {
31
+ await befly.mysql.updData({
23
32
  table: "beflySysConfig",
33
+ data: {
34
+ value: ctx.body.value
35
+ },
36
+ where: { id: ctx.body.id }
37
+ });
38
+ } else {
39
+ await befly.mysql.updData({
40
+ table: "beflySysConfig",
41
+ data: {
42
+ name: ctx.body.name,
43
+ code: ctx.body.code,
44
+ value: ctx.body.value,
45
+ valueType: ctx.body.valueType,
46
+ group: ctx.body.group,
47
+ sort: ctx.body.sort,
48
+ description: ctx.body.description,
49
+ state: ctx.body.state
50
+ },
24
51
  where: { id: ctx.body.id }
25
52
  });
26
-
27
- if (!config.data?.id) {
28
- return befly.tool.No("配置不存在");
29
- }
30
-
31
- if (config.data.isSystem === 1) {
32
- await befly.mysql.updData({
33
- table: "beflySysConfig",
34
- data: {
35
- value: ctx.body.value
36
- },
37
- where: { id: ctx.body.id }
38
- });
39
- } else {
40
- await befly.mysql.updData({
41
- table: "beflySysConfig",
42
- data: {
43
- name: ctx.body.name,
44
- code: ctx.body.code,
45
- value: ctx.body.value,
46
- valueType: ctx.body.valueType,
47
- group: ctx.body.group,
48
- sort: ctx.body.sort,
49
- description: ctx.body.description,
50
- state: ctx.body.state
51
- },
52
- where: { id: ctx.body.id }
53
- });
54
- }
55
-
56
- return befly.tool.Yes("操作成功");
57
- } catch (error) {
58
- befly.logger.error("更新系统配置失败", error);
59
- return befly.tool.No("操作失败");
60
53
  }
54
+
55
+ return befly.tool.Yes("操作成功");
61
56
  }
62
57
  };
@@ -0,0 +1,41 @@
1
+ import { addDays, getDateYmdNumber } from "#root/utils/datetime.js";
2
+
3
+ export function getTongJiNumber(value) {
4
+ const num = Number(value);
5
+
6
+ if (!Number.isFinite(num)) {
7
+ return 0;
8
+ }
9
+
10
+ return num;
11
+ }
12
+
13
+ export function getTongJiWeekStartDate(timestamp = Date.now()) {
14
+ const date = Reflect.construct(Date, [timestamp]);
15
+ const day = date.getDay();
16
+ const offset = day === 0 ? -6 : 1 - day;
17
+
18
+ return getDateYmdNumber(addDays(timestamp, offset));
19
+ }
20
+
21
+ export function getTongJiMonthStartDate(timestamp = Date.now()) {
22
+ const date = Reflect.construct(Date, [timestamp]);
23
+
24
+ return getDateYmdNumber(Reflect.construct(Date, [date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0]).getTime());
25
+ }
26
+
27
+ export function getTongJiRecentDateList(now = Date.now(), limit = 30) {
28
+ const list = [];
29
+
30
+ for (let i = limit - 1; i >= 0; i -= 1) {
31
+ list.push(getDateYmdNumber(addDays(now, -i)));
32
+ }
33
+
34
+ return list;
35
+ }
36
+
37
+ export async function expireTongJiRedisKeys(befly, keys, ttl) {
38
+ for (const key of keys) {
39
+ await befly.redis.expire(key, ttl);
40
+ }
41
+ }
@@ -0,0 +1,192 @@
1
+ import { getDateYmdNumber } from "#root/utils/datetime.js";
2
+
3
+ import { getTongJiMonthStartDate, getTongJiNumber, getTongJiWeekStartDate } from "./_tongJi.js";
4
+
5
+ const ONLINE_ACTIVE_KEY = "online:active";
6
+ const INFO_STATS_FALLBACK_COUNT_KEY = "stats:fallback:infoStats:count";
7
+ const ERROR_STATS_FALLBACK_COUNT_KEY = "stats:fallback:errorStats:count";
8
+
9
+ function getInfoStatsFallbackDailyCountKey(reportDate) {
10
+ return `stats:fallback:infoStats:count:day:${reportDate}`;
11
+ }
12
+
13
+ function getErrorStatsFallbackDailyCountKey(reportDate) {
14
+ return `stats:fallback:errorStats:count:day:${reportDate}`;
15
+ }
16
+
17
+ function getOnlineReportTimeKey(periodType, periodValue) {
18
+ return `online:${periodType}:${periodValue}:reportTime`;
19
+ }
20
+
21
+ function getInfoReportTimeKey(periodType, periodValue) {
22
+ return `info:${periodType}:${periodValue}:reportTime`;
23
+ }
24
+
25
+ function getErrorCountKey(periodType, periodValue) {
26
+ return `error:${periodType}:${periodValue}:count`;
27
+ }
28
+
29
+ async function getRedisStringNumber(redis, key) {
30
+ return getTongJiNumber(await redis.getString(key));
31
+ }
32
+
33
+ async function getRedisSetCount(redis, key) {
34
+ if (typeof redis.scard !== "function") {
35
+ return 0;
36
+ }
37
+
38
+ return getTongJiNumber(await redis.scard(key));
39
+ }
40
+
41
+ async function getRedisSortedSetCount(redis, key) {
42
+ if (typeof redis.zcard !== "function") {
43
+ return 0;
44
+ }
45
+
46
+ return getTongJiNumber(await redis.zcard(key));
47
+ }
48
+
49
+ async function getRedisConnectivity(redis) {
50
+ if (!redis || typeof redis.ping !== "function") {
51
+ return {
52
+ status: "unavailable",
53
+ responseTime: -1
54
+ };
55
+ }
56
+
57
+ try {
58
+ const start = Date.now();
59
+ await redis.ping();
60
+
61
+ return {
62
+ status: "running",
63
+ responseTime: Date.now() - start
64
+ };
65
+ } catch {
66
+ return {
67
+ status: "stopped",
68
+ responseTime: -1
69
+ };
70
+ }
71
+ }
72
+
73
+ export default {
74
+ name: "获取统计缓存健康状态",
75
+ method: "POST",
76
+ body: "none",
77
+ auth: true,
78
+ fields: {},
79
+ required: [],
80
+ handler: async (befly) => {
81
+ const now = Date.now();
82
+ const reportDate = getDateYmdNumber(now);
83
+ const weekStartDate = getTongJiWeekStartDate(now);
84
+ const monthStartDate = getTongJiMonthStartDate(now);
85
+ const redisConnectivity = await getRedisConnectivity(befly.redis);
86
+
87
+ if (!befly.redis) {
88
+ return befly.tool.Yes("获取成功", {
89
+ queryTime: now,
90
+ redis: redisConnectivity,
91
+ online: null,
92
+ info: null,
93
+ error: null,
94
+ roleCache: null,
95
+ hints: ["Redis 不可用,统计接口将回退到 MySQL 聚合"]
96
+ });
97
+ }
98
+
99
+ const onlineTodayReportTime = await getRedisStringNumber(befly.redis, getOnlineReportTimeKey("day", reportDate));
100
+ const onlineWeekReportTime = await getRedisStringNumber(befly.redis, getOnlineReportTimeKey("week", weekStartDate));
101
+ const onlineMonthReportTime = await getRedisStringNumber(befly.redis, getOnlineReportTimeKey("month", monthStartDate));
102
+
103
+ const infoTodayReportTime = await getRedisStringNumber(befly.redis, getInfoReportTimeKey("day", reportDate));
104
+ const infoWeekReportTime = await getRedisStringNumber(befly.redis, getInfoReportTimeKey("week", weekStartDate));
105
+ const infoMonthReportTime = await getRedisStringNumber(befly.redis, getInfoReportTimeKey("month", monthStartDate));
106
+
107
+ const errorDayCount = await getRedisStringNumber(befly.redis, getErrorCountKey("day", reportDate));
108
+ const errorWeekCount = await getRedisStringNumber(befly.redis, getErrorCountKey("week", weekStartDate));
109
+ const errorMonthCount = await getRedisStringNumber(befly.redis, getErrorCountKey("month", monthStartDate));
110
+
111
+ const roleApisVersion = String((await befly.redis.getString("role:apis:activeVersion")) || "");
112
+ const roleMenusVersion = String((await befly.redis.getString("role:menus:activeVersion")) || "");
113
+
114
+ const online = {
115
+ activeMembers: await getRedisSortedSetCount(befly.redis, ONLINE_ACTIVE_KEY),
116
+ reportTime: {
117
+ today: onlineTodayReportTime,
118
+ week: onlineWeekReportTime,
119
+ month: onlineMonthReportTime
120
+ },
121
+ ready: onlineTodayReportTime > 0 || onlineWeekReportTime > 0 || onlineMonthReportTime > 0
122
+ };
123
+
124
+ const info = {
125
+ reportTime: {
126
+ today: infoTodayReportTime,
127
+ week: infoWeekReportTime,
128
+ month: infoMonthReportTime
129
+ },
130
+ daySourcesCount: await getRedisSetCount(befly.redis, `info:day:${reportDate}:sources:names`),
131
+ ready: infoTodayReportTime > 0 || infoWeekReportTime > 0 || infoMonthReportTime > 0
132
+ };
133
+
134
+ const error = {
135
+ count: {
136
+ today: errorDayCount,
137
+ week: errorWeekCount,
138
+ month: errorMonthCount
139
+ },
140
+ ready: errorDayCount > 0 || errorWeekCount > 0 || errorMonthCount > 0
141
+ };
142
+
143
+ const roleCache = {
144
+ apis: {
145
+ activeVersion: roleApisVersion,
146
+ ready: roleApisVersion.length > 0
147
+ },
148
+ menus: {
149
+ activeVersion: roleMenusVersion,
150
+ ready: roleMenusVersion.length > 0
151
+ }
152
+ };
153
+
154
+ const fallback = {
155
+ infoStats: await getRedisStringNumber(befly.redis, INFO_STATS_FALLBACK_COUNT_KEY),
156
+ errorStats: await getRedisStringNumber(befly.redis, ERROR_STATS_FALLBACK_COUNT_KEY),
157
+ today: {
158
+ infoStats: await getRedisStringNumber(befly.redis, getInfoStatsFallbackDailyCountKey(reportDate)),
159
+ errorStats: await getRedisStringNumber(befly.redis, getErrorStatsFallbackDailyCountKey(reportDate))
160
+ }
161
+ };
162
+
163
+ const hints = [];
164
+
165
+ if (!online.ready) {
166
+ hints.push("online 统计预聚合尚未建立,onlineStats 将返回空或低命中结果");
167
+ }
168
+ if (!info.ready) {
169
+ hints.push("info 统计预聚合尚未建立,infoStats 将回退到 MySQL 聚合");
170
+ }
171
+ if (!error.ready) {
172
+ hints.push("error 统计预聚合尚未建立,errorStats 将回退到 MySQL 聚合");
173
+ }
174
+ if (!roleCache.apis.ready || !roleCache.menus.ready) {
175
+ hints.push("角色权限版本缓存未就绪,建议执行一次 cacheRefresh");
176
+ }
177
+ if (fallback.infoStats > 0 || fallback.errorStats > 0) {
178
+ hints.push("检测到统计查询回退到 MySQL,可关注预聚合写入是否稳定,必要时可执行 tongJi/fallbackReset 重置观测计数");
179
+ }
180
+
181
+ return befly.tool.Yes("获取成功", {
182
+ queryTime: now,
183
+ redis: redisConnectivity,
184
+ online: online,
185
+ info: info,
186
+ error: error,
187
+ roleCache: roleCache,
188
+ fallback: fallback,
189
+ hints: hints
190
+ });
191
+ }
192
+ };
@@ -1,26 +1,27 @@
1
+ import { queryFields } from "#root/apis/_apis.js";
2
+ import errorReportTable from "#root/tables/errorReport.json";
3
+
1
4
  export default {
2
5
  name: "获取错误报告列表",
3
6
  method: "POST",
4
7
  body: "none",
5
8
  auth: true,
6
9
  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: 100 },
10
- errorType: { name: "错误类型", input: "string", min: 0, max: 50 },
11
- source: { name: "来源", input: "string", min: 0, max: 50 },
12
- productName: { name: "产品名称", input: "string", min: 0, max: 100 },
13
- productCode: { name: "产品代号", input: "string", min: 0, max: 100 },
14
- productVersion: { name: "产品版本", input: "string", min: 0, max: 100 },
15
- deviceType: { name: "设备类型", input: "string", min: 0, max: 50 },
16
- browserName: { name: "浏览器", input: "string", min: 0, max: 100 },
17
- osName: { name: "操作系统", input: "string", min: 0, max: 100 }
10
+ ...queryFields,
11
+ errorType: errorReportTable.errorType,
12
+ source: errorReportTable.source,
13
+ productName: errorReportTable.productName,
14
+ productCode: errorReportTable.productCode,
15
+ productVersion: errorReportTable.productVersion,
16
+ deviceType: errorReportTable.deviceType,
17
+ browserName: errorReportTable.browserName,
18
+ osName: errorReportTable.osName
18
19
  },
19
20
  required: [],
20
21
  handler: async (befly, ctx) => {
21
22
  const tableExistsResult = await befly.mysql.tableExists("beflyErrorReport");
22
- const page = Number(ctx.body.page || 1);
23
- const limit = Number(ctx.body.limit || 20);
23
+ const page = ctx.body.page === undefined ? 1 : ctx.body.page;
24
+ const limit = ctx.body.limit === undefined ? 20 : ctx.body.limit;
24
25
 
25
26
  if (tableExistsResult.data !== true) {
26
27
  return befly.tool.Yes("获取成功", {
@@ -43,20 +44,18 @@ export default {
43
44
  const where = {};
44
45
 
45
46
  if (keyword) {
46
- where["$or"] = [
47
- { pagePath$like: keyword },
48
- { pageName$like: keyword },
49
- { message$like: keyword },
50
- { errorType$like: keyword },
51
- { productName$like: keyword },
52
- { productCode$like: keyword },
53
- { productVersion$like: keyword },
54
- { browserName$like: keyword },
55
- { osName$like: keyword },
56
- { deviceType$like: keyword },
57
- { deviceVendor$like: keyword },
58
- { deviceModel$like: keyword }
59
- ];
47
+ where.pagePath$like$or = keyword;
48
+ where.pageName$like$or = keyword;
49
+ where.message$like$or = keyword;
50
+ where.errorType$like$or = keyword;
51
+ where.productName$like$or = keyword;
52
+ where.productCode$like$or = keyword;
53
+ where.productVersion$like$or = keyword;
54
+ where.browserName$like$or = keyword;
55
+ where.osName$like$or = keyword;
56
+ where.deviceType$like$or = keyword;
57
+ where.deviceVendor$like$or = keyword;
58
+ where.deviceModel$like$or = keyword;
60
59
  }
61
60
 
62
61
  if (errorType) {