befly 3.62.0 → 3.63.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/auth/login.js +65 -13
- package/apis/tongJi/_tongJi.js +92 -41
- package/apis/tongJi/dailyReport.js +73 -51
- package/apis/tongJi/dailyStats.js +98 -125
- package/apis/tongJi/dailyStatsDistribution.js +86 -0
- package/apis/tongJi/errorList.js +5 -9
- package/apis/tongJi/errorReport.js +81 -44
- package/apis/tongJi/errorStats.js +19 -125
- package/index.js +5 -6
- package/lib/logger.js +20 -15
- package/package.json +1 -1
- package/plugins/cron.js +96 -0
- package/sql/befly.sql +53 -25
- package/tables/dailyReport.json +124 -0
- package/tables/errorReport.json +31 -48
- package/tables/loginLog.json +29 -32
- package/utils/is.js +3 -36
- package/utils/userAgent.js +229 -104
- package/apis/tongJi/_daily.js +0 -159
|
@@ -1,74 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { getErrorStatsDayBucketCountKey, getErrorStatsDayBucketsKey, getErrorStatsDayTypeCountKey, getErrorStatsDayTypesKey, getErrorStatsPeriodCountKey, getTongJiDateRangeList, getTongJiMonthStartDate, getTongJiNumber, getTongJiRecentDateList, getTongJiWeekStartDate } from "./_tongJi.js";
|
|
1
|
+
import { getTongJiDateRangeList, getTongJiDateYmdNumber, getTongJiMonthStartDate, getTongJiRecentDateList } from "./_tongJi.js";
|
|
4
2
|
|
|
5
3
|
const ERROR_STATS_DAY_LIMIT = 30;
|
|
6
4
|
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async function getErrorStatsRedisSummary(befly, periodType, periodValue) {
|
|
12
|
-
return {
|
|
13
|
-
count: getTongJiNumber(await befly.redis.getString(getErrorStatsPeriodCountKey(periodType, periodValue)))
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function getErrorStatsRedisTrend(befly, bucketDate) {
|
|
18
|
-
const bucketSet = await befly.redis.smembers(getErrorStatsDayBucketsKey(bucketDate));
|
|
19
|
-
const trend = await Promise.all(
|
|
20
|
-
bucketSet.map(async (bucketTime) => {
|
|
21
|
-
const itemBucketTime = getTongJiNumber(bucketTime);
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
bucketTime: itemBucketTime,
|
|
25
|
-
bucketDate: bucketDate,
|
|
26
|
-
count: getTongJiNumber(await befly.redis.getString(getErrorStatsDayBucketCountKey(bucketDate, itemBucketTime)))
|
|
27
|
-
};
|
|
28
|
-
})
|
|
29
|
-
);
|
|
5
|
+
function toCount(value) {
|
|
6
|
+
const num = Number(value);
|
|
30
7
|
|
|
31
|
-
|
|
32
|
-
return trend;
|
|
8
|
+
return Number.isFinite(num) ? num : 0;
|
|
33
9
|
}
|
|
34
10
|
|
|
35
|
-
|
|
36
|
-
return Promise.all(
|
|
37
|
-
recentDateList.map(async (item) => {
|
|
38
|
-
return {
|
|
39
|
-
bucketDate: item,
|
|
40
|
-
count: getTongJiNumber(await befly.redis.getString(getErrorStatsPeriodCountKey("day", item)))
|
|
41
|
-
};
|
|
42
|
-
})
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function getErrorStatsRedisTopTypes(befly, bucketDate) {
|
|
47
|
-
const typeSet = await befly.redis.smembers(getErrorStatsDayTypesKey(bucketDate));
|
|
48
|
-
const topTypes = await Promise.all(
|
|
49
|
-
typeSet.map(async (errorType) => {
|
|
50
|
-
const name = String(errorType || "unknown");
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
errorType: name,
|
|
54
|
-
count: getTongJiNumber(await befly.redis.getString(getErrorStatsDayTypeCountKey(bucketDate, name)))
|
|
55
|
-
};
|
|
56
|
-
})
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
return topTypes
|
|
60
|
-
.filter((item) => item.count > 0)
|
|
61
|
-
.toSorted((a, b) => {
|
|
62
|
-
if (b.count !== a.count) {
|
|
63
|
-
return b.count - a.count;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return String(a.errorType).localeCompare(String(b.errorType), "zh-CN");
|
|
67
|
-
})
|
|
68
|
-
.slice(0, 5);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function buildErrorStatsResponse(bucketDate, weekStartDate, monthStartDate, recentDateList, todaySummary, yesterdaySummary, dayBeforeYesterdaySummary, weekSummary, monthSummary, recent30Count, trend, days, topTypes) {
|
|
11
|
+
function buildErrorStatsResponse(bucketDate, monthStartDate, recentDateList, todaySummary, yesterdaySummary, dayBeforeYesterdaySummary, monthSummary, recent30Count, trend, days, topTypes) {
|
|
72
12
|
return {
|
|
73
13
|
today: {
|
|
74
14
|
bucketDate: bucketDate,
|
|
@@ -82,11 +22,6 @@ function buildErrorStatsResponse(bucketDate, weekStartDate, monthStartDate, rece
|
|
|
82
22
|
bucketDate: recentDateList[recentDateList.length - 3] || recentDateList[recentDateList.length - 2] || bucketDate,
|
|
83
23
|
count: dayBeforeYesterdaySummary.count
|
|
84
24
|
},
|
|
85
|
-
week: {
|
|
86
|
-
startDate: weekStartDate,
|
|
87
|
-
endDate: bucketDate,
|
|
88
|
-
count: weekSummary.count
|
|
89
|
-
},
|
|
90
25
|
month: {
|
|
91
26
|
startDate: monthStartDate,
|
|
92
27
|
endDate: bucketDate,
|
|
@@ -103,37 +38,12 @@ function buildErrorStatsResponse(bucketDate, weekStartDate, monthStartDate, rece
|
|
|
103
38
|
};
|
|
104
39
|
}
|
|
105
40
|
|
|
106
|
-
function isRedisResultEmpty(todaySummary, yesterdaySummary, dayBeforeYesterdaySummary, weekSummary, monthSummary, recent30Count, trend, topTypes) {
|
|
107
|
-
return todaySummary.count <= 0 && yesterdaySummary.count <= 0 && dayBeforeYesterdaySummary.count <= 0 && weekSummary.count <= 0 && monthSummary.count <= 0 && recent30Count <= 0 && trend.length === 0 && topTypes.length === 0;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function getErrorStatsFromRedis(befly, bucketDate, weekStartDate, monthStartDate, recentDateList) {
|
|
111
|
-
const [todaySummary, yesterdaySummary, dayBeforeYesterdaySummary, weekSummary, monthSummary, trend, days, topTypes] = await Promise.all([
|
|
112
|
-
getErrorStatsRedisSummary(befly, "day", bucketDate),
|
|
113
|
-
getErrorStatsRedisSummary(befly, "day", recentDateList[recentDateList.length - 2] || bucketDate),
|
|
114
|
-
getErrorStatsRedisSummary(befly, "day", recentDateList[recentDateList.length - 3] || recentDateList[recentDateList.length - 2] || bucketDate),
|
|
115
|
-
getErrorStatsRedisSummary(befly, "week", weekStartDate),
|
|
116
|
-
getErrorStatsRedisSummary(befly, "month", monthStartDate),
|
|
117
|
-
getErrorStatsRedisTrend(befly, bucketDate),
|
|
118
|
-
getErrorStatsRedisDays(befly, recentDateList),
|
|
119
|
-
getErrorStatsRedisTopTypes(befly, bucketDate)
|
|
120
|
-
]);
|
|
121
|
-
|
|
122
|
-
const recent30Count = sumCounts(days);
|
|
123
|
-
|
|
124
|
-
if (isRedisResultEmpty(todaySummary, yesterdaySummary, dayBeforeYesterdaySummary, weekSummary, monthSummary, recent30Count, trend, topTypes)) {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return buildErrorStatsResponse(bucketDate, weekStartDate, monthStartDate, recentDateList, todaySummary, yesterdaySummary, dayBeforeYesterdaySummary, weekSummary, monthSummary, recent30Count, trend, days, topTypes);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
41
|
async function getErrorStatsDayCountMap(befly, startDate, endDate) {
|
|
132
|
-
const result = await befly.mysql.execute("SELECT bucket_date as bucketDate,
|
|
42
|
+
const result = await befly.mysql.execute("SELECT bucket_date as bucketDate, COUNT(*) as count FROM befly_error_report WHERE state = 1 AND bucket_date BETWEEN ? AND ? GROUP BY bucket_date", [startDate, endDate]);
|
|
133
43
|
const map = new Map();
|
|
134
44
|
|
|
135
45
|
for (const item of result.data || []) {
|
|
136
|
-
map.set(Number(item.bucketDate),
|
|
46
|
+
map.set(Number(item.bucketDate), toCount(item.count));
|
|
137
47
|
}
|
|
138
48
|
|
|
139
49
|
return map;
|
|
@@ -143,37 +53,35 @@ function sumDateCounts(map, dateList) {
|
|
|
143
53
|
return dateList.reduce((sum, date) => sum + (map.get(date) || 0), 0);
|
|
144
54
|
}
|
|
145
55
|
|
|
146
|
-
async function getErrorStatsFromMysql(befly, bucketDate,
|
|
56
|
+
async function getErrorStatsFromMysql(befly, bucketDate, monthStartDate, recentDateList) {
|
|
147
57
|
const yesterdayDate = recentDateList[recentDateList.length - 2] || bucketDate;
|
|
148
58
|
const dayBeforeYesterdayDate = recentDateList[recentDateList.length - 3] || yesterdayDate;
|
|
149
|
-
const weekDateList = getTongJiDateRangeList(weekStartDate, bucketDate);
|
|
150
59
|
const monthDateList = getTongJiDateRangeList(monthStartDate, bucketDate);
|
|
151
60
|
|
|
152
61
|
const [dayCountMap, trendRes, topTypesRes] = await Promise.all([
|
|
153
62
|
getErrorStatsDayCountMap(befly, recentDateList[0], bucketDate),
|
|
154
|
-
befly.mysql.execute("SELECT bucket_time as bucketTime, bucket_date as bucketDate,
|
|
155
|
-
befly.mysql.execute("SELECT error_type as errorType,
|
|
63
|
+
befly.mysql.execute("SELECT bucket_time as bucketTime, bucket_date as bucketDate, 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]),
|
|
64
|
+
befly.mysql.execute("SELECT error_type as errorType, COUNT(*) as count FROM befly_error_report WHERE state = 1 AND bucket_date = ? GROUP BY error_type ORDER BY count DESC LIMIT 5", [bucketDate])
|
|
156
65
|
]);
|
|
157
66
|
|
|
158
67
|
const todaySummary = { count: dayCountMap.get(bucketDate) || 0 };
|
|
159
68
|
const yesterdaySummary = { count: dayCountMap.get(yesterdayDate) || 0 };
|
|
160
69
|
const dayBeforeYesterdaySummary = { count: dayCountMap.get(dayBeforeYesterdayDate) || 0 };
|
|
161
|
-
const weekSummary = { count: sumDateCounts(dayCountMap, weekDateList) };
|
|
162
70
|
const monthSummary = { count: sumDateCounts(dayCountMap, monthDateList) };
|
|
163
71
|
const recent30Count = sumDateCounts(dayCountMap, recentDateList);
|
|
164
72
|
|
|
165
73
|
const trend = (trendRes.data || []).map((item) => {
|
|
166
74
|
return {
|
|
167
|
-
bucketTime:
|
|
168
|
-
bucketDate:
|
|
169
|
-
count:
|
|
75
|
+
bucketTime: toCount(item.bucketTime),
|
|
76
|
+
bucketDate: toCount(item.bucketDate),
|
|
77
|
+
count: toCount(item.count)
|
|
170
78
|
};
|
|
171
79
|
});
|
|
172
80
|
|
|
173
81
|
const topTypes = (topTypesRes.data || []).map((item) => {
|
|
174
82
|
return {
|
|
175
83
|
errorType: String(item.errorType || "unknown"),
|
|
176
|
-
count:
|
|
84
|
+
count: toCount(item.count)
|
|
177
85
|
};
|
|
178
86
|
});
|
|
179
87
|
|
|
@@ -184,7 +92,7 @@ async function getErrorStatsFromMysql(befly, bucketDate, weekStartDate, monthSta
|
|
|
184
92
|
};
|
|
185
93
|
});
|
|
186
94
|
|
|
187
|
-
return buildErrorStatsResponse(bucketDate,
|
|
95
|
+
return buildErrorStatsResponse(bucketDate, monthStartDate, recentDateList, todaySummary, yesterdaySummary, dayBeforeYesterdaySummary, monthSummary, recent30Count, trend, days, topTypes);
|
|
188
96
|
}
|
|
189
97
|
|
|
190
98
|
function buildEmptyDays(recentDateList) {
|
|
@@ -196,7 +104,7 @@ function buildEmptyDays(recentDateList) {
|
|
|
196
104
|
});
|
|
197
105
|
}
|
|
198
106
|
|
|
199
|
-
function buildEmptyErrorStats(bucketDate,
|
|
107
|
+
function buildEmptyErrorStats(bucketDate, monthStartDate, recentDateList) {
|
|
200
108
|
return {
|
|
201
109
|
today: {
|
|
202
110
|
bucketDate: bucketDate,
|
|
@@ -210,11 +118,6 @@ function buildEmptyErrorStats(bucketDate, weekStartDate, monthStartDate, recentD
|
|
|
210
118
|
bucketDate: recentDateList[recentDateList.length - 3] || recentDateList[recentDateList.length - 2] || bucketDate,
|
|
211
119
|
count: 0
|
|
212
120
|
},
|
|
213
|
-
week: {
|
|
214
|
-
startDate: weekStartDate,
|
|
215
|
-
endDate: bucketDate,
|
|
216
|
-
count: 0
|
|
217
|
-
},
|
|
218
121
|
month: {
|
|
219
122
|
startDate: monthStartDate,
|
|
220
123
|
endDate: bucketDate,
|
|
@@ -242,23 +145,14 @@ export default {
|
|
|
242
145
|
const tableExistsResult = await befly.mysql.tableExists("beflyErrorReport");
|
|
243
146
|
const tableReady = tableExistsResult.data === true;
|
|
244
147
|
const now = Date.now();
|
|
245
|
-
const bucketDate =
|
|
246
|
-
const weekStartDate = getTongJiWeekStartDate(now);
|
|
148
|
+
const bucketDate = getTongJiDateYmdNumber(now);
|
|
247
149
|
const monthStartDate = getTongJiMonthStartDate(now);
|
|
248
150
|
const recentDateList = getTongJiRecentDateList(now, ERROR_STATS_DAY_LIMIT);
|
|
249
151
|
|
|
250
152
|
if (!tableReady) {
|
|
251
|
-
return befly.tool.Yes("获取成功", buildEmptyErrorStats(bucketDate,
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (befly.redis) {
|
|
255
|
-
const redisResult = await getErrorStatsFromRedis(befly, bucketDate, weekStartDate, monthStartDate, recentDateList);
|
|
256
|
-
|
|
257
|
-
if (redisResult) {
|
|
258
|
-
return befly.tool.Yes("获取成功", redisResult);
|
|
259
|
-
}
|
|
153
|
+
return befly.tool.Yes("获取成功", buildEmptyErrorStats(bucketDate, monthStartDate, recentDateList));
|
|
260
154
|
}
|
|
261
155
|
|
|
262
|
-
return befly.tool.Yes("获取成功", await getErrorStatsFromMysql(befly, bucketDate,
|
|
156
|
+
return befly.tool.Yes("获取成功", await getErrorStatsFromMysql(befly, bucketDate, monthStartDate, recentDateList));
|
|
263
157
|
}
|
|
264
158
|
};
|
package/index.js
CHANGED
|
@@ -120,6 +120,11 @@ export async function createBefly(env = {}, config = {}, menus = []) {
|
|
|
120
120
|
const mergedMenus = deepMerge(prefixMenuPaths(beflyMenus, "core"), menus);
|
|
121
121
|
mergedConfig.projectLists = mergeProjectListsByCode(mergedConfig.projectLists);
|
|
122
122
|
|
|
123
|
+
Logger.configure({
|
|
124
|
+
runtimeEnv: mergedConfig.runMode,
|
|
125
|
+
...mergedConfig.logger
|
|
126
|
+
});
|
|
127
|
+
|
|
123
128
|
const configHasError = await checkConfig(mergedConfig);
|
|
124
129
|
const { apis, tables, plugins, hooks } = await scanSources();
|
|
125
130
|
|
|
@@ -138,12 +143,6 @@ export async function createBefly(env = {}, config = {}, menus = []) {
|
|
|
138
143
|
});
|
|
139
144
|
}
|
|
140
145
|
|
|
141
|
-
// 在插件加载前用项目 runMode 初始化 Logger,确保后续所有日志目录正确
|
|
142
|
-
Logger.configure({
|
|
143
|
-
runtimeEnv: mergedConfig.runMode,
|
|
144
|
-
...mergedConfig.logger
|
|
145
|
-
});
|
|
146
|
-
|
|
147
146
|
return new Befly({
|
|
148
147
|
env: env,
|
|
149
148
|
config: mergedConfig,
|
package/lib/logger.js
CHANGED
|
@@ -69,6 +69,10 @@ class LogFileSink {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
getFilePath(date, index) {
|
|
72
|
+
if (this.prefix === "debug") {
|
|
73
|
+
return nodePathJoin(config.dir, "dev.log");
|
|
74
|
+
}
|
|
75
|
+
|
|
72
76
|
const suffix = index > 0 ? `.${index}` : "";
|
|
73
77
|
const filename = this.prefix === "app" ? `${date}${suffix}.log` : `${this.prefix}.${date}${suffix}.log`;
|
|
74
78
|
return nodePathJoin(config.dir, filename);
|
|
@@ -89,6 +93,10 @@ class LogFileSink {
|
|
|
89
93
|
this.openStream(this.getFilePath(date, 0));
|
|
90
94
|
}
|
|
91
95
|
|
|
96
|
+
if (this.prefix === "debug") {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
92
100
|
if (this.stream && this.streamSizeBytes + nextChunkBytes > MAX_FILE_BYTES) {
|
|
93
101
|
this.closeStreamSync();
|
|
94
102
|
this.streamIndex += 1;
|
|
@@ -177,28 +185,20 @@ export async function shutdown() {
|
|
|
177
185
|
}
|
|
178
186
|
}
|
|
179
187
|
|
|
180
|
-
function getRuntimeLogDir(
|
|
181
|
-
|
|
182
|
-
if (runtimeEnv === "development") {
|
|
183
|
-
return nodePathResolve(cwd, "logsDev");
|
|
184
|
-
}
|
|
185
|
-
return nodePathResolve(cwd, "logs");
|
|
188
|
+
function getRuntimeLogDir() {
|
|
189
|
+
return nodePathResolve(process.cwd(), "logs");
|
|
186
190
|
}
|
|
187
191
|
|
|
188
192
|
function prepareLogDir(runtimeEnv, shouldClearDev = false) {
|
|
189
193
|
const logDir = getRuntimeLogDir(runtimeEnv);
|
|
190
|
-
const
|
|
194
|
+
const devLogPath = nodePathResolve(logDir, "dev.log");
|
|
191
195
|
|
|
192
196
|
try {
|
|
193
197
|
if (!existsSync(logDir)) {
|
|
194
198
|
mkdirSync(logDir, { recursive: true });
|
|
195
199
|
}
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
if (shouldClearDev && runtimeEnv === "development" && existsSync(devLogDir)) {
|
|
200
|
-
rmSync(devLogDir, { recursive: true, force: true });
|
|
201
|
-
mkdirSync(devLogDir, { recursive: true });
|
|
200
|
+
if (shouldClearDev && runtimeEnv === "development" && existsSync(devLogPath)) {
|
|
201
|
+
rmSync(devLogPath, { force: true });
|
|
202
202
|
}
|
|
203
203
|
} catch (error) {
|
|
204
204
|
throw new Error(`创建日志目录失败: ${logDir}. ${error?.message || error}`, {
|
|
@@ -214,8 +214,6 @@ function ensureLogDirExists() {
|
|
|
214
214
|
config.dir = prepareLogDir(config.runtimeEnv);
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
// 方案B:删除“启动时清理旧日志”功能(减少 I/O 与复杂度)。
|
|
218
|
-
|
|
219
217
|
/**
|
|
220
218
|
* 配置日志
|
|
221
219
|
*/
|
|
@@ -285,6 +283,13 @@ function buildJsonLine(level, timeMs, record) {
|
|
|
285
283
|
function ensureSinksReady(kind) {
|
|
286
284
|
ensureLogDirExists();
|
|
287
285
|
|
|
286
|
+
if (config.runtimeEnv === "development") {
|
|
287
|
+
if (!appFileSink) {
|
|
288
|
+
appFileSink = new LogFileSink("debug");
|
|
289
|
+
}
|
|
290
|
+
return appFileSink;
|
|
291
|
+
}
|
|
292
|
+
|
|
288
293
|
if (kind === "app") {
|
|
289
294
|
if (!appFileSink) {
|
|
290
295
|
appFileSink = new LogFileSink("app");
|
package/package.json
CHANGED
package/plugins/cron.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 定时任务插件
|
|
3
|
+
* 基于 Bun.cron 提供进程内定时任务能力。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Logger } from "../lib/logger.js";
|
|
7
|
+
import { isPrimaryProcess } from "../utils/is.js";
|
|
8
|
+
|
|
9
|
+
const jobs = new Map();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 注册一个定时任务
|
|
13
|
+
* @param {string} name - 任务名称,用于日志标识
|
|
14
|
+
* @param {string} schedule - cron 表达式或预定义昵称
|
|
15
|
+
* @param {function} task - 任务函数,接收 befly 上下文
|
|
16
|
+
* @returns {CronJob|null}
|
|
17
|
+
*/
|
|
18
|
+
function register(name, schedule, task) {
|
|
19
|
+
if (!isPrimaryProcess(this.env)) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (jobs.has(name)) {
|
|
24
|
+
return jobs.get(name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const context = this;
|
|
28
|
+
const job = Bun.cron(schedule, async function () {
|
|
29
|
+
Logger.info(`[定时任务] ${name} 开始执行`);
|
|
30
|
+
try {
|
|
31
|
+
await task(context);
|
|
32
|
+
Logger.info(`[定时任务] ${name} 执行完成`);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
Logger.error(`[定时任务] ${name} 执行失败`, error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
jobs.set(name, job);
|
|
39
|
+
return job;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 注册一个按秒执行的定时任务
|
|
44
|
+
*/
|
|
45
|
+
function second(name, seconds, task) {
|
|
46
|
+
if (!isPrimaryProcess(this.env)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (jobs.has(name)) {
|
|
51
|
+
return jobs.get(name);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const context = this;
|
|
55
|
+
let running = false;
|
|
56
|
+
const timer = setInterval(async () => {
|
|
57
|
+
if (running) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
running = true;
|
|
62
|
+
try {
|
|
63
|
+
await task(context);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
Logger.error(`[定时任务] ${name} 执行失败`, error);
|
|
66
|
+
} finally {
|
|
67
|
+
running = false;
|
|
68
|
+
}
|
|
69
|
+
}, seconds * 1000);
|
|
70
|
+
const job = {
|
|
71
|
+
stop: () => clearInterval(timer)
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
jobs.set(name, job);
|
|
75
|
+
return job;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 停止所有已注册的定时任务
|
|
80
|
+
*/
|
|
81
|
+
export function stopAll() {
|
|
82
|
+
for (const job of jobs.values()) {
|
|
83
|
+
job.stop();
|
|
84
|
+
}
|
|
85
|
+
jobs.clear();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default {
|
|
89
|
+
order: 8,
|
|
90
|
+
handler: async function (befly) {
|
|
91
|
+
return {
|
|
92
|
+
register: register.bind(befly),
|
|
93
|
+
second: second.bind(befly)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
};
|
package/sql/befly.sql
CHANGED
|
@@ -31,6 +31,33 @@ CREATE TABLE IF NOT EXISTS `befly_api` (
|
|
|
31
31
|
PRIMARY KEY (`id`)
|
|
32
32
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
33
33
|
|
|
34
|
+
CREATE TABLE IF NOT EXISTS `befly_daily_report` (
|
|
35
|
+
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
36
|
+
`report_time` BIGINT NOT NULL DEFAULT 0,
|
|
37
|
+
`report_date` INT NOT NULL DEFAULT 0,
|
|
38
|
+
`member_type` VARCHAR(6) NOT NULL DEFAULT '',
|
|
39
|
+
`member` VARCHAR(500) NOT NULL DEFAULT '',
|
|
40
|
+
`user_id` BIGINT NOT NULL DEFAULT 0,
|
|
41
|
+
`client_id` VARCHAR(120) NOT NULL DEFAULT '',
|
|
42
|
+
`ip` VARCHAR(50) NOT NULL DEFAULT '',
|
|
43
|
+
`client_type` VARCHAR(20) NOT NULL DEFAULT '',
|
|
44
|
+
`product_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
45
|
+
`product_code` VARCHAR(100) NOT NULL DEFAULT '',
|
|
46
|
+
`product_version` VARCHAR(50) NOT NULL DEFAULT '',
|
|
47
|
+
`client_version` VARCHAR(100) NOT NULL DEFAULT '',
|
|
48
|
+
`os` VARCHAR(100) NOT NULL DEFAULT '',
|
|
49
|
+
`system` VARCHAR(100) NOT NULL DEFAULT '',
|
|
50
|
+
`brand` VARCHAR(100) NOT NULL DEFAULT '',
|
|
51
|
+
`model` VARCHAR(100) NOT NULL DEFAULT '',
|
|
52
|
+
`state` TINYINT NOT NULL DEFAULT 1,
|
|
53
|
+
`created_at` BIGINT NOT NULL DEFAULT 0,
|
|
54
|
+
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
55
|
+
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
56
|
+
PRIMARY KEY (`id`),
|
|
57
|
+
UNIQUE KEY `uk_befly_daily_report_report_date_member` (`report_date`, `member`),
|
|
58
|
+
KEY `idx_befly_daily_report_report_date_product_code` (`report_date`, `product_code`)
|
|
59
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
60
|
+
|
|
34
61
|
CREATE TABLE IF NOT EXISTS `befly_dict` (
|
|
35
62
|
`id` BIGINT NOT NULL,
|
|
36
63
|
`type_code` VARCHAR(50) NOT NULL DEFAULT '',
|
|
@@ -100,21 +127,20 @@ CREATE TABLE IF NOT EXISTS `befly_file` (
|
|
|
100
127
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
101
128
|
|
|
102
129
|
CREATE TABLE IF NOT EXISTS `befly_login_log` (
|
|
103
|
-
`id` BIGINT NOT NULL,
|
|
130
|
+
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
104
131
|
`admin_id` BIGINT NOT NULL DEFAULT 0,
|
|
105
132
|
`username` VARCHAR(50) NOT NULL DEFAULT '',
|
|
106
133
|
`nickname` VARCHAR(50) NOT NULL DEFAULT '',
|
|
107
134
|
`ip` VARCHAR(50) NOT NULL DEFAULT '',
|
|
108
|
-
`
|
|
109
|
-
`
|
|
110
|
-
`
|
|
111
|
-
`
|
|
112
|
-
`
|
|
113
|
-
`
|
|
114
|
-
`
|
|
115
|
-
`
|
|
116
|
-
`
|
|
117
|
-
`cpu_architecture` VARCHAR(20) NOT NULL DEFAULT '',
|
|
135
|
+
`client_type` VARCHAR(20) NOT NULL DEFAULT '',
|
|
136
|
+
`product_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
137
|
+
`product_code` VARCHAR(100) NOT NULL DEFAULT '',
|
|
138
|
+
`product_version` VARCHAR(50) NOT NULL DEFAULT '',
|
|
139
|
+
`client_version` VARCHAR(100) NOT NULL DEFAULT '',
|
|
140
|
+
`os` VARCHAR(100) NOT NULL DEFAULT '',
|
|
141
|
+
`system` VARCHAR(100) NOT NULL DEFAULT '',
|
|
142
|
+
`brand` VARCHAR(100) NOT NULL DEFAULT '',
|
|
143
|
+
`model` VARCHAR(100) NOT NULL DEFAULT '',
|
|
118
144
|
`login_time` BIGINT NOT NULL DEFAULT 0,
|
|
119
145
|
`login_result` TINYINT NOT NULL DEFAULT 0,
|
|
120
146
|
`fail_reason` VARCHAR(200) NOT NULL DEFAULT '',
|
|
@@ -122,7 +148,8 @@ CREATE TABLE IF NOT EXISTS `befly_login_log` (
|
|
|
122
148
|
`created_at` BIGINT NOT NULL DEFAULT 0,
|
|
123
149
|
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
124
150
|
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
125
|
-
PRIMARY KEY (`id`)
|
|
151
|
+
PRIMARY KEY (`id`),
|
|
152
|
+
KEY `idx_befly_login_log_login_time` (`login_time`)
|
|
126
153
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
127
154
|
|
|
128
155
|
CREATE TABLE IF NOT EXISTS `befly_menu` (
|
|
@@ -194,13 +221,19 @@ CREATE TABLE IF NOT EXISTS `befly_sys_config` (
|
|
|
194
221
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
195
222
|
|
|
196
223
|
CREATE TABLE IF NOT EXISTS `befly_error_report` (
|
|
197
|
-
`id` BIGINT NOT NULL,
|
|
224
|
+
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
198
225
|
`report_time` BIGINT NOT NULL DEFAULT 0,
|
|
199
226
|
`first_report_time` BIGINT NOT NULL DEFAULT 0,
|
|
227
|
+
`last_report_time` BIGINT NOT NULL DEFAULT 0,
|
|
200
228
|
`bucket_time` BIGINT NOT NULL DEFAULT 0,
|
|
201
229
|
`bucket_date` INT NOT NULL DEFAULT 0,
|
|
202
230
|
`hit_count` BIGINT NOT NULL DEFAULT 1,
|
|
203
|
-
`
|
|
231
|
+
`client_type` VARCHAR(20) NOT NULL DEFAULT '',
|
|
232
|
+
`client_id` VARCHAR(120) NOT NULL DEFAULT '',
|
|
233
|
+
`member_type` VARCHAR(6) NOT NULL DEFAULT '',
|
|
234
|
+
`member` VARCHAR(200) NOT NULL DEFAULT '',
|
|
235
|
+
`user_id` BIGINT NULL DEFAULT NULL,
|
|
236
|
+
`ip` VARCHAR(50) NULL DEFAULT NULL,
|
|
204
237
|
`product_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
205
238
|
`product_code` VARCHAR(100) NOT NULL DEFAULT '',
|
|
206
239
|
`product_version` VARCHAR(100) NOT NULL DEFAULT '',
|
|
@@ -208,22 +241,17 @@ CREATE TABLE IF NOT EXISTS `befly_error_report` (
|
|
|
208
241
|
`page_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
209
242
|
`error_type` VARCHAR(50) NOT NULL DEFAULT '',
|
|
210
243
|
`message` VARCHAR(500) NOT NULL DEFAULT '',
|
|
244
|
+
`extracted_message` VARCHAR(200) NOT NULL DEFAULT '',
|
|
211
245
|
`detail` TEXT NULL,
|
|
212
|
-
`user_agent` VARCHAR(500) NOT NULL DEFAULT '',
|
|
213
|
-
`browser_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
214
|
-
`browser_version` VARCHAR(100) NOT NULL DEFAULT '',
|
|
215
|
-
`os_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
216
|
-
`os_version` VARCHAR(100) NOT NULL DEFAULT '',
|
|
217
|
-
`device_type` VARCHAR(50) NOT NULL DEFAULT '',
|
|
218
|
-
`device_vendor` VARCHAR(100) NOT NULL DEFAULT '',
|
|
219
|
-
`device_model` VARCHAR(100) NOT NULL DEFAULT '',
|
|
220
|
-
`engine_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
221
|
-
`cpu_architecture` VARCHAR(100) NOT NULL DEFAULT '',
|
|
222
246
|
`state` TINYINT NOT NULL DEFAULT 1,
|
|
223
247
|
`created_at` BIGINT NOT NULL DEFAULT 0,
|
|
224
248
|
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
225
249
|
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
226
250
|
PRIMARY KEY (`id`),
|
|
227
251
|
KEY `idx_befly_error_report_time` (`report_time`),
|
|
228
|
-
KEY `idx_befly_error_report_bucket_date` (`bucket_date`)
|
|
252
|
+
KEY `idx_befly_error_report_bucket_date` (`bucket_date`),
|
|
253
|
+
UNIQUE KEY `idx_befly_error_report_unique` (`bucket_date`, `error_type`, `extracted_message`, `product_code`, `member`),
|
|
254
|
+
KEY `idx_befly_error_report_bucket_date_error_type` (`bucket_date`, `error_type`),
|
|
255
|
+
KEY `idx_befly_error_report_product_code_report_time` (`product_code`, `report_time`),
|
|
256
|
+
KEY `idx_befly_error_report_error_type_report_time` (`error_type`, `report_time`)
|
|
229
257
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|