befly 2.1.1 → 2.2.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.
@@ -0,0 +1,471 @@
1
+ import { Env } from '../config/env.js';
2
+ import { Logger } from './logger.js';
3
+ import { createQueryBuilder } from './sqlBuilder.js';
4
+
5
+ // 数据库管理器(从 plugins/db.js 提取)
6
+ export class SqlManager {
7
+ // 私有属性
8
+ #sql;
9
+ #befly;
10
+
11
+ constructor(client, befly) {
12
+ this.#sql = client;
13
+ this.#befly = befly;
14
+ }
15
+
16
+ // 原始连接池访问(兼容占位)
17
+ get pool() {
18
+ return {
19
+ activeConnections: () => 0,
20
+ totalConnections: () => 0,
21
+ idleConnections: () => 0,
22
+ taskQueueSize: () => 0
23
+ };
24
+ }
25
+
26
+ // 创建查询构造器
27
+ query() {
28
+ return createQueryBuilder();
29
+ }
30
+
31
+ // 私有方法:通用数据处理函数 - 自动添加ID、时间戳和状态
32
+ async #processDataForInsert(data) {
33
+ const now = Date.now();
34
+ const genId = async () => (this.#befly?.redis?.genTimeID ? await this.#befly.redis.genTimeID() : now);
35
+
36
+ if (Array.isArray(data)) {
37
+ return await Promise.all(
38
+ data.map(async (item) => ({
39
+ ...item,
40
+ id: await genId(),
41
+ state: 0,
42
+ created_at: now,
43
+ updated_at: now
44
+ }))
45
+ );
46
+ } else {
47
+ return {
48
+ ...data,
49
+ id: await genId(),
50
+ state: 0,
51
+ created_at: now,
52
+ updated_at: now
53
+ };
54
+ }
55
+ }
56
+
57
+ // 私有方法:添加默认的state过滤条件
58
+ #addDefaultStateFilter(where = {}) {
59
+ const hasStateCondition = Object.keys(where).some((key) => key === 'state' || key.startsWith('state$'));
60
+ if (!hasStateCondition) return { ...where, state$ne: 2 };
61
+ return where;
62
+ }
63
+
64
+ // 私有方法:执行 SQL(支持传入事务连接对象)
65
+ async #executeWithConn(query, params = [], conn = null) {
66
+ if (!query || typeof query !== 'string') {
67
+ throw new Error('SQL 语句是必需的');
68
+ }
69
+
70
+ const isSelectLike = /^\s*(with|select|show|desc|explain)\b/i.test(query);
71
+ const isWriteLike = /^\s*(insert|update|delete|replace)\b/i.test(query);
72
+ const client = conn || this.#sql;
73
+ try {
74
+ if (Env.MYSQL_DEBUG === 1) {
75
+ Logger.debug('执行SQL:', { sql: query, params });
76
+ }
77
+ // 读查询
78
+ if (isSelectLike) {
79
+ if (params && params.length > 0) {
80
+ return await client.unsafe(query, params);
81
+ } else {
82
+ return await client.unsafe(query);
83
+ }
84
+ }
85
+
86
+ // 写查询
87
+ if (isWriteLike) {
88
+ const runOn = conn ? client : await this.#sql.reserve();
89
+ try {
90
+ if (params && params.length > 0) {
91
+ await runOn.unsafe(query, params);
92
+ } else {
93
+ await runOn.unsafe(query);
94
+ }
95
+ const [{ affectedRows }] = await runOn`SELECT ROW_COUNT() AS affectedRows`;
96
+ const [{ insertId }] = await runOn`SELECT LAST_INSERT_ID() AS insertId`;
97
+ return { affectedRows: Number(affectedRows) || 0, insertId: Number(insertId) || 0 };
98
+ } finally {
99
+ if (!conn && runOn && typeof runOn.release === 'function') runOn.release();
100
+ }
101
+ }
102
+
103
+ // 其他(DDL等)
104
+ if (params && params.length > 0) {
105
+ return await client.unsafe(query, params);
106
+ } else {
107
+ return await client.unsafe(query);
108
+ }
109
+ } catch (error) {
110
+ Logger.error('SQL 执行失败:', { sql: query, params, error: error.message });
111
+ throw error;
112
+ }
113
+ }
114
+
115
+ // 私有方法:获取单条记录详情(支持传入连接对象)
116
+ async #getDetailWithConn(table, options = {}, conn = null) {
117
+ if (!table || typeof table !== 'string') {
118
+ throw new Error('表名是必需的');
119
+ }
120
+
121
+ const { where = {}, fields = '*', leftJoins = [] } = typeof options === 'object' && !Array.isArray(options) ? options : { where: options };
122
+
123
+ try {
124
+ const filteredWhere = this.#addDefaultStateFilter(where);
125
+ const builder = createQueryBuilder().select(fields).from(table).where(filteredWhere).limit(1);
126
+
127
+ leftJoins.forEach((join) => {
128
+ if (typeof join === 'string') {
129
+ const parts = join.split(' ON ');
130
+ if (parts.length === 2) builder.leftJoin(parts[0].trim(), parts[1].trim());
131
+ } else if (join && typeof join === 'object' && join.table && join.on) {
132
+ builder.leftJoin(join.table, join.on);
133
+ }
134
+ });
135
+
136
+ const { sql: q, params } = builder.toSelectSql();
137
+ const result = await this.#executeWithConn(q, params, conn);
138
+ return result[0] || null;
139
+ } catch (error) {
140
+ Logger.error('getDetail 执行失败:', error);
141
+ throw error;
142
+ }
143
+ }
144
+
145
+ // 私有方法:获取列表(支持传入连接对象)
146
+ async #getListWithConn(table, options = {}, conn = null) {
147
+ if (!table || typeof table !== 'string') {
148
+ throw new Error('表名是必需的');
149
+ }
150
+
151
+ const { where = {}, fields = '*', leftJoins = [], orderBy = [], groupBy = [], having = [], page = 1, pageSize = 10 } = options;
152
+
153
+ try {
154
+ const filteredWhere = this.#addDefaultStateFilter(where);
155
+ const builder = createQueryBuilder().select(fields).from(table).where(filteredWhere);
156
+
157
+ leftJoins.forEach((join) => {
158
+ if (typeof join === 'string') {
159
+ const parts = join.split(' ON ');
160
+ if (parts.length === 2) builder.leftJoin(parts[0].trim(), parts[1].trim());
161
+ } else if (join && typeof join === 'object' && join.table && join.on) {
162
+ builder.leftJoin(join.table, join.on);
163
+ }
164
+ });
165
+
166
+ if (Array.isArray(groupBy) && groupBy.length > 0) builder.groupBy(groupBy);
167
+ if (Array.isArray(having) && having.length > 0) having.forEach((h) => builder.having(h));
168
+ if (Array.isArray(orderBy) && orderBy.length > 0) builder.orderBy(orderBy);
169
+
170
+ const numPage = parseInt(page) || 1;
171
+ const numPageSize = parseInt(pageSize) || 10;
172
+ if (numPage > 0 && numPageSize > 0) {
173
+ const offset = (numPage - 1) * numPageSize;
174
+ builder.limit(numPageSize, offset);
175
+ }
176
+
177
+ const { sql: q, params } = builder.toSelectSql();
178
+ const rows = await this.#executeWithConn(q, params, conn);
179
+
180
+ let total = 0;
181
+ if (numPage > 0 && numPageSize > 0) {
182
+ const countBuilder = createQueryBuilder().from(table).where(filteredWhere);
183
+ leftJoins.forEach((join) => {
184
+ if (typeof join === 'string') {
185
+ const parts = join.split(' ON ');
186
+ if (parts.length === 2) countBuilder.leftJoin(parts[0].trim(), parts[1].trim());
187
+ } else if (join && typeof join === 'object' && join.table && join.on) {
188
+ countBuilder.leftJoin(join.table, join.on);
189
+ }
190
+ });
191
+
192
+ const { sql: countSql, params: countParams } = countBuilder.toCountSql();
193
+ const countResult = await this.#executeWithConn(countSql, countParams, conn);
194
+ total = countResult[0]?.total || 0;
195
+ }
196
+
197
+ return {
198
+ rows: Array.isArray(rows) ? rows : [],
199
+ total,
200
+ page: numPage,
201
+ pageSize: numPageSize
202
+ };
203
+ } catch (error) {
204
+ Logger.error('getList 执行失败:', error);
205
+ throw error;
206
+ }
207
+ }
208
+
209
+ // 私有方法:获取所有记录(支持传入连接对象)
210
+ async #getAllWithConn(table, options = {}, conn = null) {
211
+ if (!table || typeof table !== 'string') {
212
+ throw new Error('表名是必需的');
213
+ }
214
+
215
+ const { where = {}, fields = '*', leftJoins = [], orderBy = [] } = typeof options === 'object' && !Array.isArray(options) ? options : { where: options };
216
+
217
+ try {
218
+ const filteredWhere = this.#addDefaultStateFilter(where);
219
+ const builder = createQueryBuilder().select(fields).from(table).where(filteredWhere);
220
+
221
+ leftJoins.forEach((join) => {
222
+ if (typeof join === 'string') {
223
+ const parts = join.split(' ON ');
224
+ if (parts.length === 2) builder.leftJoin(parts[0].trim(), parts[1].trim());
225
+ } else if (join && typeof join === 'object' && join.table && join.on) {
226
+ builder.leftJoin(join.table, join.on);
227
+ }
228
+ });
229
+
230
+ if (Array.isArray(orderBy) && orderBy.length > 0) builder.orderBy(orderBy);
231
+
232
+ const { sql: q, params } = builder.toSelectSql();
233
+ const result = await this.#executeWithConn(q, params, conn);
234
+ return Array.isArray(result) ? result : [];
235
+ } catch (error) {
236
+ Logger.error('getAll 执行失败:', error);
237
+ throw error;
238
+ }
239
+ }
240
+
241
+ // 私有方法:插入数据(支持传入连接对象)
242
+ async #insDataWithConn(table, data, conn = null) {
243
+ if (!table || typeof table !== 'string') {
244
+ throw new Error('表名是必需的');
245
+ }
246
+ if (!data) {
247
+ throw new Error('插入数据是必需的');
248
+ }
249
+ try {
250
+ const processedData = await this.#processDataForInsert(data);
251
+ const builder = createQueryBuilder();
252
+ const { sql: q, params } = builder.toInsertSql(table, processedData);
253
+ return await this.#executeWithConn(q, params, conn);
254
+ } catch (error) {
255
+ Logger.error('insData 执行失败:', error);
256
+ throw error;
257
+ }
258
+ }
259
+
260
+ // 私有方法:更新数据(支持传入连接对象)
261
+ async #updDataWithConn(table, data, where, conn = null) {
262
+ if (!table || typeof table !== 'string') {
263
+ throw new Error('表名是必需的');
264
+ }
265
+ if (!data || typeof data !== 'object') {
266
+ throw new Error('更新数据是必需的');
267
+ }
268
+ if (!where) {
269
+ throw new Error('更新操作需要 WHERE 条件');
270
+ }
271
+ try {
272
+ const filteredData = Object.fromEntries(Object.entries(data).filter(([key, value]) => value !== undefined && !['id', 'created_at', 'deleted_at'].includes(key)));
273
+ const updateData = { ...filteredData, updated_at: Date.now() };
274
+ const builder = createQueryBuilder().where(where);
275
+ const { sql: q, params } = builder.toUpdateSql(table, updateData);
276
+ return await this.#executeWithConn(q, params, conn);
277
+ } catch (error) {
278
+ Logger.error('updData 执行失败:', error);
279
+ throw error;
280
+ }
281
+ }
282
+
283
+ // 私有方法:删除数据(支持传入连接对象)
284
+ async #delDataWithConn(table, where, conn = null) {
285
+ if (!table || typeof table !== 'string') {
286
+ throw new Error('表名是必需的');
287
+ }
288
+ if (!where) {
289
+ throw new Error('删除操作需要 WHERE 条件');
290
+ }
291
+ try {
292
+ const builder = createQueryBuilder().where(where);
293
+ const { sql: q, params } = builder.toDeleteSql(table);
294
+ return await this.#executeWithConn(q, params, conn);
295
+ } catch (error) {
296
+ Logger.error('delData 执行失败:', error);
297
+ throw error;
298
+ }
299
+ }
300
+
301
+ // 私有方法:软删除数据(支持传入连接对象)
302
+ async #delData2WithConn(table, where, conn = null) {
303
+ if (!table || typeof table !== 'string') {
304
+ throw new Error('表名是必需的');
305
+ }
306
+ if (!where) {
307
+ throw new Error('软删除操作需要 WHERE 条件');
308
+ }
309
+ try {
310
+ const updateData = { state: 2, updated_at: Date.now() };
311
+ const builder = createQueryBuilder().where(where);
312
+ const { sql: q, params } = builder.toUpdateSql(table, updateData);
313
+ return await this.#executeWithConn(q, params, conn);
314
+ } catch (error) {
315
+ Logger.error('delData2 执行失败:', error);
316
+ throw error;
317
+ }
318
+ }
319
+
320
+ // 私有方法:批量插入(支持传入连接对象)
321
+ async #insBatchWithConn(table, dataArray, conn = null) {
322
+ if (!table || typeof table !== 'string') {
323
+ throw new Error('表名是必需的');
324
+ }
325
+ if (!Array.isArray(dataArray) || dataArray.length === 0) {
326
+ throw new Error('批量插入数据不能为空');
327
+ }
328
+ try {
329
+ const processedDataArray = await this.#processDataForInsert(dataArray);
330
+ const builder = createQueryBuilder();
331
+ const { sql: q, params } = builder.toInsertSql(table, processedDataArray);
332
+ return await this.#executeWithConn(q, params, conn);
333
+ } catch (error) {
334
+ Logger.error('insBatch 执行失败:', error);
335
+ throw error;
336
+ }
337
+ }
338
+
339
+ // 私有方法:获取记录总数(支持传入连接对象)
340
+ async #getCountWithConn(table, options = {}, conn = null) {
341
+ if (!table || typeof table !== 'string') {
342
+ throw new Error('表名是必需的');
343
+ }
344
+ const { where = {}, leftJoins = [] } = typeof options === 'object' && !Array.isArray(options) ? options : { where: options };
345
+ try {
346
+ const filteredWhere = this.#addDefaultStateFilter(where);
347
+ const builder = createQueryBuilder().from(table).where(filteredWhere);
348
+
349
+ leftJoins.forEach((join) => {
350
+ if (typeof join === 'string') {
351
+ const parts = join.split(' ON ');
352
+ if (parts.length === 2) builder.leftJoin(parts[0].trim(), parts[1].trim());
353
+ } else if (join && typeof join === 'object' && join.table && join.on) {
354
+ builder.leftJoin(join.table, join.on);
355
+ }
356
+ });
357
+
358
+ const { sql: q, params } = builder.toCountSql();
359
+ const result = await this.#executeWithConn(q, params, conn);
360
+ return result[0]?.total || 0;
361
+ } catch (error) {
362
+ Logger.error('getCount 执行失败:', error);
363
+ throw error;
364
+ }
365
+ }
366
+
367
+ // 执行原始 SQL - 核心方法
368
+ async execute(sql, params = []) {
369
+ return await this.#executeWithConn(sql, params);
370
+ }
371
+
372
+ // 获取单条记录详情
373
+ async getDetail(table, options = {}) {
374
+ return await this.#getDetailWithConn(table, options);
375
+ }
376
+
377
+ // 获取列表(支持分页)
378
+ async getList(table, options = {}) {
379
+ return await this.#getListWithConn(table, options);
380
+ }
381
+
382
+ // 获取所有记录
383
+ async getAll(table, options = {}) {
384
+ return await this.#getAllWithConn(table, options);
385
+ }
386
+
387
+ // 插入数据 - 增强版
388
+ async insData(table, data) {
389
+ return await this.#insDataWithConn(table, data);
390
+ }
391
+
392
+ // 更新数据 - 增强版
393
+ async updData(table, data, where) {
394
+ return await this.#updDataWithConn(table, data, where);
395
+ }
396
+
397
+ // 删除数据
398
+ async delData(table, where) {
399
+ return await this.#delDataWithConn(table, where);
400
+ }
401
+
402
+ // 软删除数据
403
+ async delData2(table, where) {
404
+ return await this.#delData2WithConn(table, where);
405
+ }
406
+
407
+ // 批量插入 - 增强版
408
+ async insBatch(table, dataArray) {
409
+ return await this.#insBatchWithConn(table, dataArray);
410
+ }
411
+
412
+ // 获取记录总数
413
+ async getCount(table, options = {}) {
414
+ return await this.#getCountWithConn(table, options);
415
+ }
416
+
417
+ // 事务处理
418
+ async trans(callback) {
419
+ if (typeof callback !== 'function') {
420
+ throw new Error('事务回调函数是必需的');
421
+ }
422
+ try {
423
+ const result = await this.#sql.begin(async (tx) => {
424
+ const txMethods = {
425
+ query: async (query, params = []) => this.#executeWithConn(query, params, tx),
426
+ execute: async (query, params = []) => this.#executeWithConn(query, params, tx),
427
+
428
+ getDetail: async (table, options = {}) => this.#getDetailWithConn(table, options, tx),
429
+ getList: async (table, options = {}) => this.#getListWithConn(table, options, tx),
430
+ getAll: async (table, options = {}) => this.#getAllWithConn(table, options, tx),
431
+ insData: async (table, data) => this.#insDataWithConn(table, data, tx),
432
+ updData: async (table, data, where) => this.#updDataWithConn(table, data, where, tx),
433
+ delData: async (table, where) => this.#delDataWithConn(table, where, tx),
434
+ delData2: async (table, where) => this.#delData2WithConn(table, where, tx),
435
+ getCount: async (table, options = {}) => this.#getCountWithConn(table, options, tx),
436
+ insBatch: async (table, dataArray) => this.#insBatchWithConn(table, dataArray, tx)
437
+ };
438
+ return await callback(txMethods);
439
+ });
440
+ return result;
441
+ } catch (error) {
442
+ Logger.info('事务已回滚');
443
+ throw error;
444
+ }
445
+ }
446
+
447
+ // 获取连接池状态
448
+ getPoolStatus() {
449
+ return {
450
+ activeConnections: 0,
451
+ totalConnections: 0,
452
+ idleConnections: 0,
453
+ taskQueueSize: 0
454
+ };
455
+ }
456
+
457
+ // 关闭连接池
458
+ async close() {
459
+ if (this.#sql) {
460
+ try {
461
+ await this.#sql.close();
462
+ Logger.info('数据库连接已关闭');
463
+ } catch (error) {
464
+ Logger.error('关闭数据库连接失败:', error);
465
+ throw error;
466
+ }
467
+ }
468
+ }
469
+ }
470
+
471
+ export default SqlManager;
package/utils/tool.js ADDED
@@ -0,0 +1,31 @@
1
+ import { omitFields } from './index.js';
2
+
3
+ // 工具类:通过构造函数注入 befly
4
+ export class Tool {
5
+ constructor(befly) {
6
+ this.befly = befly;
7
+ }
8
+
9
+ async updData(data, now = Date.now()) {
10
+ const cleaned = omitFields(data ?? {}, ['id', 'created_at', 'deleted_at'], [undefined]);
11
+ return { ...cleaned, updated_at: now };
12
+ }
13
+
14
+ async insData(data, now = Date.now()) {
15
+ const genId = async () => await this.befly.redis.genTimeID();
16
+
17
+ if (Array.isArray(data)) {
18
+ return await Promise.all(
19
+ data.map(async (item) => ({
20
+ ...omitFields(item ?? {}, [], [undefined]),
21
+ id: await genId(),
22
+ created_at: now,
23
+ updated_at: now
24
+ }))
25
+ );
26
+ } else {
27
+ const cleaned = omitFields(data ?? {}, [], [undefined]);
28
+ return { ...cleaned, id: await genId(), created_at: now, updated_at: now };
29
+ }
30
+ }
31
+ }
package/utils/validate.js CHANGED
@@ -1,6 +1,6 @@
1
- import { isType, parseFieldRule } from './util.js';
1
+ import { isType, parseFieldRule } from './index.js';
2
2
 
