befly 3.8.19 → 3.8.21

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.
Files changed (78) hide show
  1. package/README.md +7 -6
  2. package/bunfig.toml +1 -1
  3. package/checks/checkApi.ts +92 -0
  4. package/checks/checkApp.ts +31 -0
  5. package/{check.ts → checks/checkTable.ts} +28 -159
  6. package/config.ts +71 -0
  7. package/hooks/auth.ts +30 -0
  8. package/hooks/cors.ts +48 -0
  9. package/hooks/errorHandler.ts +23 -0
  10. package/hooks/parser.ts +67 -0
  11. package/hooks/permission.ts +54 -0
  12. package/hooks/rateLimit.ts +70 -0
  13. package/hooks/requestId.ts +24 -0
  14. package/hooks/requestLogger.ts +25 -0
  15. package/hooks/responseFormatter.ts +64 -0
  16. package/hooks/validator.ts +34 -0
  17. package/lib/database.ts +28 -25
  18. package/lib/dbHelper.ts +3 -3
  19. package/lib/jwt.ts +90 -99
  20. package/lib/logger.ts +44 -23
  21. package/lib/redisHelper.ts +19 -22
  22. package/loader/loadApis.ts +69 -114
  23. package/loader/loadHooks.ts +65 -0
  24. package/loader/loadPlugins.ts +50 -219
  25. package/main.ts +106 -133
  26. package/package.json +23 -14
  27. package/paths.ts +20 -0
  28. package/plugins/cache.ts +1 -3
  29. package/plugins/db.ts +8 -11
  30. package/plugins/logger.ts +5 -3
  31. package/plugins/redis.ts +10 -14
  32. package/router/api.ts +60 -106
  33. package/router/root.ts +15 -12
  34. package/router/static.ts +54 -58
  35. package/sync/syncAll.ts +58 -0
  36. package/sync/syncApi.ts +264 -0
  37. package/sync/syncDb/apply.ts +194 -0
  38. package/sync/syncDb/constants.ts +76 -0
  39. package/sync/syncDb/ddl.ts +194 -0
  40. package/sync/syncDb/helpers.ts +200 -0
  41. package/sync/syncDb/index.ts +164 -0
  42. package/sync/syncDb/schema.ts +201 -0
  43. package/sync/syncDb/sqlite.ts +50 -0
  44. package/sync/syncDb/table.ts +321 -0
  45. package/sync/syncDb/tableCreate.ts +146 -0
  46. package/sync/syncDb/version.ts +72 -0
  47. package/sync/syncDb.ts +19 -0
  48. package/sync/syncDev.ts +206 -0
  49. package/sync/syncMenu.ts +331 -0
  50. package/tests/cipher.test.ts +248 -0
  51. package/tests/dbHelper-advanced.test.ts +717 -0
  52. package/tests/dbHelper-columns.test.ts +266 -0
  53. package/tests/dbHelper-execute.test.ts +240 -0
  54. package/tests/fields-redis-cache.test.ts +123 -0
  55. package/tests/fields-validate.test.ts +99 -0
  56. package/tests/integration.test.ts +202 -0
  57. package/tests/jwt.test.ts +122 -0
  58. package/tests/logger.test.ts +94 -0
  59. package/tests/redisHelper.test.ts +231 -0
  60. package/tests/sqlBuilder-advanced.test.ts +593 -0
  61. package/tests/sqlBuilder.test.ts +184 -0
  62. package/tests/util.test.ts +95 -0
  63. package/tests/validator-advanced.test.ts +653 -0
  64. package/tests/validator.test.ts +148 -0
  65. package/tests/xml.test.ts +101 -0
  66. package/tsconfig.json +2 -4
  67. package/types/api.d.ts +6 -0
  68. package/types/befly.d.ts +152 -28
  69. package/types/context.d.ts +29 -3
  70. package/types/hook.d.ts +35 -0
  71. package/types/index.ts +14 -1
  72. package/types/plugin.d.ts +6 -7
  73. package/types/sync.d.ts +403 -0
  74. package/env.ts +0 -106
  75. package/lib/middleware.ts +0 -275
  76. package/types/env.ts +0 -65
  77. package/types/util.d.ts +0 -45
  78. package/util.ts +0 -257
