befly 3.18.23 → 3.19.5

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.
@@ -18,7 +18,8 @@ export default {
18
18
  password: adminTable.password,
19
19
  loginType: {
20
20
  name: "登录类型",
21
- input: "/^(username|email|phone)$/",
21
+ input: "enum",
22
+ check: "username|email|phone",
22
23
  min: null,
23
24
  max: null
24
25
  }
@@ -96,7 +97,6 @@ export default {
96
97
  id: admin.data.id,
97
98
  username: admin.data.username,
98
99
  nickname: admin.data.nickname,
99
- state: admin.data.state,
100
100
  roleCode: admin.data.roleCode,
101
101
  roleType: admin.data.roleType,
102
102
  loginAt: Date.now()
@@ -113,7 +113,6 @@ export default {
113
113
  id: sessionData.id,
114
114
  username: sessionData.username,
115
115
  nickname: sessionData.nickname,
116
- state: sessionData.state,
117
116
  roleCode: sessionData.roleCode,
118
117
  roleType: sessionData.roleType
119
118
  }
@@ -0,0 +1,102 @@
1
+ export default {
2
+ name: "获取错误报告列表",
3
+ method: "POST",
4
+ body: "none",
5
+ auth: true,
6
+ fields: {
7
+ page: { name: "页码", input: "integer", min: 1, max: 9999 },
8
+ limit: { name: "每页数量", input: "integer", min: 1, max: 100 },
9
+ keyword: { name: "关键词", input: "string", min: 0, max: 100 },
10
+ errorType: { name: "错误类型", input: "string", min: 0, max: 50 },
11
+ source: { name: "来源", input: "string", min: 0, max: 50 },
12
+ productName: { name: "产品名称", input: "string", min: 0, max: 100 },
13
+ productCode: { name: "产品代号", input: "string", min: 0, max: 100 },
14
+ productVersion: { name: "产品版本", input: "string", min: 0, max: 100 },
15
+ deviceType: { name: "设备类型", input: "string", min: 0, max: 50 },
16
+ browserName: { name: "浏览器", input: "string", min: 0, max: 100 },
17
+ osName: { name: "操作系统", input: "string", min: 0, max: 100 }
18
+ },
19
+ required: [],
20
+ handler: async (befly, ctx) => {
21
+ const tableExistsResult = await befly.mysql.tableExists("beflyErrorReport");
22
+ const page = Number(ctx.body.page || 1);
23
+ const limit = Number(ctx.body.limit || 20);
24
+
25
+ if (tableExistsResult.data !== true) {
26
+ return befly.tool.Yes("获取成功", {
27
+ lists: [],
28
+ total: 0,
29
+ page: page,
30
+ limit: limit
31
+ });
32
+ }
33
+
34
+ const keyword = String(ctx.body.keyword || "").trim();
35
+ const errorType = String(ctx.body.errorType || "").trim();
36
+ const source = String(ctx.body.source || "").trim();
37
+ const productName = String(ctx.body.productName || "").trim();
38
+ const productCode = String(ctx.body.productCode || "").trim();
39
+ const productVersion = String(ctx.body.productVersion || "").trim();
40
+ const deviceType = String(ctx.body.deviceType || "").trim();
41
+ const browserName = String(ctx.body.browserName || "").trim();
42
+ const osName = String(ctx.body.osName || "").trim();
43
+ const where = {};
44
+
45
+ if (keyword) {
46
+ where["$or"] = [{ pagePath$like: keyword }, { pageName$like: keyword }, { message$like: keyword }, { errorType$like: keyword }, { productName$like: keyword }, { productCode$like: keyword }, { productVersion$like: keyword }, { browserName$like: keyword }, { osName$like: keyword }, { deviceType$like: keyword }, { deviceVendor$like: keyword }, { deviceModel$like: keyword }];
47
+ }
48
+
49
+ if (errorType) {
50
+ where.errorType = errorType;
51
+ }
52
+
53
+ if (source) {
54
+ where.source = source;
55
+ }
56
+
57
+ if (productName) {
58
+ where.productName = productName;
59
+ }
60
+
61
+ if (productCode) {
62
+ where.productCode = productCode;
63
+ }
64
+
65
+ if (productVersion) {
66
+ where.productVersion = productVersion;
67
+ }
68
+
69
+ if (deviceType) {
70
+ where.deviceType = deviceType;
71
+ }
72
+
73
+ if (browserName) {
74
+ where.browserName = browserName;
75
+ }
76
+
77
+ if (osName) {
78
+ where.osName = osName;
79
+ }
80
+
81
+ const result = await befly.mysql.getList({
82
+ table: "beflyErrorReport",
83
+ where: where,
84
+ page: page,
85
+ limit: limit,
86
+ orderBy: ["reportTime#DESC"]
87
+ });
88
+
89
+ const lists = [];
90
+ for (const item of result.data?.lists || []) {
91
+ const row = Object.assign({}, item);
92
+ row.firstReportTime = Number(item.firstReportTime || 0);
93
+ row.hitCount = Number(item.hitCount || 0) || 1;
94
+ lists.push(row);
95
+ }
96
+
97
+ const data = Object.assign({}, result.data);
98
+ data.lists = lists;
99
+
100
+ return befly.tool.Yes("获取成功", data);
101
+ }
102
+ };
@@ -0,0 +1,90 @@
1
+ import { UAParser } from "ua-parser-js";
2
+ import { getDateYmdNumber, getTimeBucketStart } from "../../utils/datetime.js";
3
+ import { VISIT_STATS_BUCKET_MS } from "../../utils/visitStats.js";
4
+
5
+ function getErrorReportUaData(ctx) {
6
+ let userAgent = "";
7
+
8
+ if (typeof ctx.req?.headers?.get === "function") {
9
+ userAgent = ctx.req.headers.get("user-agent") || "";
10
+ } else if (typeof ctx.headers?.get === "function") {
11
+ userAgent = ctx.headers.get("user-agent") || "";
12
+ }
13
+
14
+ const uaResult = UAParser(userAgent);
15
+
16
+ return {
17
+ userAgent: userAgent,
18
+ browserName: uaResult.browser.name || "",
19
+ browserVersion: uaResult.browser.version || "",
20
+ osName: uaResult.os.name || "",
21
+ osVersion: uaResult.os.version || "",
22
+ deviceType: uaResult.device.type || "desktop",
23
+ deviceVendor: uaResult.device.vendor || "",
24
+ deviceModel: uaResult.device.model || "",
25
+ engineName: uaResult.engine.name || "",
26
+ cpuArchitecture: uaResult.cpu.architecture || ""
27
+ };
28
+ }
29
+
30
+ export default {
31
+ name: "上报错误报告",
32
+ method: "POST",
33
+ body: "none",
34
+ auth: false,
35
+ fields: {
36
+ pagePath: { name: "页面路径", input: "string", min: 0, max: 200 },
37
+ pageName: { name: "页面名称", input: "string", min: 0, max: 100 },
38
+ source: { name: "来源", input: "string", min: 0, max: 50 },
39
+ productName: { name: "产品名称", input: "string", min: 0, max: 100 },
40
+ productCode: { name: "产品代号", input: "string", min: 0, max: 100 },
41
+ productVersion: { name: "产品版本", input: "string", min: 0, max: 100 },
42
+ errorType: { name: "错误类型", input: "string", min: 0, max: 50 },
43
+ message: { name: "错误信息", input: "string", min: 0, max: 500 },
44
+ detail: { name: "错误详情", input: "string", min: 0, max: 5000 }
45
+ },
46
+ required: [],
47
+ handler: async (befly, ctx) => {
48
+ const now = Date.now();
49
+ const bucketTime = getTimeBucketStart(now, VISIT_STATS_BUCKET_MS);
50
+ const bucketDate = getDateYmdNumber(now);
51
+ const uaData = getErrorReportUaData(ctx);
52
+
53
+ await befly.mysql.insData({
54
+ table: "beflyErrorReport",
55
+ data: {
56
+ reportTime: now,
57
+ firstReportTime: now,
58
+ bucketTime: bucketTime,
59
+ bucketDate: bucketDate,
60
+ hitCount: 1,
61
+ source: ctx.body?.source,
62
+ productName: ctx.body?.productName || "",
63
+ productCode: ctx.body?.productCode || "",
64
+ productVersion: ctx.body?.productVersion || "",
65
+ pagePath: ctx.body?.pagePath || "",
66
+ pageName: ctx.body?.pageName || "",
67
+ errorType: ctx.body?.errorType || "",
68
+ message: ctx.body?.message || "",
69
+ detail: ctx.body?.detail || "",
70
+ userAgent: uaData.userAgent,
71
+ browserName: uaData.browserName,
72
+ browserVersion: uaData.browserVersion,
73
+ osName: uaData.osName,
74
+ osVersion: uaData.osVersion,
75
+ deviceType: uaData.deviceType,
76
+ deviceVendor: uaData.deviceVendor,
77
+ deviceModel: uaData.deviceModel,
78
+ engineName: uaData.engineName,
79
+ cpuArchitecture: uaData.cpuArchitecture
80
+ }
81
+ });
82
+
83
+ return befly.tool.Yes("上报成功", {
84
+ reportTime: now,
85
+ bucketTime: bucketTime,
86
+ bucketDate: bucketDate,
87
+ stored: true
88
+ });
89
+ }
90
+ };
@@ -0,0 +1,70 @@
1
+ import { getDateYmdNumber } from "../../utils/datetime.js";
2
+
3
+ function toNumber(value) {
4
+ const num = Number(value);
5
+ if (!Number.isFinite(num)) {
6
+ return 0;
7
+ }
8
+
9
+ return num;
10
+ }
11
+
12
+ export default {
13
+ name: "获取错误统计",
14
+ method: "POST",
15
+ body: "none",
16
+ auth: true,
17
+ fields: {},
18
+ required: [],
19
+ handler: async (befly) => {
20
+ const tableExistsResult = await befly.mysql.tableExists("beflyErrorReport");
21
+ const tableReady = tableExistsResult.data === true;
22
+ const now = Date.now();
23
+ const bucketDate = getDateYmdNumber(now);
24
+
25
+ if (!tableReady) {
26
+ return befly.tool.Yes("获取成功", {
27
+ trend: [],
28
+ days: [],
29
+ topTypes: []
30
+ });
31
+ }
32
+
33
+ const trendRes = await befly.mysql.execute("SELECT bucket_time as bucketTime, bucket_date as bucketDate, SUM(hit_count) as count FROM befly_error_report WHERE state = 1 AND bucket_date = ? GROUP BY bucket_time, bucket_date ORDER BY bucket_time ASC", [bucketDate]);
34
+ const daysRes = await befly.mysql.execute("SELECT bucket_date as bucketDate, SUM(hit_count) as count FROM befly_error_report WHERE state = 1 GROUP BY bucket_date ORDER BY bucket_date DESC LIMIT 7", []);
35
+ const topTypesRes = await befly.mysql.execute("SELECT error_type as errorType, SUM(hit_count) as count FROM befly_error_report WHERE state = 1 AND bucket_date = ? GROUP BY error_type ORDER BY count DESC LIMIT 5", [bucketDate]);
36
+
37
+ const trend = [];
38
+ for (const item of trendRes.data || []) {
39
+ trend.push({
40
+ bucketTime: toNumber(item.bucketTime),
41
+ bucketDate: toNumber(item.bucketDate),
42
+ count: toNumber(item.count)
43
+ });
44
+ }
45
+
46
+ const days = [];
47
+ for (const item of daysRes.data || []) {
48
+ days.push({
49
+ bucketDate: toNumber(item.bucketDate),
50
+ count: toNumber(item.count)
51
+ });
52
+ }
53
+
54
+ days.sort((a, b) => a.bucketDate - b.bucketDate);
55
+
56
+ const topTypes = [];
57
+ for (const item of topTypesRes.data || []) {
58
+ topTypes.push({
59
+ errorType: String(item.errorType || "unknown"),
60
+ count: toNumber(item.count)
61
+ });
62
+ }
63
+
64
+ return befly.tool.Yes("获取成功", {
65
+ trend: trend,
66
+ days: days,
67
+ topTypes: topTypes
68
+ });
69
+ }
70
+ };
@@ -0,0 +1,241 @@
1
+ import { UAParser } from "ua-parser-js";
2
+
3
+ import { isValidPositiveInt } from "../../utils/is.js";
4
+ import { getDateYmdNumber, getTimeBucketStart } from "../../utils/datetime.js";
5
+ import { VISIT_STATS_BUCKET_MS, VISIT_STATS_ONLINE_TTL_SECONDS, VISIT_STATS_PRODUCT_FIELDS, VISIT_STATS_REDIS_TTL_SECONDS, VISIT_STATS_UA_FIELDS, getVisitStatsBucketKey, getVisitStatsBucketProductPvKey, getVisitStatsBucketProductUvKey, getVisitStatsDayKey, getVisitStatsDayProductCountKey, getVisitStatsDayProductKeysKey, getVisitStatsDayProductPvKey, getVisitStatsDayProductUvKey, getVisitStatsDayProductValuesKey, getVisitStatsDayUaCountKey, getVisitStatsDayUaValuesKey, getVisitStatsProductKey, getVisitStatsProductMeta, getVisitStatsProductMetaKey, normalizeVisitStatsProductValue, normalizeVisitStatsUaValue, toVisitStatsNumber } from "../../utils/visitStats.js";
6
+
7
+ function getVisitStatsMember(ctx) {
8
+ if (isValidPositiveInt(ctx.userId)) {
9
+ return `user:${ctx.userId}`;
10
+ }
11
+
12
+ return `ip:${ctx.ip || "unknown"}`;
13
+ }
14
+
15
+ function getVisitStatsUaData(ctx) {
16
+ let userAgent = "";
17
+
18
+ if (typeof ctx.req?.headers?.get === "function") {
19
+ userAgent = String(ctx.req.headers.get("user-agent") || "");
20
+ } else if (typeof ctx.headers?.get === "function") {
21
+ userAgent = String(ctx.headers.get("user-agent") || "");
22
+ }
23
+
24
+ const uaResult = UAParser(userAgent);
25
+
26
+ return {
27
+ deviceType: normalizeVisitStatsUaValue(uaResult.device.type, "desktop"),
28
+ browserName: normalizeVisitStatsUaValue(uaResult.browser.name),
29
+ browserVersion: normalizeVisitStatsUaValue(uaResult.browser.version),
30
+ osName: normalizeVisitStatsUaValue(uaResult.os.name),
31
+ osVersion: normalizeVisitStatsUaValue(uaResult.os.version),
32
+ deviceVendor: normalizeVisitStatsUaValue(uaResult.device.vendor),
33
+ deviceModel: normalizeVisitStatsUaValue(uaResult.device.model),
34
+ engineName: normalizeVisitStatsUaValue(uaResult.engine.name),
35
+ cpuArchitecture: normalizeVisitStatsUaValue(uaResult.cpu.architecture)
36
+ };
37
+ }
38
+
39
+ function getVisitStatsProductData(ctx) {
40
+ return getVisitStatsProductMeta({
41
+ productName: ctx.body?.productName,
42
+ productCode: ctx.body?.productCode,
43
+ productVersion: ctx.body?.productVersion
44
+ });
45
+ }
46
+
47
+ async function saveVisitStatsUaData(befly, bucketDate, uaData) {
48
+ const saddItems = [];
49
+ const expireItems = [];
50
+ const countTasks = [];
51
+
52
+ for (const item of VISIT_STATS_UA_FIELDS) {
53
+ const field = item.field;
54
+ const defaultValue = item.defaultValue;
55
+ const value = normalizeVisitStatsUaValue(uaData[field], defaultValue);
56
+ const valuesKey = getVisitStatsDayUaValuesKey(bucketDate, field);
57
+ const countKey = getVisitStatsDayUaCountKey(bucketDate, field, value);
58
+
59
+ saddItems.push({ key: valuesKey, members: [value] });
60
+ expireItems.push({ key: valuesKey, seconds: VISIT_STATS_REDIS_TTL_SECONDS });
61
+ countTasks.push(befly.redis.incrWithExpire(countKey, VISIT_STATS_REDIS_TTL_SECONDS));
62
+ }
63
+
64
+ await befly.redis.saddBatch(saddItems);
65
+ await befly.redis.expireBatch(expireItems);
66
+ await Promise.all(countTasks);
67
+ }
68
+
69
+ async function saveVisitStatsProductData(befly, bucketDate, productData) {
70
+ const saddItems = [];
71
+ const expireItems = [];
72
+ const countTasks = [];
73
+
74
+ for (const item of VISIT_STATS_PRODUCT_FIELDS) {
75
+ const field = item.field;
76
+ const defaultValue = item.defaultValue;
77
+ const value = normalizeVisitStatsProductValue(productData[field], defaultValue);
78
+ const valuesKey = getVisitStatsDayProductValuesKey(bucketDate, field);
79
+ const countKey = getVisitStatsDayProductCountKey(bucketDate, field, value);
80
+
81
+ saddItems.push({ key: valuesKey, members: [value] });
82
+ expireItems.push({ key: valuesKey, seconds: VISIT_STATS_REDIS_TTL_SECONDS });
83
+ countTasks.push(befly.redis.incrWithExpire(countKey, VISIT_STATS_REDIS_TTL_SECONDS));
84
+ }
85
+
86
+ await befly.redis.saddBatch(saddItems);
87
+ await befly.redis.expireBatch(expireItems);
88
+ await Promise.all(countTasks);
89
+ }
90
+
91
+ async function saveVisitStatsProductSummary(befly, bucketTime, bucketDate, member, productData) {
92
+ const productKey = getVisitStatsProductKey(productData);
93
+ const metaKey = getVisitStatsProductMetaKey(productKey);
94
+ const dayKeysKey = getVisitStatsDayProductKeysKey(bucketDate);
95
+ const bucketPvKey = getVisitStatsBucketProductPvKey(bucketTime, productKey);
96
+ const bucketUvKey = getVisitStatsBucketProductUvKey(bucketTime, productKey);
97
+ const dayPvKey = getVisitStatsDayProductPvKey(bucketDate, productKey);
98
+ const dayUvKey = getVisitStatsDayProductUvKey(bucketDate, productKey);
99
+
100
+ await befly.redis.setString(metaKey, JSON.stringify(productData), VISIT_STATS_REDIS_TTL_SECONDS);
101
+ await befly.redis.sadd(dayKeysKey, [productKey]);
102
+ await befly.redis.expire(dayKeysKey, VISIT_STATS_REDIS_TTL_SECONDS);
103
+ await befly.redis.incrWithExpire(bucketPvKey, VISIT_STATS_REDIS_TTL_SECONDS);
104
+ await befly.redis.sadd(bucketUvKey, [member]);
105
+ await befly.redis.expire(bucketUvKey, VISIT_STATS_REDIS_TTL_SECONDS);
106
+ await befly.redis.incrWithExpire(dayPvKey, VISIT_STATS_REDIS_TTL_SECONDS);
107
+ await befly.redis.sadd(dayUvKey, [member]);
108
+ await befly.redis.expire(dayUvKey, VISIT_STATS_REDIS_TTL_SECONDS);
109
+ }
110
+
111
+ async function ensureVisitStatsTableReady(befly) {
112
+ if (befly.visitStatsTableReady !== undefined) {
113
+ return befly.visitStatsTableReady;
114
+ }
115
+
116
+ const tableExistsResult = await befly.mysql.tableExists("beflyVisitStats");
117
+ befly.visitStatsTableReady = tableExistsResult.data === true;
118
+ return befly.visitStatsTableReady;
119
+ }
120
+
121
+ async function flushVisitStatsBucket(befly, bucketTime) {
122
+ if (bucketTime <= 0) {
123
+ return;
124
+ }
125
+
126
+ const currentBucketTime = getTimeBucketStart(Date.now(), VISIT_STATS_BUCKET_MS);
127
+ if (bucketTime >= currentBucketTime) {
128
+ return;
129
+ }
130
+
131
+ const tableReady = await ensureVisitStatsTableReady(befly);
132
+ if (!tableReady) {
133
+ return;
134
+ }
135
+
136
+ const flushedKey = `visitStats:flushed:${bucketTime}`;
137
+ const flushed = await befly.redis.exists(flushedKey);
138
+ if (flushed) {
139
+ return;
140
+ }
141
+
142
+ const bucketDate = getDateYmdNumber(bucketTime);
143
+ const bucketKey = getVisitStatsBucketKey(bucketTime);
144
+ const pv = toVisitStatsNumber(await befly.redis.getString(`${bucketKey}:pv`));
145
+ const uv = await befly.redis.scard(`${bucketKey}:uv`);
146
+ const errorCount = toVisitStatsNumber(await befly.redis.getString(`${bucketKey}:errorCount`));
147
+ const durationSum = toVisitStatsNumber(await befly.redis.getString(`${bucketKey}:durationSum`));
148
+ const durationCount = toVisitStatsNumber(await befly.redis.getString(`${bucketKey}:durationCount`));
149
+ const avgDuration = durationCount > 0 ? Math.round(durationSum / durationCount) : 0;
150
+
151
+ if (pv <= 0 && uv <= 0 && errorCount <= 0 && durationCount <= 0) {
152
+ await befly.redis.setString(flushedKey, "1", VISIT_STATS_REDIS_TTL_SECONDS);
153
+ return;
154
+ }
155
+
156
+ const data = {
157
+ bucketTime: bucketTime,
158
+ bucketDate: bucketDate,
159
+ pv: pv,
160
+ uv: uv,
161
+ errorCount: errorCount,
162
+ durationSum: durationSum,
163
+ durationCount: durationCount,
164
+ avgDuration: avgDuration
165
+ };
166
+
167
+ const detail = await befly.mysql.getOne({
168
+ table: "beflyVisitStats",
169
+ where: { bucketTime: bucketTime }
170
+ });
171
+
172
+ if (detail.data?.id) {
173
+ await befly.mysql.updData({
174
+ table: "beflyVisitStats",
175
+ data: data,
176
+ where: { bucketTime: bucketTime }
177
+ });
178
+ } else {
179
+ await befly.mysql.insData({
180
+ table: "beflyVisitStats",
181
+ data: data
182
+ });
183
+ }
184
+
185
+ await befly.redis.setString(flushedKey, "1", VISIT_STATS_REDIS_TTL_SECONDS);
186
+ }
187
+
188
+ async function flushVisitStatsPreviousBucket(befly, now = Date.now()) {
189
+ const currentBucketTime = getTimeBucketStart(now, VISIT_STATS_BUCKET_MS);
190
+ await flushVisitStatsBucket(befly, currentBucketTime - 30 * 60 * 1000);
191
+ }
192
+
193
+ export default {
194
+ name: "上报访问统计",
195
+ method: "POST",
196
+ body: "none",
197
+ auth: false,
198
+ fields: {
199
+ pagePath: { name: "页面路径", input: "string", min: 0, max: 200 },
200
+ pageName: { name: "页面名称", input: "string", min: 0, max: 100 },
201
+ source: { name: "来源", input: "string", min: 0, max: 50 },
202
+ productName: { name: "产品名称", input: "string", min: 0, max: 100 },
203
+ productCode: { name: "产品代号", input: "string", min: 0, max: 100 },
204
+ productVersion: { name: "产品版本", input: "string", min: 0, max: 100 }
205
+ },
206
+ required: [],
207
+ handler: async (befly, ctx) => {
208
+ const now = Date.now();
209
+ const bucketTime = getTimeBucketStart(now, VISIT_STATS_BUCKET_MS);
210
+ const bucketDate = getDateYmdNumber(now);
211
+ const bucketKey = getVisitStatsBucketKey(bucketTime);
212
+ const dayKey = getVisitStatsDayKey(bucketDate);
213
+ const member = getVisitStatsMember(ctx);
214
+ const uaData = getVisitStatsUaData(ctx);
215
+ const productData = getVisitStatsProductData(ctx);
216
+
217
+ await flushVisitStatsPreviousBucket(befly, now);
218
+
219
+ await befly.redis.incrWithExpire(`${bucketKey}:pv`, VISIT_STATS_REDIS_TTL_SECONDS);
220
+ await befly.redis.incrWithExpire(`${dayKey}:pv`, VISIT_STATS_REDIS_TTL_SECONDS);
221
+
222
+ await befly.redis.sadd(`${bucketKey}:uv`, [member]);
223
+ await befly.redis.expire(`${bucketKey}:uv`, VISIT_STATS_REDIS_TTL_SECONDS);
224
+ await befly.redis.sadd(`${dayKey}:uv`, [member]);
225
+ await befly.redis.expire(`${dayKey}:uv`, VISIT_STATS_REDIS_TTL_SECONDS);
226
+
227
+ await befly.redis.setString(`visitStats:online:visitor:${member}`, "1", VISIT_STATS_ONLINE_TTL_SECONDS);
228
+ await befly.redis.sadd("visitStats:online:visitors", [member]);
229
+ await befly.redis.expire("visitStats:online:visitors", VISIT_STATS_REDIS_TTL_SECONDS);
230
+ await saveVisitStatsUaData(befly, bucketDate, uaData);
231
+ await saveVisitStatsProductData(befly, bucketDate, productData);
232
+ await saveVisitStatsProductSummary(befly, bucketTime, bucketDate, member, productData);
233
+
234
+ const result = {
235
+ bucketTime: bucketTime,
236
+ bucketDate: bucketDate
237
+ };
238
+
239
+ return befly.tool.Yes("上报成功", result);
240
+ }
241
+ };