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.
Files changed (71) hide show
  1. package/apis/_apis.js +20 -0
  2. package/apis/admin/delete.js +3 -1
  3. package/apis/admin/detail.js +1 -3
  4. package/apis/admin/select.js +9 -7
  5. package/apis/admin/update.js +2 -2
  6. package/apis/api/all.js +6 -11
  7. package/apis/api/select.js +18 -19
  8. package/apis/dashboard/environmentInfo.js +6 -1
  9. package/apis/dashboard/serviceStatus.js +78 -60
  10. package/apis/dict/_dict.js +24 -0
  11. package/apis/dict/all.js +6 -4
  12. package/apis/dict/detail.js +9 -5
  13. package/apis/dict/insert.js +4 -11
  14. package/apis/dict/items.js +5 -6
  15. package/apis/dict/select.js +13 -9
  16. package/apis/dict/update.js +9 -13
  17. package/apis/dictType/select.js +4 -4
  18. package/apis/email/config.js +9 -11
  19. package/apis/email/logList.js +14 -4
  20. package/apis/email/send.js +10 -3
  21. package/apis/email/verify.js +9 -7
  22. package/apis/loginLog/select.js +11 -4
  23. package/apis/menu/all.js +13 -19
  24. package/apis/menu/select.js +19 -14
  25. package/apis/operateLog/select.js +13 -4
  26. package/apis/role/_role.js +21 -0
  27. package/apis/role/all.js +1 -3
  28. package/apis/role/apiSave.js +8 -15
  29. package/apis/role/apis.js +4 -10
  30. package/apis/role/delete.js +28 -36
  31. package/apis/role/detail.js +4 -10
  32. package/apis/role/insert.js +12 -10
  33. package/apis/role/menuSave.js +9 -15
  34. package/apis/role/menus.js +4 -10
  35. package/apis/role/save.js +19 -23
  36. package/apis/role/select.js +12 -9
  37. package/apis/role/update.js +14 -15
  38. package/apis/source/imageList.js +3 -3
  39. package/apis/sysConfig/get.js +11 -23
  40. package/apis/sysConfig/insert.js +22 -27
  41. package/apis/sysConfig/select.js +11 -5
  42. package/apis/sysConfig/update.js +33 -38
  43. package/apis/tongJi/_tongJi.js +41 -0
  44. package/apis/tongJi/cacheHealth.js +192 -0
  45. package/apis/tongJi/errorList.js +26 -27
  46. package/apis/tongJi/errorReport.js +72 -18
  47. package/apis/tongJi/errorStats.js +154 -30
  48. package/apis/tongJi/fallbackReset.js +61 -0
  49. package/apis/tongJi/infoReport.js +112 -24
  50. package/apis/tongJi/infoStats.js +164 -84
  51. package/apis/tongJi/onlineReport.js +58 -73
  52. package/apis/tongJi/onlineStats.js +140 -151
  53. package/checks/config.js +44 -1
  54. package/configs/beflyConfig.json +10 -1
  55. package/hooks/permission.js +6 -2
  56. package/hooks/rateLimit.js +245 -0
  57. package/index.js +25 -0
  58. package/lib/cacheHelper.js +105 -60
  59. package/lib/dbHelper.js +1 -1
  60. package/lib/dbParse.js +61 -99
  61. package/lib/dbUtil.js +101 -21
  62. package/lib/redisHelper.js +93 -0
  63. package/lib/sqlBuilder.js +6 -0
  64. package/package.json +2 -2
  65. package/plugins/email.js +4 -3
  66. package/utils/email.js +3 -0
  67. package/apis/admin/cacheRefresh.js +0 -122
  68. package/apis/dashboard/configStatus.js +0 -39
  69. package/apis/dashboard/performanceMetrics.js +0 -20
  70. package/apis/dashboard/permissionStats.js +0 -27
  71. package/apis/dashboard/systemInfo.js +0 -19
