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
package/apis/tongJi/infoStats.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getDateYmdNumber } from "#root/utils/datetime.js";
|
|
2
|
+
|
|
3
|
+
import { getTongJiMonthStartDate, getTongJiWeekStartDate } from "./_tongJi.js";
|
|
2
4
|
|
|
3
5
|
const INFO_STATS_FIELDS = [
|
|
4
6
|
{ dbField: "source", key: "sources" },
|
|
@@ -17,25 +19,18 @@ const INFO_STATS_FIELDS = [
|
|
|
17
19
|
{ dbField: "engine_name", key: "engines" },
|
|
18
20
|
{ dbField: "cpu_architecture", key: "cpuArchitectures" }
|
|
19
21
|
];
|
|
22
|
+
const INFO_STATS_FALLBACK_COUNT_KEY = "stats:fallback:infoStats:count";
|
|
23
|
+
const INFO_STATS_FALLBACK_DAILY_COUNT_KEY_PREFIX = "stats:fallback:infoStats:count:day:";
|
|
24
|
+
const INFO_STATS_FALLBACK_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
20
25
|
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
const day = date.getDay();
|
|
24
|
-
const offset = day === 0 ? -6 : 1 - day;
|
|
25
|
-
|
|
26
|
-
return getDateYmdNumber(addDays(timestamp, offset));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getInfoStatsMonthStartDate(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());
|
|
26
|
+
function getInfoStatsFallbackDailyCountKey(reportDate) {
|
|
27
|
+
return `${INFO_STATS_FALLBACK_DAILY_COUNT_KEY_PREFIX}${reportDate}`;
|
|
33
28
|
}
|
|
34
29
|
|
|
35
30
|
function formatInfoStatsList(rows = []) {
|
|
36
31
|
const list = [];
|
|
37
32
|
|
|
38
|
-
for (const item of rows
|
|
33
|
+
for (const item of rows) {
|
|
39
34
|
const name = String(item.name || "").trim();
|
|
40
35
|
const count = Number(item.count || 0);
|
|
41
36
|
|
|
@@ -52,96 +47,181 @@ function formatInfoStatsList(rows = []) {
|
|
|
52
47
|
return list;
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
function getInfoStatsNamesKey(periodType, periodValue, fieldKey) {
|
|
51
|
+
return `info:${periodType}:${periodValue}:${fieldKey}:names`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getInfoStatsCountKey(periodType, periodValue, fieldKey, value) {
|
|
55
|
+
return `info:${periodType}:${periodValue}:${fieldKey}:count:${encodeURIComponent(String(value || ""))}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getInfoStatsReportTimeKey(periodType, periodValue) {
|
|
59
|
+
return `info:${periodType}:${periodValue}:reportTime`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function getInfoStatsFieldListFromRedis(befly, periodType, periodValue, fieldKey) {
|
|
63
|
+
const values = await befly.redis.smembers(getInfoStatsNamesKey(periodType, periodValue, fieldKey));
|
|
64
|
+
const rows = [];
|
|
65
|
+
|
|
66
|
+
for (const value of values) {
|
|
67
|
+
const name = String(value || "").trim();
|
|
68
|
+
|
|
69
|
+
if (!name) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
rows.push({
|
|
74
|
+
name: name,
|
|
75
|
+
count: Number(await befly.redis.getString(getInfoStatsCountKey(periodType, periodValue, fieldKey, name))) || 0
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
rows.sort((a, b) => {
|
|
80
|
+
if (b.count !== a.count) {
|
|
81
|
+
return b.count - a.count;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return String(a.name).localeCompare(String(b.name), "zh-CN");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return formatInfoStatsList(rows.slice(0, 10));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function getInfoStatsPeriodFromRedis(befly, periodType, periodValue) {
|
|
91
|
+
const reportTime = Number(await befly.redis.getString(getInfoStatsReportTimeKey(periodType, periodValue))) || 0;
|
|
92
|
+
|
|
93
|
+
if (reportTime <= 0) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const data = {};
|
|
98
|
+
|
|
99
|
+
for (const item of INFO_STATS_FIELDS) {
|
|
100
|
+
data[item.key] = await getInfoStatsFieldListFromRedis(befly, periodType, periodValue, item.key);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return data;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function incrInfoStatsFallbackCount(befly, reportDate) {
|
|
107
|
+
if (!befly.redis) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const dailyKey = getInfoStatsFallbackDailyCountKey(reportDate);
|
|
112
|
+
|
|
113
|
+
if (typeof befly.redis.incrWithExpire === "function") {
|
|
114
|
+
await befly.redis.incrWithExpire(INFO_STATS_FALLBACK_COUNT_KEY, INFO_STATS_FALLBACK_TTL_SECONDS);
|
|
115
|
+
await befly.redis.incrWithExpire(dailyKey, INFO_STATS_FALLBACK_TTL_SECONDS);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await befly.redis.incr(INFO_STATS_FALLBACK_COUNT_KEY);
|
|
120
|
+
await befly.redis.expire(INFO_STATS_FALLBACK_COUNT_KEY, INFO_STATS_FALLBACK_TTL_SECONDS);
|
|
121
|
+
await befly.redis.incr(dailyKey);
|
|
122
|
+
await befly.redis.expire(dailyKey, INFO_STATS_FALLBACK_TTL_SECONDS);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function getInfoStatsFieldList(befly, startDate, endDate, dbField, productName) {
|
|
126
|
+
const queryParams = [startDate, endDate];
|
|
127
|
+
let sql = `SELECT ${dbField} as name, COUNT(*) as count FROM befly_info_report WHERE state = 1 AND report_date BETWEEN ? AND ?`;
|
|
128
|
+
|
|
129
|
+
if (productName) {
|
|
130
|
+
sql += " AND product_name = ?";
|
|
131
|
+
queryParams.push(productName);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
sql += ` AND ${dbField} <> '' GROUP BY ${dbField} ORDER BY count DESC, name ASC LIMIT 10`;
|
|
135
|
+
|
|
136
|
+
const result = await befly.mysql.execute(sql, queryParams);
|
|
57
137
|
|
|
58
138
|
return formatInfoStatsList(result.data || []);
|
|
59
139
|
}
|
|
60
140
|
|
|
61
|
-
async function getInfoStatsRange(befly, startDate, endDate) {
|
|
141
|
+
async function getInfoStatsRange(befly, startDate, endDate, productName) {
|
|
62
142
|
const data = {};
|
|
63
143
|
|
|
64
144
|
for (const item of INFO_STATS_FIELDS) {
|
|
65
|
-
data[item.key] = await getInfoStatsFieldList(befly, startDate, endDate, item.dbField);
|
|
145
|
+
data[item.key] = await getInfoStatsFieldList(befly, startDate, endDate, item.dbField, productName);
|
|
66
146
|
}
|
|
67
147
|
|
|
68
148
|
return data;
|
|
69
149
|
}
|
|
70
150
|
|
|
151
|
+
function buildInfoStatsPeriod(periodData, startDate = null, endDate = null, reportDate = null) {
|
|
152
|
+
const result = {
|
|
153
|
+
sources: periodData.sources,
|
|
154
|
+
productNames: periodData.productNames,
|
|
155
|
+
productCodes: periodData.productCodes,
|
|
156
|
+
productVersions: periodData.productVersions,
|
|
157
|
+
pagePaths: periodData.pagePaths,
|
|
158
|
+
pageNames: periodData.pageNames,
|
|
159
|
+
deviceTypes: periodData.deviceTypes,
|
|
160
|
+
browsers: periodData.browsers,
|
|
161
|
+
browserVersions: periodData.browserVersions,
|
|
162
|
+
osList: periodData.osList,
|
|
163
|
+
osVersions: periodData.osVersions,
|
|
164
|
+
deviceVendors: periodData.deviceVendors,
|
|
165
|
+
deviceModels: periodData.deviceModels,
|
|
166
|
+
engines: periodData.engines,
|
|
167
|
+
cpuArchitectures: periodData.cpuArchitectures
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
if (reportDate !== null) {
|
|
171
|
+
result.reportDate = reportDate;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (startDate !== null && endDate !== null) {
|
|
175
|
+
result.startDate = startDate;
|
|
176
|
+
result.endDate = endDate;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildInfoStatsResult(today, week, month, reportDate, weekStartDate, monthStartDate) {
|
|
183
|
+
return {
|
|
184
|
+
today: buildInfoStatsPeriod(today, null, null, reportDate),
|
|
185
|
+
week: buildInfoStatsPeriod(week, weekStartDate, reportDate, null),
|
|
186
|
+
month: buildInfoStatsPeriod(month, monthStartDate, reportDate, null)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
71
190
|
export default {
|
|
72
191
|
name: "获取访问信息统计",
|
|
73
192
|
method: "POST",
|
|
74
193
|
body: "none",
|
|
75
194
|
auth: true,
|
|
76
|
-
fields: {
|
|
195
|
+
fields: {
|
|
196
|
+
productName: { name: "产品名称", input: "string", min: 0, max: 100 }
|
|
197
|
+
},
|
|
77
198
|
required: [],
|
|
78
|
-
handler: async (befly) => {
|
|
199
|
+
handler: async (befly, ctx) => {
|
|
79
200
|
const now = Date.now();
|
|
80
201
|
const reportDate = getDateYmdNumber(now);
|
|
81
|
-
const weekStartDate =
|
|
82
|
-
const monthStartDate =
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
202
|
+
const weekStartDate = getTongJiWeekStartDate(now);
|
|
203
|
+
const monthStartDate = getTongJiMonthStartDate(now);
|
|
204
|
+
const productName = String(ctx?.body?.productName || "").trim();
|
|
205
|
+
const hasProductFilter = productName.length > 0;
|
|
206
|
+
|
|
207
|
+
if (befly.redis && !hasProductFilter) {
|
|
208
|
+
const today = await getInfoStatsPeriodFromRedis(befly, "day", reportDate);
|
|
209
|
+
const week = await getInfoStatsPeriodFromRedis(befly, "week", weekStartDate);
|
|
210
|
+
const month = await getInfoStatsPeriodFromRedis(befly, "month", monthStartDate);
|
|
211
|
+
|
|
212
|
+
if (today && week && month) {
|
|
213
|
+
return befly.tool.Yes("获取成功", buildInfoStatsResult(today, week, month, reportDate, weekStartDate, monthStartDate));
|
|
144
214
|
}
|
|
145
|
-
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (befly.redis && !hasProductFilter) {
|
|
218
|
+
await incrInfoStatsFallbackCount(befly, reportDate);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const today = await getInfoStatsRange(befly, reportDate, reportDate, productName);
|
|
222
|
+
const week = await getInfoStatsRange(befly, weekStartDate, reportDate, productName);
|
|
223
|
+
const month = await getInfoStatsRange(befly, monthStartDate, reportDate, productName);
|
|
224
|
+
|
|
225
|
+
return befly.tool.Yes("获取成功", buildInfoStatsResult(today, week, month, reportDate, weekStartDate, monthStartDate));
|
|
146
226
|
}
|
|
147
227
|
};
|
|
@@ -1,97 +1,77 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getDateYmdNumber } from "#root/utils/datetime.js";
|
|
2
2
|
import { isValidPositiveInt } from "#root/utils/is.js";
|
|
3
3
|
|
|
4
|
+
import { expireTongJiRedisKeys, getTongJiMonthStartDate, getTongJiWeekStartDate } from "./_tongJi.js";
|
|
5
|
+
|
|
4
6
|
const ONLINE_STATS_ONLINE_TTL_SECONDS = 10 * 60;
|
|
5
7
|
const ONLINE_STATS_REDIS_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
6
8
|
const ONLINE_STATS_TEMP_TTL_SECONDS = 45 * 24 * 60 * 60;
|
|
9
|
+
const ONLINE_STATS_ACTIVE_KEY = "online:active";
|
|
7
10
|
|
|
8
11
|
function getOnlineStatsMember(ctx) {
|
|
9
12
|
if (isValidPositiveInt(ctx.userId)) {
|
|
10
13
|
return `user:${ctx.userId}`;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function getOnlineStatsWeekStartDate(timestamp = Date.now()) {
|
|
17
|
-
const date = Reflect.construct(Date, [timestamp]);
|
|
18
|
-
const day = date.getDay();
|
|
19
|
-
const offset = day === 0 ? -6 : 1 - day;
|
|
20
|
-
|
|
21
|
-
return getDateYmdNumber(addDays(timestamp, offset));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function getOnlineStatsMonthStartDate(timestamp = Date.now()) {
|
|
25
|
-
const date = Reflect.construct(Date, [timestamp]);
|
|
26
|
-
|
|
27
|
-
return getDateYmdNumber(Reflect.construct(Date, [date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0]).getTime());
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function getPeriodKeys(periodType, periodValue) {
|
|
31
|
-
return {
|
|
32
|
-
pv: `online:${periodType}:${periodValue}:pv`,
|
|
33
|
-
members: `online:${periodType}:${periodValue}:members`,
|
|
34
|
-
reportTime: `online:${periodType}:${periodValue}:reportTime`
|
|
35
|
-
};
|
|
36
|
-
}
|
|
16
|
+
const clientId = String(ctx.body.clientId || "").trim();
|
|
37
17
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function getOnlineStatsProductPeriodKeys(periodType, periodValue, productName) {
|
|
43
|
-
const productKey = getOnlineStatsProductKey(productName);
|
|
18
|
+
if (clientId) {
|
|
19
|
+
return `client:${clientId}`;
|
|
20
|
+
}
|
|
44
21
|
|
|
45
|
-
return {
|
|
46
|
-
pv: `online:${periodType}:${periodValue}:product:${productKey}:pv`,
|
|
47
|
-
members: `online:${periodType}:${periodValue}:product:${productKey}:members`,
|
|
48
|
-
reportTime: `online:${periodType}:${periodValue}:product:${productKey}:reportTime`
|
|
49
|
-
};
|
|
22
|
+
return `ip:${ctx.ip || "unknown"}`;
|
|
50
23
|
}
|
|
51
24
|
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
}
|
|
25
|
+
async function updateOnlineStatsActiveMemberProduct(befly, member, productName, expireAt) {
|
|
26
|
+
const normalizedProductName = String(productName || "").trim();
|
|
55
27
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
await befly.redis.expire(key, ttl);
|
|
28
|
+
if (!normalizedProductName) {
|
|
29
|
+
return;
|
|
59
30
|
}
|
|
60
|
-
}
|
|
61
31
|
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
32
|
+
const memberProductKey = `online:active:member:${encodeURIComponent(member)}:product`;
|
|
33
|
+
const previousProductName = String((await befly.redis.getString(memberProductKey)) || "").trim();
|
|
34
|
+
|
|
35
|
+
if (previousProductName && previousProductName !== normalizedProductName) {
|
|
36
|
+
await befly.redis.zrem(`online:active:product:${encodeURIComponent(previousProductName)}`, [member]);
|
|
66
37
|
}
|
|
67
38
|
|
|
68
|
-
const
|
|
69
|
-
const expiredMembers = [];
|
|
70
|
-
let onlineCount = 0;
|
|
39
|
+
const activeProductKey = `online:active:product:${encodeURIComponent(normalizedProductName)}`;
|
|
71
40
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
41
|
+
await befly.redis.setString(memberProductKey, normalizedProductName, ONLINE_STATS_ONLINE_TTL_SECONDS);
|
|
42
|
+
await befly.redis.zadd(activeProductKey, [
|
|
43
|
+
{
|
|
44
|
+
score: expireAt,
|
|
45
|
+
member: member
|
|
76
46
|
}
|
|
47
|
+
]);
|
|
48
|
+
await befly.redis.expire(activeProductKey, ONLINE_STATS_REDIS_TTL_SECONDS);
|
|
49
|
+
}
|
|
77
50
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (expiredMembers.length > 0) {
|
|
82
|
-
await befly.redis.srem("online:visitors", expiredMembers);
|
|
83
|
-
}
|
|
51
|
+
async function updateOnlineStatsActiveMember(befly, member, now, productName) {
|
|
52
|
+
const expireAt = now + ONLINE_STATS_ONLINE_TTL_SECONDS * 1000;
|
|
84
53
|
|
|
85
|
-
|
|
54
|
+
await befly.redis.zadd(ONLINE_STATS_ACTIVE_KEY, [
|
|
55
|
+
{
|
|
56
|
+
score: expireAt,
|
|
57
|
+
member: member
|
|
58
|
+
}
|
|
59
|
+
]);
|
|
60
|
+
await updateOnlineStatsActiveMemberProduct(befly, member, productName, expireAt);
|
|
61
|
+
await befly.redis.expire(ONLINE_STATS_ACTIVE_KEY, ONLINE_STATS_REDIS_TTL_SECONDS);
|
|
86
62
|
}
|
|
87
63
|
|
|
88
64
|
async function updateOnlineStatsPeriod(befly, periodType, periodValue, member) {
|
|
89
|
-
const keys =
|
|
65
|
+
const keys = {
|
|
66
|
+
pv: `online:${periodType}:${periodValue}:pv`,
|
|
67
|
+
members: `online:${periodType}:${periodValue}:members`,
|
|
68
|
+
reportTime: `online:${periodType}:${periodValue}:reportTime`
|
|
69
|
+
};
|
|
90
70
|
|
|
91
71
|
await befly.redis.incr(keys.pv);
|
|
92
72
|
await befly.redis.sadd(keys.members, [member]);
|
|
93
73
|
await befly.redis.setString(keys.reportTime, String(Date.now()), ONLINE_STATS_TEMP_TTL_SECONDS);
|
|
94
|
-
await
|
|
74
|
+
await expireTongJiRedisKeys(befly, Object.values(keys), ONLINE_STATS_TEMP_TTL_SECONDS);
|
|
95
75
|
}
|
|
96
76
|
|
|
97
77
|
async function updateOnlineStatsProductPeriod(befly, periodType, periodValue, member, productName) {
|
|
@@ -99,14 +79,19 @@ async function updateOnlineStatsProductPeriod(befly, periodType, periodValue, me
|
|
|
99
79
|
return;
|
|
100
80
|
}
|
|
101
81
|
|
|
102
|
-
const
|
|
103
|
-
const
|
|
82
|
+
const productKey = encodeURIComponent(productName);
|
|
83
|
+
const keys = {
|
|
84
|
+
pv: `online:${periodType}:${periodValue}:product:${productKey}:pv`,
|
|
85
|
+
members: `online:${periodType}:${periodValue}:product:${productKey}:members`,
|
|
86
|
+
reportTime: `online:${periodType}:${periodValue}:product:${productKey}:reportTime`
|
|
87
|
+
};
|
|
88
|
+
const productsKey = `online:${periodType}:${periodValue}:products`;
|
|
104
89
|
|
|
105
90
|
await befly.redis.incr(keys.pv);
|
|
106
91
|
await befly.redis.sadd(keys.members, [member]);
|
|
107
92
|
await befly.redis.setString(keys.reportTime, String(Date.now()), ONLINE_STATS_TEMP_TTL_SECONDS);
|
|
108
93
|
await befly.redis.sadd(productsKey, [productName]);
|
|
109
|
-
await
|
|
94
|
+
await expireTongJiRedisKeys(befly, [...Object.values(keys), productsKey], ONLINE_STATS_TEMP_TTL_SECONDS);
|
|
110
95
|
}
|
|
111
96
|
|
|
112
97
|
export default {
|
|
@@ -118,6 +103,7 @@ export default {
|
|
|
118
103
|
pagePath: { name: "页面路径", input: "string", min: 0, max: 200 },
|
|
119
104
|
pageName: { name: "页面名称", input: "string", min: 0, max: 100 },
|
|
120
105
|
source: { name: "来源", input: "string", min: 0, max: 50 },
|
|
106
|
+
clientId: { name: "客户端标识", input: "string", min: 0, max: 120 },
|
|
121
107
|
productName: { name: "产品名称", input: "string", min: 0, max: 100 },
|
|
122
108
|
productCode: { name: "产品代号", input: "string", min: 0, max: 100 },
|
|
123
109
|
productVersion: { name: "产品版本", input: "string", min: 0, max: 50 }
|
|
@@ -126,16 +112,15 @@ export default {
|
|
|
126
112
|
handler: async (befly, ctx) => {
|
|
127
113
|
const now = Date.now();
|
|
128
114
|
const reportDate = getDateYmdNumber(now);
|
|
129
|
-
const weekStartDate =
|
|
130
|
-
const monthStartDate =
|
|
115
|
+
const weekStartDate = getTongJiWeekStartDate(now);
|
|
116
|
+
const monthStartDate = getTongJiMonthStartDate(now);
|
|
131
117
|
const member = getOnlineStatsMember(ctx);
|
|
132
|
-
const productName = ctx.body
|
|
118
|
+
const productName = String(ctx.body.productName || "").trim();
|
|
133
119
|
|
|
134
|
-
await befly
|
|
135
|
-
await befly.redis.sadd("online:visitors", [member]);
|
|
136
|
-
await befly.redis.expire("online:visitors", ONLINE_STATS_REDIS_TTL_SECONDS);
|
|
120
|
+
await updateOnlineStatsActiveMember(befly, member, now, productName);
|
|
137
121
|
|
|
138
|
-
|
|
122
|
+
await befly.redis.zremrangebyscore(ONLINE_STATS_ACTIVE_KEY, "-inf", now);
|
|
123
|
+
const onlineCount = await befly.redis.zcard(ONLINE_STATS_ACTIVE_KEY);
|
|
139
124
|
|
|
140
125
|
await updateOnlineStatsPeriod(befly, "day", reportDate, member);
|
|
141
126
|
await updateOnlineStatsPeriod(befly, "week", weekStartDate, member);
|