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.
- 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/dashboard/environmentInfo.js +6 -1
- package/apis/dashboard/serviceStatus.js +78 -60
- 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 +192 -0
- package/apis/tongJi/errorList.js +26 -27
- package/apis/tongJi/errorReport.js +72 -18
- package/apis/tongJi/errorStats.js +154 -30
- package/apis/tongJi/fallbackReset.js +61 -0
- package/apis/tongJi/infoReport.js +112 -24
- package/apis/tongJi/infoStats.js +164 -84
- package/apis/tongJi/onlineReport.js +58 -73
- package/apis/tongJi/onlineStats.js +140 -151
- package/checks/config.js +44 -1
- package/configs/beflyConfig.json +10 -1
- package/hooks/permission.js +6 -2
- package/hooks/rateLimit.js +245 -0
- package/index.js +25 -0
- package/lib/cacheHelper.js +105 -60
- package/lib/dbHelper.js +1 -1
- package/lib/dbParse.js +61 -99
- package/lib/dbUtil.js +101 -21
- package/lib/redisHelper.js +93 -0
- package/lib/sqlBuilder.js +6 -0
- package/package.json +2 -2
- package/plugins/email.js +4 -3
- 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 -20
- package/apis/dashboard/permissionStats.js +0 -27
- package/apis/dashboard/systemInfo.js +0 -19
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { UAParser } from "ua-parser-js";
|
|
2
2
|
|
|
3
|
+
import errorReportTable from "#root/tables/errorReport.json";
|
|
3
4
|
import { getDateYmdNumber, getTimeBucketStart } from "#root/utils/datetime.js";
|
|
4
5
|
|
|
6
|
+
import { expireTongJiRedisKeys, getTongJiMonthStartDate, getTongJiWeekStartDate } from "./_tongJi.js";
|
|
7
|
+
|
|
5
8
|
const VISIT_STATS_BUCKET_MS = 30 * 60 * 1000;
|
|
9
|
+
const ERROR_STATS_REDIS_TTL_SECONDS = 45 * 24 * 60 * 60;
|
|
6
10
|
|
|
7
11
|
function getErrorReportUaData(ctx) {
|
|
8
12
|
let userAgent = "";
|
|
@@ -29,21 +33,69 @@ function getErrorReportUaData(ctx) {
|
|
|
29
33
|
};
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
function getErrorStatsPeriodCountKey(periodType, periodValue) {
|
|
37
|
+
return `error:${periodType}:${periodValue}:count`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getErrorStatsDayBucketsKey(bucketDate) {
|
|
41
|
+
return `error:day:${bucketDate}:buckets`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getErrorStatsDayBucketCountKey(bucketDate, bucketTime) {
|
|
45
|
+
return `error:day:${bucketDate}:bucket:${bucketTime}:count`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getErrorStatsDayTypesKey(bucketDate) {
|
|
49
|
+
return `error:day:${bucketDate}:types`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getErrorStatsDayTypeCountKey(bucketDate, errorType) {
|
|
53
|
+
return `error:day:${bucketDate}:type:${encodeURIComponent(String(errorType || "unknown"))}:count`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function updateErrorStatsRedis(befly, now, bucketDate, bucketTime, errorType) {
|
|
57
|
+
if (!befly.redis) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const weekStartDate = getTongJiWeekStartDate(now);
|
|
62
|
+
const monthStartDate = getTongJiMonthStartDate(now);
|
|
63
|
+
const dayCountKey = getErrorStatsPeriodCountKey("day", bucketDate);
|
|
64
|
+
const weekCountKey = getErrorStatsPeriodCountKey("week", weekStartDate);
|
|
65
|
+
const monthCountKey = getErrorStatsPeriodCountKey("month", monthStartDate);
|
|
66
|
+
const dayBucketsKey = getErrorStatsDayBucketsKey(bucketDate);
|
|
67
|
+
const dayBucketCountKey = getErrorStatsDayBucketCountKey(bucketDate, bucketTime);
|
|
68
|
+
const dayTypesKey = getErrorStatsDayTypesKey(bucketDate);
|
|
69
|
+
const dayTypeCountKey = getErrorStatsDayTypeCountKey(bucketDate, errorType);
|
|
70
|
+
|
|
71
|
+
await befly.redis.incr(dayCountKey);
|
|
72
|
+
await befly.redis.incr(weekCountKey);
|
|
73
|
+
await befly.redis.incr(monthCountKey);
|
|
74
|
+
|
|
75
|
+
await befly.redis.sadd(dayBucketsKey, [String(bucketTime)]);
|
|
76
|
+
await befly.redis.incr(dayBucketCountKey);
|
|
77
|
+
|
|
78
|
+
await befly.redis.sadd(dayTypesKey, [String(errorType || "unknown")]);
|
|
79
|
+
await befly.redis.incr(dayTypeCountKey);
|
|
80
|
+
|
|
81
|
+
await expireTongJiRedisKeys(befly, [dayCountKey, weekCountKey, monthCountKey, dayBucketsKey, dayBucketCountKey, dayTypesKey, dayTypeCountKey], ERROR_STATS_REDIS_TTL_SECONDS);
|
|
82
|
+
}
|
|
83
|
+
|
|
32
84
|
export default {
|
|
33
85
|
name: "上报错误报告",
|
|
34
86
|
method: "POST",
|
|
35
87
|
body: "none",
|
|
36
88
|
auth: false,
|
|
37
89
|
fields: {
|
|
38
|
-
pagePath:
|
|
39
|
-
pageName:
|
|
40
|
-
source:
|
|
41
|
-
productName:
|
|
42
|
-
productCode:
|
|
43
|
-
productVersion:
|
|
44
|
-
errorType:
|
|
45
|
-
message:
|
|
46
|
-
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
|
|
47
99
|
},
|
|
48
100
|
required: [],
|
|
49
101
|
handler: async (befly, ctx) => {
|
|
@@ -60,15 +112,15 @@ export default {
|
|
|
60
112
|
bucketTime: bucketTime,
|
|
61
113
|
bucketDate: bucketDate,
|
|
62
114
|
hitCount: 1,
|
|
63
|
-
source: ctx.body
|
|
64
|
-
productName: ctx.body
|
|
65
|
-
productCode: ctx.body
|
|
66
|
-
productVersion: ctx.body
|
|
67
|
-
pagePath: ctx.body
|
|
68
|
-
pageName: ctx.body
|
|
69
|
-
errorType: ctx.body
|
|
70
|
-
message: ctx.body
|
|
71
|
-
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 || "",
|
|
72
124
|
userAgent: uaData.userAgent,
|
|
73
125
|
browserName: uaData.browserName,
|
|
74
126
|
browserVersion: uaData.browserVersion,
|
|
@@ -82,6 +134,8 @@ export default {
|
|
|
82
134
|
}
|
|
83
135
|
});
|
|
84
136
|
|
|
137
|
+
await updateErrorStatsRedis(befly, now, bucketDate, bucketTime, ctx.body.errorType || "unknown");
|
|
138
|
+
|
|
85
139
|
return befly.tool.Yes("上报成功", {
|
|
86
140
|
reportTime: now,
|
|
87
141
|
bucketTime: bucketTime,
|
|
@@ -1,38 +1,152 @@
|
|
|
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;
|
|
6
|
+
const ERROR_STATS_FALLBACK_COUNT_KEY = "stats:fallback:errorStats:count";
|
|
7
|
+
const ERROR_STATS_FALLBACK_DAILY_COUNT_KEY_PREFIX = "stats:fallback:errorStats:count:day:";
|
|
8
|
+
const ERROR_STATS_FALLBACK_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
9
|
+
|
|
10
|
+
function getErrorStatsFallbackDailyCountKey(bucketDate) {
|
|
11
|
+
return `${ERROR_STATS_FALLBACK_DAILY_COUNT_KEY_PREFIX}${bucketDate}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getErrorStatsPeriodCountKey(periodType, periodValue) {
|
|
15
|
+
return `error:${periodType}:${periodValue}:count`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getErrorStatsDayBucketsKey(bucketDate) {
|
|
19
|
+
return `error:day:${bucketDate}:buckets`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getErrorStatsDayBucketCountKey(bucketDate, bucketTime) {
|
|
23
|
+
return `error:day:${bucketDate}:bucket:${bucketTime}:count`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getErrorStatsDayTypesKey(bucketDate) {
|
|
27
|
+
return `error:day:${bucketDate}:types`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getErrorStatsDayTypeCountKey(bucketDate, errorType) {
|
|
31
|
+
return `error:day:${bucketDate}:type:${encodeURIComponent(String(errorType || "unknown"))}:count`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function getErrorStatsRedisSummary(befly, periodType, periodValue) {
|
|
35
|
+
return {
|
|
36
|
+
count: getTongJiNumber(await befly.redis.getString(getErrorStatsPeriodCountKey(periodType, periodValue)))
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function getErrorStatsRedisTrend(befly, bucketDate) {
|
|
41
|
+
const bucketSet = await befly.redis.smembers(getErrorStatsDayBucketsKey(bucketDate));
|
|
42
|
+
const trend = [];
|
|
43
|
+
|
|
44
|
+
for (const bucketTime of bucketSet) {
|
|
45
|
+
const itemBucketTime = getTongJiNumber(bucketTime);
|
|
46
|
+
|
|
47
|
+
trend.push({
|
|
48
|
+
bucketTime: itemBucketTime,
|
|
49
|
+
bucketDate: bucketDate,
|
|
50
|
+
count: getTongJiNumber(await befly.redis.getString(getErrorStatsDayBucketCountKey(bucketDate, itemBucketTime)))
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
trend.sort((a, b) => a.bucketTime - b.bucketTime);
|
|
55
|
+
return trend;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function getErrorStatsRedisDays(befly, recentDateList) {
|
|
59
|
+
const days = [];
|
|
4
60
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
61
|
+
for (const item of recentDateList) {
|
|
62
|
+
days.push({
|
|
63
|
+
bucketDate: item,
|
|
64
|
+
count: getTongJiNumber(await befly.redis.getString(getErrorStatsPeriodCountKey("day", item)))
|
|
65
|
+
});
|
|
9
66
|
}
|
|
10
67
|
|
|
11
|
-
return
|
|
68
|
+
return days;
|
|
12
69
|
}
|
|
13
70
|
|
|
14
|
-
function
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
71
|
+
async function getErrorStatsRedisTopTypes(befly, bucketDate) {
|
|
72
|
+
const typeSet = await befly.redis.smembers(getErrorStatsDayTypesKey(bucketDate));
|
|
73
|
+
const topTypes = [];
|
|
74
|
+
|
|
75
|
+
for (const errorType of typeSet) {
|
|
76
|
+
const name = String(errorType || "unknown");
|
|
77
|
+
const count = getTongJiNumber(await befly.redis.getString(getErrorStatsDayTypeCountKey(bucketDate, name)));
|
|
78
|
+
|
|
79
|
+
if (count <= 0) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
topTypes.push({
|
|
84
|
+
errorType: name,
|
|
85
|
+
count: count
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
topTypes.sort((a, b) => {
|
|
90
|
+
if (b.count !== a.count) {
|
|
91
|
+
return b.count - a.count;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return String(a.errorType).localeCompare(String(b.errorType), "zh-CN");
|
|
95
|
+
});
|
|
18
96
|
|
|
19
|
-
return
|
|
97
|
+
return topTypes.slice(0, 5);
|
|
20
98
|
}
|
|
21
99
|
|
|
22
|
-
function
|
|
23
|
-
const
|
|
100
|
+
async function getErrorStatsFromRedis(befly, bucketDate, weekStartDate, monthStartDate, recentDateList) {
|
|
101
|
+
const todaySummary = await getErrorStatsRedisSummary(befly, "day", bucketDate);
|
|
102
|
+
const weekSummary = await getErrorStatsRedisSummary(befly, "week", weekStartDate);
|
|
103
|
+
const monthSummary = await getErrorStatsRedisSummary(befly, "month", monthStartDate);
|
|
104
|
+
const trend = await getErrorStatsRedisTrend(befly, bucketDate);
|
|
105
|
+
const days = await getErrorStatsRedisDays(befly, recentDateList);
|
|
106
|
+
const topTypes = await getErrorStatsRedisTopTypes(befly, bucketDate);
|
|
24
107
|
|
|
25
|
-
|
|
108
|
+
if (todaySummary.count <= 0 && weekSummary.count <= 0 && monthSummary.count <= 0 && trend.length === 0 && topTypes.length === 0) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
today: {
|
|
114
|
+
bucketDate: bucketDate,
|
|
115
|
+
count: todaySummary.count
|
|
116
|
+
},
|
|
117
|
+
week: {
|
|
118
|
+
startDate: weekStartDate,
|
|
119
|
+
endDate: bucketDate,
|
|
120
|
+
count: weekSummary.count
|
|
121
|
+
},
|
|
122
|
+
month: {
|
|
123
|
+
startDate: monthStartDate,
|
|
124
|
+
endDate: bucketDate,
|
|
125
|
+
count: monthSummary.count
|
|
126
|
+
},
|
|
127
|
+
trend: trend,
|
|
128
|
+
days: days,
|
|
129
|
+
topTypes: topTypes
|
|
130
|
+
};
|
|
26
131
|
}
|
|
27
132
|
|
|
28
|
-
function
|
|
29
|
-
|
|
133
|
+
async function incrErrorStatsFallbackCount(befly, bucketDate) {
|
|
134
|
+
if (!befly.redis) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const dailyKey = getErrorStatsFallbackDailyCountKey(bucketDate);
|
|
30
139
|
|
|
31
|
-
|
|
32
|
-
|
|
140
|
+
if (typeof befly.redis.incrWithExpire === "function") {
|
|
141
|
+
await befly.redis.incrWithExpire(ERROR_STATS_FALLBACK_COUNT_KEY, ERROR_STATS_FALLBACK_TTL_SECONDS);
|
|
142
|
+
await befly.redis.incrWithExpire(dailyKey, ERROR_STATS_FALLBACK_TTL_SECONDS);
|
|
143
|
+
return;
|
|
33
144
|
}
|
|
34
145
|
|
|
35
|
-
|
|
146
|
+
await befly.redis.incr(ERROR_STATS_FALLBACK_COUNT_KEY);
|
|
147
|
+
await befly.redis.expire(ERROR_STATS_FALLBACK_COUNT_KEY, ERROR_STATS_FALLBACK_TTL_SECONDS);
|
|
148
|
+
await befly.redis.incr(dailyKey);
|
|
149
|
+
await befly.redis.expire(dailyKey, ERROR_STATS_FALLBACK_TTL_SECONDS);
|
|
36
150
|
}
|
|
37
151
|
|
|
38
152
|
async function getErrorStatsSummary(befly, startDate, endDate) {
|
|
@@ -40,7 +154,7 @@ async function getErrorStatsSummary(befly, startDate, endDate) {
|
|
|
40
154
|
const detail = result.data?.[0] || {};
|
|
41
155
|
|
|
42
156
|
return {
|
|
43
|
-
count:
|
|
157
|
+
count: getTongJiNumber(detail.count)
|
|
44
158
|
};
|
|
45
159
|
}
|
|
46
160
|
|
|
@@ -56,6 +170,9 @@ export default {
|
|
|
56
170
|
const tableReady = tableExistsResult.data === true;
|
|
57
171
|
const now = Date.now();
|
|
58
172
|
const bucketDate = getDateYmdNumber(now);
|
|
173
|
+
const weekStartDate = getTongJiWeekStartDate(now);
|
|
174
|
+
const monthStartDate = getTongJiMonthStartDate(now);
|
|
175
|
+
const recentDateList = getTongJiRecentDateList(now, ERROR_STATS_DAY_LIMIT);
|
|
59
176
|
|
|
60
177
|
if (!tableReady) {
|
|
61
178
|
return befly.tool.Yes("获取成功", {
|
|
@@ -64,12 +181,12 @@ export default {
|
|
|
64
181
|
count: 0
|
|
65
182
|
},
|
|
66
183
|
week: {
|
|
67
|
-
startDate:
|
|
184
|
+
startDate: weekStartDate,
|
|
68
185
|
endDate: bucketDate,
|
|
69
186
|
count: 0
|
|
70
187
|
},
|
|
71
188
|
month: {
|
|
72
|
-
startDate:
|
|
189
|
+
startDate: monthStartDate,
|
|
73
190
|
endDate: bucketDate,
|
|
74
191
|
count: 0
|
|
75
192
|
},
|
|
@@ -79,9 +196,16 @@ export default {
|
|
|
79
196
|
});
|
|
80
197
|
}
|
|
81
198
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
199
|
+
if (befly.redis) {
|
|
200
|
+
const redisResult = await getErrorStatsFromRedis(befly, bucketDate, weekStartDate, monthStartDate, recentDateList);
|
|
201
|
+
|
|
202
|
+
if (redisResult) {
|
|
203
|
+
return befly.tool.Yes("获取成功", redisResult);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
await incrErrorStatsFallbackCount(befly, bucketDate);
|
|
207
|
+
}
|
|
208
|
+
|
|
85
209
|
const todaySummary = await getErrorStatsSummary(befly, bucketDate, bucketDate);
|
|
86
210
|
const weekSummary = await getErrorStatsSummary(befly, weekStartDate, bucketDate);
|
|
87
211
|
const monthSummary = await getErrorStatsSummary(befly, monthStartDate, bucketDate);
|
|
@@ -92,9 +216,9 @@ export default {
|
|
|
92
216
|
const trend = [];
|
|
93
217
|
for (const item of trendRes.data || []) {
|
|
94
218
|
trend.push({
|
|
95
|
-
bucketTime:
|
|
96
|
-
bucketDate:
|
|
97
|
-
count:
|
|
219
|
+
bucketTime: getTongJiNumber(item.bucketTime),
|
|
220
|
+
bucketDate: getTongJiNumber(item.bucketDate),
|
|
221
|
+
count: getTongJiNumber(item.count)
|
|
98
222
|
});
|
|
99
223
|
}
|
|
100
224
|
|
|
@@ -108,7 +232,7 @@ export default {
|
|
|
108
232
|
}
|
|
109
233
|
|
|
110
234
|
for (const item of daysRes.data || []) {
|
|
111
|
-
const itemBucketDate =
|
|
235
|
+
const itemBucketDate = getTongJiNumber(item.bucketDate);
|
|
112
236
|
|
|
113
237
|
if (!daysMap.has(itemBucketDate)) {
|
|
114
238
|
continue;
|
|
@@ -116,7 +240,7 @@ export default {
|
|
|
116
240
|
|
|
117
241
|
daysMap.set(itemBucketDate, {
|
|
118
242
|
bucketDate: itemBucketDate,
|
|
119
|
-
count:
|
|
243
|
+
count: getTongJiNumber(item.count)
|
|
120
244
|
});
|
|
121
245
|
}
|
|
122
246
|
|
|
@@ -126,7 +250,7 @@ export default {
|
|
|
126
250
|
for (const item of topTypesRes.data || []) {
|
|
127
251
|
topTypes.push({
|
|
128
252
|
errorType: String(item.errorType || "unknown"),
|
|
129
|
-
count:
|
|
253
|
+
count: getTongJiNumber(item.count)
|
|
130
254
|
});
|
|
131
255
|
}
|
|
132
256
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getDateYmdNumber } from "#root/utils/datetime.js";
|
|
2
|
+
|
|
3
|
+
import { getTongJiNumber } from "./_tongJi.js";
|
|
4
|
+
|
|
5
|
+
const INFO_STATS_FALLBACK_COUNT_KEY = "stats:fallback:infoStats:count";
|
|
6
|
+
const ERROR_STATS_FALLBACK_COUNT_KEY = "stats:fallback:errorStats:count";
|
|
7
|
+
|
|
8
|
+
function getInfoStatsFallbackDailyCountKey(reportDate) {
|
|
9
|
+
return `stats:fallback:infoStats:count:day:${reportDate}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getErrorStatsFallbackDailyCountKey(reportDate) {
|
|
13
|
+
return `stats:fallback:errorStats:count:day:${reportDate}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
name: "重置统计回退计数",
|
|
18
|
+
method: "POST",
|
|
19
|
+
body: "none",
|
|
20
|
+
auth: true,
|
|
21
|
+
fields: {},
|
|
22
|
+
required: [],
|
|
23
|
+
handler: async (befly) => {
|
|
24
|
+
if (!befly.redis) {
|
|
25
|
+
return befly.tool.No("Redis 不可用,无法重置", {
|
|
26
|
+
reset: false
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const reportDate = getDateYmdNumber(Date.now());
|
|
31
|
+
const infoDailyKey = getInfoStatsFallbackDailyCountKey(reportDate);
|
|
32
|
+
const errorDailyKey = getErrorStatsFallbackDailyCountKey(reportDate);
|
|
33
|
+
const resetKeys = [INFO_STATS_FALLBACK_COUNT_KEY, ERROR_STATS_FALLBACK_COUNT_KEY, infoDailyKey, errorDailyKey];
|
|
34
|
+
const before = {
|
|
35
|
+
infoStats: getTongJiNumber(await befly.redis.getString(INFO_STATS_FALLBACK_COUNT_KEY)),
|
|
36
|
+
errorStats: getTongJiNumber(await befly.redis.getString(ERROR_STATS_FALLBACK_COUNT_KEY)),
|
|
37
|
+
today: {
|
|
38
|
+
infoStats: getTongJiNumber(await befly.redis.getString(infoDailyKey)),
|
|
39
|
+
errorStats: getTongJiNumber(await befly.redis.getString(errorDailyKey))
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const removedCount = await befly.redis.delBatch(resetKeys);
|
|
44
|
+
|
|
45
|
+
return befly.tool.Yes("重置成功", {
|
|
46
|
+
reset: true,
|
|
47
|
+
reportDate: reportDate,
|
|
48
|
+
removedCount: getTongJiNumber(removedCount),
|
|
49
|
+
keys: resetKeys,
|
|
50
|
+
before: before,
|
|
51
|
+
after: {
|
|
52
|
+
infoStats: 0,
|
|
53
|
+
errorStats: 0,
|
|
54
|
+
today: {
|
|
55
|
+
infoStats: 0,
|
|
56
|
+
errorStats: 0
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
import { UAParser } from "ua-parser-js";
|
|
2
2
|
|
|
3
|
+
import infoReportTable from "#root/tables/infoReport.json";
|
|
3
4
|
import { getDateYmdNumber } from "#root/utils/datetime.js";
|
|
4
5
|
import { isValidPositiveInt } from "#root/utils/is.js";
|
|
5
6
|
|
|
7
|
+
import { getTongJiMonthStartDate, getTongJiWeekStartDate } from "./_tongJi.js";
|
|
8
|
+
|
|
9
|
+
const INFO_STATS_REDIS_TTL_SECONDS = 45 * 24 * 60 * 60;
|
|
10
|
+
const INFO_STATS_FIELDS = [
|
|
11
|
+
{ key: "sources", valueKey: "source" },
|
|
12
|
+
{ key: "productNames", valueKey: "productName" },
|
|
13
|
+
{ key: "productCodes", valueKey: "productCode" },
|
|
14
|
+
{ key: "productVersions", valueKey: "productVersion" },
|
|
15
|
+
{ key: "pagePaths", valueKey: "pagePath" },
|
|
16
|
+
{ key: "pageNames", valueKey: "pageName" },
|
|
17
|
+
{ key: "deviceTypes", valueKey: "deviceType" },
|
|
18
|
+
{ key: "browsers", valueKey: "browserName" },
|
|
19
|
+
{ key: "browserVersions", valueKey: "browserVersion" },
|
|
20
|
+
{ key: "osList", valueKey: "osName" },
|
|
21
|
+
{ key: "osVersions", valueKey: "osVersion" },
|
|
22
|
+
{ key: "deviceVendors", valueKey: "deviceVendor" },
|
|
23
|
+
{ key: "deviceModels", valueKey: "deviceModel" },
|
|
24
|
+
{ key: "engines", valueKey: "engineName" },
|
|
25
|
+
{ key: "cpuArchitectures", valueKey: "cpuArchitecture" }
|
|
26
|
+
];
|
|
27
|
+
|
|
6
28
|
function getInfoStatsMember(ctx) {
|
|
7
29
|
if (isValidPositiveInt(ctx.userId)) {
|
|
8
30
|
return `user:${ctx.userId}`;
|
|
@@ -11,19 +33,66 @@ function getInfoStatsMember(ctx) {
|
|
|
11
33
|
return `ip:${ctx.ip || "unknown"}`;
|
|
12
34
|
}
|
|
13
35
|
|
|
36
|
+
function getInfoStatsNamesKey(periodType, periodValue, fieldKey) {
|
|
37
|
+
return `info:${periodType}:${periodValue}:${fieldKey}:names`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getInfoStatsCountKey(periodType, periodValue, fieldKey, value) {
|
|
41
|
+
return `info:${periodType}:${periodValue}:${fieldKey}:count:${encodeURIComponent(String(value || ""))}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getInfoStatsReportTimeKey(periodType, periodValue) {
|
|
45
|
+
return `info:${periodType}:${periodValue}:reportTime`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function updateInfoStatsPeriodRedis(befly, periodType, periodValue, statData, now) {
|
|
49
|
+
const reportTimeKey = getInfoStatsReportTimeKey(periodType, periodValue);
|
|
50
|
+
await befly.redis.setString(reportTimeKey, String(now), INFO_STATS_REDIS_TTL_SECONDS);
|
|
51
|
+
await befly.redis.expire(reportTimeKey, INFO_STATS_REDIS_TTL_SECONDS);
|
|
52
|
+
|
|
53
|
+
for (const item of INFO_STATS_FIELDS) {
|
|
54
|
+
const rawValue = String(statData[item.valueKey] || "").trim();
|
|
55
|
+
|
|
56
|
+
if (!rawValue) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const namesKey = getInfoStatsNamesKey(periodType, periodValue, item.key);
|
|
61
|
+
const countKey = getInfoStatsCountKey(periodType, periodValue, item.key, rawValue);
|
|
62
|
+
|
|
63
|
+
await befly.redis.sadd(namesKey, [rawValue]);
|
|
64
|
+
await befly.redis.incr(countKey);
|
|
65
|
+
await befly.redis.expire(namesKey, INFO_STATS_REDIS_TTL_SECONDS);
|
|
66
|
+
await befly.redis.expire(countKey, INFO_STATS_REDIS_TTL_SECONDS);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function updateInfoStatsRedis(befly, now, reportDate, statData) {
|
|
71
|
+
if (!befly.redis) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const weekStartDate = getTongJiWeekStartDate(now);
|
|
76
|
+
const monthStartDate = getTongJiMonthStartDate(now);
|
|
77
|
+
|
|
78
|
+
await updateInfoStatsPeriodRedis(befly, "day", reportDate, statData, now);
|
|
79
|
+
await updateInfoStatsPeriodRedis(befly, "week", weekStartDate, statData, now);
|
|
80
|
+
await updateInfoStatsPeriodRedis(befly, "month", monthStartDate, statData, now);
|
|
81
|
+
}
|
|
82
|
+
|
|
14
83
|
export default {
|
|
15
84
|
name: "上报访问信息统计",
|
|
16
85
|
method: "POST",
|
|
17
86
|
body: "none",
|
|
18
87
|
auth: false,
|
|
19
88
|
fields: {
|
|
20
|
-
pagePath:
|
|
21
|
-
pageName:
|
|
22
|
-
source:
|
|
23
|
-
productName:
|
|
24
|
-
productCode:
|
|
25
|
-
productVersion:
|
|
26
|
-
detail:
|
|
89
|
+
pagePath: infoReportTable.pagePath,
|
|
90
|
+
pageName: infoReportTable.pageName,
|
|
91
|
+
source: infoReportTable.source,
|
|
92
|
+
productName: infoReportTable.productName,
|
|
93
|
+
productCode: infoReportTable.productCode,
|
|
94
|
+
productVersion: infoReportTable.productVersion,
|
|
95
|
+
detail: infoReportTable.detail
|
|
27
96
|
},
|
|
28
97
|
required: [],
|
|
29
98
|
handler: async (befly, ctx) => {
|
|
@@ -31,7 +100,6 @@ export default {
|
|
|
31
100
|
const reportDate = getDateYmdNumber(now);
|
|
32
101
|
const member = getInfoStatsMember(ctx);
|
|
33
102
|
const tableReady = (await befly.mysql.tableExists("beflyInfoReport")).data === true;
|
|
34
|
-
const body = ctx.body || {};
|
|
35
103
|
let userAgent = "";
|
|
36
104
|
|
|
37
105
|
if (!tableReady) {
|
|
@@ -68,32 +136,52 @@ export default {
|
|
|
68
136
|
});
|
|
69
137
|
}
|
|
70
138
|
|
|
139
|
+
const statData = {
|
|
140
|
+
source: ctx.body.source || "",
|
|
141
|
+
productName: ctx.body.productName || "",
|
|
142
|
+
productCode: ctx.body.productCode || "",
|
|
143
|
+
productVersion: ctx.body.productVersion || "",
|
|
144
|
+
pagePath: ctx.body.pagePath || "",
|
|
145
|
+
pageName: ctx.body.pageName || "",
|
|
146
|
+
deviceType: uaResult.device.type || "desktop",
|
|
147
|
+
browserName: uaResult.browser.name || "",
|
|
148
|
+
browserVersion: uaResult.browser.version || "",
|
|
149
|
+
osName: uaResult.os.name || "",
|
|
150
|
+
osVersion: uaResult.os.version || "",
|
|
151
|
+
deviceVendor: uaResult.device.vendor || "",
|
|
152
|
+
deviceModel: uaResult.device.model || "",
|
|
153
|
+
engineName: uaResult.engine.name || "",
|
|
154
|
+
cpuArchitecture: uaResult.cpu.architecture || ""
|
|
155
|
+
};
|
|
156
|
+
|
|
71
157
|
await befly.mysql.insData({
|
|
72
158
|
table: "beflyInfoReport",
|
|
73
159
|
data: {
|
|
74
160
|
reportTime: now,
|
|
75
161
|
reportDate: reportDate,
|
|
76
162
|
memberKey: member,
|
|
77
|
-
source:
|
|
78
|
-
productName:
|
|
79
|
-
productCode:
|
|
80
|
-
productVersion:
|
|
81
|
-
pagePath:
|
|
82
|
-
pageName:
|
|
83
|
-
detail: body.detail || "",
|
|
163
|
+
source: statData.source,
|
|
164
|
+
productName: statData.productName,
|
|
165
|
+
productCode: statData.productCode,
|
|
166
|
+
productVersion: statData.productVersion,
|
|
167
|
+
pagePath: statData.pagePath,
|
|
168
|
+
pageName: statData.pageName,
|
|
169
|
+
detail: ctx.body.detail || "",
|
|
84
170
|
userAgent: userAgent,
|
|
85
|
-
deviceType:
|
|
86
|
-
browserName:
|
|
87
|
-
browserVersion:
|
|
88
|
-
osName:
|
|
89
|
-
osVersion:
|
|
90
|
-
deviceVendor:
|
|
91
|
-
deviceModel:
|
|
92
|
-
engineName:
|
|
93
|
-
cpuArchitecture:
|
|
171
|
+
deviceType: statData.deviceType,
|
|
172
|
+
browserName: statData.browserName,
|
|
173
|
+
browserVersion: statData.browserVersion,
|
|
174
|
+
osName: statData.osName,
|
|
175
|
+
osVersion: statData.osVersion,
|
|
176
|
+
deviceVendor: statData.deviceVendor,
|
|
177
|
+
deviceModel: statData.deviceModel,
|
|
178
|
+
engineName: statData.engineName,
|
|
179
|
+
cpuArchitecture: statData.cpuArchitecture
|
|
94
180
|
}
|
|
95
181
|
});
|
|
96
182
|
|
|
183
|
+
await updateInfoStatsRedis(befly, now, reportDate, statData);
|
|
184
|
+
|
|
97
185
|
return befly.tool.Yes("上报成功", {
|
|
98
186
|
reportTime: now,
|
|
99
187
|
reportDate: reportDate,
|