@@ -1,4 +1,6 @@
1
- import { addDays, getDateYmdNumber } from "#root/utils/datetime.js";
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 getInfoStatsWeekStartDate(timestamp = Date.now()) {
22
- const date = Reflect.construct(Date, [timestamp]);
23
- const day = date.getDay();
24
- const offset = day === 0 ? -6 : 1 - day;
25
-
26
- return getDateYmdNumber(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
- async function getInfoStatsFieldList(befly, startDate, endDate, dbField) {
56
- const result = await befly.mysql.execute(`SELECT ${dbField} as name, COUNT(*) as count FROM befly_info_report WHERE state = 1 AND report_date BETWEEN ? AND ? AND ${dbField} <> '' GROUP BY ${dbField} ORDER BY count DESC, name ASC LIMIT 10`, [startDate, endDate]);
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 = getInfoStatsWeekStartDate(now);
82
- const monthStartDate = getInfoStatsMonthStartDate(now);
83
-
84
- const today = await getInfoStatsRange(befly, reportDate, reportDate);
85
- const week = await getInfoStatsRange(befly, weekStartDate, reportDate);
86
- const month = await getInfoStatsRange(befly, monthStartDate, reportDate);
87
-
88
- return befly.tool.Yes("获取成功", {
89
- today: {
90
- reportDate: reportDate,
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
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 { addDays, getDateYmdNumber } from "#root/utils/datetime.js";
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
- return `ip:${ctx.ip || "unknown"}`;
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
- function getOnlineStatsProductKey(productName) {
39
- return encodeURIComponent(String(productName || ""));
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 getOnlineStatsProductsKey(periodType, periodValue) {
53
- return `online:${periodType}:${periodValue}:products`;
54
- }
25
+ async function updateOnlineStatsActiveMemberProduct(befly, member, productName, expireAt) {
26
+ const normalizedProductName = String(productName || "").trim();
55
27
 
56
- async function touchRedisKeys(befly, keys, ttl) {
57
- for (const key of keys) {
58
- await befly.redis.expire(key, ttl);
28
+ if (!normalizedProductName) {
29
+ return;
59
30
  }
60
- }
61
31
 
62
- async function getOnlineStatsOnlineCount(befly) {
63
- const members = await befly.redis.smembers("online:visitors");
64
- if (members.length === 0) {
65
- return 0;
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 existsList = await befly.redis.existsBatch(members.map((member) => `online:visitor:${member}`));
69
- const expiredMembers = [];
70
- let onlineCount = 0;
39
+ const activeProductKey = `online:active:product:${encodeURIComponent(normalizedProductName)}`;
71
40
 
72
- for (let i = 0; i < members.length; i++) {
73
- if (existsList[i]) {
74
- onlineCount += 1;
75
- continue;
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
- expiredMembers.push(members[i]);
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
- return onlineCount;
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 = getPeriodKeys(periodType, periodValue);
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 touchRedisKeys(befly, Object.values(keys), ONLINE_STATS_TEMP_TTL_SECONDS);
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 keys = getOnlineStatsProductPeriodKeys(periodType, periodValue, productName);
103
- const productsKey = getOnlineStatsProductsKey(periodType, periodValue);
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 touchRedisKeys(befly, [...Object.values(keys), productsKey], ONLINE_STATS_TEMP_TTL_SECONDS);
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 = getOnlineStatsWeekStartDate(now);
130
- const monthStartDate = getOnlineStatsMonthStartDate(now);
115
+ const weekStartDate = getTongJiWeekStartDate(now);
116
+ const monthStartDate = getTongJiMonthStartDate(now);
131
117
  const member = getOnlineStatsMember(ctx);
132
- const productName = ctx.body?.productName || "";
118
+ const productName = String(ctx.body.productName || "").trim();
133
119
 
134
- await befly.redis.setString(`online:visitor:${member}`, "1", ONLINE_STATS_ONLINE_TTL_SECONDS);
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
- const onlineCount = await getOnlineStatsOnlineCount(befly);
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);