befly 3.36.0 → 3.38.0
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/dashboard/serviceStatus.js +1 -33
- package/apis/tongJi/_tongJi.js +48 -0
- package/apis/tongJi/errorReport.js +1 -21
- package/apis/tongJi/errorStats.js +65 -51
- package/apis/tongJi/infoReport.js +47 -148
- package/apis/tongJi/infoStats.js +151 -186
- package/apis/tongJi/onlineReport.js +24 -59
- package/apis/tongJi/onlineStats.js +34 -191
- package/configs/beflyConfig.json +2 -2
- package/package.json +1 -1
- package/scripts/syncDb/diff.js +27 -1
- package/scripts/syncDb/index.js +6 -4
- package/sql/befly.sql +0 -32
- package/tables/errorReport.json +2 -1
- package/apis/tongJi/cacheHealth.js +0 -192
- package/apis/tongJi/fallbackReset.js +0 -61
- package/tables/infoReport.json +0 -123
package/apis/tongJi/infoStats.js
CHANGED
|
@@ -1,227 +1,192 @@
|
|
|
1
|
-
import { getDateYmdNumber } from "#befly/utils/datetime.js";
|
|
2
|
-
|
|
3
|
-
import { getTongJiMonthStartDate, getTongJiWeekStartDate } from "./_tongJi.js";
|
|
4
|
-
|
|
5
|
-
const INFO_STATS_FIELDS = [
|
|
6
|
-
{ dbField: "source", key: "sources" },
|
|
7
|
-
{ dbField: "product_name", key: "productNames" },
|
|
8
|
-
{ dbField: "product_code", key: "productCodes" },
|
|
9
|
-
{ dbField: "product_version", key: "productVersions" },
|
|
10
|
-
{ dbField: "page_path", key: "pagePaths" },
|
|
11
|
-
{ dbField: "page_name", key: "pageNames" },
|
|
12
|
-
{ dbField: "device_type", key: "deviceTypes" },
|
|
13
|
-
{ dbField: "browser_name", key: "browsers" },
|
|
14
|
-
{ dbField: "browser_version", key: "browserVersions" },
|
|
15
|
-
{ dbField: "os_name", key: "osList" },
|
|
16
|
-
{ dbField: "os_version", key: "osVersions" },
|
|
17
|
-
{ dbField: "device_vendor", key: "deviceVendors" },
|
|
18
|
-
{ dbField: "device_model", key: "deviceModels" },
|
|
19
|
-
{ dbField: "engine_name", key: "engines" },
|
|
20
|
-
{ dbField: "cpu_architecture", key: "cpuArchitectures" }
|
|
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;
|
|
25
|
-
|
|
26
|
-
function getInfoStatsFallbackDailyCountKey(reportDate) {
|
|
27
|
-
return `${INFO_STATS_FALLBACK_DAILY_COUNT_KEY_PREFIX}${reportDate}`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function formatInfoStatsList(rows = []) {
|
|
31
|
-
const list = [];
|
|
1
|
+
import { addDays, getDateYmdNumber, getDayStartTime } from "#befly/utils/datetime.js";
|
|
32
2
|
|
|
33
|
-
|
|
34
|
-
const name = String(item.name || "").trim();
|
|
35
|
-
const count = Number(item.count || 0);
|
|
36
|
-
|
|
37
|
-
if (!name || count <= 0) {
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
3
|
+
import { getTongJiMonthStartDate, getTongJiNumber, getTongJiRecentDateList, getVisitStatsDayMembersKey, getVisitStatsDayProductMembersKey, getVisitStatsMonthMembersKey, getVisitStatsMonthProductMembersKey, getVisitStatsRecentMembersKey, getVisitStatsRecentProductMembersKey } from "./_tongJi.js";
|
|
40
4
|
|
|
41
|
-
|
|
42
|
-
name: name,
|
|
43
|
-
count: count
|
|
44
|
-
});
|
|
45
|
-
}
|
|
5
|
+
const VISIT_STATS_DAY_LIMIT = 30;
|
|
46
6
|
|
|
47
|
-
|
|
7
|
+
async function getRedisSetCount(befly, key) {
|
|
8
|
+
return getTongJiNumber(await befly.redis.scard(key));
|
|
48
9
|
}
|
|
49
10
|
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
}
|
|
11
|
+
async function getRedisRecentCount(befly, key, now) {
|
|
12
|
+
const cutoffTime = getDayStartTime(addDays(now, -29));
|
|
53
13
|
|
|
54
|
-
|
|
55
|
-
return
|
|
14
|
+
await befly.redis.zremrangebyscore(key, "-inf", cutoffTime - 1);
|
|
15
|
+
return getTongJiNumber(await befly.redis.zcard(key));
|
|
56
16
|
}
|
|
57
17
|
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
}
|
|
18
|
+
async function getVisitStatsDays(befly, recentDateList, productCode) {
|
|
19
|
+
const days = [];
|
|
61
20
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
21
|
+
for (const reportDate of recentDateList) {
|
|
22
|
+
days.push({
|
|
23
|
+
reportDate: reportDate,
|
|
24
|
+
count: await getRedisSetCount(befly, productCode ? getVisitStatsDayProductMembersKey(reportDate, productCode) : getVisitStatsDayMembersKey(reportDate))
|
|
76
25
|
});
|
|
77
26
|
}
|
|
78
27
|
|
|
79
|
-
|
|
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);
|
|
28
|
+
return days;
|
|
123
29
|
}
|
|
124
30
|
|
|
125
|
-
async function
|
|
126
|
-
const
|
|
127
|
-
|
|
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);
|
|
31
|
+
async function getVisitStatsSummary(befly, now, reportDate, monthStartDate, recentDateList, productCode) {
|
|
32
|
+
const yesterdayDate = recentDateList[recentDateList.length - 2] || reportDate;
|
|
33
|
+
const dayBeforeYesterdayDate = recentDateList[recentDateList.length - 3] || yesterdayDate;
|
|
137
34
|
|
|
138
|
-
return
|
|
35
|
+
return {
|
|
36
|
+
today: {
|
|
37
|
+
reportDate: reportDate,
|
|
38
|
+
count: await getRedisSetCount(befly, productCode ? getVisitStatsDayProductMembersKey(reportDate, productCode) : getVisitStatsDayMembersKey(reportDate))
|
|
39
|
+
},
|
|
40
|
+
yesterday: {
|
|
41
|
+
reportDate: yesterdayDate,
|
|
42
|
+
count: await getRedisSetCount(befly, productCode ? getVisitStatsDayProductMembersKey(yesterdayDate, productCode) : getVisitStatsDayMembersKey(yesterdayDate))
|
|
43
|
+
},
|
|
44
|
+
dayBeforeYesterday: {
|
|
45
|
+
reportDate: dayBeforeYesterdayDate,
|
|
46
|
+
count: await getRedisSetCount(befly, productCode ? getVisitStatsDayProductMembersKey(dayBeforeYesterdayDate, productCode) : getVisitStatsDayMembersKey(dayBeforeYesterdayDate))
|
|
47
|
+
},
|
|
48
|
+
month: {
|
|
49
|
+
startDate: monthStartDate,
|
|
50
|
+
endDate: reportDate,
|
|
51
|
+
count: await getRedisSetCount(befly, productCode ? getVisitStatsMonthProductMembersKey(monthStartDate, productCode) : getVisitStatsMonthMembersKey(monthStartDate))
|
|
52
|
+
},
|
|
53
|
+
recent30: {
|
|
54
|
+
startDate: recentDateList[0],
|
|
55
|
+
endDate: reportDate,
|
|
56
|
+
count: await getRedisRecentCount(befly, productCode ? getVisitStatsRecentProductMembersKey(productCode) : getVisitStatsRecentMembersKey(), now)
|
|
57
|
+
},
|
|
58
|
+
days: await getVisitStatsDays(befly, recentDateList, productCode)
|
|
59
|
+
};
|
|
139
60
|
}
|
|
140
61
|
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
62
|
+
function buildEmptyVisitStats(reportDate, monthStartDate, recentDateList, projectLists) {
|
|
63
|
+
const yesterdayDate = recentDateList[recentDateList.length - 2] || reportDate;
|
|
64
|
+
const dayBeforeYesterdayDate = recentDateList[recentDateList.length - 3] || yesterdayDate;
|
|
65
|
+
const days = recentDateList.map((item) => {
|
|
66
|
+
return {
|
|
67
|
+
reportDate: item,
|
|
68
|
+
count: 0
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
const products = [];
|
|
72
|
+
|
|
73
|
+
for (const item of projectLists) {
|
|
74
|
+
products.push({
|
|
75
|
+
key: item.productCode,
|
|
76
|
+
productName: item.productName,
|
|
77
|
+
productCode: item.productCode,
|
|
78
|
+
productVersion: item.productVersion || "",
|
|
79
|
+
today: 0,
|
|
80
|
+
yesterday: 0,
|
|
81
|
+
dayBeforeYesterday: 0,
|
|
82
|
+
month: 0,
|
|
83
|
+
recent30: 0,
|
|
84
|
+
days: days
|
|
85
|
+
});
|
|
146
86
|
}
|
|
147
87
|
|
|
148
|
-
return
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
88
|
+
return {
|
|
89
|
+
today: {
|
|
90
|
+
reportDate: reportDate,
|
|
91
|
+
count: 0
|
|
92
|
+
},
|
|
93
|
+
yesterday: {
|
|
94
|
+
reportDate: yesterdayDate,
|
|
95
|
+
count: 0
|
|
96
|
+
},
|
|
97
|
+
dayBeforeYesterday: {
|
|
98
|
+
reportDate: dayBeforeYesterdayDate,
|
|
99
|
+
count: 0
|
|
100
|
+
},
|
|
101
|
+
month: {
|
|
102
|
+
startDate: monthStartDate,
|
|
103
|
+
endDate: reportDate,
|
|
104
|
+
count: 0
|
|
105
|
+
},
|
|
106
|
+
recent30: {
|
|
107
|
+
startDate: recentDateList[0],
|
|
108
|
+
endDate: reportDate,
|
|
109
|
+
count: 0
|
|
110
|
+
},
|
|
111
|
+
days: days,
|
|
112
|
+
products: products
|
|
168
113
|
};
|
|
114
|
+
}
|
|
169
115
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
116
|
+
async function buildVisitStatsProducts(befly, now, reportDate, monthStartDate, recentDateList, projectLists) {
|
|
117
|
+
const products = [];
|
|
118
|
+
|
|
119
|
+
for (const item of projectLists) {
|
|
120
|
+
const summary = await getVisitStatsSummary(befly, now, reportDate, monthStartDate, recentDateList, item.productCode);
|
|
121
|
+
|
|
122
|
+
products.push({
|
|
123
|
+
key: item.productCode,
|
|
124
|
+
productName: item.productName,
|
|
125
|
+
productCode: item.productCode,
|
|
126
|
+
productVersion: item.productVersion || "",
|
|
127
|
+
today: summary.today.count,
|
|
128
|
+
yesterday: summary.yesterday.count,
|
|
129
|
+
dayBeforeYesterday: summary.dayBeforeYesterday.count,
|
|
130
|
+
month: summary.month.count,
|
|
131
|
+
recent30: summary.recent30.count,
|
|
132
|
+
days: summary.days
|
|
133
|
+
});
|
|
177
134
|
}
|
|
178
135
|
|
|
179
|
-
return
|
|
180
|
-
|
|
136
|
+
return products.toSorted((a, b) => {
|
|
137
|
+
if (b.recent30 !== a.recent30) {
|
|
138
|
+
return b.recent30 - a.recent30;
|
|
139
|
+
}
|
|
181
140
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
today: buildInfoStatsPeriod(today, null, null, reportDate),
|
|
185
|
-
week: buildInfoStatsPeriod(week, weekStartDate, reportDate, null),
|
|
186
|
-
month: buildInfoStatsPeriod(month, monthStartDate, reportDate, null)
|
|
187
|
-
};
|
|
141
|
+
return String(a.productName).localeCompare(String(b.productName), "zh-CN");
|
|
142
|
+
});
|
|
188
143
|
}
|
|
189
144
|
|
|
190
145
|
export default {
|
|
191
|
-
name: "
|
|
146
|
+
name: "获取独立访客统计",
|
|
192
147
|
method: "POST",
|
|
193
148
|
body: "none",
|
|
194
149
|
auth: true,
|
|
195
150
|
fields: {
|
|
196
|
-
|
|
151
|
+
productCode: { name: "产品代号", input: "string", min: 0, max: 100 }
|
|
197
152
|
},
|
|
198
153
|
required: [],
|
|
199
154
|
handler: async (befly, ctx) => {
|
|
200
155
|
const now = Date.now();
|
|
201
156
|
const reportDate = getDateYmdNumber(now);
|
|
202
|
-
const weekStartDate = getTongJiWeekStartDate(now);
|
|
203
157
|
const monthStartDate = getTongJiMonthStartDate(now);
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
158
|
+
const recentDateList = getTongJiRecentDateList(now, VISIT_STATS_DAY_LIMIT);
|
|
159
|
+
const productCode = ctx?.body?.productCode ?? "";
|
|
160
|
+
const hasProductFilter = productCode.length > 0;
|
|
161
|
+
const projectLists = hasProductFilter ? [] : befly.config?.projectLists || [];
|
|
162
|
+
|
|
163
|
+
if (!befly.redis) {
|
|
164
|
+
const emptyStats = buildEmptyVisitStats(reportDate, monthStartDate, recentDateList, projectLists);
|
|
165
|
+
|
|
166
|
+
return befly.tool.Yes("获取成功", {
|
|
167
|
+
queryTime: now,
|
|
168
|
+
today: emptyStats.today,
|
|
169
|
+
yesterday: emptyStats.yesterday,
|
|
170
|
+
dayBeforeYesterday: emptyStats.dayBeforeYesterday,
|
|
171
|
+
month: emptyStats.month,
|
|
172
|
+
recent30: emptyStats.recent30,
|
|
173
|
+
days: emptyStats.days,
|
|
174
|
+
products: emptyStats.products
|
|
175
|
+
});
|
|
215
176
|
}
|
|
216
177
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
178
|
+
const summary = await getVisitStatsSummary(befly, now, reportDate, monthStartDate, recentDateList, productCode);
|
|
179
|
+
const products = await buildVisitStatsProducts(befly, now, reportDate, monthStartDate, recentDateList, projectLists);
|
|
180
|
+
|
|
181
|
+
return befly.tool.Yes("获取成功", {
|
|
182
|
+
queryTime: now,
|
|
183
|
+
today: summary.today,
|
|
184
|
+
yesterday: summary.yesterday,
|
|
185
|
+
dayBeforeYesterday: summary.dayBeforeYesterday,
|
|
186
|
+
month: summary.month,
|
|
187
|
+
recent30: summary.recent30,
|
|
188
|
+
days: summary.days,
|
|
189
|
+
products: products
|
|
190
|
+
});
|
|
226
191
|
}
|
|
227
192
|
};
|
|
@@ -1,42 +1,41 @@
|
|
|
1
1
|
import { getDateYmdNumber } from "#befly/utils/datetime.js";
|
|
2
2
|
import { isValidPositiveInt } from "#befly/utils/is.js";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { getOnlineStatsActiveProductKey } from "./_tongJi.js";
|
|
5
5
|
|
|
6
6
|
const ONLINE_STATS_ONLINE_TTL_SECONDS = 10 * 60;
|
|
7
7
|
const ONLINE_STATS_REDIS_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
8
|
-
const ONLINE_STATS_TEMP_TTL_SECONDS = 45 * 24 * 60 * 60;
|
|
9
8
|
const ONLINE_STATS_ACTIVE_KEY = "online:active";
|
|
10
9
|
|
|
11
10
|
function getOnlineStatsMember(ctx) {
|
|
12
|
-
if (isValidPositiveInt(ctx
|
|
11
|
+
if (isValidPositiveInt(ctx?.userId)) {
|
|
13
12
|
return `user:${ctx.userId}`;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
const clientId = ctx
|
|
15
|
+
const clientId = ctx?.body?.clientId ?? "";
|
|
17
16
|
|
|
18
17
|
if (clientId) {
|
|
19
18
|
return `client:${clientId}`;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
return `ip:${ctx
|
|
21
|
+
return `ip:${ctx?.ip || "unknown"}`;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
async function updateOnlineStatsActiveMemberProduct(befly, member,
|
|
26
|
-
if (!
|
|
24
|
+
async function updateOnlineStatsActiveMemberProduct(befly, member, productCode, expireAt) {
|
|
25
|
+
if (!productCode) {
|
|
27
26
|
return;
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
const memberProductKey = `online:active:member:${encodeURIComponent(member)}:product`;
|
|
31
|
-
const
|
|
30
|
+
const previousProductCode = String((await befly.redis.getString(memberProductKey)) || "").trim();
|
|
32
31
|
|
|
33
|
-
if (
|
|
34
|
-
await befly.redis.zrem(
|
|
32
|
+
if (previousProductCode && previousProductCode !== productCode) {
|
|
33
|
+
await befly.redis.zrem(getOnlineStatsActiveProductKey(previousProductCode), [member]);
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
const activeProductKey =
|
|
36
|
+
const activeProductKey = getOnlineStatsActiveProductKey(productCode);
|
|
38
37
|
|
|
39
|
-
await befly.redis.setString(memberProductKey,
|
|
38
|
+
await befly.redis.setString(memberProductKey, productCode, ONLINE_STATS_ONLINE_TTL_SECONDS);
|
|
40
39
|
await befly.redis.zadd(activeProductKey, [
|
|
41
40
|
{
|
|
42
41
|
score: expireAt,
|
|
@@ -46,7 +45,7 @@ async function updateOnlineStatsActiveMemberProduct(befly, member, productName,
|
|
|
46
45
|
await befly.redis.expire(activeProductKey, ONLINE_STATS_REDIS_TTL_SECONDS);
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
async function updateOnlineStatsActiveMember(befly, member, now,
|
|
48
|
+
async function updateOnlineStatsActiveMember(befly, member, now, productCode) {
|
|
50
49
|
const expireAt = now + ONLINE_STATS_ONLINE_TTL_SECONDS * 1000;
|
|
51
50
|
|
|
52
51
|
await befly.redis.zadd(ONLINE_STATS_ACTIVE_KEY, [
|
|
@@ -55,43 +54,10 @@ async function updateOnlineStatsActiveMember(befly, member, now, productName) {
|
|
|
55
54
|
member: member
|
|
56
55
|
}
|
|
57
56
|
]);
|
|
58
|
-
await updateOnlineStatsActiveMemberProduct(befly, member,
|
|
57
|
+
await updateOnlineStatsActiveMemberProduct(befly, member, productCode, expireAt);
|
|
59
58
|
await befly.redis.expire(ONLINE_STATS_ACTIVE_KEY, ONLINE_STATS_REDIS_TTL_SECONDS);
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
async function updateOnlineStatsPeriod(befly, periodType, periodValue, member) {
|
|
63
|
-
const keys = {
|
|
64
|
-
pv: `online:${periodType}:${periodValue}:pv`,
|
|
65
|
-
members: `online:${periodType}:${periodValue}:members`,
|
|
66
|
-
reportTime: `online:${periodType}:${periodValue}:reportTime`
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
await befly.redis.incr(keys.pv);
|
|
70
|
-
await befly.redis.sadd(keys.members, [member]);
|
|
71
|
-
await befly.redis.setString(keys.reportTime, String(Date.now()), ONLINE_STATS_TEMP_TTL_SECONDS);
|
|
72
|
-
await expireTongJiRedisKeys(befly, Object.values(keys), ONLINE_STATS_TEMP_TTL_SECONDS);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function updateOnlineStatsProductPeriod(befly, periodType, periodValue, member, productName) {
|
|
76
|
-
if (!productName) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const productKey = encodeURIComponent(productName);
|
|
81
|
-
const keys = {
|
|
82
|
-
pv: `online:${periodType}:${periodValue}:product:${productKey}:pv`,
|
|
83
|
-
members: `online:${periodType}:${periodValue}:product:${productKey}:members`,
|
|
84
|
-
reportTime: `online:${periodType}:${periodValue}:product:${productKey}:reportTime`
|
|
85
|
-
};
|
|
86
|
-
const productsKey = `online:${periodType}:${periodValue}:products`;
|
|
87
|
-
|
|
88
|
-
await befly.redis.incr(keys.pv);
|
|
89
|
-
await befly.redis.sadd(keys.members, [member]);
|
|
90
|
-
await befly.redis.setString(keys.reportTime, String(Date.now()), ONLINE_STATS_TEMP_TTL_SECONDS);
|
|
91
|
-
await befly.redis.sadd(productsKey, [productName]);
|
|
92
|
-
await expireTongJiRedisKeys(befly, [...Object.values(keys), productsKey], ONLINE_STATS_TEMP_TTL_SECONDS);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
61
|
export default {
|
|
96
62
|
name: "上报在线统计",
|
|
97
63
|
method: "POST",
|
|
@@ -110,23 +76,22 @@ export default {
|
|
|
110
76
|
handler: async (befly, ctx) => {
|
|
111
77
|
const now = Date.now();
|
|
112
78
|
const reportDate = getDateYmdNumber(now);
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
79
|
+
const productCode = ctx?.body?.productCode ?? "";
|
|
80
|
+
|
|
81
|
+
if (!befly.redis) {
|
|
82
|
+
return befly.tool.Yes("上报成功", {
|
|
83
|
+
reportTime: now,
|
|
84
|
+
reportDate: reportDate,
|
|
85
|
+
onlineCount: 0,
|
|
86
|
+
stored: false
|
|
87
|
+
});
|
|
88
|
+
}
|
|
117
89
|
|
|
118
|
-
await updateOnlineStatsActiveMember(befly,
|
|
90
|
+
await updateOnlineStatsActiveMember(befly, getOnlineStatsMember(ctx), now, productCode);
|
|
119
91
|
|
|
120
92
|
await befly.redis.zremrangebyscore(ONLINE_STATS_ACTIVE_KEY, "-inf", now);
|
|
121
93
|
const onlineCount = await befly.redis.zcard(ONLINE_STATS_ACTIVE_KEY);
|
|
122
94
|
|
|
123
|
-
await updateOnlineStatsPeriod(befly, "day", reportDate, member);
|
|
124
|
-
await updateOnlineStatsPeriod(befly, "week", weekStartDate, member);
|
|
125
|
-
await updateOnlineStatsPeriod(befly, "month", monthStartDate, member);
|
|
126
|
-
await updateOnlineStatsProductPeriod(befly, "day", reportDate, member, productName);
|
|
127
|
-
await updateOnlineStatsProductPeriod(befly, "week", weekStartDate, member, productName);
|
|
128
|
-
await updateOnlineStatsProductPeriod(befly, "month", monthStartDate, member, productName);
|
|
129
|
-
|
|
130
95
|
return befly.tool.Yes("上报成功", {
|
|
131
96
|
reportTime: now,
|
|
132
97
|
reportDate: reportDate,
|