befly 3.17.9 → 3.17.11
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/lib/dbHelper/execute.js +12 -4
- package/lib/redisHelper.js +15 -200
- package/package.json +2 -2
package/lib/dbHelper/execute.js
CHANGED
|
@@ -19,10 +19,18 @@ export const executeMethods = {
|
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
21
|
let queryResult;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
// Bun 事件循环 bug workaround:
|
|
23
|
+
// 当事件循环中只有一个 I/O 源(SQL socket)时,Bun 会走单源快速路径,
|
|
24
|
+
// 在 I/O 回调内直接唤醒 JS Promise,跳过正常调度,导致 SQL 连接状态
|
|
25
|
+
// 未完全重置,下一条查询的 Promise resolver 永久丢失(远程 TCP 连接必现)。
|
|
26
|
+
// 预注册一个 timer 使事件循环中同时存在两种事件源,强制 Bun 走正常多源调度路径。
|
|
27
|
+
// timer 时长无关紧要,查询完成后会被 clearTimeout 清理。
|
|
28
|
+
const _timer = setTimeout(() => {}, 2147483647);
|
|
29
|
+
const queryPromise = safeParams.length > 0 ? this.sql.unsafe(sql, safeParams) : this.sql.unsafe(sql);
|
|
30
|
+
try {
|
|
31
|
+
queryResult = await queryPromise;
|
|
32
|
+
} finally {
|
|
33
|
+
clearTimeout(_timer);
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
const duration = Date.now() - startTime;
|
package/lib/redisHelper.js
CHANGED
|
@@ -12,8 +12,6 @@ import { isFiniteNumber, isNonEmptyString, isString } from "../utils/is.js";
|
|
|
12
12
|
* 约定:除构造函数外,方法默认不抛异常;失败时返回 null/false/0/[] 并记录日志。
|
|
13
13
|
*/
|
|
14
14
|
export class RedisHelper {
|
|
15
|
-
slowThresholdMs = 500;
|
|
16
|
-
|
|
17
15
|
/**
|
|
18
16
|
* 构造函数
|
|
19
17
|
* @param prefix - Key 前缀
|
|
@@ -30,18 +28,6 @@ export class RedisHelper {
|
|
|
30
28
|
this.prefix = prefix ? `${prefix}:` : "";
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
logSlow(cmd, key, duration, extra = {}) {
|
|
34
|
-
if (duration <= this.slowThresholdMs) return;
|
|
35
|
-
Logger.warn("🐌 Redis 慢操作", {
|
|
36
|
-
subsystem: "redis",
|
|
37
|
-
event: "slow",
|
|
38
|
-
duration: duration,
|
|
39
|
-
cmd: cmd,
|
|
40
|
-
key: key,
|
|
41
|
-
extra: extra
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
31
|
/**
|
|
46
32
|
* 设置对象到 Redis
|
|
47
33
|
* @param key - 键名
|
|
@@ -54,19 +40,11 @@ export class RedisHelper {
|
|
|
54
40
|
const data = JSON.stringify(obj);
|
|
55
41
|
const pkey = `${this.prefix}${key}`;
|
|
56
42
|
|
|
57
|
-
const startTime = Date.now();
|
|
58
|
-
|
|
59
43
|
if (ttl) {
|
|
60
|
-
|
|
61
|
-
const duration = Date.now() - startTime;
|
|
62
|
-
this.logSlow("SETEX", pkey, duration, { ttl: ttl });
|
|
63
|
-
return res;
|
|
44
|
+
return await this.client.setex(pkey, ttl, data);
|
|
64
45
|
}
|
|
65
46
|
|
|
66
|
-
|
|
67
|
-
const duration = Date.now() - startTime;
|
|
68
|
-
this.logSlow("SET", pkey, duration);
|
|
69
|
-
return res;
|
|
47
|
+
return await this.client.set(pkey, data);
|
|
70
48
|
} catch (error) {
|
|
71
49
|
Logger.error("Redis setObject 错误", error);
|
|
72
50
|
return null;
|
|
@@ -81,11 +59,7 @@ export class RedisHelper {
|
|
|
81
59
|
async getObject(key) {
|
|
82
60
|
try {
|
|
83
61
|
const pkey = `${this.prefix}${key}`;
|
|
84
|
-
|
|
85
|
-
const startTime = Date.now();
|
|
86
62
|
const data = await this.client.get(pkey);
|
|
87
|
-
const duration = Date.now() - startTime;
|
|
88
|
-
this.logSlow("GET", pkey, duration);
|
|
89
63
|
return data ? JSON.parse(data) : null;
|
|
90
64
|
} catch (error) {
|
|
91
65
|
Logger.error("Redis getObject 错误", error);
|
|
@@ -100,11 +74,7 @@ export class RedisHelper {
|
|
|
100
74
|
async delObject(key) {
|
|
101
75
|
try {
|
|
102
76
|
const pkey = `${this.prefix}${key}`;
|
|
103
|
-
|
|
104
|
-
const startTime = Date.now();
|
|
105
77
|
await this.client.del(pkey);
|
|
106
|
-
const duration = Date.now() - startTime;
|
|
107
|
-
this.logSlow("DEL", pkey, duration);
|
|
108
78
|
} catch (error) {
|
|
109
79
|
Logger.error("Redis delObject 错误", error);
|
|
110
80
|
}
|
|
@@ -124,16 +94,11 @@ export class RedisHelper {
|
|
|
124
94
|
const timestamp = Date.now();
|
|
125
95
|
const key = `${this.prefix}time_id:${timestamp}`;
|
|
126
96
|
|
|
127
|
-
const startTime = Date.now();
|
|
128
|
-
|
|
129
97
|
const counter = await this.client.incr(key);
|
|
130
98
|
if (counter === 1) {
|
|
131
99
|
await this.client.expire(key, 1);
|
|
132
100
|
}
|
|
133
101
|
|
|
134
|
-
const duration = Date.now() - startTime;
|
|
135
|
-
this.logSlow("INCR", key, duration, { expireSeconds: 1 });
|
|
136
|
-
|
|
137
102
|
// 基于时间戳偏移起点,后缀 100-999 循环
|
|
138
103
|
const suffix = 100 + (((timestamp % 900) + counter - 1) % 900);
|
|
139
104
|
|
|
@@ -150,104 +115,17 @@ export class RedisHelper {
|
|
|
150
115
|
async setString(key, value, ttl = null) {
|
|
151
116
|
try {
|
|
152
117
|
const pkey = `${this.prefix}${key}`;
|
|
153
|
-
|
|
154
|
-
const startTime = Date.now();
|
|
155
118
|
if (ttl) {
|
|
156
|
-
|
|
157
|
-
const duration = Date.now() - startTime;
|
|
158
|
-
this.logSlow("SETEX", pkey, duration, { ttl: ttl });
|
|
159
|
-
return res;
|
|
119
|
+
return await this.client.setex(pkey, ttl, value);
|
|
160
120
|
}
|
|
161
121
|
|
|
162
|
-
|
|
163
|
-
const duration = Date.now() - startTime;
|
|
164
|
-
this.logSlow("SET", pkey, duration);
|
|
165
|
-
return res;
|
|
122
|
+
return await this.client.set(pkey, value);
|
|
166
123
|
} catch (error) {
|
|
167
124
|
Logger.error("Redis setString 错误", error);
|
|
168
125
|
return null;
|
|
169
126
|
}
|
|
170
127
|
}
|
|
171
128
|
|
|
172
|
-
/**
|
|
173
|
-
* 尝试获取分布式锁(SET key token NX PX ttlMs)
|
|
174
|
-
*/
|
|
175
|
-
async tryAcquireLock(key, token, ttlMs) {
|
|
176
|
-
try {
|
|
177
|
-
if (!isNonEmptyString(key)) {
|
|
178
|
-
throw new Error("tryAcquireLock: key 必须是非空字符串", {
|
|
179
|
-
cause: null,
|
|
180
|
-
code: "validation"
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
if (!isNonEmptyString(token)) {
|
|
184
|
-
throw new Error("tryAcquireLock: token 必须是非空字符串", {
|
|
185
|
-
cause: null,
|
|
186
|
-
code: "validation"
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
if (!(isFiniteNumber(ttlMs) && ttlMs > 0)) {
|
|
190
|
-
throw new Error("tryAcquireLock: ttlMs 必须是正数", {
|
|
191
|
-
cause: null,
|
|
192
|
-
code: "validation"
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const pkey = `${this.prefix}${key}`;
|
|
197
|
-
const startTime = Date.now();
|
|
198
|
-
const ttlMsString = String(Math.floor(ttlMs));
|
|
199
|
-
const res = await this.client.set(pkey, token, "NX", "PX", ttlMsString);
|
|
200
|
-
const duration = Date.now() - startTime;
|
|
201
|
-
this.logSlow("SET NX PX", pkey, duration, { ttlMs: ttlMs });
|
|
202
|
-
|
|
203
|
-
return res === "OK";
|
|
204
|
-
} catch (error) {
|
|
205
|
-
Logger.error("Redis tryAcquireLock 错误", error);
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* 释放分布式锁(原子校验 token 后删除)
|
|
212
|
-
*/
|
|
213
|
-
async releaseLock(key, token) {
|
|
214
|
-
try {
|
|
215
|
-
if (!isNonEmptyString(key)) {
|
|
216
|
-
throw new Error("releaseLock: key 必须是非空字符串", {
|
|
217
|
-
cause: null,
|
|
218
|
-
code: "validation"
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
if (!isNonEmptyString(token)) {
|
|
222
|
-
throw new Error("releaseLock: token 必须是非空字符串", {
|
|
223
|
-
cause: null,
|
|
224
|
-
code: "validation"
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const pkey = `${this.prefix}${key}`;
|
|
229
|
-
|
|
230
|
-
// 注意:Bun.RedisClient 当前未暴露 Lua/EVAL 等原子 compare-and-del API。
|
|
231
|
-
// 这里使用 get + del 的最小窗口实现;理论上存在极端竞态(TTL 恰好过期并被他人抢占)。
|
|
232
|
-
// 对启动期同步场景来说风险可接受;同时锁本身带 TTL,最坏情况下依赖 TTL 自动释放。
|
|
233
|
-
const startTime = Date.now();
|
|
234
|
-
|
|
235
|
-
const current = await this.client.get(pkey);
|
|
236
|
-
if (current !== token) {
|
|
237
|
-
return false;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const deleted = await this.client.del(pkey);
|
|
241
|
-
const duration = Date.now() - startTime;
|
|
242
|
-
this.logSlow("GET+DEL", pkey, duration);
|
|
243
|
-
|
|
244
|
-
return deleted > 0;
|
|
245
|
-
} catch (error) {
|
|
246
|
-
Logger.error("Redis releaseLock 错误", error);
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
129
|
/**
|
|
252
130
|
* 获取字符串值
|
|
253
131
|
* @param key - 键名
|
|
@@ -255,12 +133,7 @@ export class RedisHelper {
|
|
|
255
133
|
async getString(key) {
|
|
256
134
|
try {
|
|
257
135
|
const pkey = `${this.prefix}${key}`;
|
|
258
|
-
|
|
259
|
-
const startTime = Date.now();
|
|
260
|
-
const res = await this.client.get(pkey);
|
|
261
|
-
const duration = Date.now() - startTime;
|
|
262
|
-
this.logSlow("GET", pkey, duration);
|
|
263
|
-
return res;
|
|
136
|
+
return await this.client.get(pkey);
|
|
264
137
|
} catch (error) {
|
|
265
138
|
Logger.error("Redis getString 错误", error);
|
|
266
139
|
return null;
|
|
@@ -275,12 +148,7 @@ export class RedisHelper {
|
|
|
275
148
|
async exists(key) {
|
|
276
149
|
try {
|
|
277
150
|
const pkey = `${this.prefix}${key}`;
|
|
278
|
-
|
|
279
|
-
const startTime = Date.now();
|
|
280
|
-
const res = await this.client.exists(pkey);
|
|
281
|
-
const duration = Date.now() - startTime;
|
|
282
|
-
this.logSlow("EXISTS", pkey, duration);
|
|
283
|
-
return res;
|
|
151
|
+
return await this.client.exists(pkey);
|
|
284
152
|
} catch (error) {
|
|
285
153
|
Logger.error("Redis exists 错误", error);
|
|
286
154
|
return false;
|
|
@@ -295,12 +163,7 @@ export class RedisHelper {
|
|
|
295
163
|
async incr(key) {
|
|
296
164
|
try {
|
|
297
165
|
const pkey = `${this.prefix}${key}`;
|
|
298
|
-
|
|
299
|
-
const startTime = Date.now();
|
|
300
|
-
const res = await this.client.incr(pkey);
|
|
301
|
-
const duration = Date.now() - startTime;
|
|
302
|
-
this.logSlow("INCR", pkey, duration);
|
|
303
|
-
return res;
|
|
166
|
+
return await this.client.incr(pkey);
|
|
304
167
|
} catch (error) {
|
|
305
168
|
Logger.error("Redis incr 错误", error);
|
|
306
169
|
return 0;
|
|
@@ -316,14 +179,10 @@ export class RedisHelper {
|
|
|
316
179
|
async incrWithExpire(key, seconds) {
|
|
317
180
|
try {
|
|
318
181
|
const pkey = `${this.prefix}${key}`;
|
|
319
|
-
|
|
320
|
-
const startTime = Date.now();
|
|
321
182
|
const res = await this.client.incr(pkey);
|
|
322
183
|
if (res === 1) {
|
|
323
184
|
await this.client.expire(pkey, seconds);
|
|
324
185
|
}
|
|
325
|
-
const duration = Date.now() - startTime;
|
|
326
|
-
this.logSlow("INCR", pkey, duration, { expireSeconds: seconds });
|
|
327
186
|
return res;
|
|
328
187
|
} catch (error) {
|
|
329
188
|
Logger.error("Redis incrWithExpire 错误", error);
|
|
@@ -339,12 +198,7 @@ export class RedisHelper {
|
|
|
339
198
|
async expire(key, seconds) {
|
|
340
199
|
try {
|
|
341
200
|
const pkey = `${this.prefix}${key}`;
|
|
342
|
-
|
|
343
|
-
const startTime = Date.now();
|
|
344
|
-
const res = await this.client.expire(pkey, seconds);
|
|
345
|
-
const duration = Date.now() - startTime;
|
|
346
|
-
this.logSlow("EXPIRE", pkey, duration, { seconds: seconds });
|
|
347
|
-
return res;
|
|
201
|
+
return await this.client.expire(pkey, seconds);
|
|
348
202
|
} catch (error) {
|
|
349
203
|
Logger.error("Redis expire 错误", error);
|
|
350
204
|
return 0;
|
|
@@ -358,12 +212,7 @@ export class RedisHelper {
|
|
|
358
212
|
async ttl(key) {
|
|
359
213
|
try {
|
|
360
214
|
const pkey = `${this.prefix}${key}`;
|
|
361
|
-
|
|
362
|
-
const startTime = Date.now();
|
|
363
|
-
const res = await this.client.ttl(pkey);
|
|
364
|
-
const duration = Date.now() - startTime;
|
|
365
|
-
this.logSlow("TTL", pkey, duration);
|
|
366
|
-
return res;
|
|
215
|
+
return await this.client.ttl(pkey);
|
|
367
216
|
} catch (error) {
|
|
368
217
|
Logger.error("Redis ttl 错误", error);
|
|
369
218
|
return -1;
|
|
@@ -400,18 +249,11 @@ export class RedisHelper {
|
|
|
400
249
|
if (members.length === 0) return 0;
|
|
401
250
|
|
|
402
251
|
const pkey = `${this.prefix}${key}`;
|
|
403
|
-
|
|
404
|
-
const startTime = Date.now();
|
|
405
252
|
const args = [pkey];
|
|
406
253
|
for (const member of members) {
|
|
407
254
|
args.push(member);
|
|
408
255
|
}
|
|
409
|
-
|
|
410
|
-
const duration = Date.now() - startTime;
|
|
411
|
-
this.logSlow("SADD", pkey, duration, {
|
|
412
|
-
membersCount: members.length
|
|
413
|
-
});
|
|
414
|
-
return res;
|
|
256
|
+
return await this.client.sadd.apply(this.client, args);
|
|
415
257
|
} catch (error) {
|
|
416
258
|
Logger.error("Redis sadd 错误", error);
|
|
417
259
|
return 0;
|
|
@@ -427,12 +269,7 @@ export class RedisHelper {
|
|
|
427
269
|
async sismember(key, member) {
|
|
428
270
|
try {
|
|
429
271
|
const pkey = `${this.prefix}${key}`;
|
|
430
|
-
|
|
431
|
-
const startTime = Date.now();
|
|
432
|
-
const res = await this.client.sismember(pkey, member);
|
|
433
|
-
const duration = Date.now() - startTime;
|
|
434
|
-
this.logSlow("SISMEMBER", pkey, duration);
|
|
435
|
-
return res;
|
|
272
|
+
return await this.client.sismember(pkey, member);
|
|
436
273
|
} catch (error) {
|
|
437
274
|
Logger.error("Redis sismember 错误", error);
|
|
438
275
|
return false;
|
|
@@ -447,12 +284,7 @@ export class RedisHelper {
|
|
|
447
284
|
async scard(key) {
|
|
448
285
|
try {
|
|
449
286
|
const pkey = `${this.prefix}${key}`;
|
|
450
|
-
|
|
451
|
-
const startTime = Date.now();
|
|
452
|
-
const res = await this.client.scard(pkey);
|
|
453
|
-
const duration = Date.now() - startTime;
|
|
454
|
-
this.logSlow("SCARD", pkey, duration);
|
|
455
|
-
return res;
|
|
287
|
+
return await this.client.scard(pkey);
|
|
456
288
|
} catch (error) {
|
|
457
289
|
Logger.error("Redis scard 错误", error);
|
|
458
290
|
return 0;
|
|
@@ -467,12 +299,7 @@ export class RedisHelper {
|
|
|
467
299
|
async smembers(key) {
|
|
468
300
|
try {
|
|
469
301
|
const pkey = `${this.prefix}${key}`;
|
|
470
|
-
|
|
471
|
-
const startTime = Date.now();
|
|
472
|
-
const res = await this.client.smembers(pkey);
|
|
473
|
-
const duration = Date.now() - startTime;
|
|
474
|
-
this.logSlow("SMEMBERS", pkey, duration);
|
|
475
|
-
return res;
|
|
302
|
+
return await this.client.smembers(pkey);
|
|
476
303
|
} catch (error) {
|
|
477
304
|
Logger.error("Redis smembers 错误", error);
|
|
478
305
|
return [];
|
|
@@ -524,12 +351,7 @@ export class RedisHelper {
|
|
|
524
351
|
async del(key) {
|
|
525
352
|
try {
|
|
526
353
|
const pkey = `${this.prefix}${key}`;
|
|
527
|
-
|
|
528
|
-
const startTime = Date.now();
|
|
529
|
-
const res = await this.client.del(pkey);
|
|
530
|
-
const duration = Date.now() - startTime;
|
|
531
|
-
this.logSlow("DEL", pkey, duration);
|
|
532
|
-
return res;
|
|
354
|
+
return await this.client.del(pkey);
|
|
533
355
|
} catch (error) {
|
|
534
356
|
Logger.error("Redis del 错误", error);
|
|
535
357
|
return 0;
|
|
@@ -649,10 +471,7 @@ export class RedisHelper {
|
|
|
649
471
|
});
|
|
650
472
|
}
|
|
651
473
|
|
|
652
|
-
const startTime = Date.now();
|
|
653
474
|
const res = await client.info(section);
|
|
654
|
-
const duration = Date.now() - startTime;
|
|
655
|
-
this.logSlow("INFO", "(no-key)", duration, { section: section ?? "" });
|
|
656
475
|
return res;
|
|
657
476
|
} catch (error) {
|
|
658
477
|
Logger.error("Redis info 错误", error);
|
|
@@ -666,11 +485,7 @@ export class RedisHelper {
|
|
|
666
485
|
*/
|
|
667
486
|
async ping() {
|
|
668
487
|
try {
|
|
669
|
-
|
|
670
|
-
const res = await this.client.ping();
|
|
671
|
-
const duration = Date.now() - startTime;
|
|
672
|
-
this.logSlow("PING", "(no-key)", duration);
|
|
673
|
-
return res;
|
|
488
|
+
return await this.client.ping();
|
|
674
489
|
} catch (error) {
|
|
675
490
|
Logger.error("Redis ping 错误", error);
|
|
676
491
|
throw new Error("Redis ping 错误", {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.17.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.17.11",
|
|
4
|
+
"gitHead": "d6f49d8da98a65d26d4d4866ff3a3671e178c1fb",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
|
|
7
7
|
"keywords": [
|