befly 3.24.18 → 3.24.19
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/admin/cacheRefresh.js +2 -2
- package/apis/dashboard/environmentInfo.js +6 -1
- package/apis/dashboard/performanceMetrics.js +11 -8
- package/apis/dashboard/serviceStatus.js +78 -60
- package/apis/tongJi/cacheHealth.js +214 -0
- package/apis/tongJi/errorReport.js +72 -1
- package/apis/tongJi/errorStats.js +160 -5
- package/apis/tongJi/fallbackReset.js +69 -0
- package/apis/tongJi/infoReport.js +116 -16
- package/apis/tongJi/infoStats.js +160 -68
- package/apis/tongJi/onlineReport.js +14 -23
- package/apis/tongJi/onlineStats.js +94 -91
- package/hooks/permission.js +6 -2
- package/hooks/rateLimit.js +245 -0
- package/lib/cacheHelper.js +105 -60
- package/lib/redisHelper.js +68 -0
- package/lib/requestMetrics.js +203 -0
- package/package.json +2 -2
- package/plugins/email.js +6 -2
- package/router/api.js +7 -0
package/apis/tongJi/infoStats.js
CHANGED
|
@@ -17,6 +17,13 @@ const INFO_STATS_FIELDS = [
|
|
|
17
17
|
{ dbField: "engine_name", key: "engines" },
|
|
18
18
|
{ dbField: "cpu_architecture", key: "cpuArchitectures" }
|
|
19
19
|
];
|
|
20
|
+
const INFO_STATS_FALLBACK_COUNT_KEY = "stats:fallback:infoStats:count";
|
|
21
|
+
const INFO_STATS_FALLBACK_DAILY_COUNT_KEY_PREFIX = "stats:fallback:infoStats:count:day:";
|
|
22
|
+
const INFO_STATS_FALLBACK_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
23
|
+
|
|
24
|
+
function getInfoStatsFallbackDailyCountKey(reportDate) {
|
|
25
|
+
return `${INFO_STATS_FALLBACK_DAILY_COUNT_KEY_PREFIX}${reportDate}`;
|
|
26
|
+
}
|
|
20
27
|
|
|
21
28
|
function getInfoStatsWeekStartDate(timestamp = Date.now()) {
|
|
22
29
|
const date = Reflect.construct(Date, [timestamp]);
|
|
@@ -35,7 +42,7 @@ function getInfoStatsMonthStartDate(timestamp = Date.now()) {
|
|
|
35
42
|
function formatInfoStatsList(rows = []) {
|
|
36
43
|
const list = [];
|
|
37
44
|
|
|
38
|
-
for (const item of rows
|
|
45
|
+
for (const item of rows) {
|
|
39
46
|
const name = String(item.name || "").trim();
|
|
40
47
|
const count = Number(item.count || 0);
|
|
41
48
|
|
|
@@ -52,96 +59,181 @@ function formatInfoStatsList(rows = []) {
|
|
|
52
59
|
return list;
|
|
53
60
|
}
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
function getInfoStatsNamesKey(periodType, periodValue, fieldKey) {
|
|
63
|
+
return `info:${periodType}:${periodValue}:${fieldKey}:names`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getInfoStatsCountKey(periodType, periodValue, fieldKey, value) {
|
|
67
|
+
return `info:${periodType}:${periodValue}:${fieldKey}:count:${encodeURIComponent(String(value || ""))}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getInfoStatsReportTimeKey(periodType, periodValue) {
|
|
71
|
+
return `info:${periodType}:${periodValue}:reportTime`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function getInfoStatsFieldListFromRedis(befly, periodType, periodValue, fieldKey) {
|
|
75
|
+
const values = await befly.redis.smembers(getInfoStatsNamesKey(periodType, periodValue, fieldKey));
|
|
76
|
+
const rows = [];
|
|
77
|
+
|
|
78
|
+
for (const value of values) {
|
|
79
|
+
const name = String(value || "").trim();
|
|
80
|
+
|
|
81
|
+
if (!name) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
rows.push({
|
|
86
|
+
name: name,
|
|
87
|
+
count: Number(await befly.redis.getString(getInfoStatsCountKey(periodType, periodValue, fieldKey, name))) || 0
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
rows.sort((a, b) => {
|
|
92
|
+
if (b.count !== a.count) {
|
|
93
|
+
return b.count - a.count;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return String(a.name).localeCompare(String(b.name), "zh-CN");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return formatInfoStatsList(rows.slice(0, 10));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function getInfoStatsPeriodFromRedis(befly, periodType, periodValue) {
|
|
103
|
+
const reportTime = Number(await befly.redis.getString(getInfoStatsReportTimeKey(periodType, periodValue))) || 0;
|
|
104
|
+
|
|
105
|
+
if (reportTime <= 0) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const data = {};
|
|
110
|
+
|
|
111
|
+
for (const item of INFO_STATS_FIELDS) {
|
|
112
|
+
data[item.key] = await getInfoStatsFieldListFromRedis(befly, periodType, periodValue, item.key);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return data;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function incrInfoStatsFallbackCount(befly, reportDate) {
|
|
119
|
+
if (!befly.redis) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const dailyKey = getInfoStatsFallbackDailyCountKey(reportDate);
|
|
124
|
+
|
|
125
|
+
if (typeof befly.redis.incrWithExpire === "function") {
|
|
126
|
+
await befly.redis.incrWithExpire(INFO_STATS_FALLBACK_COUNT_KEY, INFO_STATS_FALLBACK_TTL_SECONDS);
|
|
127
|
+
await befly.redis.incrWithExpire(dailyKey, INFO_STATS_FALLBACK_TTL_SECONDS);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await befly.redis.incr(INFO_STATS_FALLBACK_COUNT_KEY);
|
|
132
|
+
await befly.redis.expire(INFO_STATS_FALLBACK_COUNT_KEY, INFO_STATS_FALLBACK_TTL_SECONDS);
|
|
133
|
+
await befly.redis.incr(dailyKey);
|
|
134
|
+
await befly.redis.expire(dailyKey, INFO_STATS_FALLBACK_TTL_SECONDS);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function getInfoStatsFieldList(befly, startDate, endDate, dbField, productName) {
|
|
138
|
+
const queryParams = [startDate, endDate];
|
|
139
|
+
let sql = `SELECT ${dbField} as name, COUNT(*) as count FROM befly_info_report WHERE state = 1 AND report_date BETWEEN ? AND ?`;
|
|
140
|
+
|
|
141
|
+
if (productName) {
|
|
142
|
+
sql += " AND product_name = ?";
|
|
143
|
+
queryParams.push(productName);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
sql += ` AND ${dbField} <> '' GROUP BY ${dbField} ORDER BY count DESC, name ASC LIMIT 10`;
|
|
147
|
+
|
|
148
|
+
const result = await befly.mysql.execute(sql, queryParams);
|
|
57
149
|
|
|
58
150
|
return formatInfoStatsList(result.data || []);
|
|
59
151
|
}
|
|
60
152
|
|
|
61
|
-
async function getInfoStatsRange(befly, startDate, endDate) {
|
|
153
|
+
async function getInfoStatsRange(befly, startDate, endDate, productName) {
|
|
62
154
|
const data = {};
|
|
63
155
|
|
|
64
156
|
for (const item of INFO_STATS_FIELDS) {
|
|
65
|
-
data[item.key] = await getInfoStatsFieldList(befly, startDate, endDate, item.dbField);
|
|
157
|
+
data[item.key] = await getInfoStatsFieldList(befly, startDate, endDate, item.dbField, productName);
|
|
66
158
|
}
|
|
67
159
|
|
|
68
160
|
return data;
|
|
69
161
|
}
|
|
70
162
|
|
|
163
|
+
function buildInfoStatsPeriod(periodData, startDate = null, endDate = null, reportDate = null) {
|
|
164
|
+
const result = {
|
|
165
|
+
sources: periodData.sources,
|
|
166
|
+
productNames: periodData.productNames,
|
|
167
|
+
productCodes: periodData.productCodes,
|
|
168
|
+
productVersions: periodData.productVersions,
|
|
169
|
+
pagePaths: periodData.pagePaths,
|
|
170
|
+
pageNames: periodData.pageNames,
|
|
171
|
+
deviceTypes: periodData.deviceTypes,
|
|
172
|
+
browsers: periodData.browsers,
|
|
173
|
+
browserVersions: periodData.browserVersions,
|
|
174
|
+
osList: periodData.osList,
|
|
175
|
+
osVersions: periodData.osVersions,
|
|
176
|
+
deviceVendors: periodData.deviceVendors,
|
|
177
|
+
deviceModels: periodData.deviceModels,
|
|
178
|
+
engines: periodData.engines,
|
|
179
|
+
cpuArchitectures: periodData.cpuArchitectures
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if (reportDate !== null) {
|
|
183
|
+
result.reportDate = reportDate;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (startDate !== null && endDate !== null) {
|
|
187
|
+
result.startDate = startDate;
|
|
188
|
+
result.endDate = endDate;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function buildInfoStatsResult(today, week, month, reportDate, weekStartDate, monthStartDate) {
|
|
195
|
+
return {
|
|
196
|
+
today: buildInfoStatsPeriod(today, null, null, reportDate),
|
|
197
|
+
week: buildInfoStatsPeriod(week, weekStartDate, reportDate, null),
|
|
198
|
+
month: buildInfoStatsPeriod(month, monthStartDate, reportDate, null)
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
71
202
|
export default {
|
|
72
203
|
name: "获取访问信息统计",
|
|
73
204
|
method: "POST",
|
|
74
205
|
body: "none",
|
|
75
206
|
auth: true,
|
|
76
|
-
fields: {
|
|
207
|
+
fields: {
|
|
208
|
+
productName: { name: "产品名称", input: "string", min: 0, max: 100 }
|
|
209
|
+
},
|
|
77
210
|
required: [],
|
|
78
|
-
handler: async (befly) => {
|
|
211
|
+
handler: async (befly, ctx) => {
|
|
79
212
|
const now = Date.now();
|
|
80
213
|
const reportDate = getDateYmdNumber(now);
|
|
81
214
|
const weekStartDate = getInfoStatsWeekStartDate(now);
|
|
82
215
|
const monthStartDate = getInfoStatsMonthStartDate(now);
|
|
216
|
+
const productName = String(ctx?.body?.productName || "").trim();
|
|
217
|
+
const hasProductFilter = productName.length > 0;
|
|
83
218
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
today
|
|
90
|
-
|
|
91
|
-
sources: today.sources,
|
|
92
|
-
productNames: today.productNames,
|
|
93
|
-
productCodes: today.productCodes,
|
|
94
|
-
productVersions: today.productVersions,
|
|
95
|
-
pagePaths: today.pagePaths,
|
|
96
|
-
pageNames: today.pageNames,
|
|
97
|
-
deviceTypes: today.deviceTypes,
|
|
98
|
-
browsers: today.browsers,
|
|
99
|
-
browserVersions: today.browserVersions,
|
|
100
|
-
osList: today.osList,
|
|
101
|
-
osVersions: today.osVersions,
|
|
102
|
-
deviceVendors: today.deviceVendors,
|
|
103
|
-
deviceModels: today.deviceModels,
|
|
104
|
-
engines: today.engines,
|
|
105
|
-
cpuArchitectures: today.cpuArchitectures
|
|
106
|
-
},
|
|
107
|
-
week: {
|
|
108
|
-
startDate: weekStartDate,
|
|
109
|
-
endDate: reportDate,
|
|
110
|
-
sources: week.sources,
|
|
111
|
-
productNames: week.productNames,
|
|
112
|
-
productCodes: week.productCodes,
|
|
113
|
-
productVersions: week.productVersions,
|
|
114
|
-
pagePaths: week.pagePaths,
|
|
115
|
-
pageNames: week.pageNames,
|
|
116
|
-
deviceTypes: week.deviceTypes,
|
|
117
|
-
browsers: week.browsers,
|
|
118
|
-
browserVersions: week.browserVersions,
|
|
119
|
-
osList: week.osList,
|
|
120
|
-
osVersions: week.osVersions,
|
|
121
|
-
deviceVendors: week.deviceVendors,
|
|
122
|
-
deviceModels: week.deviceModels,
|
|
123
|
-
engines: week.engines,
|
|
124
|
-
cpuArchitectures: week.cpuArchitectures
|
|
125
|
-
},
|
|
126
|
-
month: {
|
|
127
|
-
startDate: monthStartDate,
|
|
128
|
-
endDate: reportDate,
|
|
129
|
-
sources: month.sources,
|
|
130
|
-
productNames: month.productNames,
|
|
131
|
-
productCodes: month.productCodes,
|
|
132
|
-
productVersions: month.productVersions,
|
|
133
|
-
pagePaths: month.pagePaths,
|
|
134
|
-
pageNames: month.pageNames,
|
|
135
|
-
deviceTypes: month.deviceTypes,
|
|
136
|
-
browsers: month.browsers,
|
|
137
|
-
browserVersions: month.browserVersions,
|
|
138
|
-
osList: month.osList,
|
|
139
|
-
osVersions: month.osVersions,
|
|
140
|
-
deviceVendors: month.deviceVendors,
|
|
141
|
-
deviceModels: month.deviceModels,
|
|
142
|
-
engines: month.engines,
|
|
143
|
-
cpuArchitectures: month.cpuArchitectures
|
|
219
|
+
if (befly.redis && !hasProductFilter) {
|
|
220
|
+
const today = await getInfoStatsPeriodFromRedis(befly, "day", reportDate);
|
|
221
|
+
const week = await getInfoStatsPeriodFromRedis(befly, "week", weekStartDate);
|
|
222
|
+
const month = await getInfoStatsPeriodFromRedis(befly, "month", monthStartDate);
|
|
223
|
+
|
|
224
|
+
if (today && week && month) {
|
|
225
|
+
return befly.tool.Yes("获取成功", buildInfoStatsResult(today, week, month, reportDate, weekStartDate, monthStartDate));
|
|
144
226
|
}
|
|
145
|
-
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (befly.redis && !hasProductFilter) {
|
|
230
|
+
await incrInfoStatsFallbackCount(befly, reportDate);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const today = await getInfoStatsRange(befly, reportDate, reportDate, productName);
|
|
234
|
+
const week = await getInfoStatsRange(befly, weekStartDate, reportDate, productName);
|
|
235
|
+
const month = await getInfoStatsRange(befly, monthStartDate, reportDate, productName);
|
|
236
|
+
|
|
237
|
+
return befly.tool.Yes("获取成功", buildInfoStatsResult(today, week, month, reportDate, weekStartDate, monthStartDate));
|
|
146
238
|
}
|
|
147
239
|
};
|
|
@@ -4,6 +4,7 @@ import { isValidPositiveInt } from "#root/utils/is.js";
|
|
|
4
4
|
const ONLINE_STATS_ONLINE_TTL_SECONDS = 10 * 60;
|
|
5
5
|
const ONLINE_STATS_REDIS_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
6
6
|
const ONLINE_STATS_TEMP_TTL_SECONDS = 45 * 24 * 60 * 60;
|
|
7
|
+
const ONLINE_STATS_ACTIVE_KEY = "online:active";
|
|
7
8
|
|
|
8
9
|
function getOnlineStatsMember(ctx) {
|
|
9
10
|
if (isValidPositiveInt(ctx.userId)) {
|
|
@@ -60,29 +61,21 @@ async function touchRedisKeys(befly, keys, ttl) {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
async function getOnlineStatsOnlineCount(befly) {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
await befly.redis.zremrangebyscore(ONLINE_STATS_ACTIVE_KEY, "-inf", now);
|
|
66
|
+
return await befly.redis.zcard(ONLINE_STATS_ACTIVE_KEY);
|
|
67
|
+
}
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
let onlineCount = 0;
|
|
69
|
+
async function updateOnlineStatsActiveMember(befly, member, now) {
|
|
70
|
+
const expireAt = now + ONLINE_STATS_ONLINE_TTL_SECONDS * 1000;
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
await befly.redis.zadd(ONLINE_STATS_ACTIVE_KEY, [
|
|
73
|
+
{
|
|
74
|
+
score: expireAt,
|
|
75
|
+
member: member
|
|
76
76
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (expiredMembers.length > 0) {
|
|
82
|
-
await befly.redis.srem("online:visitors", expiredMembers);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return onlineCount;
|
|
77
|
+
]);
|
|
78
|
+
await befly.redis.expire(ONLINE_STATS_ACTIVE_KEY, ONLINE_STATS_REDIS_TTL_SECONDS);
|
|
86
79
|
}
|
|
87
80
|
|
|
88
81
|
async function updateOnlineStatsPeriod(befly, periodType, periodValue, member) {
|
|
@@ -131,9 +124,7 @@ export default {
|
|
|
131
124
|
const member = getOnlineStatsMember(ctx);
|
|
132
125
|
const productName = ctx.body?.productName || "";
|
|
133
126
|
|
|
134
|
-
await befly
|
|
135
|
-
await befly.redis.sadd("online:visitors", [member]);
|
|
136
|
-
await befly.redis.expire("online:visitors", ONLINE_STATS_REDIS_TTL_SECONDS);
|
|
127
|
+
await updateOnlineStatsActiveMember(befly, member, now);
|
|
137
128
|
|
|
138
129
|
const onlineCount = await getOnlineStatsOnlineCount(befly);
|
|
139
130
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { addDays, getDateYmdNumber } from "#root/utils/datetime.js";
|
|
2
2
|
|
|
3
3
|
const ONLINE_STATS_DAY_LIMIT = 30;
|
|
4
|
+
const ONLINE_STATS_ACTIVE_KEY = "online:active";
|
|
4
5
|
|
|
5
6
|
function toOnlineStatsNumber(value) {
|
|
6
7
|
const num = Number(value);
|
|
@@ -75,6 +76,14 @@ async function getOnlineStatsPeriodData(befly, periodType, periodValue) {
|
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
async function getOnlineStatsPeriodDataByProduct(befly, periodType, periodValue, productName) {
|
|
80
|
+
if (!productName) {
|
|
81
|
+
return await getOnlineStatsPeriodData(befly, periodType, periodValue);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return await getOnlineStatsProductPeriodData(befly, periodType, periodValue, productName);
|
|
85
|
+
}
|
|
86
|
+
|
|
78
87
|
async function getOnlineStatsProductPeriodData(befly, periodType, periodValue, productName) {
|
|
79
88
|
const keys = getOnlineStatsProductPeriodKeys(periodType, periodValue, productName);
|
|
80
89
|
|
|
@@ -102,29 +111,9 @@ async function getOnlineStatsProductNames(befly, recentDateList) {
|
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
async function getOnlineStatsOnlineCount(befly) {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const existsList = await befly.redis.existsBatch(members.map((member) => `online:visitor:${member}`));
|
|
111
|
-
const expiredMembers = [];
|
|
112
|
-
let onlineCount = 0;
|
|
113
|
-
|
|
114
|
-
for (let i = 0; i < members.length; i++) {
|
|
115
|
-
if (existsList[i]) {
|
|
116
|
-
onlineCount += 1;
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
expiredMembers.push(members[i]);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (expiredMembers.length > 0) {
|
|
124
|
-
await befly.redis.srem("online:visitors", expiredMembers);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return onlineCount;
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
await befly.redis.zremrangebyscore(ONLINE_STATS_ACTIVE_KEY, "-inf", now);
|
|
116
|
+
return await befly.redis.zcard(ONLINE_STATS_ACTIVE_KEY);
|
|
128
117
|
}
|
|
129
118
|
|
|
130
119
|
function sumProductTrendField(list, field) {
|
|
@@ -137,30 +126,41 @@ function sumProductTrendField(list, field) {
|
|
|
137
126
|
return total;
|
|
138
127
|
}
|
|
139
128
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
129
|
+
async function buildOnlineStatsDays(befly, recentDateList, productName) {
|
|
130
|
+
const days = [];
|
|
131
|
+
|
|
132
|
+
for (const item of recentDateList) {
|
|
133
|
+
const dayData = await getOnlineStatsPeriodDataByProduct(befly, "day", item, productName);
|
|
134
|
+
|
|
135
|
+
if (dayData.reportTime <= 0) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
days.push({
|
|
140
|
+
reportDate: item,
|
|
141
|
+
reportTime: dayData.reportTime,
|
|
142
|
+
pv: dayData.pv,
|
|
143
|
+
uv: dayData.uv
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return days;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function buildOnlineStatsProducts(befly, recentDateList, reportDate, weekStartDate, monthStartDate, productNames) {
|
|
151
|
+
const products = [];
|
|
152
|
+
|
|
153
|
+
for (const productItemName of productNames) {
|
|
154
|
+
const productDays = [];
|
|
155
155
|
|
|
156
156
|
for (const item of recentDateList) {
|
|
157
|
-
const dayData = await
|
|
157
|
+
const dayData = await getOnlineStatsProductPeriodData(befly, "day", item, productItemName);
|
|
158
158
|
|
|
159
159
|
if (dayData.reportTime <= 0) {
|
|
160
160
|
continue;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
productDays.push({
|
|
164
164
|
reportDate: item,
|
|
165
165
|
reportTime: dayData.reportTime,
|
|
166
166
|
pv: dayData.pv,
|
|
@@ -168,62 +168,65 @@ export default {
|
|
|
168
168
|
});
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
const onlineCount = await getOnlineStatsOnlineCount(befly);
|
|
175
|
-
const productNames = await getOnlineStatsProductNames(befly, recentDateList);
|
|
171
|
+
const productToday = await getOnlineStatsProductPeriodData(befly, "day", reportDate, productItemName);
|
|
172
|
+
const productWeek = await getOnlineStatsProductPeriodData(befly, "week", weekStartDate, productItemName);
|
|
173
|
+
const productMonth = await getOnlineStatsProductPeriodData(befly, "month", monthStartDate, productItemName);
|
|
176
174
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
175
|
+
products.push({
|
|
176
|
+
key: productItemName,
|
|
177
|
+
productName: productItemName,
|
|
178
|
+
today: {
|
|
179
|
+
pv: productToday.pv,
|
|
180
|
+
uv: productToday.uv
|
|
181
|
+
},
|
|
182
|
+
week: {
|
|
183
|
+
pv: productWeek.pv,
|
|
184
|
+
uv: productWeek.uv
|
|
185
|
+
},
|
|
186
|
+
month: {
|
|
187
|
+
pv: productMonth.pv,
|
|
188
|
+
uv: productMonth.uv
|
|
189
|
+
},
|
|
190
|
+
days: productDays,
|
|
191
|
+
totalPv: sumProductTrendField(productDays, "pv"),
|
|
192
|
+
totalUv: sumProductTrendField(productDays, "uv")
|
|
193
|
+
});
|
|
194
|
+
}
|
|
194
195
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
products.push({
|
|
200
|
-
key: productName,
|
|
201
|
-
productName: productName,
|
|
202
|
-
today: {
|
|
203
|
-
pv: productToday.pv,
|
|
204
|
-
uv: productToday.uv
|
|
205
|
-
},
|
|
206
|
-
week: {
|
|
207
|
-
pv: productWeek.pv,
|
|
208
|
-
uv: productWeek.uv
|
|
209
|
-
},
|
|
210
|
-
month: {
|
|
211
|
-
pv: productMonth.pv,
|
|
212
|
-
uv: productMonth.uv
|
|
213
|
-
},
|
|
214
|
-
days: productDays,
|
|
215
|
-
totalPv: sumProductTrendField(productDays, "pv"),
|
|
216
|
-
totalUv: sumProductTrendField(productDays, "uv")
|
|
217
|
-
});
|
|
196
|
+
return products.toSorted((a, b) => {
|
|
197
|
+
if (b.totalPv !== a.totalPv) {
|
|
198
|
+
return b.totalPv - a.totalPv;
|
|
218
199
|
}
|
|
219
200
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
201
|
+
return String(a.productName).localeCompare(String(b.productName), "zh-CN");
|
|
202
|
+
});
|
|
203
|
+
}
|
|
224
204
|
|
|
225
|
-
|
|
226
|
-
|
|
205
|
+
export default {
|
|
206
|
+
name: "获取在线统计",
|
|
207
|
+
method: "POST",
|
|
208
|
+
body: "none",
|
|
209
|
+
auth: true,
|
|
210
|
+
fields: {
|
|
211
|
+
productName: { name: "产品名称", input: "string", min: 0, max: 100 }
|
|
212
|
+
},
|
|
213
|
+
required: [],
|
|
214
|
+
handler: async (befly, ctx) => {
|
|
215
|
+
const now = Date.now();
|
|
216
|
+
const reportDate = getDateYmdNumber(now);
|
|
217
|
+
const weekStartDate = getOnlineStatsWeekStartDate(now);
|
|
218
|
+
const monthStartDate = getOnlineStatsMonthStartDate(now);
|
|
219
|
+
const recentDateList = getOnlineStatsRecentDateList(now, ONLINE_STATS_DAY_LIMIT);
|
|
220
|
+
const productName = String(ctx?.body?.productName || "").trim();
|
|
221
|
+
const hasProductFilter = productName.length > 0;
|
|
222
|
+
const days = await buildOnlineStatsDays(befly, recentDateList, productName);
|
|
223
|
+
|
|
224
|
+
const currentDay = await getOnlineStatsPeriodDataByProduct(befly, "day", reportDate, productName);
|
|
225
|
+
const weekCurrent = await getOnlineStatsPeriodDataByProduct(befly, "week", weekStartDate, productName);
|
|
226
|
+
const monthCurrent = await getOnlineStatsPeriodDataByProduct(befly, "month", monthStartDate, productName);
|
|
227
|
+
const onlineCount = await getOnlineStatsOnlineCount(befly);
|
|
228
|
+
const productNames = hasProductFilter ? [] : await getOnlineStatsProductNames(befly, recentDateList);
|
|
229
|
+
const products = await buildOnlineStatsProducts(befly, recentDateList, reportDate, weekStartDate, monthStartDate, productNames);
|
|
227
230
|
|
|
228
231
|
return befly.tool.Yes("获取成功", {
|
|
229
232
|
queryTime: now,
|
package/hooks/permission.js
CHANGED
|
@@ -47,9 +47,13 @@ export default {
|
|
|
47
47
|
|
|
48
48
|
// 4. 角色权限检查
|
|
49
49
|
// apiPath 在 apiHandler 中已统一生成并写入 ctx.apiPath
|
|
50
|
+
let hasPermission = false;
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
if (befly.cache && typeof befly.cache.checkRoleApis === "function") {
|
|
53
|
+
hasPermission = await befly.cache.checkRoleApis(ctx.roleCode, ctx.apiPath);
|
|
54
|
+
} else {
|
|
55
|
+
hasPermission = await befly.redis.sismember(`role:apis:${ctx.roleCode}`, ctx.apiPath);
|
|
56
|
+
}
|
|
53
57
|
|
|
54
58
|
if (!hasPermission) {
|
|
55
59
|
ctx.response = ErrorResponse(
|