3
- // 移除本文件重复实现,统一复用 util.js 导出的校验函数与 parseFieldRule
3
+ // 移除本文件重复实现,统一复用 index.js 导出的校验函数与 parseFieldRule
4
4
 
5
5
  /**
6
6
  * 验证器类
package/.gitignore DELETED
@@ -1,94 +0,0 @@
1
- # Logs
2
- logs
3
- *.log
4
- npm-debug.log*
5
- esdata
6
- data
7
- .cache
8
- lab
9
- labs
10
- cache
11
- .VSCodeCounter
12
- oclif.manifest.json
13
- dist
14
- report.html
15
- dist-ssr
16
- *.local
17
- .vscode-test/
18
- *.vsix
19
- out
20
- CHANGELOG.md
21
- note.md
22
- .changeset
23
- addons2
24
- pnpm-lock.yaml
25
- bun.lock
26
- other
27
-
28
- # Editor directories and files
29
- .vscode/*
30
- .cache
31
- .yicode/auto-imports.d.ts
32
- !.vscode/extensions.json
33
- .idea
34
- .DS_Store
35
- *.suo
36
- *.ntvs*
37
- *.njsproj
38
- *.sln
39
- *.sw?
40
-
41
- # Runtime data
42
- pids
43
- *.pid
44
- *.seed
45
-
46
- # Directory for instrumented libs generated by jscoverage/JSCover
47
- lib-cov
48
-
49
- # Coverage directory used by tools like istanbul
50
- coverage
51
-
52
- # nyc test coverage
53
- .nyc_output
54
-
55
- # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
56
- .grunt
57
-
58
- # node-waf configuration
59
- .lock-wscript
60
-
61
- # Compiled binary addons (http://nodejs.org/api/addons.html)
62
- build/Release
63
-
64
- # Dependency directories
65
- node_modules
66
- jspm_packages
67
-
68
- # Optional npm cache directory
69
- .npm
70
-
71
- # Optional REPL history
72
- .node_repl_history
73
-
74
- # 0x
75
- profile-*
76
-
77
- # mac files
78
- .DS_Store
79
-
80
- # vim swap files
81
- *.swp
82
-
83
- # webstorm
84
- .idea
85
-
86
- # vscode
87
- .vscode
88
- *code-workspace
89
-
90
- # clinic
91
- profile*
92
- *clinic*
93
- *flamegraph*
94
- logs
package/libs/jwt.js DELETED
@@ -1,97 +0,0 @@
1
- import { createHmac } from 'crypto';
2
-
3
- /**
4
- * JWT基础工具类
5
- * 提供JWT相关的基础工具方法
6
- */
7
- export class Jwt {
8
- // 支持的算法映射
9
- static ALGORITHMS = {
10
- HS256: 'sha256',
11
- HS384: 'sha384',
12
- HS512: 'sha512'
13
- };
14
-
15
- /**
16
- * Base64 URL 编码
17
- * @param {string|Buffer} input - 需要编码的输入
18
- * @returns {string} Base64 URL编码结果
19
- */
20
- static base64UrlEncode(input) {
21
- const base64 = Buffer.isBuffer(input) ? input.toString('base64') : Buffer.from(input, 'utf8').toString('base64');
22
- return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
23
- }
24
-
25
- /**
26
- * Base64 URL 解码
27
- * @param {string} str - Base64 URL编码的字符串
28
- * @returns {string} 解码后的字符串
29
- */
30
- static base64UrlDecode(str) {
31
- const padding = 4 - (str.length % 4);
32
- if (padding !== 4) str += '='.repeat(padding);
33
- str = str.replace(/-/g, '+').replace(/_/g, '/');
34
- return Buffer.from(str, 'base64').toString('utf8');
35
- }
36
-
37
- /**
38
- * 解析过期时间为秒数
39
- * @param {string|number} expiresIn - 过期时间表达式
40
- * @returns {number} 过期时间(秒数)
41
- */
42
- static parseExpiration(expiresIn) {
43
- if (typeof expiresIn === 'number') return expiresIn;
44
- if (typeof expiresIn !== 'string') throw new Error('过期时间格式无效');
45
-
46
- // 如果是纯数字字符串,直接转换为数字
47
- const numericValue = parseInt(expiresIn);
48
- if (!isNaN(numericValue) && numericValue.toString() === expiresIn) {
49
- return numericValue;
50
- }
51
-
52
- // 支持毫秒(ms)和其他时间单位
53
- const match = expiresIn.match(/^(\d+)(ms|[smhdwy])$/);
54
- if (!match) throw new Error('过期时间格式无效');
55
-
56
- const value = parseInt(match[1]);
57
- const unit = match[2];
58
-
59
- if (unit === 'ms') {
60
- return Math.floor(value / 1000); // 毫秒转秒,向下取整
61
- }
62
-
63
- const multipliers = { s: 1, m: 60, h: 3600, d: 86400, w: 604800, y: 31536000 };
64
- return value * multipliers[unit];
65
- }
66
-
67
- /**
68
- * 创建HMAC签名
69
- * @param {string} algorithm - 算法名称
70
- * @param {string} secret - 密钥
71
- * @param {string} data - 待签名数据
72
- * @returns {string} Base64 URL编码的签名
73
- */
74
- static createSignature(algorithm, secret, data) {
75
- const hashAlgorithm = this.ALGORITHMS[algorithm];
76
- if (!hashAlgorithm) throw new Error(`不支持的算法: ${algorithm}`);
77
-
78
- const hmac = createHmac(hashAlgorithm, secret);
79
- hmac.update(data);
80
- return this.base64UrlEncode(hmac.digest());
81
- }
82
-
83
- /**
84
- * 常量时间字符串比较(防止时序攻击)
85
- * @param {string} a - 字符串A
86
- * @param {string} b - 字符串B
87
- * @returns {boolean} 是否相等
88
- */
89
- static constantTimeCompare(a, b) {
90
- if (a.length !== b.length) return false;
91
- let result = 0;
92
- for (let i = 0; i < a.length; i++) {
93
- result |= a.charCodeAt(i) ^ b.charCodeAt(i);
94
- }
95
- return result === 0;
96
- }
97
- }