befly 3.69.0 → 3.71.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.
@@ -1,5 +1,5 @@
1
1
  import adminTable from "#befly/tables/admin.json";
2
- import { isString } from "#befly/utils/is.js";
2
+ import { hashPassword } from "#befly/utils/crypto.js";
3
3
 
4
4
  export default {
5
5
  name: "添加管理员",
@@ -43,9 +43,7 @@ export default {
43
43
  return befly.tool.No("角色不存在");
44
44
  }
45
45
 
46
- const passwordSource = isString(ctx.body.password) ? ctx.body.password.toLowerCase() : "";
47
- const isSha256Hex = /^[a-f0-9]{64}$/.test(passwordSource);
48
- const hashedPassword = isSha256Hex ? befly.tool.sha256(passwordSource) : befly.tool.sha256(befly.tool.sha256(ctx.body.password));
46
+ const hashedPassword = await hashPassword(ctx.body.password);
49
47
 
50
48
  const adminId = await befly.mysql.insData({
51
49
  table: "beflyAdmin",
@@ -1,4 +1,5 @@
1
1
  import adminTable from "#befly/tables/admin.json";
2
+ import { hashPassword } from "#befly/utils/crypto.js";
2
3
 
3
4
  export default {
4
5
  name: "更新管理员",
@@ -74,7 +75,9 @@ export default {
74
75
  if (ctx.body.email !== undefined) updateData.email = ctx.body.email;
75
76
  if (ctx.body.lastLoginIp !== undefined) updateData.lastLoginIp = ctx.body.lastLoginIp;
76
77
  if (ctx.body.lastLoginTime !== undefined) updateData.lastLoginTime = ctx.body.lastLoginTime;
77
- if (ctx.body.password !== undefined) updateData.password = ctx.body.password;
78
+ if (ctx.body.password !== undefined) {
79
+ updateData.password = await hashPassword(ctx.body.password);
80
+ }
78
81
  if (ctx.body.phone !== undefined) updateData.phone = ctx.body.phone;
79
82
  if (ctx.body.roleType !== undefined) updateData.roleType = ctx.body.roleType;
80
83
  if (ctx.body.username !== undefined) updateData.username = ctx.body.username;
@@ -1,5 +1,6 @@
1
1
  import adminTable from "#befly/tables/admin.json";
2
2
  import loginLogTable from "#befly/tables/loginLog.json";
3
+ import { verifyPassword } from "#befly/utils/crypto.js";
3
4
  import { toSessionTtlSeconds } from "#befly/utils/util.js";
4
5
 
5
6
  export default {
@@ -79,7 +80,7 @@ export default {
79
80
  logData.username = admin.data.username;
80
81
  logData.nickname = admin.data.nickname;
81
82
 
82
- if (befly.tool.sha256(ctx.body.password) !== admin.data.password) {
83
+ if (!(await verifyPassword(ctx.body.password, admin.data.password))) {
83
84
  logData.failReason = "密码错误";
84
85
  await befly.mysql.insData({ table: "beflyLoginLog", data: logData });
85
86
  return befly.tool.No("账号或密码错误");
@@ -0,0 +1,47 @@
1
+ import { getTongJiDateYmdNumber } from "./_tongJi.js";
2
+
3
+ async function getTodayCount(befly, reportDate, productCode) {
4
+ if (!befly.redis) {
5
+ return 0;
6
+ }
7
+
8
+ return Number(await befly.redis.scard(`daily:day:${reportDate}:members:${productCode}`)) || 0;
9
+ }
10
+
11
+ async function loadProjects(befly) {
12
+ if (!befly.mysql) {
13
+ return [];
14
+ }
15
+
16
+ const result = await befly.mysql.getAll({
17
+ table: "beflyProject",
18
+ fields: ["code"],
19
+ where: { state: 1 }
20
+ });
21
+
22
+ return result.data?.lists || [];
23
+ }
24
+
25
+ export default {
26
+ name: "获取今日在线总数",
27
+ method: "POST",
28
+ body: "none",
29
+ auth: true,
30
+ fields: {},
31
+ required: [],
32
+ handler: async (befly, _ctx) => {
33
+ const reportDate = getTongJiDateYmdNumber(Date.now(), befly.config?.tz);
34
+ const projects = await loadProjects(befly);
35
+ let total = 0;
36
+
37
+ for (const item of projects) {
38
+ total += await getTodayCount(befly, reportDate, item.code);
39
+ }
40
+
41
+ return befly.tool.Yes("获取成功", {
42
+ today: {
43
+ count: total
44
+ }
45
+ });
46
+ }
47
+ };
package/lib/dbHelper.js CHANGED
@@ -79,10 +79,6 @@ function safeStringify(value) {
79
79
  }
80
80
 
81
81
  function getExecuteErrorMessage(error) {
82
- if (error instanceof Error && typeof error.message === "string" && error.message.trim().length > 0) {
83
- return error.message.trim();
84
- }
85
-
86
82
  if (!error || typeof error !== "object") {
87
83
  return String(error);
88
84
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.69.0",
3
+ "version": "3.71.0",
4
4
  "gitHead": "49c39d36695036e85fc64083cc43c1652fff96cb",
5
5
  "private": false,
6
6
  "description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
package/plugins/tool.js CHANGED
@@ -103,53 +103,13 @@ export function Raw(ctx, data, options = {}) {
103
103
  });
104
104
  }
105
105
 
106
- /**
107
- * 计算 md5(hex)
108
- * @param value 输入字符串
109
- * @returns md5 hex
110
- */
111
- export function md5(value) {
112
- if (!isString(value) || value.length < 1) {
113
- throw new Error("md5 输入必须是非空字符串", {
114
- cause: null,
115
- code: "validation",
116
- subsystem: "tool",
117
- operation: "md5"
118
- });
119
- }
120
- const hasher = new Bun.CryptoHasher("md5");
121
- hasher.update(value);
122
- return hasher.digest("hex");
123
- }
124
-
125
- /**
126
- * 计算 sha256(hex)
127
- * @param value 输入字符串
128
- * @returns sha256 hex
129
- */
130
- export function sha256(value) {
131
- if (!isString(value) || value.length < 1) {
132
- throw new Error("sha256 输入必须是非空字符串", {
133
- cause: null,
134
- code: "validation",
135
- subsystem: "tool",
136
- operation: "sha256"
137
- });
138
- }
139
- const hasher = new Bun.CryptoHasher("sha256");
140
- hasher.update(value);
141
- return hasher.digest("hex");
142
- }
143
-
144
106
  export default {
145
107
  order: 3,
146
108
  handler: () => {
147
109
  return {
148
110
  Yes: Yes,
149
111
  No: No,
150
- Raw: Raw,
151
- md5: md5,
152
- sha256: sha256
112
+ Raw: Raw
153
113
  };
154
114
  }
155
115
  };
package/sync/dev.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { hashPassword } from "../utils/crypto.js";
1
2
  import { isNumber } from "../utils/is.js";
2
3
 
3
4
  const MENU_TABLE_NAME = "beflyMenu";
@@ -72,7 +73,7 @@ export async function syncDev(ctx) {
72
73
  });
73
74
  }
74
75
 
75
- const password = ctx.tool.sha256(ctx.tool.sha256(ctx.config.devPassword));
76
+ const password = await hashPassword(ctx.config.devPassword);
76
77
 
77
78
  const devAdminData = {
78
79
  nickname: "开发者",
@@ -0,0 +1,62 @@
1
+ /**
2
+ * 通用加密/哈希工具
3
+ */
4
+
5
+ import { isString } from "./is.js";
6
+
7
+ /**
8
+ * 计算 md5(hex)
9
+ * @param value 输入字符串
10
+ * @returns md5 hex
11
+ */
12
+ export function md5(value) {
13
+ if (!isString(value) || value.length < 1) {
14
+ throw new Error("md5 输入必须是非空字符串", {
15
+ cause: null,
16
+ code: "validation",
17
+ subsystem: "crypto",
18
+ operation: "md5"
19
+ });
20
+ }
21
+ const hasher = new Bun.CryptoHasher("md5");
22
+ hasher.update(value);
23
+ return hasher.digest("hex");
24
+ }
25
+
26
+ /**
27
+ * 计算 sha256(hex)
28
+ * @param value 输入字符串
29
+ * @returns sha256 hex
30
+ */
31
+ export function sha256(value) {
32
+ if (!isString(value) || value.length < 1) {
33
+ throw new Error("sha256 输入必须是非空字符串", {
34
+ cause: null,
35
+ code: "validation",
36
+ subsystem: "crypto",
37
+ operation: "sha256"
38
+ });
39
+ }
40
+ const hasher = new Bun.CryptoHasher("sha256");
41
+ hasher.update(value);
42
+ return hasher.digest("hex");
43
+ }
44
+
45
+ /**
46
+ * 使用 bcrypt 对密码进行哈希
47
+ * @param password 原始密码
48
+ * @returns bcrypt hash 字符串
49
+ */
50
+ export async function hashPassword(password) {
51
+ return Bun.password.hash(password, { algorithm: "bcrypt" });
52
+ }
53
+
54
+ /**
55
+ * 校验密码与 bcrypt hash 是否匹配
56
+ * @param password 原始密码
57
+ * @param hashedPassword 数据库中存储的 bcrypt hash
58
+ * @returns 是否匹配
59
+ */
60
+ export async function verifyPassword(password, hashedPassword) {
61
+ return Bun.password.verify(password, hashedPassword);
62
+ }
@@ -1,297 +0,0 @@
1
- const WINDOWS_VERSION_MAP = {
2
- "10.0": "10",
3
- 6.3: "8.1",
4
- 6.2: "8",
5
- 6.1: "7"
6
- };
7
-
8
- function firstMatch(text, regexp) {
9
- const matched = regexp.exec(text);
10
-
11
- if (!matched) {
12
- return null;
13
- }
14
-
15
- for (let index = 1; index < matched.length; index++) {
16
- if (matched[index]) {
17
- return matched[index];
18
- }
19
- }
20
-
21
- return null;
22
- }
23
-
24
- function firstNonEmptyMatch(text, regexp) {
25
- const matched = regexp.exec(text);
26
-
27
- if (!matched) {
28
- return null;
29
- }
30
-
31
- for (let index = 1; index < matched.length; index++) {
32
- if (matched[index]) {
33
- return matched[index];
34
- }
35
- }
36
-
37
- return null;
38
- }
39
-
40
- function hasMatch(text, regexp) {
41
- return regexp.test(text) ? "" : null;
42
- }
43
-
44
- function normalizeVersion(version) {
45
- return String(version || "").replace(/_/g, ".");
46
- }
47
-
48
- function majorVersion(version) {
49
- const parts = String(version || "").split(".");
50
- return parts[0] || "";
51
- }
52
-
53
- function matchBrowser(text) {
54
- const rules = [
55
- { name: "wxwork", regexp: /wxwork\/([\d.]+)/ },
56
- { name: "WeChat", regexp: /MicroMessenger\/([\d.]+)/ },
57
- { name: "DingTalk", regexp: /DingTalk\/([\d.]+)/ },
58
- { name: "Alipay", regexp: /AlipayClient\/([\d.]+)/ },
59
- { name: "Taobao", regexp: /(?:TaoBao|AliApp\(TB|WindVane)/, match: hasMatch },
60
- { name: "Jingdong", regexp: /jdapp/i, match: hasMatch },
61
- { name: "Weibo", regexp: /Weibo|__weibo__/, match: hasMatch },
62
- { name: "Douyin", regexp: /(?:aweme|Trill|Douyin)/i, match: hasMatch },
63
- { name: "Lark", regexp: /(?:Lark|Feishu)\S*?\/([\d.]+)/ },
64
- { name: "QQ Browser", regexp: /(?:MQQBrowser|QQBrowser)\/([\d.]+)/ },
65
- { name: "UC Browser", regexp: /UCBrowser\/([\d.]+)/ },
66
- { name: "Quark", regexp: /Quark\/([\d.]+)/ },
67
- { name: "Baidu", regexp: /(?:baiduboxapp|baiduapp|BaiduBrowser|FlyFlow)\S*?\/([\d.]+)/i },
68
- { name: "Sogou", regexp: /(?:SogouMobileBrowser|SogouMSE|MetaSr)\S*?\/([\d.]+)/i },
69
- { name: "360", regexp: /(?:360SE|360EE|QHBrowser|QihooBrowser)\S*?(?:\s|v|\/)([\d.]+)/i },
70
- { name: "Huawei Browser", regexp: /HuaweiBrowser\/([\d.]+)/ },
71
- { name: "MiuiBrowser", regexp: /MiuiBrowser\/([\d.]+)/ },
72
- { name: "HeyTapBrowser", regexp: /HeyTapBrowser\/([\d.]+)/ },
73
- { name: "VivoBrowser", regexp: /VivoBrowser\/([\d.]+)/ },
74
- { name: "Samsung Browser", regexp: /SamsungBrowser\/([\d.]+)/ },
75
- { name: "Maxthon", regexp: /Maxthon\/([\d.]+)/ },
76
- { name: "Edge", regexp: /Edg(?:e|A|iOS)?\/([\d.]+)/ },
77
- { name: "Opera", regexp: /(?:OPR|Opera)\/([\d.]+)/ },
78
- { name: "Firefox", regexp: /(?:FxiOS|Firefox)\/([\d.]+)/ },
79
- { name: "Chrome", regexp: /(?:CriOS|Chrome)\/([\d.]+)/ },
80
- { name: "Safari", regexp: /Version\/([\d.]+).*Safari/ },
81
- { name: "IE", regexp: /(?:MSIE\s([\d.]+)|Trident\/.*rv:([\d.]+))/, match: firstNonEmptyMatch }
82
- ];
83
-
84
- for (const rule of rules) {
85
- const matcher = rule.match || firstMatch;
86
- const version = matcher(text, rule.regexp);
87
-
88
- if (version !== null) {
89
- const name = rule.name === "Safari" && /iPhone|iPad|iPod/i.test(text) ? "Mobile Safari" : rule.name;
90
-
91
- return { name: name, version: version };
92
- }
93
- }
94
-
95
- return { name: "", version: "" };
96
- }
97
-
98
- function matchEngine(text) {
99
- if (/AppleWebKit/i.test(text) && /iPhone|iPad|iPod/i.test(text)) {
100
- return { name: "WebKit", version: firstMatch(text, /AppleWebKit\/([\d.]+)/) };
101
- }
102
-
103
- const rules = [
104
- { name: "Blink", regexp: /Chrome\/([\d.]+)/ },
105
- { name: "Blink", regexp: /CriOS\/([\d.]+)/ },
106
- { name: "Blink", regexp: /Edg(?:e|A|iOS)?\/([\d.]+)/ },
107
- { name: "Blink", regexp: /OPR\/([\d.]+)/ },
108
- { name: "Gecko", regexp: /Firefox\/([\d.]+)/ },
109
- { name: "Gecko", regexp: /FxiOS\/([\d.]+)/ },
110
- { name: "WebKit", regexp: /AppleWebKit\/([\d.]+)/ },
111
- { name: "Trident", regexp: /Trident\/([\d.]+)/ },
112
- { name: "Trident", regexp: /MSIE\s([\d.]+)/ }
113
- ];
114
-
115
- for (const rule of rules) {
116
- const version = firstMatch(text, rule.regexp);
117
-
118
- if (version) {
119
- return { name: rule.name, version: version };
120
- }
121
- }
122
-
123
- return { name: "", version: "" };
124
- }
125
-
126
- function matchOs(text) {
127
- const androidVersion = firstMatch(text, /Android\s([\d.]+)/);
128
-
129
- if (androidVersion) {
130
- return { name: "Android", version: androidVersion };
131
- }
132
-
133
- const iosVersion = firstMatch(text, /(?:iPhone|iPad|iPod).*OS\s([\d_]+)/);
134
-
135
- if (iosVersion) {
136
- return { name: "iOS", version: normalizeVersion(iosVersion) };
137
- }
138
-
139
- const windowsVersion = firstMatch(text, /Windows NT\s([\d.]+)/);
140
-
141
- if (windowsVersion) {
142
- return { name: "Windows", version: WINDOWS_VERSION_MAP[windowsVersion] || windowsVersion };
143
- }
144
-
145
- const macVersion = firstMatch(text, /Mac OS X\s([\d_]+)/);
146
-
147
- if (macVersion) {
148
- return { name: "macOS", version: normalizeVersion(macVersion) };
149
- }
150
-
151
- if (/HarmonyOS/i.test(text)) {
152
- return { name: "HarmonyOS", version: "" };
153
- }
154
-
155
- if (/Linux/i.test(text)) {
156
- return { name: "Linux", version: "" };
157
- }
158
-
159
- if (/CrOS/i.test(text)) {
160
- return { name: "Chrome OS", version: "" };
161
- }
162
-
163
- return { name: "", version: "" };
164
- }
165
-
166
- function getDeviceType(text) {
167
- if (/iPad|Tablet|PlayBook|Silk/i.test(text) || (/Android/i.test(text) && !/Mobile/i.test(text))) {
168
- return "tablet";
169
- }
170
-
171
- if (/PlayStation|Xbox|Nintendo|CrKey|Roku|AppleTV|SmartTV|TV/i.test(text)) {
172
- return "console";
173
- }
174
-
175
- if (/bot|crawler|spider|googlebot|bingbot|baiduspider|slurp|yandex|duckduckgo/i.test(text)) {
176
- return "bot";
177
- }
178
-
179
- if (/Mobi|iPhone|iPod|Phone\s*\d|Android.*Mobile|Windows Phone/i.test(text)) {
180
- return "mobile";
181
- }
182
-
183
- if (/HarmonyOS/i.test(text) && !/Pad|Tablet/i.test(text)) {
184
- return "mobile";
185
- }
186
-
187
- if (/HarmonyOS/i.test(text)) {
188
- return "tablet";
189
- }
190
-
191
- return "desktop";
192
- }
193
-
194
- function matchAndroidModel(text) {
195
- const matched = /Android[\d.\s]*;\s*([^;)]+?)(?:\s+Build\/|;|\))/i.exec(text);
196
- return matched ? matched[1].trim() : "";
197
- }
198
-
199
- function matchVendor(text) {
200
- if (/(iPhone|iPad|iPod|Macintosh)\b/i.test(text)) {
201
- return "Apple";
202
- }
203
-
204
- if (/Samsung|SM-\w+/i.test(text)) {
205
- return "Samsung";
206
- }
207
-
208
- if (/Huawei|HUAWEI|Honor|HONOR/i.test(text)) {
209
- return "Huawei";
210
- }
211
-
212
- if (/Xiaomi|Redmi|Mi\s|MI\s|M200|M201|M202|M210|M211|M220|M221|M230|M231|M240/i.test(text)) {
213
- return "Xiaomi";
214
- }
215
-
216
- if (/OPPO|PB[A-Z]|PCHM\d+|PCAM\d+/i.test(text)) {
217
- return "OPPO";
218
- }
219
-
220
- if (/vivo|V\d{4}[A-Z]/i.test(text)) {
221
- return "vivo";
222
- }
223
-
224
- return "";
225
- }
226
-
227
- function matchDevice(text) {
228
- const type = getDeviceType(text);
229
-
230
- if (/iPad/i.test(text)) {
231
- return { vendor: "Apple", model: "iPad", type: type };
232
- }
233
-
234
- if (/iPod/i.test(text)) {
235
- return { vendor: "Apple", model: "iPod", type: type };
236
- }
237
-
238
- if (/iPhone/i.test(text)) {
239
- return { vendor: "Apple", model: "iPhone", type: type };
240
- }
241
-
242
- return { vendor: matchVendor(text), model: matchAndroidModel(text), type: type };
243
- }
244
-
245
- function matchCpuArchitecture(text) {
246
- if (/x86_64|Win64|x64|amd64/i.test(text)) {
247
- return "amd64";
248
- }
249
-
250
- if (/arm64|aarch64/i.test(text)) {
251
- return "arm64";
252
- }
253
-
254
- if (/arm/i.test(text)) {
255
- return "arm";
256
- }
257
-
258
- return "";
259
- }
260
-
261
- export function parseUserAgent(userAgent) {
262
- const text = String(userAgent || "");
263
- const browser = matchBrowser(text);
264
- const engine = matchEngine(text);
265
- const os = matchOs(text);
266
- const device = matchDevice(text);
267
- const cpu = { architecture: matchCpuArchitecture(text) };
268
-
269
- return {
270
- userAgent: text,
271
- browserName: browser.name,
272
- browserVersion: browser.version,
273
- osName: os.name,
274
- osVersion: os.version,
275
- deviceType: device.type,
276
- deviceVendor: device.vendor,
277
- deviceModel: device.model,
278
- engineName: engine.name,
279
- engineVersion: engine.version,
280
- cpuArchitecture: cpu.architecture,
281
- browser: { name: browser.name, version: browser.version, major: majorVersion(browser.version) },
282
- engine: engine,
283
- os: os,
284
- device: device,
285
- cpu: cpu
286
- };
287
- }
288
-
289
- export function getUserAgentFields(userAgent) {
290
- const text = typeof userAgent === "string" ? userAgent.trim() : "";
291
-
292
- if (text.length === 0) {
293
- return null;
294
- }
295
-
296
- return parseUserAgent(text);
297
- }