befly 3.19.0 → 3.19.6
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 +0 -2
- package/apis/tongJi/errorList.js +105 -0
- package/apis/tongJi/errorReport.js +91 -0
- package/apis/tongJi/errorStats.js +153 -0
- package/apis/tongJi/infoReport.js +104 -0
- package/apis/tongJi/infoStats.js +147 -0
- package/apis/tongJi/onlineReport.js +110 -0
- package/apis/tongJi/onlineStats.js +147 -0
- package/checks/config.js +1 -0
- package/configs/beflyConfig.json +1 -0
- package/configs/beflyMenus.json +5 -0
- package/index.js +1 -1
- package/lib/logger.js +3 -3
- package/lib/redisHelper.js +38 -0
- package/lib/sqlBuilder/compiler.js +0 -9
- package/lib/sqlBuilder/index.js +1 -19
- package/lib/sqlBuilder/parser.js +0 -21
- package/package.json +4 -4
- package/paths.js +14 -1
- package/router/static.js +3 -3
- package/sql/befly.sql +77 -10
- package/utils/datetime.js +76 -0
- package/utils/formatYmdHms.js +0 -23
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { addDays, getDateYmdNumber } from "../../utils/datetime.js";
|
|
2
|
+
import { isValidPositiveInt } from "../../utils/is.js";
|
|
3
|
+
|
|
4
|
+
const ONLINE_STATS_ONLINE_TTL_SECONDS = 10 * 60;
|
|
5
|
+
const ONLINE_STATS_REDIS_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
6
|
+
const ONLINE_STATS_TEMP_TTL_SECONDS = 45 * 24 * 60 * 60;
|
|
7
|
+
|
|
8
|
+
function getOnlineStatsMember(ctx) {
|
|
9
|
+
if (isValidPositiveInt(ctx.userId)) {
|
|
10
|
+
return `user:${ctx.userId}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
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
|
+
}
|
|
37
|
+
|
|
38
|
+
async function touchRedisKeys(befly, keys, ttl) {
|
|
39
|
+
for (const key of keys) {
|
|
40
|
+
await befly.redis.expire(key, ttl);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function getOnlineStatsOnlineCount(befly) {
|
|
45
|
+
const members = await befly.redis.smembers("online:visitors");
|
|
46
|
+
if (members.length === 0) {
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const existsList = await befly.redis.existsBatch(members.map((member) => `online:visitor:${member}`));
|
|
51
|
+
const expiredMembers = [];
|
|
52
|
+
let onlineCount = 0;
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < members.length; i++) {
|
|
55
|
+
if (existsList[i]) {
|
|
56
|
+
onlineCount += 1;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
expiredMembers.push(members[i]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (expiredMembers.length > 0) {
|
|
64
|
+
await befly.redis.srem("online:visitors", expiredMembers);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return onlineCount;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function updateOnlineStatsPeriod(befly, periodType, periodValue, member) {
|
|
71
|
+
const keys = getPeriodKeys(periodType, periodValue);
|
|
72
|
+
|
|
73
|
+
await befly.redis.incr(keys.pv);
|
|
74
|
+
await befly.redis.sadd(keys.members, [member]);
|
|
75
|
+
await befly.redis.setString(keys.reportTime, String(Date.now()), ONLINE_STATS_TEMP_TTL_SECONDS);
|
|
76
|
+
await touchRedisKeys(befly, Object.values(keys), ONLINE_STATS_TEMP_TTL_SECONDS);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default {
|
|
80
|
+
name: "上报在线统计",
|
|
81
|
+
method: "POST",
|
|
82
|
+
body: "none",
|
|
83
|
+
auth: false,
|
|
84
|
+
fields: {},
|
|
85
|
+
required: [],
|
|
86
|
+
handler: async (befly, ctx) => {
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
const reportDate = getDateYmdNumber(now);
|
|
89
|
+
const weekStartDate = getOnlineStatsWeekStartDate(now);
|
|
90
|
+
const monthStartDate = getOnlineStatsMonthStartDate(now);
|
|
91
|
+
const member = getOnlineStatsMember(ctx);
|
|
92
|
+
|
|
93
|
+
await befly.redis.setString(`online:visitor:${member}`, "1", ONLINE_STATS_ONLINE_TTL_SECONDS);
|
|
94
|
+
await befly.redis.sadd("online:visitors", [member]);
|
|
95
|
+
await befly.redis.expire("online:visitors", ONLINE_STATS_REDIS_TTL_SECONDS);
|
|
96
|
+
|
|
97
|
+
const onlineCount = await getOnlineStatsOnlineCount(befly);
|
|
98
|
+
|
|
99
|
+
await updateOnlineStatsPeriod(befly, "day", reportDate, member);
|
|
100
|
+
await updateOnlineStatsPeriod(befly, "week", weekStartDate, member);
|
|
101
|
+
await updateOnlineStatsPeriod(befly, "month", monthStartDate, member);
|
|
102
|
+
|
|
103
|
+
return befly.tool.Yes("上报成功", {
|
|
104
|
+
reportTime: now,
|
|
105
|
+
reportDate: reportDate,
|
|
106
|
+
onlineCount: onlineCount,
|
|
107
|
+
stored: true
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { addDays, getDateYmdNumber } from "../../utils/datetime.js";
|
|
2
|
+
|
|
3
|
+
const ONLINE_STATS_DAY_LIMIT = 30;
|
|
4
|
+
|
|
5
|
+
function toOnlineStatsNumber(value) {
|
|
6
|
+
const num = Number(value);
|
|
7
|
+
|
|
8
|
+
if (!Number.isFinite(num)) {
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return num;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getOnlineStatsWeekStartDate(timestamp = Date.now()) {
|
|
16
|
+
const date = Reflect.construct(Date, [timestamp]);
|
|
17
|
+
const day = date.getDay();
|
|
18
|
+
const offset = day === 0 ? -6 : 1 - day;
|
|
19
|
+
|
|
20
|
+
return getDateYmdNumber(addDays(timestamp, offset));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getOnlineStatsMonthStartDate(timestamp = Date.now()) {
|
|
24
|
+
const date = Reflect.construct(Date, [timestamp]);
|
|
25
|
+
|
|
26
|
+
return getDateYmdNumber(Reflect.construct(Date, [date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0]).getTime());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getOnlineStatsRecentDateList(now = Date.now(), limit = ONLINE_STATS_DAY_LIMIT) {
|
|
30
|
+
const list = [];
|
|
31
|
+
|
|
32
|
+
for (let i = limit - 1; i >= 0; i--) {
|
|
33
|
+
list.push(getDateYmdNumber(addDays(now, -i)));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return list;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getPeriodKeys(periodType, periodValue) {
|
|
40
|
+
return {
|
|
41
|
+
pv: `online:${periodType}:${periodValue}:pv`,
|
|
42
|
+
members: `online:${periodType}:${periodValue}:members`,
|
|
43
|
+
reportTime: `online:${periodType}:${periodValue}:reportTime`
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function getRedisNumber(befly, key) {
|
|
48
|
+
return toOnlineStatsNumber(await befly.redis.getString(key));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function getOnlineStatsPeriodData(befly, periodType, periodValue) {
|
|
52
|
+
const keys = getPeriodKeys(periodType, periodValue);
|
|
53
|
+
return {
|
|
54
|
+
reportTime: await getRedisNumber(befly, keys.reportTime),
|
|
55
|
+
pv: await getRedisNumber(befly, keys.pv),
|
|
56
|
+
uv: toOnlineStatsNumber(await befly.redis.scard(keys.members))
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function getOnlineStatsOnlineCount(befly) {
|
|
61
|
+
const members = await befly.redis.smembers("online:visitors");
|
|
62
|
+
if (members.length === 0) {
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const existsList = await befly.redis.existsBatch(members.map((member) => `online:visitor:${member}`));
|
|
67
|
+
const expiredMembers = [];
|
|
68
|
+
let onlineCount = 0;
|
|
69
|
+
|
|
70
|
+
for (let i = 0; i < members.length; i++) {
|
|
71
|
+
if (existsList[i]) {
|
|
72
|
+
onlineCount += 1;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
expiredMembers.push(members[i]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (expiredMembers.length > 0) {
|
|
80
|
+
await befly.redis.srem("online:visitors", expiredMembers);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return onlineCount;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default {
|
|
87
|
+
name: "获取在线统计",
|
|
88
|
+
method: "POST",
|
|
89
|
+
body: "none",
|
|
90
|
+
auth: true,
|
|
91
|
+
fields: {},
|
|
92
|
+
required: [],
|
|
93
|
+
handler: async (befly) => {
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
const reportDate = getDateYmdNumber(now);
|
|
96
|
+
const weekStartDate = getOnlineStatsWeekStartDate(now);
|
|
97
|
+
const monthStartDate = getOnlineStatsMonthStartDate(now);
|
|
98
|
+
const recentDateList = getOnlineStatsRecentDateList(now, ONLINE_STATS_DAY_LIMIT);
|
|
99
|
+
const days = [];
|
|
100
|
+
|
|
101
|
+
for (const item of recentDateList) {
|
|
102
|
+
const dayData = await getOnlineStatsPeriodData(befly, "day", item);
|
|
103
|
+
|
|
104
|
+
if (dayData.reportTime <= 0) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
days.push({
|
|
109
|
+
reportDate: item,
|
|
110
|
+
reportTime: dayData.reportTime,
|
|
111
|
+
pv: dayData.pv,
|
|
112
|
+
uv: dayData.uv
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const currentDay = await getOnlineStatsPeriodData(befly, "day", reportDate);
|
|
117
|
+
const weekCurrent = await getOnlineStatsPeriodData(befly, "week", weekStartDate);
|
|
118
|
+
const monthCurrent = await getOnlineStatsPeriodData(befly, "month", monthStartDate);
|
|
119
|
+
const onlineCount = await getOnlineStatsOnlineCount(befly);
|
|
120
|
+
|
|
121
|
+
return befly.tool.Yes("获取成功", {
|
|
122
|
+
queryTime: now,
|
|
123
|
+
onlineCount: onlineCount,
|
|
124
|
+
today: {
|
|
125
|
+
reportDate: reportDate,
|
|
126
|
+
reportTime: currentDay.reportTime,
|
|
127
|
+
pv: currentDay.pv,
|
|
128
|
+
uv: currentDay.uv
|
|
129
|
+
},
|
|
130
|
+
week: {
|
|
131
|
+
startDate: weekStartDate,
|
|
132
|
+
endDate: reportDate,
|
|
133
|
+
reportTime: weekCurrent.reportTime,
|
|
134
|
+
pv: weekCurrent.pv,
|
|
135
|
+
uv: weekCurrent.uv
|
|
136
|
+
},
|
|
137
|
+
month: {
|
|
138
|
+
startDate: monthStartDate,
|
|
139
|
+
endDate: reportDate,
|
|
140
|
+
reportTime: monthCurrent.reportTime,
|
|
141
|
+
pv: monthCurrent.pv,
|
|
142
|
+
uv: monthCurrent.uv
|
|
143
|
+
},
|
|
144
|
+
days: days
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
};
|
package/checks/config.js
CHANGED
package/configs/beflyConfig.json
CHANGED
package/configs/beflyMenus.json
CHANGED
package/index.js
CHANGED
|
@@ -202,7 +202,7 @@ export class Befly {
|
|
|
202
202
|
|
|
203
203
|
// 启动 HTTP服务器
|
|
204
204
|
const apiFetch = apiHandler(this.apis, this.hooks, this.context);
|
|
205
|
-
const staticFetch = staticHandler(this.context.config.cors);
|
|
205
|
+
const staticFetch = staticHandler(this.context.config.cors, this.context.config.publicDir);
|
|
206
206
|
|
|
207
207
|
const server = Bun.serve({
|
|
208
208
|
port: this.context.config.appPort || 3000,
|
package/lib/logger.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createWriteStream, existsSync, mkdirSync } from "node:fs";
|
|
|
6
6
|
import { stat } from "node:fs/promises";
|
|
7
7
|
import { join as nodePathJoin, resolve as nodePathResolve } from "node:path";
|
|
8
8
|
|
|
9
|
-
import { formatYmdHms } from "../utils/
|
|
9
|
+
import { formatYmdHms } from "../utils/datetime.js";
|
|
10
10
|
import { buildSensitiveKeyMatcher, sanitizeLogObject } from "../utils/loggerUtils.js";
|
|
11
11
|
import { isFiniteNumber, isNumber, isPlainObject, isString } from "../utils/is.js";
|
|
12
12
|
import { normalizePositiveInt } from "../utils/util.js";
|
|
@@ -236,7 +236,7 @@ class LogFileSink {
|
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
async ensureStreamReady(nextChunkBytes) {
|
|
239
|
-
const date = formatYmdHms(
|
|
239
|
+
const date = formatYmdHms(Date.now(), "date");
|
|
240
240
|
|
|
241
241
|
// 日期变化:切新文件
|
|
242
242
|
if (this.stream && this.streamDate && date !== this.streamDate) {
|
|
@@ -403,7 +403,7 @@ export function setMockLogger(mock) {
|
|
|
403
403
|
function buildJsonLine(level, timeMs, record) {
|
|
404
404
|
const base = {
|
|
405
405
|
level: level,
|
|
406
|
-
time: formatYmdHms(
|
|
406
|
+
time: formatYmdHms(timeMs),
|
|
407
407
|
pid: process.pid
|
|
408
408
|
};
|
|
409
409
|
|
package/lib/redisHelper.js
CHANGED
|
@@ -169,6 +169,22 @@ export class RedisHelper {
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
/**
|
|
173
|
+
* 原子按指定值自增
|
|
174
|
+
* @param key - 键名
|
|
175
|
+
* @param value - 自增值
|
|
176
|
+
* @returns 自增后的值
|
|
177
|
+
*/
|
|
178
|
+
async incrBy(key, value) {
|
|
179
|
+
try {
|
|
180
|
+
const pkey = `${this.prefix}${key}`;
|
|
181
|
+
return await this.client.incrby(pkey, value);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
Logger.error("Redis incrBy 错误", error);
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
172
188
|
/**
|
|
173
189
|
* 原子自增并在首次自增时设置过期时间(常用于限流/计数)
|
|
174
190
|
* @param key - 键名
|
|
@@ -305,6 +321,28 @@ export class RedisHelper {
|
|
|
305
321
|
}
|
|
306
322
|
}
|
|
307
323
|
|
|
324
|
+
/**
|
|
325
|
+
* 从 Set 中删除一个或多个成员
|
|
326
|
+
* @param key - 键名
|
|
327
|
+
* @param members - 成员数组
|
|
328
|
+
* @returns 删除的成员数量
|
|
329
|
+
*/
|
|
330
|
+
async srem(key, members) {
|
|
331
|
+
try {
|
|
332
|
+
if (members.length === 0) return 0;
|
|
333
|
+
|
|
334
|
+
const pkey = `${this.prefix}${key}`;
|
|
335
|
+
const args = [pkey];
|
|
336
|
+
for (const member of members) {
|
|
337
|
+
args.push(member);
|
|
338
|
+
}
|
|
339
|
+
return await this.client.srem.apply(this.client, args);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
Logger.error("Redis srem 错误", error);
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
308
346
|
/**
|
|
309
347
|
* 批量向多个 Set 添加成员(利用 Bun Redis 自动管道优化)
|
|
310
348
|
* @param items - [{ key, members }] 数组
|
|
@@ -48,15 +48,6 @@ export function compileSelect(model, quoteIdent) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
if (model.groupBy.length > 0) {
|
|
52
|
-
const groupSql = model.groupBy.map((field) => escapeField(field, quoteIdent)).join(", ");
|
|
53
|
-
sql += ` GROUP BY ${groupSql}`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (model.having.length > 0) {
|
|
57
|
-
sql += ` HAVING ${model.having.join(" AND ")}`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
51
|
if (model.orderBy.length > 0) {
|
|
61
52
|
const orderSql = model.orderBy
|
|
62
53
|
.map((item) => {
|
package/lib/sqlBuilder/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { resolveQuoteIdent, normalizeLimitValue, normalizeOffsetValue } from "./util.js";
|
|
7
|
-
import {
|
|
7
|
+
import { appendJoinItem, appendOrderByItems, appendSelectItems, appendSelectRaw, appendWhereInput, appendWhereRaw, createWhereRoot, setFromValue } from "./parser.js";
|
|
8
8
|
import { compileCount, compileDelete, compileInsert, compileSelect, compileUpdate, compileWhere } from "./compiler.js";
|
|
9
9
|
import { toDeleteInSql, toUpdateCaseByIdSql } from "./batch.js";
|
|
10
10
|
|
|
@@ -15,8 +15,6 @@ function createModel() {
|
|
|
15
15
|
where: createWhereRoot(),
|
|
16
16
|
joins: [],
|
|
17
17
|
orderBy: [],
|
|
18
|
-
groupBy: [],
|
|
19
|
-
having: [],
|
|
20
18
|
limit: null,
|
|
21
19
|
offset: null
|
|
22
20
|
};
|
|
@@ -131,22 +129,6 @@ export class SqlBuilder {
|
|
|
131
129
|
return this;
|
|
132
130
|
}
|
|
133
131
|
|
|
134
|
-
/**
|
|
135
|
-
* GROUP BY
|
|
136
|
-
*/
|
|
137
|
-
groupBy(field) {
|
|
138
|
-
appendGroupByItems(this._model.groupBy, field);
|
|
139
|
-
return this;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* HAVING
|
|
144
|
-
*/
|
|
145
|
-
having(condition) {
|
|
146
|
-
appendHavingItems(this._model.having, condition);
|
|
147
|
-
return this;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
132
|
/**
|
|
151
133
|
* LIMIT
|
|
152
134
|
*/
|
package/lib/sqlBuilder/parser.js
CHANGED
|
@@ -105,27 +105,6 @@ export function appendOrderByItems(list, fields) {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
export function appendGroupByItems(list, field) {
|
|
109
|
-
if (Array.isArray(field)) {
|
|
110
|
-
for (const item of field) {
|
|
111
|
-
if (isString(item)) {
|
|
112
|
-
list.push(item);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (isString(field)) {
|
|
119
|
-
list.push(field);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export function appendHavingItems(list, condition) {
|
|
124
|
-
if (isString(condition)) {
|
|
125
|
-
list.push(condition);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
108
|
export function appendWhereInput(root, conditionOrField, value) {
|
|
130
109
|
if (conditionOrField && typeof conditionOrField === "object" && !Array.isArray(conditionOrField)) {
|
|
131
110
|
const node = parseWhereObject(conditionOrField);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.19.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.19.6",
|
|
4
|
+
"gitHead": "84b6e4e0adde7f566cfc890887b024ccf10b0516",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
|
|
7
7
|
"keywords": [
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"test": "bun test"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"fast-xml-parser": "^5.
|
|
56
|
-
"nodemailer": "^8.0.
|
|
55
|
+
"fast-xml-parser": "^5.5.5",
|
|
56
|
+
"nodemailer": "^8.0.2",
|
|
57
57
|
"pathe": "^2.0.3",
|
|
58
58
|
"ua-parser-js": "^2.0.9",
|
|
59
59
|
"zod": "^4.0.0"
|
package/paths.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
|
|
15
|
-
import { dirname, join, normalize } from "pathe";
|
|
15
|
+
import { dirname, isAbsolute, join, normalize, resolve } from "pathe";
|
|
16
16
|
|
|
17
17
|
// 当前文件的路径信息
|
|
18
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -110,3 +110,16 @@ export const appApiDir = join(appDir, "apis");
|
|
|
110
110
|
* @usage 存放用户业务表定义(JSON 格式)
|
|
111
111
|
*/
|
|
112
112
|
export const appTableDir = join(appDir, "tables");
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 项目公共静态目录
|
|
116
|
+
* @description 默认 {appDir}/public,可通过 config.publicDir 覆盖
|
|
117
|
+
* @usage 用于静态文件访问与本地上传保存目录解析
|
|
118
|
+
*/
|
|
119
|
+
export function getAppPublicDir(publicDir = "./public") {
|
|
120
|
+
if (isAbsolute(publicDir)) {
|
|
121
|
+
return resolve(publicDir);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return resolve(appDir, publicDir);
|
|
125
|
+
}
|
package/router/static.js
CHANGED
|
@@ -8,19 +8,19 @@ import { join } from "pathe";
|
|
|
8
8
|
|
|
9
9
|
import { Logger } from "../lib/logger.js";
|
|
10
10
|
// 相对导入
|
|
11
|
-
import {
|
|
11
|
+
import { getAppPublicDir } from "../paths.js";
|
|
12
12
|
import { setCorsOptions } from "../utils/cors.js";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* 静态文件处理器工厂
|
|
16
16
|
*/
|
|
17
|
-
export function staticHandler(corsConfig = undefined) {
|
|
17
|
+
export function staticHandler(corsConfig = undefined, publicDir = "./public") {
|
|
18
18
|
return async (req) => {
|
|
19
19
|
// 设置 CORS 响应头
|
|
20
20
|
const corsHeaders = setCorsOptions(req, corsConfig);
|
|
21
21
|
|
|
22
22
|
const url = new URL(req.url);
|
|
23
|
-
const filePath = join(
|
|
23
|
+
const filePath = join(getAppPublicDir(publicDir), url.pathname);
|
|
24
24
|
|
|
25
25
|
try {
|
|
26
26
|
// OPTIONS预检请求
|
package/sql/befly.sql
CHANGED
|
@@ -15,7 +15,7 @@ CREATE TABLE IF NOT EXISTS `befly_admin` (
|
|
|
15
15
|
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
16
16
|
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
17
17
|
PRIMARY KEY (`id`)
|
|
18
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
18
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
19
19
|
|
|
20
20
|
CREATE TABLE IF NOT EXISTS `befly_api` (
|
|
21
21
|
`id` BIGINT NOT NULL,
|
|
@@ -29,7 +29,7 @@ CREATE TABLE IF NOT EXISTS `befly_api` (
|
|
|
29
29
|
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
30
30
|
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
31
31
|
PRIMARY KEY (`id`)
|
|
32
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
32
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
33
33
|
|
|
34
34
|
CREATE TABLE IF NOT EXISTS `befly_dict` (
|
|
35
35
|
`id` BIGINT NOT NULL,
|
|
@@ -43,7 +43,7 @@ CREATE TABLE IF NOT EXISTS `befly_dict` (
|
|
|
43
43
|
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
44
44
|
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
45
45
|
PRIMARY KEY (`id`)
|
|
46
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
46
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
47
47
|
|
|
48
48
|
CREATE TABLE IF NOT EXISTS `befly_dict_type` (
|
|
49
49
|
`id` BIGINT NOT NULL,
|
|
@@ -56,7 +56,7 @@ CREATE TABLE IF NOT EXISTS `befly_dict_type` (
|
|
|
56
56
|
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
57
57
|
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
58
58
|
PRIMARY KEY (`id`)
|
|
59
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
59
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
60
60
|
|
|
61
61
|
CREATE TABLE IF NOT EXISTS `befly_email_log` (
|
|
62
62
|
`id` BIGINT NOT NULL,
|
|
@@ -77,7 +77,7 @@ CREATE TABLE IF NOT EXISTS `befly_email_log` (
|
|
|
77
77
|
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
78
78
|
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
79
79
|
PRIMARY KEY (`id`)
|
|
80
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
80
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
81
81
|
|
|
82
82
|
CREATE TABLE IF NOT EXISTS `befly_login_log` (
|
|
83
83
|
`id` BIGINT NOT NULL,
|
|
@@ -103,7 +103,7 @@ CREATE TABLE IF NOT EXISTS `befly_login_log` (
|
|
|
103
103
|
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
104
104
|
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
105
105
|
PRIMARY KEY (`id`)
|
|
106
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
106
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
107
107
|
|
|
108
108
|
CREATE TABLE IF NOT EXISTS `befly_menu` (
|
|
109
109
|
`id` BIGINT NOT NULL,
|
|
@@ -116,7 +116,7 @@ CREATE TABLE IF NOT EXISTS `befly_menu` (
|
|
|
116
116
|
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
117
117
|
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
118
118
|
PRIMARY KEY (`id`)
|
|
119
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
119
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
120
120
|
|
|
121
121
|
CREATE TABLE IF NOT EXISTS `befly_operate_log` (
|
|
122
122
|
`id` BIGINT NOT NULL,
|
|
@@ -139,7 +139,7 @@ CREATE TABLE IF NOT EXISTS `befly_operate_log` (
|
|
|
139
139
|
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
140
140
|
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
141
141
|
PRIMARY KEY (`id`)
|
|
142
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
142
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
143
143
|
|
|
144
144
|
CREATE TABLE IF NOT EXISTS `befly_role` (
|
|
145
145
|
`id` BIGINT NOT NULL,
|
|
@@ -154,7 +154,7 @@ CREATE TABLE IF NOT EXISTS `befly_role` (
|
|
|
154
154
|
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
155
155
|
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
156
156
|
PRIMARY KEY (`id`)
|
|
157
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
157
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
158
158
|
|
|
159
159
|
CREATE TABLE IF NOT EXISTS `befly_sys_config` (
|
|
160
160
|
`id` BIGINT NOT NULL,
|
|
@@ -171,4 +171,71 @@ CREATE TABLE IF NOT EXISTS `befly_sys_config` (
|
|
|
171
171
|
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
172
172
|
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
173
173
|
PRIMARY KEY (`id`)
|
|
174
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
174
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
175
|
+
|
|
176
|
+
CREATE TABLE IF NOT EXISTS `befly_error_report` (
|
|
177
|
+
`id` BIGINT NOT NULL,
|
|
178
|
+
`report_time` BIGINT NOT NULL DEFAULT 0,
|
|
179
|
+
`first_report_time` BIGINT NOT NULL DEFAULT 0,
|
|
180
|
+
`bucket_time` BIGINT NOT NULL DEFAULT 0,
|
|
181
|
+
`bucket_date` INT NOT NULL DEFAULT 0,
|
|
182
|
+
`hit_count` BIGINT NOT NULL DEFAULT 1,
|
|
183
|
+
`source` VARCHAR(50) NOT NULL DEFAULT '',
|
|
184
|
+
`product_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
185
|
+
`product_code` VARCHAR(100) NOT NULL DEFAULT '',
|
|
186
|
+
`product_version` VARCHAR(100) NOT NULL DEFAULT '',
|
|
187
|
+
`page_path` VARCHAR(200) NOT NULL DEFAULT '',
|
|
188
|
+
`page_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
189
|
+
`error_type` VARCHAR(50) NOT NULL DEFAULT '',
|
|
190
|
+
`message` VARCHAR(500) NOT NULL DEFAULT '',
|
|
191
|
+
`detail` TEXT NULL,
|
|
192
|
+
`user_agent` VARCHAR(500) NOT NULL DEFAULT '',
|
|
193
|
+
`browser_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
194
|
+
`browser_version` VARCHAR(100) NOT NULL DEFAULT '',
|
|
195
|
+
`os_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
196
|
+
`os_version` VARCHAR(100) NOT NULL DEFAULT '',
|
|
197
|
+
`device_type` VARCHAR(50) NOT NULL DEFAULT '',
|
|
198
|
+
`device_vendor` VARCHAR(100) NOT NULL DEFAULT '',
|
|
199
|
+
`device_model` VARCHAR(100) NOT NULL DEFAULT '',
|
|
200
|
+
`engine_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
201
|
+
`cpu_architecture` VARCHAR(100) NOT NULL DEFAULT '',
|
|
202
|
+
`state` TINYINT NOT NULL DEFAULT 1,
|
|
203
|
+
`created_at` BIGINT NOT NULL DEFAULT 0,
|
|
204
|
+
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
205
|
+
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
206
|
+
PRIMARY KEY (`id`),
|
|
207
|
+
KEY `idx_befly_error_report_time` (`report_time`),
|
|
208
|
+
KEY `idx_befly_error_report_bucket_date` (`bucket_date`)
|
|
209
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
210
|
+
|
|
211
|
+
CREATE TABLE IF NOT EXISTS `befly_info_report` (
|
|
212
|
+
`id` BIGINT NOT NULL,
|
|
213
|
+
`report_time` BIGINT NOT NULL DEFAULT 0,
|
|
214
|
+
`report_date` INT NOT NULL DEFAULT 0,
|
|
215
|
+
`member_key` VARCHAR(100) NOT NULL DEFAULT '',
|
|
216
|
+
`source` VARCHAR(50) NOT NULL DEFAULT '',
|
|
217
|
+
`product_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
218
|
+
`product_code` VARCHAR(100) NOT NULL DEFAULT '',
|
|
219
|
+
`product_version` VARCHAR(100) NOT NULL DEFAULT '',
|
|
220
|
+
`page_path` VARCHAR(200) NOT NULL DEFAULT '',
|
|
221
|
+
`page_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
222
|
+
`detail` TEXT NULL,
|
|
223
|
+
`user_agent` VARCHAR(500) NOT NULL DEFAULT '',
|
|
224
|
+
`browser_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
225
|
+
`browser_version` VARCHAR(100) NOT NULL DEFAULT '',
|
|
226
|
+
`os_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
227
|
+
`os_version` VARCHAR(100) NOT NULL DEFAULT '',
|
|
228
|
+
`device_type` VARCHAR(50) NOT NULL DEFAULT '',
|
|
229
|
+
`device_vendor` VARCHAR(100) NOT NULL DEFAULT '',
|
|
230
|
+
`device_model` VARCHAR(100) NOT NULL DEFAULT '',
|
|
231
|
+
`engine_name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
232
|
+
`cpu_architecture` VARCHAR(100) NOT NULL DEFAULT '',
|
|
233
|
+
`state` TINYINT NOT NULL DEFAULT 1,
|
|
234
|
+
`created_at` BIGINT NOT NULL DEFAULT 0,
|
|
235
|
+
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
236
|
+
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
237
|
+
PRIMARY KEY (`id`),
|
|
238
|
+
UNIQUE KEY `uk_befly_info_report_date_member` (`report_date`, `member_key`),
|
|
239
|
+
KEY `idx_befly_info_report_time` (`report_time`),
|
|
240
|
+
KEY `idx_befly_info_report_date` (`report_date`)
|
|
241
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|