befly 3.62.0 → 3.64.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/dailyReportSelect.js +36 -0
- package/apis/tongJi/dailyStats.js +114 -126
- 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/apis/tongJi/projectDelete.js +20 -0
- package/apis/tongJi/projectInsert.js +35 -0
- package/apis/tongJi/projectSelect.js +31 -0
- package/apis/tongJi/projectUpdate.js +52 -0
- package/configs/beflyMenus.json +20 -3
- 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 +67 -25
- package/tables/dailyReport.json +124 -0
- package/tables/errorReport.json +31 -48
- package/tables/loginLog.json +29 -32
- package/tables/project.json +42 -0
- 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
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import projectTable from "#befly/tables/project.json";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
name: "删除产品",
|
|
5
|
+
method: "POST",
|
|
6
|
+
body: "none",
|
|
7
|
+
auth: true,
|
|
8
|
+
fields: {
|
|
9
|
+
id: projectTable.id
|
|
10
|
+
},
|
|
11
|
+
required: ["id"],
|
|
12
|
+
handler: async (befly, ctx) => {
|
|
13
|
+
await befly.mysql.delData({
|
|
14
|
+
table: "beflyProject",
|
|
15
|
+
where: { id: ctx.body.id }
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return befly.tool.Yes("删除成功");
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import projectTable from "#befly/tables/project.json";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
name: "添加产品",
|
|
5
|
+
method: "POST",
|
|
6
|
+
body: "none",
|
|
7
|
+
auth: true,
|
|
8
|
+
fields: {
|
|
9
|
+
code: projectTable.code,
|
|
10
|
+
name: projectTable.name,
|
|
11
|
+
sort: projectTable.sort
|
|
12
|
+
},
|
|
13
|
+
required: ["code", "name"],
|
|
14
|
+
handler: async (befly, ctx) => {
|
|
15
|
+
const existing = await befly.mysql.getOne({
|
|
16
|
+
table: "beflyProject",
|
|
17
|
+
where: { code: ctx.body.code }
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (existing.data?.id) {
|
|
21
|
+
return befly.tool.No("产品代号已存在");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const projectId = await befly.mysql.insData({
|
|
25
|
+
table: "beflyProject",
|
|
26
|
+
data: {
|
|
27
|
+
code: ctx.body.code,
|
|
28
|
+
name: ctx.body.name,
|
|
29
|
+
sort: ctx.body.sort
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return befly.tool.Yes("添加成功", { id: projectId.data });
|
|
34
|
+
}
|
|
35
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { queryFields } from "#befly/apis/_apis.js";
|
|
2
|
+
import projectTable from "#befly/tables/project.json";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: "获取产品列表",
|
|
6
|
+
method: "POST",
|
|
7
|
+
body: "none",
|
|
8
|
+
auth: true,
|
|
9
|
+
fields: {
|
|
10
|
+
...queryFields,
|
|
11
|
+
code: projectTable.code,
|
|
12
|
+
state: projectTable.state
|
|
13
|
+
},
|
|
14
|
+
required: [],
|
|
15
|
+
handler: async (befly, ctx) => {
|
|
16
|
+
const result = await befly.mysql.getList({
|
|
17
|
+
table: "beflyProject",
|
|
18
|
+
fields: ["id", "code", "name", "sort", "state", "createdAt", "updatedAt"],
|
|
19
|
+
where: {
|
|
20
|
+
code$like$or: ctx.body.code,
|
|
21
|
+
name$like$or: ctx.body.keyword,
|
|
22
|
+
state: ctx.body.state
|
|
23
|
+
},
|
|
24
|
+
page: ctx.body.page,
|
|
25
|
+
limit: ctx.body.limit,
|
|
26
|
+
orderBy: ["sort#asc"]
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return befly.tool.Yes("获取成功", result.data);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import projectTable from "#befly/tables/project.json";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
name: "更新产品",
|
|
5
|
+
method: "POST",
|
|
6
|
+
body: "none",
|
|
7
|
+
auth: true,
|
|
8
|
+
fields: {
|
|
9
|
+
id: projectTable.id,
|
|
10
|
+
code: projectTable.code,
|
|
11
|
+
name: projectTable.name,
|
|
12
|
+
sort: projectTable.sort,
|
|
13
|
+
state: projectTable.state
|
|
14
|
+
},
|
|
15
|
+
required: ["id"],
|
|
16
|
+
handler: async (befly, ctx) => {
|
|
17
|
+
const current = await befly.mysql.getOne({
|
|
18
|
+
table: "beflyProject",
|
|
19
|
+
where: { id: ctx.body.id }
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (!current.data?.id) {
|
|
23
|
+
return befly.tool.No("产品不存在");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (ctx.body.code !== undefined && ctx.body.code !== current.data.code) {
|
|
27
|
+
const existing = await befly.mysql.getOne({
|
|
28
|
+
table: "beflyProject",
|
|
29
|
+
where: { code: ctx.body.code }
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (existing.data?.id) {
|
|
33
|
+
return befly.tool.No("产品代号已存在");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const updateData = {};
|
|
38
|
+
|
|
39
|
+
if (ctx.body.code !== undefined) updateData.code = ctx.body.code;
|
|
40
|
+
if (ctx.body.name !== undefined) updateData.name = ctx.body.name;
|
|
41
|
+
if (ctx.body.sort !== undefined) updateData.sort = ctx.body.sort;
|
|
42
|
+
if (ctx.body.state !== undefined) updateData.state = ctx.body.state;
|
|
43
|
+
|
|
44
|
+
await befly.mysql.updData({
|
|
45
|
+
table: "beflyProject",
|
|
46
|
+
data: updateData,
|
|
47
|
+
where: { id: ctx.body.id }
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return befly.tool.Yes("更新成功");
|
|
51
|
+
}
|
|
52
|
+
};
|
package/configs/beflyMenus.json
CHANGED
|
@@ -5,9 +5,26 @@
|
|
|
5
5
|
"sort": 0
|
|
6
6
|
},
|
|
7
7
|
{
|
|
8
|
-
"name": "
|
|
9
|
-
"path": "/
|
|
10
|
-
"sort":
|
|
8
|
+
"name": "统计管理",
|
|
9
|
+
"path": "/tong-ji",
|
|
10
|
+
"sort": 2,
|
|
11
|
+
"children": [
|
|
12
|
+
{
|
|
13
|
+
"name": "统计数据",
|
|
14
|
+
"path": "/statistics-data",
|
|
15
|
+
"sort": 1
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "项目管理",
|
|
19
|
+
"path": "/project-manage",
|
|
20
|
+
"sort": 2
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "统计记录",
|
|
24
|
+
"path": "/statistics-record",
|
|
25
|
+
"sort": 3
|
|
26
|
+
}
|
|
27
|
+
]
|
|
11
28
|
},
|
|
12
29
|
{
|
|
13
30
|
"name": "人员管理",
|
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
|
+
};
|