befly 3.2.1 → 3.3.1
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/bin/index.ts +138 -0
- package/checks/conflict.ts +35 -25
- package/checks/table.ts +6 -6
- package/commands/addon.ts +57 -0
- package/commands/build.ts +74 -0
- package/commands/dev.ts +94 -0
- package/commands/index.ts +252 -0
- package/commands/script.ts +303 -0
- package/commands/start.ts +80 -0
- package/commands/syncApi.ts +327 -0
- package/{scripts → commands}/syncDb/apply.ts +2 -2
- package/{scripts → commands}/syncDb/constants.ts +13 -7
- package/{scripts → commands}/syncDb/ddl.ts +7 -5
- package/{scripts → commands}/syncDb/helpers.ts +18 -18
- package/{scripts → commands}/syncDb/index.ts +37 -23
- package/{scripts → commands}/syncDb/sqlite.ts +1 -1
- package/{scripts → commands}/syncDb/state.ts +10 -4
- package/{scripts → commands}/syncDb/table.ts +7 -7
- package/{scripts → commands}/syncDb/tableCreate.ts +7 -6
- package/{scripts → commands}/syncDb/types.ts +5 -5
- package/{scripts → commands}/syncDb/version.ts +1 -1
- package/commands/syncDb.ts +35 -0
- package/commands/syncDev.ts +174 -0
- package/commands/syncMenu.ts +368 -0
- package/config/env.ts +4 -4
- package/config/menu.json +67 -0
- package/{utils/crypto.ts → lib/cipher.ts} +16 -67
- package/lib/database.ts +296 -0
- package/{utils → lib}/dbHelper.ts +102 -56
- package/{utils → lib}/jwt.ts +124 -151
- package/{utils → lib}/logger.ts +47 -24
- package/lib/middleware.ts +271 -0
- package/{utils → lib}/redisHelper.ts +4 -4
- package/{utils/validate.ts → lib/validator.ts} +101 -78
- package/lifecycle/bootstrap.ts +63 -0
- package/lifecycle/checker.ts +165 -0
- package/lifecycle/cluster.ts +241 -0
- package/lifecycle/lifecycle.ts +139 -0
- package/lifecycle/loader.ts +513 -0
- package/main.ts +14 -12
- package/package.json +21 -9
- package/paths.ts +34 -0
- package/plugins/cache.ts +187 -0
- package/plugins/db.ts +4 -4
- package/plugins/logger.ts +1 -1
- package/plugins/redis.ts +4 -4
- package/router/api.ts +155 -0
- package/router/root.ts +53 -0
- package/router/static.ts +76 -0
- package/types/api.d.ts +0 -36
- package/types/befly.d.ts +8 -6
- package/types/common.d.ts +1 -1
- package/types/context.d.ts +3 -3
- package/types/util.d.ts +45 -0
- package/util.ts +299 -0
- package/config/fields.ts +0 -55
- package/config/regexAliases.ts +0 -51
- package/config/reserved.ts +0 -96
- package/scripts/syncDb/tests/constants.test.ts +0 -105
- package/scripts/syncDb/tests/ddl.test.ts +0 -134
- package/scripts/syncDb/tests/helpers.test.ts +0 -70
- package/scripts/syncDb.ts +0 -10
- package/types/index.d.ts +0 -450
- package/types/index.ts +0 -438
- package/types/validator.ts +0 -43
- package/utils/colors.ts +0 -221
- package/utils/database.ts +0 -348
- package/utils/helper.ts +0 -812
- package/utils/index.ts +0 -33
- package/utils/requestContext.ts +0 -167
- /package/{scripts → commands}/syncDb/schema.ts +0 -0
- /package/{utils → lib}/sqlBuilder.ts +0 -0
- /package/{utils → lib}/xml.ts +0 -0
package/{utils → lib}/jwt.ts
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JWT 工具类 -
|
|
3
|
-
*
|
|
2
|
+
* JWT 工具类 - Befly 项目专用
|
|
3
|
+
* 直接集成环境变量,提供开箱即用的 JWT 功能
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { createHmac } from 'crypto';
|
|
@@ -96,36 +96,22 @@ export class Jwt {
|
|
|
96
96
|
/**
|
|
97
97
|
* 签名 JWT token
|
|
98
98
|
* @param payload - JWT载荷数据
|
|
99
|
-
* @param options -
|
|
99
|
+
* @param options - 签名选项(自动使用 Env.JWT_SECRET)
|
|
100
100
|
* @returns JWT token字符串
|
|
101
101
|
*/
|
|
102
|
-
static sign(payload: JwtPayload, options
|
|
103
|
-
static sign(payload: JwtPayload, secret: string, expiresIn: string | number): string;
|
|
104
|
-
static sign(payload: JwtPayload, optionsOrSecret: JwtSignOptions | string = {}, expiresIn?: string | number): string {
|
|
102
|
+
static sign(payload: JwtPayload, options?: JwtSignOptions): string {
|
|
105
103
|
if (!payload || typeof payload !== 'object') {
|
|
106
104
|
throw new Error('载荷必须是非空对象');
|
|
107
105
|
}
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
let options: JwtSignOptions;
|
|
111
|
-
if (typeof optionsOrSecret === 'string') {
|
|
112
|
-
// sign(payload, secret, expiresIn) 形式
|
|
113
|
-
options = {
|
|
114
|
-
secret: optionsOrSecret,
|
|
115
|
-
expiresIn: expiresIn
|
|
116
|
-
};
|
|
117
|
-
} else {
|
|
118
|
-
// sign(payload, options) 形式
|
|
119
|
-
options = optionsOrSecret;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const secret = options.secret || Env.JWT_SECRET;
|
|
123
|
-
const algorithm = (options.algorithm || Env.JWT_ALGORITHM || 'HS256') as JwtAlgorithm;
|
|
124
|
-
|
|
107
|
+
const secret = options?.secret || Env.JWT_SECRET;
|
|
125
108
|
if (!secret) {
|
|
126
|
-
throw new Error('JWT
|
|
109
|
+
throw new Error('JWT密钥未配置');
|
|
127
110
|
}
|
|
128
111
|
|
|
112
|
+
const opts = options || {};
|
|
113
|
+
const algorithm = (opts.algorithm || Env.JWT_ALGORITHM || 'HS256') as JwtAlgorithm;
|
|
114
|
+
|
|
129
115
|
const now = Math.floor(Date.now() / 1000);
|
|
130
116
|
|
|
131
117
|
// 创建 header
|
|
@@ -139,17 +125,22 @@ export class Jwt {
|
|
|
139
125
|
// 创建 payload
|
|
140
126
|
const jwtPayload: JwtPayload = { ...payload, iat: now };
|
|
141
127
|
|
|
142
|
-
if (
|
|
143
|
-
const expSeconds = Jwt.parseExpiration(
|
|
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);
|
|
144
135
|
jwtPayload.exp = now + expSeconds;
|
|
145
136
|
}
|
|
146
|
-
if (
|
|
147
|
-
if (
|
|
148
|
-
if (
|
|
149
|
-
if (
|
|
150
|
-
jwtPayload.nbf = typeof
|
|
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);
|
|
151
142
|
}
|
|
152
|
-
if (
|
|
143
|
+
if (opts.jwtId) jwtPayload.jti = opts.jwtId;
|
|
153
144
|
|
|
154
145
|
const encodedPayload = Jwt.base64UrlEncode(JSON.stringify(jwtPayload));
|
|
155
146
|
|
|
@@ -163,31 +154,21 @@ export class Jwt {
|
|
|
163
154
|
/**
|
|
164
155
|
* 验证 JWT token
|
|
165
156
|
* @param token - JWT token字符串
|
|
166
|
-
* @param options -
|
|
157
|
+
* @param options - 验证选项(自动使用 Env.JWT_SECRET)
|
|
167
158
|
* @returns 解码后的载荷数据
|
|
168
159
|
*/
|
|
169
|
-
static verify(token: string, options
|
|
170
|
-
static verify(token: string, secret: string): JwtPayload;
|
|
171
|
-
static verify(token: string, optionsOrSecret: JwtVerifyOptions | string = {}): JwtPayload {
|
|
160
|
+
static verify(token: string, options?: JwtVerifyOptions): JwtPayload {
|
|
172
161
|
if (!token || typeof token !== 'string') {
|
|
173
162
|
throw new Error('Token必须是非空字符串');
|
|
174
163
|
}
|
|
175
164
|
|
|
176
|
-
|
|
177
|
-
let options: JwtVerifyOptions;
|
|
178
|
-
if (typeof optionsOrSecret === 'string') {
|
|
179
|
-
// verify(token, secret) 形式
|
|
180
|
-
options = { secret: optionsOrSecret };
|
|
181
|
-
} else {
|
|
182
|
-
// verify(token, options) 形式
|
|
183
|
-
options = optionsOrSecret;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const secret = options.secret || Env.JWT_SECRET;
|
|
165
|
+
const secret = options?.secret || Env.JWT_SECRET;
|
|
187
166
|
if (!secret) {
|
|
188
|
-
throw new Error('JWT
|
|
167
|
+
throw new Error('JWT密钥未配置');
|
|
189
168
|
}
|
|
190
169
|
|
|
170
|
+
const opts = options || {};
|
|
171
|
+
|
|
191
172
|
const parts = token.split('.');
|
|
192
173
|
if (parts.length !== 3) {
|
|
193
174
|
throw new Error('JWT格式无效');
|
|
@@ -215,24 +196,24 @@ export class Jwt {
|
|
|
215
196
|
// 验证时间
|
|
216
197
|
const now = Math.floor(Date.now() / 1000);
|
|
217
198
|
|
|
218
|
-
if (!
|
|
199
|
+
if (!opts.ignoreExpiration && payload.exp && payload.exp < now) {
|
|
219
200
|
throw new Error('Token已过期 (expired)');
|
|
220
201
|
}
|
|
221
|
-
if (!
|
|
202
|
+
if (!opts.ignoreNotBefore && payload.nbf && payload.nbf > now) {
|
|
222
203
|
throw new Error('Token尚未生效');
|
|
223
204
|
}
|
|
224
205
|
|
|
225
206
|
// 验证 issuer、audience、subject
|
|
226
|
-
if (
|
|
207
|
+
if (opts.issuer && payload.iss !== opts.issuer) {
|
|
227
208
|
throw new Error('Token发行者无效');
|
|
228
209
|
}
|
|
229
|
-
if (
|
|
210
|
+
if (opts.audience) {
|
|
230
211
|
const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
|
|
231
|
-
if (!audiences.includes(
|
|
212
|
+
if (!audiences.includes(opts.audience)) {
|
|
232
213
|
throw new Error('Token受众无效');
|
|
233
214
|
}
|
|
234
215
|
}
|
|
235
|
-
if (
|
|
216
|
+
if (opts.subject && payload.sub !== opts.subject) {
|
|
236
217
|
throw new Error('Token主题无效');
|
|
237
218
|
}
|
|
238
219
|
|
|
@@ -298,68 +279,146 @@ export class Jwt {
|
|
|
298
279
|
return this.getTimeToExpiry(token) <= 0;
|
|
299
280
|
}
|
|
300
281
|
|
|
301
|
-
|
|
282
|
+
/**
|
|
283
|
+
* 检查 token 是否即将过期
|
|
284
|
+
* @param token - JWT token字符串
|
|
285
|
+
* @param thresholdSeconds - 过期阈值(秒),默认300秒(5分钟)
|
|
286
|
+
* @returns 是否即将过期
|
|
287
|
+
*/
|
|
288
|
+
static isNearExpiry(token: string, thresholdSeconds: number = 300): boolean {
|
|
289
|
+
const timeToExpiry = this.getTimeToExpiry(token);
|
|
290
|
+
return timeToExpiry > 0 && timeToExpiry <= thresholdSeconds;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* 检查载荷是否包含特定角色
|
|
295
|
+
* @param payload - JWT载荷
|
|
296
|
+
* @param role - 要检查的角色
|
|
297
|
+
* @returns 是否包含该角色
|
|
298
|
+
*/
|
|
299
|
+
static hasRole(payload: JwtPayload, role: string): boolean {
|
|
300
|
+
if (!payload) return false;
|
|
301
|
+
if (payload.role === role) return true;
|
|
302
|
+
if (Array.isArray(payload.roles) && payload.roles.includes(role)) return true;
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* 检查载荷是否包含任意一个角色
|
|
308
|
+
* @param payload - JWT载荷
|
|
309
|
+
* @param roles - 要检查的角色列表
|
|
310
|
+
* @returns 是否包含任意一个角色
|
|
311
|
+
*/
|
|
312
|
+
static hasAnyRole(payload: JwtPayload, roles: string[]): boolean {
|
|
313
|
+
if (!payload) return false;
|
|
314
|
+
return roles.some((role) => this.hasRole(payload, role));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 检查载荷是否包含特定权限
|
|
319
|
+
* @param payload - JWT载荷
|
|
320
|
+
* @param permission - 要检查的权限
|
|
321
|
+
* @returns 是否包含该权限
|
|
322
|
+
*/
|
|
323
|
+
static hasPermission(payload: JwtPayload, permission: string): boolean {
|
|
324
|
+
if (!payload || !payload.permissions) return false;
|
|
325
|
+
return Array.isArray(payload.permissions) && payload.permissions.includes(permission);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* 检查载荷是否包含所有权限
|
|
330
|
+
* @param payload - JWT载荷
|
|
331
|
+
* @param permissions - 要检查的权限列表
|
|
332
|
+
* @returns 是否包含所有权限
|
|
333
|
+
*/
|
|
334
|
+
static hasAllPermissions(payload: JwtPayload, permissions: string[]): boolean {
|
|
335
|
+
if (!payload || !payload.permissions) return false;
|
|
336
|
+
return permissions.every((permission) => this.hasPermission(payload, permission));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ==================== 便捷方法(自动使用环境变量) ====================
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 创建用户 token(快捷方法)
|
|
343
|
+
*/
|
|
344
|
+
static create(payload: JwtPayload): string {
|
|
345
|
+
return this.sign(payload);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 检查 token(快捷方法)
|
|
350
|
+
*/
|
|
351
|
+
static check(token: string): JwtPayload {
|
|
352
|
+
return this.verify(token);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* 解析 token(快捷方法,不验证签名)
|
|
357
|
+
*/
|
|
358
|
+
static parse(token: string): JwtPayload {
|
|
359
|
+
return this.decode(token);
|
|
360
|
+
}
|
|
302
361
|
|
|
303
362
|
/**
|
|
304
363
|
* 签名用户认证 token
|
|
305
364
|
*/
|
|
306
|
-
static signUserToken(userInfo: JwtPayload, options
|
|
365
|
+
static signUserToken(userInfo: JwtPayload, options?: JwtSignOptions): string {
|
|
307
366
|
return this.sign(userInfo, options);
|
|
308
367
|
}
|
|
309
368
|
|
|
310
369
|
/**
|
|
311
370
|
* 签名 API 访问 token
|
|
312
371
|
*/
|
|
313
|
-
static signAPIToken(payload: JwtPayload, options
|
|
372
|
+
static signAPIToken(payload: JwtPayload, options?: JwtSignOptions): string {
|
|
314
373
|
return this.sign(payload, { audience: 'api', expiresIn: '1h', ...options });
|
|
315
374
|
}
|
|
316
375
|
|
|
317
376
|
/**
|
|
318
377
|
* 签名刷新 token
|
|
319
378
|
*/
|
|
320
|
-
static signRefreshToken(payload: JwtPayload, options
|
|
379
|
+
static signRefreshToken(payload: JwtPayload, options?: JwtSignOptions): string {
|
|
321
380
|
return this.sign(payload, { audience: 'refresh', expiresIn: '30d', ...options });
|
|
322
381
|
}
|
|
323
382
|
|
|
324
383
|
/**
|
|
325
384
|
* 签名临时 token (用于重置密码等)
|
|
326
385
|
*/
|
|
327
|
-
static signTempToken(payload: JwtPayload, options
|
|
386
|
+
static signTempToken(payload: JwtPayload, options?: JwtSignOptions): string {
|
|
328
387
|
return this.sign(payload, { audience: 'temporary', expiresIn: '15m', ...options });
|
|
329
388
|
}
|
|
330
389
|
|
|
331
390
|
/**
|
|
332
391
|
* 验证用户认证 token
|
|
333
392
|
*/
|
|
334
|
-
static verifyUserToken(token: string, options
|
|
393
|
+
static verifyUserToken(token: string, options?: JwtVerifyOptions): JwtPayload {
|
|
335
394
|
return this.verify(token, options);
|
|
336
395
|
}
|
|
337
396
|
|
|
338
397
|
/**
|
|
339
398
|
* 验证 API 访问 token
|
|
340
399
|
*/
|
|
341
|
-
static verifyAPIToken(token: string, options
|
|
400
|
+
static verifyAPIToken(token: string, options?: JwtVerifyOptions): JwtPayload {
|
|
342
401
|
return this.verify(token, { audience: 'api', ...options });
|
|
343
402
|
}
|
|
344
403
|
|
|
345
404
|
/**
|
|
346
405
|
* 验证刷新 token
|
|
347
406
|
*/
|
|
348
|
-
static verifyRefreshToken(token: string, options
|
|
407
|
+
static verifyRefreshToken(token: string, options?: JwtVerifyOptions): JwtPayload {
|
|
349
408
|
return this.verify(token, { audience: 'refresh', ...options });
|
|
350
409
|
}
|
|
351
410
|
|
|
352
411
|
/**
|
|
353
412
|
* 验证临时 token
|
|
354
413
|
*/
|
|
355
|
-
static verifyTempToken(token: string, options
|
|
414
|
+
static verifyTempToken(token: string, options?: JwtVerifyOptions): JwtPayload {
|
|
356
415
|
return this.verify(token, { audience: 'temporary', ...options });
|
|
357
416
|
}
|
|
358
417
|
|
|
359
418
|
/**
|
|
360
419
|
* 验证 token 并检查权限
|
|
361
420
|
*/
|
|
362
|
-
static verifyWithPermissions(token: string, requiredPermissions: string | string[], options
|
|
421
|
+
static verifyWithPermissions(token: string, requiredPermissions: string | string[], options?: JwtVerifyOptions): JwtPayload {
|
|
363
422
|
const payload = this.verify(token, options);
|
|
364
423
|
|
|
365
424
|
if (!payload.permissions) {
|
|
@@ -379,7 +438,7 @@ export class Jwt {
|
|
|
379
438
|
/**
|
|
380
439
|
* 验证 token 并检查角色
|
|
381
440
|
*/
|
|
382
|
-
static verifyWithRoles(token: string, requiredRoles: string | string[], options
|
|
441
|
+
static verifyWithRoles(token: string, requiredRoles: string | string[], options?: JwtVerifyOptions): JwtPayload {
|
|
383
442
|
const payload = this.verify(token, options);
|
|
384
443
|
|
|
385
444
|
if (!payload.role && !payload.roles) {
|
|
@@ -401,93 +460,7 @@ export class Jwt {
|
|
|
401
460
|
/**
|
|
402
461
|
* 软验证 token (忽略过期时间)
|
|
403
462
|
*/
|
|
404
|
-
static verifySoft(token: string, options
|
|
463
|
+
static verifySoft(token: string, options?: JwtVerifyOptions): JwtPayload {
|
|
405
464
|
return this.verify(token, { ignoreExpiration: true, ...options });
|
|
406
465
|
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* 检查 token 是否即将过期
|
|
410
|
-
* @param token - JWT token字符串
|
|
411
|
-
* @param thresholdSeconds - 过期阈值(秒),默认300秒(5分钟)
|
|
412
|
-
* @returns 是否即将过期
|
|
413
|
-
*/
|
|
414
|
-
static isNearExpiry(token: string, thresholdSeconds: number = 300): boolean {
|
|
415
|
-
const timeToExpiry = this.getTimeToExpiry(token);
|
|
416
|
-
return timeToExpiry > 0 && timeToExpiry <= thresholdSeconds;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// ==================== 便捷静态方法 ====================
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* 创建 token(使用默认配置)
|
|
423
|
-
* @param payload - 载荷数据
|
|
424
|
-
* @returns JWT token字符串
|
|
425
|
-
*/
|
|
426
|
-
static create(payload: JwtPayload): string {
|
|
427
|
-
return this.sign(payload, Env.JWT_SECRET, Env.JWT_EXPIRES_IN || '7d');
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* 验证 token(使用默认配置)
|
|
432
|
-
* @param token - JWT token字符串
|
|
433
|
-
* @returns 解码后的载荷数据
|
|
434
|
-
*/
|
|
435
|
-
static check(token: string): JwtPayload {
|
|
436
|
-
return this.verify(token, { secret: Env.JWT_SECRET });
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* 解析 token(不验证签名,使用 decode)
|
|
441
|
-
* @param token - JWT token字符串
|
|
442
|
-
* @returns 解码后的载荷数据
|
|
443
|
-
*/
|
|
444
|
-
static parse(token: string): JwtPayload {
|
|
445
|
-
return this.decode(token);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* 检查载荷是否包含特定角色
|
|
450
|
-
* @param payload - JWT载荷
|
|
451
|
-
* @param role - 要检查的角色
|
|
452
|
-
* @returns 是否包含该角色
|
|
453
|
-
*/
|
|
454
|
-
static hasRole(payload: JwtPayload, role: string): boolean {
|
|
455
|
-
if (!payload) return false;
|
|
456
|
-
if (payload.role === role) return true;
|
|
457
|
-
if (Array.isArray(payload.roles) && payload.roles.includes(role)) return true;
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* 检查载荷是否包含任意一个角色
|
|
463
|
-
* @param payload - JWT载荷
|
|
464
|
-
* @param roles - 要检查的角色列表
|
|
465
|
-
* @returns 是否包含任意一个角色
|
|
466
|
-
*/
|
|
467
|
-
static hasAnyRole(payload: JwtPayload, roles: string[]): boolean {
|
|
468
|
-
if (!payload) return false;
|
|
469
|
-
return roles.some((role) => this.hasRole(payload, role));
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* 检查载荷是否包含特定权限
|
|
474
|
-
* @param payload - JWT载荷
|
|
475
|
-
* @param permission - 要检查的权限
|
|
476
|
-
* @returns 是否包含该权限
|
|
477
|
-
*/
|
|
478
|
-
static hasPermission(payload: JwtPayload, permission: string): boolean {
|
|
479
|
-
if (!payload || !payload.permissions) return false;
|
|
480
|
-
return Array.isArray(payload.permissions) && payload.permissions.includes(permission);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* 检查载荷是否包含所有权限
|
|
485
|
-
* @param payload - JWT载荷
|
|
486
|
-
* @param permissions - 要检查的权限列表
|
|
487
|
-
* @returns 是否包含所有权限
|
|
488
|
-
*/
|
|
489
|
-
static hasAllPermissions(payload: JwtPayload, permissions: string[]): boolean {
|
|
490
|
-
if (!payload || !payload.permissions) return false;
|
|
491
|
-
return permissions.every((permission) => this.hasPermission(payload, permission));
|
|
492
|
-
}
|
|
493
466
|
}
|
package/{utils → lib}/logger.ts
RENAMED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 日志系统 -
|
|
3
|
-
*
|
|
2
|
+
* 日志系统 - Befly 项目专用
|
|
3
|
+
* 直接集成环境变量,提供开箱即用的日志功能
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import { join } from 'pathe';
|
|
7
7
|
import { appendFile, stat } from 'node:fs/promises';
|
|
8
|
-
import
|
|
9
|
-
import { Colors } from './colors.js';
|
|
8
|
+
import chalk from 'chalk';
|
|
10
9
|
import { Env } from '../config/env.js';
|
|
11
10
|
import type { LogLevel } from '../types/common.js';
|
|
12
11
|
|
|
@@ -15,15 +14,31 @@ import type { LogLevel } from '../types/common.js';
|
|
|
15
14
|
*/
|
|
16
15
|
type LogMessage = string | number | boolean | null | undefined | Record<string, any> | any[];
|
|
17
16
|
|
|
17
|
+
/**
|
|
18
|
+
* 格式化日期时间
|
|
19
|
+
*/
|
|
20
|
+
function formatDate(): string {
|
|
21
|
+
const now = new Date();
|
|
22
|
+
const year = now.getFullYear();
|
|
23
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
24
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
25
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
26
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
27
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
28
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
/**
|
|
19
32
|
* 日志器类
|
|
20
33
|
*/
|
|
21
34
|
export class Logger {
|
|
22
|
-
/**
|
|
23
|
-
static
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
/** 日志配置(直接使用 Env) */
|
|
36
|
+
private static readonly config = {
|
|
37
|
+
logDir: Env.LOG_DIR || 'logs',
|
|
38
|
+
maxFileSize: Env.LOG_MAX_SIZE || 50 * 1024 * 1024,
|
|
39
|
+
enableDebug: Env.LOG_DEBUG === 1,
|
|
40
|
+
toConsole: Env.LOG_TO_CONSOLE === 1
|
|
41
|
+
};
|
|
27
42
|
|
|
28
43
|
/** 当前使用的日志文件缓存 */
|
|
29
44
|
private static currentFiles: Map<string, string> = new Map();
|
|
@@ -34,16 +49,16 @@ export class Logger {
|
|
|
34
49
|
* @param message - 日志消息
|
|
35
50
|
*/
|
|
36
51
|
static async log(level: LogLevel, message: LogMessage): Promise<void> {
|
|
37
|
-
// debug 日志特殊处理:仅当
|
|
38
|
-
if (level === 'debug' &&
|
|
52
|
+
// debug 日志特殊处理:仅当 enableDebug 为 true 时才记录
|
|
53
|
+
if (level === 'debug' && !this.config.enableDebug) return;
|
|
39
54
|
|
|
40
55
|
// 格式化消息
|
|
41
56
|
const timestamp = formatDate();
|
|
42
57
|
const colorMap = {
|
|
43
|
-
info:
|
|
44
|
-
debug:
|
|
45
|
-
warn:
|
|
46
|
-
error:
|
|
58
|
+
info: chalk.greenBright,
|
|
59
|
+
debug: chalk.cyanBright,
|
|
60
|
+
warn: chalk.yellowBright,
|
|
61
|
+
error: chalk.redBright
|
|
47
62
|
};
|
|
48
63
|
|
|
49
64
|
// 处理消息内容
|
|
@@ -59,7 +74,7 @@ export class Logger {
|
|
|
59
74
|
const coloredMessage = `[${timestamp}] ${coloredLevelStr} - ${content}`;
|
|
60
75
|
|
|
61
76
|
// 控制台输出
|
|
62
|
-
if (
|
|
77
|
+
if (this.config.toConsole) {
|
|
63
78
|
console.log(coloredMessage);
|
|
64
79
|
}
|
|
65
80
|
|
|
@@ -69,6 +84,14 @@ export class Logger {
|
|
|
69
84
|
await this.writeToFile(plainMessage, level);
|
|
70
85
|
}
|
|
71
86
|
|
|
87
|
+
/**
|
|
88
|
+
* 记录成功日志(使用 info 级别)
|
|
89
|
+
* @param message - 日志消息
|
|
90
|
+
*/
|
|
91
|
+
static async success(message: LogMessage): Promise<void> {
|
|
92
|
+
await this.log('info', message);
|
|
93
|
+
}
|
|
94
|
+
|
|
72
95
|
/**
|
|
73
96
|
* 写入日志文件
|
|
74
97
|
* @param message - 格式化后的消息
|
|
@@ -85,7 +108,7 @@ export class Logger {
|
|
|
85
108
|
if (currentLogFile) {
|
|
86
109
|
try {
|
|
87
110
|
const stats = await stat(currentLogFile);
|
|
88
|
-
if (stats.size >= this.maxFileSize) {
|
|
111
|
+
if (stats.size >= this.config.maxFileSize) {
|
|
89
112
|
this.currentFiles.delete(prefix);
|
|
90
113
|
currentLogFile = undefined;
|
|
91
114
|
}
|
|
@@ -98,7 +121,7 @@ export class Logger {
|
|
|
98
121
|
// 查找或创建新文件
|
|
99
122
|
if (!currentLogFile) {
|
|
100
123
|
const glob = new Bun.Glob(`${prefix}.*.log`);
|
|
101
|
-
const files = await Array.fromAsync(glob.scan(this.logDir));
|
|
124
|
+
const files = await Array.fromAsync(glob.scan(this.config.logDir));
|
|
102
125
|
|
|
103
126
|
// 按索引排序并查找可用文件
|
|
104
127
|
const getIndex = (f: string) => parseInt(f.match(/\.(\d+)\.log$/)?.[1] || '0');
|
|
@@ -106,10 +129,10 @@ export class Logger {
|
|
|
106
129
|
|
|
107
130
|
let foundFile = false;
|
|
108
131
|
for (let i = files.length - 1; i >= 0; i--) {
|
|
109
|
-
const filePath =
|
|
132
|
+
const filePath = join(this.config.logDir, files[i]);
|
|
110
133
|
try {
|
|
111
134
|
const stats = await stat(filePath);
|
|
112
|
-
if (stats.size < this.maxFileSize) {
|
|
135
|
+
if (stats.size < this.config.maxFileSize) {
|
|
113
136
|
currentLogFile = filePath;
|
|
114
137
|
foundFile = true;
|
|
115
138
|
break;
|
|
@@ -119,10 +142,10 @@ export class Logger {
|
|
|
119
142
|
}
|
|
120
143
|
}
|
|
121
144
|
|
|
122
|
-
//
|
|
145
|
+
// 没有可用文件,创建新文件
|
|
123
146
|
if (!foundFile) {
|
|
124
147
|
const maxIndex = files.length > 0 ? Math.max(...files.map(getIndex)) : -1;
|
|
125
|
-
currentLogFile =
|
|
148
|
+
currentLogFile = join(this.config.logDir, `${prefix}.${maxIndex + 1}.log`);
|
|
126
149
|
}
|
|
127
150
|
|
|
128
151
|
this.currentFiles.set(prefix, currentLogFile);
|
|
@@ -175,7 +198,7 @@ export class Logger {
|
|
|
175
198
|
|
|
176
199
|
/**
|
|
177
200
|
* 记录调试日志
|
|
178
|
-
* 受
|
|
201
|
+
* 受 enableDebug 配置控制,仅当 enableDebug=true 时才记录
|
|
179
202
|
* @param message - 日志消息
|
|
180
203
|
*/
|
|
181
204
|
static async debug(message: LogMessage): Promise<void> {
|