befly 3.24.19 → 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/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 +8 -30
- package/apis/tongJi/errorList.js +26 -27
- package/apis/tongJi/errorReport.js +26 -43
- package/apis/tongJi/errorStats.js +17 -48
- package/apis/tongJi/fallbackReset.js +7 -15
- package/apis/tongJi/infoReport.js +20 -32
- package/apis/tongJi/infoStats.js +5 -17
- package/apis/tongJi/onlineReport.js +50 -56
- package/apis/tongJi/onlineStats.js +97 -111
- package/checks/config.js +44 -1
- package/configs/beflyConfig.json +10 -1
- package/index.js +25 -0
- package/lib/dbHelper.js +1 -1
- package/lib/dbParse.js +61 -99
- package/lib/dbUtil.js +101 -21
- package/lib/redisHelper.js +25 -0
- package/lib/sqlBuilder.js +6 -0
- package/package.json +1 -1
- package/plugins/email.js +3 -6
- package/router/api.js +0 -7
- 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 -23
- package/apis/dashboard/permissionStats.js +0 -27
- package/apis/dashboard/systemInfo.js +0 -19
- package/lib/requestMetrics.js +0 -203
package/apis/sysConfig/insert.js
CHANGED
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
20
|
+
const existing = await befly.mysql.getOne({
|
|
21
|
+
table: "beflySysConfig",
|
|
22
|
+
where: { code: ctx.body.code }
|
|
23
|
+
});
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
if (existing.data?.id) {
|
|
26
|
+
return befly.tool.No("配置代码已存在");
|
|
27
|
+
}
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
};
|
package/apis/sysConfig/select.js
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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"]
|
package/apis/sysConfig/update.js
CHANGED
|
@@ -6,7 +6,7 @@ export default {
|
|
|
6
6
|
body: "none",
|
|
7
7
|
auth: true,
|
|
8
8
|
fields: {
|
|
9
|
-
id:
|
|
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:
|
|
17
|
+
state: sysConfigTable.state
|
|
18
18
|
},
|
|
19
19
|
required: ["id"],
|
|
20
20
|
handler: async (befly, ctx) => {
|
|
21
|
-
|
|
22
|
-
|
|
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
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getDateYmdNumber } from "#root/utils/datetime.js";
|
|
2
|
+
|
|
3
|
+
import { getTongJiMonthStartDate, getTongJiNumber, getTongJiWeekStartDate } from "./_tongJi.js";
|
|
2
4
|
|
|
3
5
|
const ONLINE_ACTIVE_KEY = "online:active";
|
|
4
6
|
const INFO_STATS_FALLBACK_COUNT_KEY = "stats:fallback:infoStats:count";
|
|
@@ -12,30 +14,6 @@ function getErrorStatsFallbackDailyCountKey(reportDate) {
|
|
|
12
14
|
return `stats:fallback:errorStats:count:day:${reportDate}`;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
function toNumber(value) {
|
|
16
|
-
const num = Number(value);
|
|
17
|
-
|
|
18
|
-
if (!Number.isFinite(num)) {
|
|
19
|
-
return 0;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return num;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function getWeekStartDate(timestamp = Date.now()) {
|
|
26
|
-
const date = Reflect.construct(Date, [timestamp]);
|
|
27
|
-
const day = date.getDay();
|
|
28
|
-
const offset = day === 0 ? -6 : 1 - day;
|
|
29
|
-
|
|
30
|
-
return getDateYmdNumber(addDays(timestamp, offset));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function getMonthStartDate(timestamp = Date.now()) {
|
|
34
|
-
const date = Reflect.construct(Date, [timestamp]);
|
|
35
|
-
|
|
36
|
-
return getDateYmdNumber(Reflect.construct(Date, [date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0]).getTime());
|
|
37
|
-
}
|
|
38
|
-
|
|
39
17
|
function getOnlineReportTimeKey(periodType, periodValue) {
|
|
40
18
|
return `online:${periodType}:${periodValue}:reportTime`;
|
|
41
19
|
}
|
|
@@ -49,7 +27,7 @@ function getErrorCountKey(periodType, periodValue) {
|
|
|
49
27
|
}
|
|
50
28
|
|
|
51
29
|
async function getRedisStringNumber(redis, key) {
|
|
52
|
-
return
|
|
30
|
+
return getTongJiNumber(await redis.getString(key));
|
|
53
31
|
}
|
|
54
32
|
|
|
55
33
|
async function getRedisSetCount(redis, key) {
|
|
@@ -57,7 +35,7 @@ async function getRedisSetCount(redis, key) {
|
|
|
57
35
|
return 0;
|
|
58
36
|
}
|
|
59
37
|
|
|
60
|
-
return
|
|
38
|
+
return getTongJiNumber(await redis.scard(key));
|
|
61
39
|
}
|
|
62
40
|
|
|
63
41
|
async function getRedisSortedSetCount(redis, key) {
|
|
@@ -65,7 +43,7 @@ async function getRedisSortedSetCount(redis, key) {
|
|
|
65
43
|
return 0;
|
|
66
44
|
}
|
|
67
45
|
|
|
68
|
-
return
|
|
46
|
+
return getTongJiNumber(await redis.zcard(key));
|
|
69
47
|
}
|
|
70
48
|
|
|
71
49
|
async function getRedisConnectivity(redis) {
|
|
@@ -102,8 +80,8 @@ export default {
|
|
|
102
80
|
handler: async (befly) => {
|
|
103
81
|
const now = Date.now();
|
|
104
82
|
const reportDate = getDateYmdNumber(now);
|
|
105
|
-
const weekStartDate =
|
|
106
|
-
const monthStartDate =
|
|
83
|
+
const weekStartDate = getTongJiWeekStartDate(now);
|
|
84
|
+
const monthStartDate = getTongJiMonthStartDate(now);
|
|
107
85
|
const redisConnectivity = await getRedisConnectivity(befly.redis);
|
|
108
86
|
|
|
109
87
|
if (!befly.redis) {
|
package/apis/tongJi/errorList.js
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 =
|
|
23
|
-
const limit =
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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) {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { UAParser } from "ua-parser-js";
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import errorReportTable from "#root/tables/errorReport.json";
|
|
4
|
+
import { getDateYmdNumber, getTimeBucketStart } from "#root/utils/datetime.js";
|
|
5
|
+
|
|
6
|
+
import { expireTongJiRedisKeys, getTongJiMonthStartDate, getTongJiWeekStartDate } from "./_tongJi.js";
|
|
4
7
|
|
|
5
8
|
const VISIT_STATS_BUCKET_MS = 30 * 60 * 1000;
|
|
6
9
|
const ERROR_STATS_REDIS_TTL_SECONDS = 45 * 24 * 60 * 60;
|
|
@@ -30,20 +33,6 @@ function getErrorReportUaData(ctx) {
|
|
|
30
33
|
};
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
function getErrorStatsWeekStartDate(timestamp = Date.now()) {
|
|
34
|
-
const date = Reflect.construct(Date, [timestamp]);
|
|
35
|
-
const day = date.getDay();
|
|
36
|
-
const offset = day === 0 ? -6 : 1 - day;
|
|
37
|
-
|
|
38
|
-
return getDateYmdNumber(addDays(timestamp, offset));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function getErrorStatsMonthStartDate(timestamp = Date.now()) {
|
|
42
|
-
const date = Reflect.construct(Date, [timestamp]);
|
|
43
|
-
|
|
44
|
-
return getDateYmdNumber(Reflect.construct(Date, [date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0]).getTime());
|
|
45
|
-
}
|
|
46
|
-
|
|
47
36
|
function getErrorStatsPeriodCountKey(periodType, periodValue) {
|
|
48
37
|
return `error:${periodType}:${periodValue}:count`;
|
|
49
38
|
}
|
|
@@ -64,19 +53,13 @@ function getErrorStatsDayTypeCountKey(bucketDate, errorType) {
|
|
|
64
53
|
return `error:day:${bucketDate}:type:${encodeURIComponent(String(errorType || "unknown"))}:count`;
|
|
65
54
|
}
|
|
66
55
|
|
|
67
|
-
async function setErrorStatsRedisTtl(befly, keys) {
|
|
68
|
-
for (const key of keys) {
|
|
69
|
-
await befly.redis.expire(key, ERROR_STATS_REDIS_TTL_SECONDS);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
56
|
async function updateErrorStatsRedis(befly, now, bucketDate, bucketTime, errorType) {
|
|
74
57
|
if (!befly.redis) {
|
|
75
58
|
return;
|
|
76
59
|
}
|
|
77
60
|
|
|
78
|
-
const weekStartDate =
|
|
79
|
-
const monthStartDate =
|
|
61
|
+
const weekStartDate = getTongJiWeekStartDate(now);
|
|
62
|
+
const monthStartDate = getTongJiMonthStartDate(now);
|
|
80
63
|
const dayCountKey = getErrorStatsPeriodCountKey("day", bucketDate);
|
|
81
64
|
const weekCountKey = getErrorStatsPeriodCountKey("week", weekStartDate);
|
|
82
65
|
const monthCountKey = getErrorStatsPeriodCountKey("month", monthStartDate);
|
|
@@ -95,7 +78,7 @@ async function updateErrorStatsRedis(befly, now, bucketDate, bucketTime, errorTy
|
|
|
95
78
|
await befly.redis.sadd(dayTypesKey, [String(errorType || "unknown")]);
|
|
96
79
|
await befly.redis.incr(dayTypeCountKey);
|
|
97
80
|
|
|
98
|
-
await
|
|
81
|
+
await expireTongJiRedisKeys(befly, [dayCountKey, weekCountKey, monthCountKey, dayBucketsKey, dayBucketCountKey, dayTypesKey, dayTypeCountKey], ERROR_STATS_REDIS_TTL_SECONDS);
|
|
99
82
|
}
|
|
100
83
|
|
|
101
84
|
export default {
|
|
@@ -104,15 +87,15 @@ export default {
|
|
|
104
87
|
body: "none",
|
|
105
88
|
auth: false,
|
|
106
89
|
fields: {
|
|
107
|
-
pagePath:
|
|
108
|
-
pageName:
|
|
109
|
-
source:
|
|
110
|
-
productName:
|
|
111
|
-
productCode:
|
|
112
|
-
productVersion:
|
|
113
|
-
errorType:
|
|
114
|
-
message:
|
|
115
|
-
detail:
|
|
90
|
+
pagePath: errorReportTable.pagePath,
|
|
91
|
+
pageName: errorReportTable.pageName,
|
|
92
|
+
source: errorReportTable.source,
|
|
93
|
+
productName: errorReportTable.productName,
|
|
94
|
+
productCode: errorReportTable.productCode,
|
|
95
|
+
productVersion: errorReportTable.productVersion,
|
|
96
|
+
errorType: errorReportTable.errorType,
|
|
97
|
+
message: errorReportTable.message,
|
|
98
|
+
detail: errorReportTable.detail
|
|
116
99
|
},
|
|
117
100
|
required: [],
|
|
118
101
|
handler: async (befly, ctx) => {
|
|
@@ -129,15 +112,15 @@ export default {
|
|
|
129
112
|
bucketTime: bucketTime,
|
|
130
113
|
bucketDate: bucketDate,
|
|
131
114
|
hitCount: 1,
|
|
132
|
-
source: ctx.body
|
|
133
|
-
productName: ctx.body
|
|
134
|
-
productCode: ctx.body
|
|
135
|
-
productVersion: ctx.body
|
|
136
|
-
pagePath: ctx.body
|
|
137
|
-
pageName: ctx.body
|
|
138
|
-
errorType: ctx.body
|
|
139
|
-
message: ctx.body
|
|
140
|
-
detail: ctx.body
|
|
115
|
+
source: ctx.body.source || "",
|
|
116
|
+
productName: ctx.body.productName || "",
|
|
117
|
+
productCode: ctx.body.productCode || "",
|
|
118
|
+
productVersion: ctx.body.productVersion || "",
|
|
119
|
+
pagePath: ctx.body.pagePath || "",
|
|
120
|
+
pageName: ctx.body.pageName || "",
|
|
121
|
+
errorType: ctx.body.errorType || "",
|
|
122
|
+
message: ctx.body.message || "",
|
|
123
|
+
detail: ctx.body.detail || "",
|
|
141
124
|
userAgent: uaData.userAgent,
|
|
142
125
|
browserName: uaData.browserName,
|
|
143
126
|
browserVersion: uaData.browserVersion,
|
|
@@ -151,7 +134,7 @@ export default {
|
|
|
151
134
|
}
|
|
152
135
|
});
|
|
153
136
|
|
|
154
|
-
await updateErrorStatsRedis(befly, now, bucketDate, bucketTime, ctx.body
|
|
137
|
+
await updateErrorStatsRedis(befly, now, bucketDate, bucketTime, ctx.body.errorType || "unknown");
|
|
155
138
|
|
|
156
139
|
return befly.tool.Yes("上报成功", {
|
|
157
140
|
reportTime: now,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getDateYmdNumber } from "#root/utils/datetime.js";
|
|
2
2
|
|
|
3
|
+
import { getTongJiMonthStartDate, getTongJiNumber, getTongJiRecentDateList, getTongJiWeekStartDate } from "./_tongJi.js";
|
|
4
|
+
|
|
3
5
|
const ERROR_STATS_DAY_LIMIT = 30;
|
|
4
6
|
const ERROR_STATS_FALLBACK_COUNT_KEY = "stats:fallback:errorStats:count";
|
|
5
7
|
const ERROR_STATS_FALLBACK_DAILY_COUNT_KEY_PREFIX = "stats:fallback:errorStats:count:day:";
|
|
@@ -9,39 +11,6 @@ function getErrorStatsFallbackDailyCountKey(bucketDate) {
|
|
|
9
11
|
return `${ERROR_STATS_FALLBACK_DAILY_COUNT_KEY_PREFIX}${bucketDate}`;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
function toNumber(value) {
|
|
13
|
-
const num = Number(value);
|
|
14
|
-
if (!Number.isFinite(num)) {
|
|
15
|
-
return 0;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return num;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function getErrorStatsWeekStartDate(timestamp = Date.now()) {
|
|
22
|
-
const date = Reflect.construct(Date, [timestamp]);
|
|
23
|
-
const day = date.getDay();
|
|
24
|
-
const offset = day === 0 ? -6 : 1 - day;
|
|
25
|
-
|
|
26
|
-
return getDateYmdNumber(timestamp + offset * 24 * 60 * 60 * 1000);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getErrorStatsMonthStartDate(timestamp = Date.now()) {
|
|
30
|
-
const date = Reflect.construct(Date, [timestamp]);
|
|
31
|
-
|
|
32
|
-
return getDateYmdNumber(Reflect.construct(Date, [date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0]).getTime());
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function getErrorStatsRecentDateList(now = Date.now(), limit = ERROR_STATS_DAY_LIMIT) {
|
|
36
|
-
const list = [];
|
|
37
|
-
|
|
38
|
-
for (let i = limit - 1; i >= 0; i--) {
|
|
39
|
-
list.push(getDateYmdNumber(now - i * 24 * 60 * 60 * 1000));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return list;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
14
|
function getErrorStatsPeriodCountKey(periodType, periodValue) {
|
|
46
15
|
return `error:${periodType}:${periodValue}:count`;
|
|
47
16
|
}
|
|
@@ -64,7 +33,7 @@ function getErrorStatsDayTypeCountKey(bucketDate, errorType) {
|
|
|
64
33
|
|
|
65
34
|
async function getErrorStatsRedisSummary(befly, periodType, periodValue) {
|
|
66
35
|
return {
|
|
67
|
-
count:
|
|
36
|
+
count: getTongJiNumber(await befly.redis.getString(getErrorStatsPeriodCountKey(periodType, periodValue)))
|
|
68
37
|
};
|
|
69
38
|
}
|
|
70
39
|
|
|
@@ -73,12 +42,12 @@ async function getErrorStatsRedisTrend(befly, bucketDate) {
|
|
|
73
42
|
const trend = [];
|
|
74
43
|
|
|
75
44
|
for (const bucketTime of bucketSet) {
|
|
76
|
-
const itemBucketTime =
|
|
45
|
+
const itemBucketTime = getTongJiNumber(bucketTime);
|
|
77
46
|
|
|
78
47
|
trend.push({
|
|
79
48
|
bucketTime: itemBucketTime,
|
|
80
49
|
bucketDate: bucketDate,
|
|
81
|
-
count:
|
|
50
|
+
count: getTongJiNumber(await befly.redis.getString(getErrorStatsDayBucketCountKey(bucketDate, itemBucketTime)))
|
|
82
51
|
});
|
|
83
52
|
}
|
|
84
53
|
|
|
@@ -92,7 +61,7 @@ async function getErrorStatsRedisDays(befly, recentDateList) {
|
|
|
92
61
|
for (const item of recentDateList) {
|
|
93
62
|
days.push({
|
|
94
63
|
bucketDate: item,
|
|
95
|
-
count:
|
|
64
|
+
count: getTongJiNumber(await befly.redis.getString(getErrorStatsPeriodCountKey("day", item)))
|
|
96
65
|
});
|
|
97
66
|
}
|
|
98
67
|
|
|
@@ -105,7 +74,7 @@ async function getErrorStatsRedisTopTypes(befly, bucketDate) {
|
|
|
105
74
|
|
|
106
75
|
for (const errorType of typeSet) {
|
|
107
76
|
const name = String(errorType || "unknown");
|
|
108
|
-
const count =
|
|
77
|
+
const count = getTongJiNumber(await befly.redis.getString(getErrorStatsDayTypeCountKey(bucketDate, name)));
|
|
109
78
|
|
|
110
79
|
if (count <= 0) {
|
|
111
80
|
continue;
|
|
@@ -185,7 +154,7 @@ async function getErrorStatsSummary(befly, startDate, endDate) {
|
|
|
185
154
|
const detail = result.data?.[0] || {};
|
|
186
155
|
|
|
187
156
|
return {
|
|
188
|
-
count:
|
|
157
|
+
count: getTongJiNumber(detail.count)
|
|
189
158
|
};
|
|
190
159
|
}
|
|
191
160
|
|
|
@@ -201,9 +170,9 @@ export default {
|
|
|
201
170
|
const tableReady = tableExistsResult.data === true;
|
|
202
171
|
const now = Date.now();
|
|
203
172
|
const bucketDate = getDateYmdNumber(now);
|
|
204
|
-
const weekStartDate =
|
|
205
|
-
const monthStartDate =
|
|
206
|
-
const recentDateList =
|
|
173
|
+
const weekStartDate = getTongJiWeekStartDate(now);
|
|
174
|
+
const monthStartDate = getTongJiMonthStartDate(now);
|
|
175
|
+
const recentDateList = getTongJiRecentDateList(now, ERROR_STATS_DAY_LIMIT);
|
|
207
176
|
|
|
208
177
|
if (!tableReady) {
|
|
209
178
|
return befly.tool.Yes("获取成功", {
|
|
@@ -247,9 +216,9 @@ export default {
|
|
|
247
216
|
const trend = [];
|
|
248
217
|
for (const item of trendRes.data || []) {
|
|
249
218
|
trend.push({
|
|
250
|
-
bucketTime:
|
|
251
|
-
bucketDate:
|
|
252
|
-
count:
|
|
219
|
+
bucketTime: getTongJiNumber(item.bucketTime),
|
|
220
|
+
bucketDate: getTongJiNumber(item.bucketDate),
|
|
221
|
+
count: getTongJiNumber(item.count)
|
|
253
222
|
});
|
|
254
223
|
}
|
|
255
224
|
|
|
@@ -263,7 +232,7 @@ export default {
|
|
|
263
232
|
}
|
|
264
233
|
|
|
265
234
|
for (const item of daysRes.data || []) {
|
|
266
|
-
const itemBucketDate =
|
|
235
|
+
const itemBucketDate = getTongJiNumber(item.bucketDate);
|
|
267
236
|
|
|
268
237
|
if (!daysMap.has(itemBucketDate)) {
|
|
269
238
|
continue;
|
|
@@ -271,7 +240,7 @@ export default {
|
|
|
271
240
|
|
|
272
241
|
daysMap.set(itemBucketDate, {
|
|
273
242
|
bucketDate: itemBucketDate,
|
|
274
|
-
count:
|
|
243
|
+
count: getTongJiNumber(item.count)
|
|
275
244
|
});
|
|
276
245
|
}
|
|
277
246
|
|
|
@@ -281,7 +250,7 @@ export default {
|
|
|
281
250
|
for (const item of topTypesRes.data || []) {
|
|
282
251
|
topTypes.push({
|
|
283
252
|
errorType: String(item.errorType || "unknown"),
|
|
284
|
-
count:
|
|
253
|
+
count: getTongJiNumber(item.count)
|
|
285
254
|
});
|
|
286
255
|
}
|
|
287
256
|
|