package/lib/jwt.ts CHANGED
@@ -4,13 +4,28 @@
4
4
  */
5
5
 
6
6
  import { createHmac } from 'crypto';
7
- import { Env } from '../env.js';
8
7
  import type { JwtPayload, JwtSignOptions, JwtVerifyOptions, JwtAlgorithm, JwtHeader, JwtDecoded } from '../types/jwt';
8
+ import type { AuthConfig } from '../types/befly';
9
9
 
10
10
  /**
11
11
  * JWT 工具类
12
12
  */
13
13
  export class Jwt {
14
+ /** 默认配置 */
15
+ private static config: AuthConfig = {
16
+ secret: 'befly-secret',
17
+ expiresIn: '7d',
18
+ algorithm: 'HS256'
19
+ };
20
+
21
+ /**
22
+ * 配置 JWT
23
+ * @param config - JWT 配置
24
+ */
25
+ static configure(config: AuthConfig) {
26
+ this.config = { ...this.config, ...config };
27
+ }
28
+
14
29
  /** 算法映射 */
15
30
  private static readonly ALGORITHMS: Record<JwtAlgorithm, string> = {
16
31
  HS256: 'sha256',
@@ -94,23 +109,15 @@ export class Jwt {
94
109
  }
95
110
 
96
111
  /**
97
- * 签名 JWT token
98
- * @param payload - JWT载荷数据
99
- * @param options - 签名选项(自动使用 Env.JWT_SECRET)
100
- * @returns JWT token字符串
112
+ * 签名生成 Token
113
+ * @param payload - 数据载荷
114
+ * @param options - 签名选项
115
+ * @returns Token 字符串
101
116
  */
102
- static sign(payload: JwtPayload, options?: JwtSignOptions): string {
103
- if (!payload || typeof payload !== 'object') {
104
- throw new Error('载荷必须是非空对象');
105
- }
106
-
107
- const secret = options?.secret || Env.JWT_SECRET;
108
- if (!secret) {
109
- throw new Error('JWT密钥未配置');
110
- }
111
-
112
- const opts = options || {};
113
- const algorithm = (opts.algorithm || Env.JWT_ALGORITHM || 'HS256') as JwtAlgorithm;
117
+ static async sign(payload: JwtPayload, options: JwtSignOptions = {}): Promise<string> {
118
+ const secret = options.secret || this.config.secret || 'befly-secret';
119
+ const expiresIn = options.expiresIn || this.config.expiresIn || '7d';
120
+ const algorithm = (options.algorithm || this.config.algorithm || 'HS256') as JwtAlgorithm;
114
121
 
115
122
  const now = Math.floor(Date.now() / 1000);
116
123
 
@@ -125,22 +132,18 @@ export class Jwt {
125
132
  // 创建 payload
126
133
  const jwtPayload: JwtPayload = { ...payload, iat: now };
127
134
 
128
- if (opts.expiresIn) {
129
- const expSeconds = Jwt.parseExpiration(opts.expiresIn);
130
- jwtPayload.exp = now + expSeconds;
131
- } else {
132
- // 使用默认过期时间
133
- const defaultExpiry = Env.JWT_EXPIRES_IN || '7d';
134
- const expSeconds = Jwt.parseExpiration(defaultExpiry);
135
+ if (expiresIn) {
136
+ const expSeconds = Jwt.parseExpiration(expiresIn);
135
137
  jwtPayload.exp = now + expSeconds;
136
138
  }
137
- if (opts.issuer) jwtPayload.iss = opts.issuer;
138
- if (opts.audience) jwtPayload.aud = opts.audience;
139
- if (opts.subject) jwtPayload.sub = opts.subject;
140
- if (opts.notBefore) {
141
- jwtPayload.nbf = typeof opts.notBefore === 'number' ? opts.notBefore : now + Jwt.parseExpiration(opts.notBefore);
139
+
140
+ if (options.issuer) jwtPayload.iss = options.issuer;
141
+ if (options.audience) jwtPayload.aud = options.audience;
142
+ if (options.subject) jwtPayload.sub = options.subject;
143
+ if (options.notBefore) {
144
+ jwtPayload.nbf = typeof options.notBefore === 'number' ? options.notBefore : now + Jwt.parseExpiration(options.notBefore);
142
145
  }
143
- if (opts.jwtId) jwtPayload.jti = opts.jwtId;
146
+ if (options.jwtId) jwtPayload.jti = options.jwtId;
144
147
 
145
148
  const encodedPayload = Jwt.base64UrlEncode(JSON.stringify(jwtPayload));
146
149
 
@@ -152,78 +155,66 @@ export class Jwt {
152
155
  }
153
156
 
154
157
  /**
155
- * 验证 JWT token
156
- * @param token - JWT token字符串
157
- * @param options - 验证选项(自动使用 Env.JWT_SECRET)
158
- * @returns 解码后的载荷数据
158
+ * 验证 Token
159
+ * @param token - Token 字符串
160
+ * @param options - 验证选项
161
+ * @returns 解码后的载荷
159
162
  */
160
- static verify(token: string, options?: JwtVerifyOptions): JwtPayload {
163
+ static async verify<T = JwtPayload>(token: string, options: JwtVerifyOptions = {}): Promise<T> {
161
164
  if (!token || typeof token !== 'string') {
162
165
  throw new Error('Token必须是非空字符串');
163
166
  }
164
167
 
165
- const secret = options?.secret || Env.JWT_SECRET;
166
- if (!secret) {
167
- throw new Error('JWT密钥未配置');
168
- }
169
-
170
- const opts = options || {};
168
+ const secret = options.secret || this.config.secret || 'befly-secret';
169
+ const algorithm = (options.algorithm || this.config.algorithm || 'HS256') as JwtAlgorithm;
171
170
 
172
171
  const parts = token.split('.');
173
172
  if (parts.length !== 3) {
174
- throw new Error('JWT格式无效');
173
+ throw new Error('Token 格式无效');
175
174
  }
176
175
 
177
- try {
178
- // 解析 header 和 payload
179
- const header = JSON.parse(Jwt.base64UrlDecode(parts[0])) as JwtHeader;
180
- const payload = JSON.parse(Jwt.base64UrlDecode(parts[1])) as JwtPayload;
181
- const signature = parts[2];
176
+ const [headerB64, payloadB64, signature] = parts;
182
177
 
183
- // 验证算法
184
- if (!Jwt.ALGORITHMS[header.alg]) {
185
- throw new Error(`不支持的算法: ${header.alg}`);
186
- }
178
+ // 验证签名
179
+ const data = `${headerB64}.${payloadB64}`;
180
+ const expectedSignature = Jwt.createSignature(algorithm, secret, data);
187
181
 
188
- // 验证签名
189
- const data = `${parts[0]}.${parts[1]}`;
190
- const expectedSignature = Jwt.createSignature(header.alg, secret, data);
182
+ if (!Jwt.constantTimeCompare(signature, expectedSignature)) {
183
+ throw new Error('Token 签名无效');
184
+ }
191
185
 
192
- if (!Jwt.constantTimeCompare(signature, expectedSignature)) {
193
- throw new Error('Token签名无效');
194
- }
186
+ // 解码 payload
187
+ const payloadStr = Jwt.base64UrlDecode(payloadB64);
188
+ let payload: JwtPayload;
189
+ try {
190
+ payload = JSON.parse(payloadStr);
191
+ } catch (e) {
192
+ throw new Error('Token 载荷无效');
193
+ }
195
194
 
196
- // 验证时间
195
+ // 验证过期时间
196
+ if (!options.ignoreExpiration) {
197
197
  const now = Math.floor(Date.now() / 1000);
198
-
199
- if (!opts.ignoreExpiration && payload.exp && payload.exp < now) {
200
- throw new Error('Token已过期 (expired)');
201
- }
202
- if (!opts.ignoreNotBefore && payload.nbf && payload.nbf > now) {
203
- throw new Error('Token尚未生效');
204
- }
205
-
206
- // 验证 issuer、audience、subject
207
- if (opts.issuer && payload.iss !== opts.issuer) {
208
- throw new Error('Token发行者无效');
209
- }
210
- if (opts.audience) {
211
- const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
212
- if (!audiences.includes(opts.audience)) {
213
- throw new Error('Token受众无效');
214
- }
215
- }
216
- if (opts.subject && payload.sub !== opts.subject) {
217
- throw new Error('Token主题无效');
198
+ if (payload.exp && payload.exp < now) {
199
+ throw new Error('Token 已过期');
218
200
  }
201
+ }
219
202
 
220
- return payload;
221
- } catch (error: any) {
222
- if (error.message.includes('JWT') || error.message.includes('Token') || error.message.includes('无效') || error.message.includes('过期') || error.message.includes('不支持')) {
223
- throw error;
224
- }
225
- throw new Error('Token验证失败: ' + error.message);
203
+ // 验证其他声明
204
+ if (options.issuer && payload.iss !== options.issuer) {
205
+ throw new Error('Token issuer 无效');
226
206
  }
207
+ if (options.audience && payload.aud !== options.audience) {
208
+ throw new Error('Token audience 无效');
209
+ }
210
+ if (options.subject && payload.sub !== options.subject) {
211
+ throw new Error('Token subject 无效');
212
+ }
213
+ if (options.jwtId && payload.jti !== options.jwtId) {
214
+ throw new Error('Token jwtId 无效');
215
+ }
216
+
217
+ return payload as T;
227
218
  }
228
219
 
229
220
  /**
@@ -341,14 +332,14 @@ export class Jwt {
341
332
  /**
342
333
  * 创建用户 token(快捷方法)
343
334
  */
344
- static create(payload: JwtPayload): string {
335
+ static async create(payload: JwtPayload): Promise<string> {
345
336
  return this.sign(payload);
346
337
  }
347
338
 
348
339
  /**
349
340
  * 检查 token(快捷方法)
350
341
  */
351
- static check(token: string): JwtPayload {
342
+ static async check(token: string): Promise<JwtPayload> {
352
343
  return this.verify(token);
353
344
  }
354
345
 
@@ -362,64 +353,64 @@ export class Jwt {
362
353
  /**
363
354
  * 签名用户认证 token
364
355
  */
365
- static signUserToken(userInfo: JwtPayload, options?: JwtSignOptions): string {
356
+ static async signUserToken(userInfo: JwtPayload, options?: JwtSignOptions): Promise<string> {
366
357
  return this.sign(userInfo, options);
367
358
  }
368
359
 
369
360
  /**
370
361
  * 签名 API 访问 token
371
362
  */
372
- static signAPIToken(payload: JwtPayload, options?: JwtSignOptions): string {
363
+ static async signAPIToken(payload: JwtPayload, options?: JwtSignOptions): Promise<string> {
373
364
  return this.sign(payload, { audience: 'api', expiresIn: '1h', ...options });
374
365
  }
375
366
 
376
367
  /**
377
368
  * 签名刷新 token
378
369
  */
379
- static signRefreshToken(payload: JwtPayload, options?: JwtSignOptions): string {
370
+ static async signRefreshToken(payload: JwtPayload, options?: JwtSignOptions): Promise<string> {
380
371
  return this.sign(payload, { audience: 'refresh', expiresIn: '30d', ...options });
381
372
  }
382
373
 
383
374
  /**
384
375
  * 签名临时 token (用于重置密码等)
385
376
  */
386
- static signTempToken(payload: JwtPayload, options?: JwtSignOptions): string {
377
+ static async signTempToken(payload: JwtPayload, options?: JwtSignOptions): Promise<string> {
387
378
  return this.sign(payload, { audience: 'temporary', expiresIn: '15m', ...options });
388
379
  }
389
380
 
390
381
  /**
391
382
  * 验证用户认证 token
392
383
  */
393
- static verifyUserToken(token: string, options?: JwtVerifyOptions): JwtPayload {
384
+ static async verifyUserToken(token: string, options?: JwtVerifyOptions): Promise<JwtPayload> {
394
385
  return this.verify(token, options);
395
386
  }
396
387
 
397
388
  /**
398
389
  * 验证 API 访问 token
399
390
  */
400
- static verifyAPIToken(token: string, options?: JwtVerifyOptions): JwtPayload {
391
+ static async verifyAPIToken(token: string, options?: JwtVerifyOptions): Promise<JwtPayload> {
401
392
  return this.verify(token, { audience: 'api', ...options });
402
393
  }
403
394
 
404
395
  /**
405
396
  * 验证刷新 token
406
397
  */
407
- static verifyRefreshToken(token: string, options?: JwtVerifyOptions): JwtPayload {
398
+ static async verifyRefreshToken(token: string, options?: JwtVerifyOptions): Promise<JwtPayload> {
408
399
  return this.verify(token, { audience: 'refresh', ...options });
409
400
  }
410
401
 
411
402
  /**
412
403
  * 验证临时 token
413
404
  */
414
- static verifyTempToken(token: string, options?: JwtVerifyOptions): JwtPayload {
405
+ static async verifyTempToken(token: string, options?: JwtVerifyOptions): Promise<JwtPayload> {
415
406
  return this.verify(token, { audience: 'temporary', ...options });
416
407
  }
417
408
 
418
409
  /**
419
410
  * 验证 token 并检查权限
420
411
  */
421
- static verifyWithPermissions(token: string, requiredPermissions: string | string[], options?: JwtVerifyOptions): JwtPayload {
422
- const payload = this.verify(token, options);
412
+ static async verifyWithPermissions(token: string, requiredPermissions: string | string[], options?: JwtVerifyOptions): Promise<JwtPayload> {
413
+ const payload = await this.verify(token, options);
423
414
 
424
415
  if (!payload.permissions) {
425
416
  throw new Error('Token中不包含权限信息');
@@ -438,8 +429,8 @@ export class Jwt {
438
429
  /**
439
430
  * 验证 token 并检查角色
440
431
  */
441
- static verifyWithRoles(token: string, requiredRoles: string | string[], options?: JwtVerifyOptions): JwtPayload {
442
- const payload = this.verify(token, options);
432
+ static async verifyWithRoles(token: string, requiredRoles: string | string[], options?: JwtVerifyOptions): Promise<JwtPayload> {
433
+ const payload = await this.verify(token, options);
443
434
 
444
435
  if (!payload.role && !payload.roles) {
445
436
  throw new Error('Token中不包含角色信息');
@@ -460,7 +451,7 @@ export class Jwt {
460
451
  /**
461
452
  * 软验证 token (忽略过期时间)
462
453
  */
463
- static verifySoft(token: string, options?: JwtVerifyOptions): JwtPayload {
454
+ static async verifySoft(token: string, options?: JwtVerifyOptions): Promise<JwtPayload> {
464
455
  return this.verify(token, { ignoreExpiration: true, ...options });
465
456
  }
466
457
  }
package/lib/logger.ts CHANGED
@@ -5,8 +5,19 @@
5
5
 
6
6
  import { join } from 'pathe';
7
7
  import { appendFile, stat } from 'node:fs/promises';
8
- import { Env } from '../env.js';
8
+ import { AsyncLocalStorage } from 'node:async_hooks';
9
9
  import type { LogLevel } from '../types/common.js';
10
+ import type { LoggerConfig } from '../types/befly.js';
11
+
12
+ /**
13
+ * 日志上下文存储
14
+ */
15
+ export interface LogContext {
16
+ requestId?: string;
17
+ [key: string]: any;
18
+ }
19
+
20
+ export const logContextStorage = new AsyncLocalStorage<LogContext>();
10
21
 
11
22
  /**
12
23
  * 日志消息类型
@@ -34,6 +45,23 @@ export class Logger {
34
45
  /** 当前使用的日志文件缓存 */
35
46
  private static currentFiles: Map<string, string> = new Map();
36
47
 
48
+ /** 日志配置 */
49
+ private static config: LoggerConfig = {
50
+ debug: 1,
51
+ excludeFields: 'password,token,secret',
52
+ dir: './logs',
53
+ console: 1,
54
+ maxSize: 10 * 1024 * 1024
55
+ };
56
+
57
+ /**
58
+ * 配置日志器
59
+ * @param config - 日志配置
60
+ */
61
+ static configure(config: LoggerConfig) {
62
+ this.config = { ...this.config, ...config };
63
+ }
64
+
37
65
  /**
38
66
  * 记录日志
39
67
  * @param level - 日志级别
@@ -41,7 +69,7 @@ export class Logger {
41
69
  */
42
70
  static async log(level: LogLevel, message: LogMessage): Promise<void> {
43
71
  // debug 日志特殊处理:仅当 LOG_DEBUG=1 时才记录
44
- if (level === 'debug' && Env.LOG_DEBUG !== 1) return;
72
+ if (level === 'debug' && this.config.debug !== 1) return;
45
73
 
46
74
  // 格式化消息
47
75
  const timestamp = formatDate();
@@ -56,10 +84,15 @@ export class Logger {
56
84
 
57
85
  // 格式化日志消息
58
86
  const levelStr = level.toUpperCase().padStart(5);
59
- const logMessage = `[${timestamp}] ${levelStr} - ${content}`;
87
+
88
+ // 获取上下文中的 requestId
89
+ const store = logContextStorage.getStore();
90
+ const requestId = store?.requestId ? ` [${store.requestId}]` : '';
91
+
92
+ const logMessage = `[${timestamp}]${requestId} ${levelStr} - ${content}`;
60
93
 
61
94
  // 控制台输出
62
- if (Env.LOG_TO_CONSOLE === 1) {
95
+ if (this.config.console === 1) {
63
96
  console.log(logMessage);
64
97
  }
65
98
 
@@ -82,6 +115,7 @@ export class Logger {
82
115
  */
83
116
  static async writeToFile(message: string, level: LogLevel = 'info'): Promise<void> {
84
117
  try {
118
+ const logDir = this.config.dir || './logs';
85
119
  // 确定文件前缀
86
120
  const prefix = level === 'debug' ? 'debug' : new Date().toISOString().split('T')[0];
87
121
 
@@ -91,7 +125,7 @@ export class Logger {
91
125
  if (currentLogFile) {
92
126
  try {
93
127
  const stats = await stat(currentLogFile);
94
- if (stats.size >= Env.LOG_MAX_SIZE) {
128
+ if (stats.size >= (this.config.maxSize || 10 * 1024 * 1024)) {
95
129
  this.currentFiles.delete(prefix);
96
130
  currentLogFile = undefined;
97
131
  }
@@ -104,7 +138,7 @@ export class Logger {
104
138
  // 查找或创建新文件
105
139
  if (!currentLogFile) {
106
140
  const glob = new Bun.Glob(`${prefix}.*.log`);
107
- const files = await Array.fromAsync(glob.scan(Env.LOG_DIR || 'logs'));
141
+ const files = await Array.fromAsync(glob.scan(this.config.dir || 'logs'));
108
142
 
109
143
  // 按索引排序并查找可用文件
110
144
  const getIndex = (f: string) => parseInt(f.match(/\.(\d+)\.log$/)?.[1] || '0');
@@ -112,10 +146,11 @@ export class Logger {
112
146
 
113
147
  let foundFile = false;
114
148
  for (let i = files.length - 1; i >= 0; i--) {
115
- const filePath = join(Env.LOG_DIR || 'logs', files[i]);
149
+ const filePath = join(this.config.dir || 'logs', files[i]);
116
150
  try {
117
151
  const stats = await stat(filePath);
118
- if (stats.size < Env.LOG_MAX_SIZE) {
152
+ // 检查文件大小
153
+ if (stats.size < (this.config.maxSize || 10 * 1024 * 1024)) {
119
154
  currentLogFile = filePath;
120
155
  foundFile = true;
121
156
  break;
@@ -128,7 +163,7 @@ export class Logger {
128
163
  // 没有可用文件,创建新文件
129
164
  if (!foundFile) {
130
165
  const maxIndex = files.length > 0 ? Math.max(...files.map(getIndex)) : -1;
131
- currentLogFile = join(Env.LOG_DIR || 'logs', `${prefix}.${maxIndex + 1}.log`);
166
+ currentLogFile = join(this.config.dir || 'logs', `${prefix}.${maxIndex + 1}.log`);
132
167
  }
133
168
 
134
169
  this.currentFiles.set(prefix, currentLogFile);
@@ -194,18 +229,4 @@ export class Logger {
194
229
  static clearCache(): void {
195
230
  this.currentFiles.clear();
196
231
  }
197
-
198
- /**
199
- * 打印当前运行环境
200
- * 用于命令开始时提示用户当前环境
201
- */
202
- static printEnv(): void {
203
- console.log('========================================');
204
- console.log('开始执行完整同步流程');
205
- console.log(`当前环境: ${Env.NODE_ENV || 'development'}`);
206
- console.log(`项目名称: ${Env.APP_NAME}`);
207
- console.log(`数据库地址: ${Env.DB_HOST}`);
208
- console.log(`数据库名称: ${Env.DB_NAME}`);
209
- console.log('========================================\n');
210
- }
211
232
  }
@@ -4,30 +4,27 @@
4
4
  */
5
5
 
6
6
  import { RedisClient } from 'bun';
7
- import { Env } from '../env.js';
8
7
  import { Logger } from '../lib/logger.js';
9
8
  import { Database } from './database.js';
10
9
 
11
- /**
12
- * Redis 键前缀
13
- */
14
- const prefix = Env.REDIS_KEY_PREFIX ? `${Env.REDIS_KEY_PREFIX}:` : '';
15
-
16
10
  /**
17
11
  * Redis 助手类
18
12
  */
19
13
  export class RedisHelper {
20
14
  private client: RedisClient;
15
+ private prefix: string;
21
16
 
22
17
  /**
23
18
  * 构造函数
19
+ * @param prefix - Key 前缀
24
20
  */
25
- constructor() {
21
+ constructor(prefix: string = '') {
26
22
  const client = Database.getRedis();
27
23
  if (!client) {
28
24
  throw new Error('Redis 客户端未初始化,请先调用 Database.connectRedis()');
29
25
  }
30
26
  this.client = client;
27
+ this.prefix = prefix ? `${prefix}:` : '';
31
28
  }
32
29
 
33
30
  /**
@@ -40,7 +37,7 @@ export class RedisHelper {
40
37
  async setObject<T = any>(key: string, obj: T, ttl: number | null = null): Promise<string | null> {
41
38
  try {
42
39
  const data = JSON.stringify(obj);
43
- const pkey = `${prefix}${key}`;
40
+ const pkey = `${this.prefix}${key}`;
44
41
 
45
42
  if (ttl) {
46
43
  return await this.client.setex(pkey, ttl, data);
@@ -59,7 +56,7 @@ export class RedisHelper {
59
56
  */
60
57
  async getObject<T = any>(key: string): Promise<T | null> {
61
58
  try {
62
- const pkey = `${prefix}${key}`;
59
+ const pkey = `${this.prefix}${key}`;
63
60
  const data = await this.client.get(pkey);
64
61
  return data ? JSON.parse(data) : null;
65
62
  } catch (error: any) {
@@ -74,7 +71,7 @@ export class RedisHelper {
74
71
  */
75
72
  async delObject(key: string): Promise<void> {
76
73
  try {
77
- const pkey = `${prefix}${key}`;
74
+ const pkey = `${this.prefix}${key}`;
78
75
  await this.client.del(pkey);
79
76
  } catch (error: any) {
80
77
  Logger.error('Redis delObject 错误', error);
@@ -90,7 +87,7 @@ export class RedisHelper {
90
87
  */
91
88
  async genTimeID(): Promise<number> {
92
89
  const timestamp = Math.floor(Date.now() / 1000); // 秒级时间戳
93
- const key = `${prefix}time_id_counter:${timestamp}`;
90
+ const key = `${this.prefix}time_id_counter:${timestamp}`;
94
91
 
95
92
  const counter = await this.client.incr(key);
96
93
  await this.client.expire(key, 1);
@@ -118,7 +115,7 @@ export class RedisHelper {
118
115
  }
119
116
 
120
117
  const timestamp = Math.floor(Date.now() / 1000); // 秒级时间戳
121
- const key = `${prefix}time_id_counter:${timestamp}`;
118
+ const key = `${this.prefix}time_id_counter:${timestamp}`;
122
119
 
123
120
  // 使用 INCRBY 一次性获取 N 个连续计数
124
121
  const startCounter = await this.client.incrby(key, count);
@@ -143,7 +140,7 @@ export class RedisHelper {
143
140
  */
144
141
  async setString(key: string, value: string, ttl: number | null = null): Promise<string | null> {
145
142
  try {
146
- const pkey = `${prefix}${key}`;
143
+ const pkey = `${this.prefix}${key}`;
147
144
  if (ttl) {
148
145
  return await this.client.setex(pkey, ttl, value);
149
146
  }
@@ -160,7 +157,7 @@ export class RedisHelper {
160
157
  */
161
158
  async getString(key: string): Promise<string | null> {
162
159
  try {
163
- const pkey = `${prefix}${key}`;
160
+ const pkey = `${this.prefix}${key}`;
164
161
  return await this.client.get(pkey);
165
162
  } catch (error: any) {
166
163
  Logger.error('Redis getString 错误', error);
@@ -174,7 +171,7 @@ export class RedisHelper {
174
171
  */
175
172
  async exists(key: string): Promise<number> {
176
173
  try {
177
- const pkey = `${prefix}${key}`;
174
+ const pkey = `${this.prefix}${key}`;
178
175
  return await this.client.exists(pkey);
179
176
  } catch (error: any) {
180
177
  Logger.error('Redis exists 错误', error);
@@ -189,7 +186,7 @@ export class RedisHelper {
189
186
  */
190
187
  async expire(key: string, seconds: number): Promise<number> {
191
188
  try {
192
- const pkey = `${prefix}${key}`;
189
+ const pkey = `${this.prefix}${key}`;
193
190
  return await this.client.expire(pkey, seconds);
194
191
  } catch (error: any) {
195
192
  Logger.error('Redis expire 错误', error);
@@ -203,7 +200,7 @@ export class RedisHelper {
203
200
  */
204
201
  async ttl(key: string): Promise<number> {
205
202
  try {
206
- const pkey = `${prefix}${key}`;
203
+ const pkey = `${this.prefix}${key}`;
207
204
  return await this.client.ttl(pkey);
208
205
  } catch (error: any) {
209
206
  Logger.error('Redis ttl 错误', error);
@@ -221,7 +218,7 @@ export class RedisHelper {
221
218
  try {
222
219
  if (members.length === 0) return 0;
223
220
 
224
- const pkey = `${prefix}${key}`;
221
+ const pkey = `${this.prefix}${key}`;
225
222
  return await this.client.sadd(pkey, ...members);
226
223
  } catch (error: any) {
227
224
  Logger.error('Redis sadd 错误', error);
@@ -237,7 +234,7 @@ export class RedisHelper {
237
234
  */
238
235
  async sismember(key: string, member: string): Promise<number> {
239
236
  try {
240
- const pkey = `${prefix}${key}`;
237
+ const pkey = `${this.prefix}${key}`;
241
238
  return await this.client.sismember(pkey, member);
242
239
  } catch (error: any) {
243
240
  Logger.error('Redis sismember 错误', error);
@@ -252,7 +249,7 @@ export class RedisHelper {
252
249
  */
253
250
  async scard(key: string): Promise<number> {
254
251
  try {
255
- const pkey = `${prefix}${key}`;
252
+ const pkey = `${this.prefix}${key}`;
256
253
  return await this.client.scard(pkey);
257
254
  } catch (error: any) {
258
255
  Logger.error('Redis scard 错误', error);
@@ -267,7 +264,7 @@ export class RedisHelper {
267
264
  */
268
265
  async smembers(key: string): Promise<string[]> {
269
266
  try {
270
- const pkey = `${prefix}${key}`;
267
+ const pkey = `${this.prefix}${key}`;
271
268
  return await this.client.smembers(pkey);
272
269
  } catch (error: any) {
273
270
  Logger.error('Redis smembers 错误', error);
@@ -282,7 +279,7 @@ export class RedisHelper {
282
279
  */
283
280
  async del(key: string): Promise<number> {
284
281
  try {
285
- const pkey = `${prefix}${key}`;
282
+ const pkey = `${this.prefix}${key}`;
286
283
  return await this.client.del(pkey);
287
284
  } catch (error: any) {
288
285
  Logger.error('Redis del 错误', error);