@be-link/http 1.0.1-beta.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.
@@ -0,0 +1,835 @@
1
+ import axios from 'axios';
2
+ import CryptoJS from 'crypto-js';
3
+
4
+ /**
5
+ * 时间同步服务
6
+ *
7
+ * 用于同步客户端和服务器时间,解决客户端时间不准确的问题
8
+ * 主要用于 Token 加密时使用服务器时间,防止重放攻击
9
+ *
10
+ * 使用 performance.now() 计算时间流逝,防止用户修改系统时间导致的问题
11
+ *
12
+ * @module TimeSyncService
13
+ */
14
+ /**
15
+ * localStorage 存储键名常量
16
+ */
17
+ const STORAGE_KEYS = {
18
+ /** 服务器时间存储键 */
19
+ SERVER_TIME: 'belink_server_time',
20
+ /** 客户端时间存储键(同步时的客户端时间,用于判断是否需要重新同步) */
21
+ CLIENT_TIME: 'belink_client_time',
22
+ };
23
+ /**
24
+ * 检测是否为浏览器环境
25
+ */
26
+ function isBrowser() {
27
+ return typeof window !== 'undefined' && typeof localStorage !== 'undefined';
28
+ }
29
+ /**
30
+ * 获取带前缀的存储 key
31
+ */
32
+ function getStorageKey(prefix, key) {
33
+ return prefix ? `${prefix}_${key}` : key;
34
+ }
35
+ /**
36
+ * 获取 performance.now(),兼容非浏览器环境
37
+ */
38
+ function getPerformanceNow() {
39
+ if (typeof performance !== 'undefined' && performance.now) {
40
+ return performance.now();
41
+ }
42
+ return Date.now();
43
+ }
44
+ /**
45
+ * 时间同步服务
46
+ *
47
+ * 负责同步客户端和服务器时间,提供以下功能:
48
+ * - 自动检测是否需要同步
49
+ * - 获取服务器时间
50
+ * - 使用 performance.now() 计算时间流逝,防止用户修改系统时间
51
+ * - 获取调整后的当前时间
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * const timeSync = new TimeSyncService({
56
+ * syncUrl: 'https://api.example.com/api/time',
57
+ * syncGapTime: 50000,
58
+ * });
59
+ *
60
+ * // 确保时间同步
61
+ * await timeSync.ensureSync();
62
+ *
63
+ * // 获取调整后的时间
64
+ * const adjustedTime = timeSync.getAdjustedTime();
65
+ * ```
66
+ */
67
+ class TimeSyncService {
68
+ /**
69
+ * 创建时间同步服务实例
70
+ */
71
+ constructor(config) {
72
+ /**
73
+ * 同步时的服务器时间(内存存储)
74
+ * 用于计算调整后的时间
75
+ */
76
+ this.syncServerTime = null;
77
+ /**
78
+ * 同步时的 performance.now() 值(内存存储)
79
+ * 使用 performance.now() 而不是 Date.now(),防止用户修改系统时间
80
+ */
81
+ this.syncPerformanceTime = null;
82
+ /**
83
+ * 正在进行的同步 Promise(用于防止并发请求时重复同步)
84
+ * 当有同步请求正在进行时,其他请求会等待这个 Promise 完成
85
+ */
86
+ this.syncPromise = null;
87
+ this.config = {
88
+ enabled: true,
89
+ syncGapTime: 50 * 1000, // 默认 50 秒
90
+ ...config,
91
+ };
92
+ }
93
+ /**
94
+ * 确保时间已同步
95
+ *
96
+ * 检查是否需要同步:
97
+ * 1. 内存中没有同步数据(页面刷新后)
98
+ * 2. 距离上次同步的时间超过了 syncGapTime 阈值
99
+ *
100
+ * 并发控制:
101
+ * - 如果已有同步请求正在进行,直接等待该请求完成
102
+ * - 避免多个并发请求同时触发多次时间同步
103
+ */
104
+ async ensureSync() {
105
+ if (!this.config.enabled || !isBrowser()) {
106
+ return;
107
+ }
108
+ // 如果已有同步请求正在进行,等待它完成
109
+ if (this.syncPromise) {
110
+ await this.syncPromise;
111
+ return;
112
+ }
113
+ // 如果内存中没有同步数据,需要同步
114
+ if (this.syncServerTime === null || this.syncPerformanceTime === null) {
115
+ await this.sync();
116
+ return;
117
+ }
118
+ // 使用 performance.now() 计算时间流逝,检查是否需要重新同步
119
+ const elapsed = getPerformanceNow() - this.syncPerformanceTime;
120
+ if (elapsed > (this.config.syncGapTime || 50 * 1000)) {
121
+ await this.sync();
122
+ }
123
+ }
124
+ /**
125
+ * 执行时间同步
126
+ *
127
+ * 向服务器发送请求获取服务器时间
128
+ * 同时记录 performance.now() 用于后续计算时间流逝
129
+ *
130
+ * 并发控制:
131
+ * - 使用 syncPromise 作为锁,确保同一时间只有一个同步请求
132
+ * - 同步完成后清除锁,允许后续同步请求
133
+ */
134
+ async sync() {
135
+ if (!isBrowser() || !this.config.syncUrl) {
136
+ return;
137
+ }
138
+ // 如果已有同步请求正在进行,等待它完成
139
+ if (this.syncPromise) {
140
+ await this.syncPromise;
141
+ return;
142
+ }
143
+ // 创建同步 Promise 并保存引用(作为锁)
144
+ this.syncPromise = this.doSync();
145
+ try {
146
+ await this.syncPromise;
147
+ }
148
+ finally {
149
+ // 同步完成后清除锁
150
+ this.syncPromise = null;
151
+ }
152
+ }
153
+ /**
154
+ * 实际执行时间同步的内部方法
155
+ */
156
+ async doSync() {
157
+ try {
158
+ // 记录请求发起时的 performance.now()
159
+ const requestStartTime = getPerformanceNow();
160
+ const response = await fetch(this.config.syncUrl, { method: 'POST' });
161
+ if (!response.ok) {
162
+ console.error('[TimeSyncService] Sync failed with status:', response.status);
163
+ this.clear();
164
+ return;
165
+ }
166
+ const res = await response.json();
167
+ const serverTime = res.data.timestamp;
168
+ if (!serverTime) {
169
+ this.clear();
170
+ return;
171
+ }
172
+ // 计算网络延迟的一半,补偿到服务器时间
173
+ const requestEndTime = getPerformanceNow();
174
+ const networkDelay = (requestEndTime - requestStartTime) / 2;
175
+ // 存储到内存(使用 performance.now())
176
+ this.syncServerTime = serverTime + networkDelay;
177
+ this.syncPerformanceTime = requestEndTime;
178
+ // 同时存储到 localStorage(用于调试和日志)
179
+ const serverTimeKey = getStorageKey(this.config.storagePrefix, STORAGE_KEYS.SERVER_TIME);
180
+ const clientTimeKey = getStorageKey(this.config.storagePrefix, STORAGE_KEYS.CLIENT_TIME);
181
+ localStorage.setItem(serverTimeKey, String(this.syncServerTime));
182
+ localStorage.setItem(clientTimeKey, String(Date.now()));
183
+ }
184
+ catch (error) {
185
+ console.error('[TimeSyncService] Failed to sync time:', error);
186
+ this.clear();
187
+ }
188
+ }
189
+ /**
190
+ * 获取服务器时间
191
+ *
192
+ * 返回同步时获取的服务器时间戳
193
+ */
194
+ getServerTime() {
195
+ return this.syncServerTime;
196
+ }
197
+ /**
198
+ * 获取时间差
199
+ *
200
+ * 返回服务器时间与客户端时间的差值
201
+ * 注意:此方法仅用于向后兼容,实际计算使用 getAdjustedTime()
202
+ */
203
+ getTimeDiff() {
204
+ if (this.syncServerTime === null) {
205
+ return null;
206
+ }
207
+ return this.syncServerTime - Date.now();
208
+ }
209
+ /**
210
+ * 获取调整后的当前时间
211
+ *
212
+ * 使用 performance.now() 计算时间流逝,得到准确的服务器时间
213
+ * 计算公式:同步时的服务器时间 + (当前 performance.now() - 同步时的 performance.now())
214
+ *
215
+ * 优势:即使用户修改系统时间,performance.now() 的流逝是正确的
216
+ *
217
+ * @returns 调整后的时间戳(毫秒),未同步时返回当前客户端时间
218
+ */
219
+ getAdjustedTime() {
220
+ if (this.syncServerTime === null || this.syncPerformanceTime === null) {
221
+ return Date.now();
222
+ }
223
+ // 使用 performance.now() 计算时间流逝
224
+ const elapsed = getPerformanceNow() - this.syncPerformanceTime;
225
+ return Math.round(this.syncServerTime + elapsed);
226
+ }
227
+ /**
228
+ * 清除同步数据
229
+ */
230
+ clear() {
231
+ this.syncServerTime = null;
232
+ this.syncPerformanceTime = null;
233
+ if (isBrowser()) {
234
+ const serverTimeKey = getStorageKey(this.config.storagePrefix, STORAGE_KEYS.SERVER_TIME);
235
+ const clientTimeKey = getStorageKey(this.config.storagePrefix, STORAGE_KEYS.CLIENT_TIME);
236
+ localStorage.removeItem(serverTimeKey);
237
+ localStorage.removeItem(clientTimeKey);
238
+ }
239
+ }
240
+ /**
241
+ * 更新配置
242
+ */
243
+ updateConfig(config) {
244
+ this.config = { ...this.config, ...config };
245
+ }
246
+ }
247
+
248
+ /**
249
+ * 加密服务
250
+ *
251
+ * 提供 Token 加密/解密功能,使用 AES-CBC 模式
252
+ * 加密后的 Token 包含时间戳,用于防止重放攻击
253
+ *
254
+ * @module EncryptionService
255
+ */
256
+ /**
257
+ * 加密服务
258
+ *
259
+ * 负责 Token 的 AES 加密和解密,提供以下功能:
260
+ * - Token 加密(带时间戳)
261
+ * - Token 解密
262
+ * - 与时间同步服务集成
263
+ *
264
+ * 加密格式说明:
265
+ * - 原始数据格式: `${token}|+|${timestamp}`
266
+ * - 加密算法: AES-128-CBC
267
+ * - 输出格式: Base64 编码
268
+ *
269
+ * @example
270
+ * ```ts
271
+ * const encryption = new EncryptionService({
272
+ * key: '1234567890123456', // 16 位
273
+ * iv: '1234567890123456', // 16 位
274
+ * });
275
+ *
276
+ * // 加密 Token
277
+ * const encryptedToken = encryption.encryptToken('my-token');
278
+ *
279
+ * // 解密 Token
280
+ * const { token, timestamp } = encryption.decryptToken(encryptedToken);
281
+ * ```
282
+ */
283
+ class EncryptionService {
284
+ /**
285
+ * 创建加密服务实例
286
+ *
287
+ * @param config - 加密配置
288
+ * @param timeSyncService - 时间同步服务实例(可选),用于获取服务器校正后的时间
289
+ */
290
+ constructor(config, timeSyncService) {
291
+ this.config = {
292
+ enabled: true,
293
+ ...config,
294
+ };
295
+ this.timeSyncService = timeSyncService;
296
+ }
297
+ /**
298
+ * 加密 Token
299
+ *
300
+ * 将 Token 与时间戳组合后进行 AES 加密
301
+ * 加密后的数据用于请求头的 Authorization 字段
302
+ *
303
+ * 加密流程:
304
+ * 1. 组合原始数据: `${token}|+|${timestamp}`
305
+ * 2. 使用 AES-128-CBC 加密
306
+ * 3. 输出 Base64 编码的密文
307
+ *
308
+ * @param token - 原始 Token 字符串
309
+ * @param timestamp - 可选的时间戳(毫秒),默认使用调整后的当前时间
310
+ * @returns 加密后的 Token 字符串(Base64 编码)
311
+ *
312
+ * @example
313
+ * ```ts
314
+ * // 使用默认时间戳
315
+ * const encrypted = encryption.encryptToken('my-token');
316
+ *
317
+ * // 使用指定时间戳
318
+ * const encrypted = encryption.encryptToken('my-token', Date.now());
319
+ * ```
320
+ */
321
+ encryptToken(token, timestamp) {
322
+ // 如果 Token 为空或加密功能禁用,直接返回原始 Token
323
+ if (!token || !this.config.enabled) {
324
+ return token || '';
325
+ }
326
+ // 获取时间戳(优先使用传入的时间戳,否则使用调整后的时间)
327
+ const time = timestamp ?? this.getAdjustedTime();
328
+ // 组合原始数据:Token + 分隔符 + 时间戳
329
+ const data = `${token}|+|${time}`;
330
+ // 使用 AES-CBC 模式加密
331
+ return CryptoJS.AES.encrypt(data, CryptoJS.enc.Utf8.parse(this.config.key), {
332
+ iv: CryptoJS.enc.Utf8.parse(this.config.iv),
333
+ mode: CryptoJS.mode.CBC,
334
+ }).toString();
335
+ }
336
+ /**
337
+ * 解密 Token
338
+ *
339
+ * 将加密后的 Token 解密,提取原始 Token 和时间戳
340
+ *
341
+ * @param encryptedToken - 加密后的 Token 字符串
342
+ * @returns 包含原始 Token 和时间戳的对象
343
+ *
344
+ * @example
345
+ * ```ts
346
+ * const { token, timestamp } = encryption.decryptToken(encryptedToken);
347
+ * console.log(token); // 'my-token'
348
+ * console.log(timestamp); // 1703577600000
349
+ * ```
350
+ */
351
+ decryptToken(encryptedToken) {
352
+ // 使用 AES-CBC 模式解密
353
+ const decrypted = CryptoJS.AES.decrypt(encryptedToken, CryptoJS.enc.Utf8.parse(this.config.key), {
354
+ iv: CryptoJS.enc.Utf8.parse(this.config.iv),
355
+ mode: CryptoJS.mode.CBC,
356
+ }).toString(CryptoJS.enc.Utf8);
357
+ // 分割原始数据,提取 Token 和时间戳
358
+ const parts = decrypted.split('|+|');
359
+ return {
360
+ token: parts[0],
361
+ timestamp: parseInt(parts[1], 10),
362
+ };
363
+ }
364
+ /**
365
+ * 获取调整后的时间戳
366
+ *
367
+ * 优先使用时间同步服务获取校正后的时间
368
+ * 如果没有时间同步服务,则使用当前客户端时间
369
+ *
370
+ * @returns 时间戳(毫秒)
371
+ */
372
+ getAdjustedTime() {
373
+ if (this.timeSyncService) {
374
+ return this.timeSyncService.getAdjustedTime();
375
+ }
376
+ return Date.now();
377
+ }
378
+ /**
379
+ * 更新配置
380
+ *
381
+ * 动态更新加密服务的配置
382
+ *
383
+ * @param config - 要更新的配置项
384
+ */
385
+ updateConfig(config) {
386
+ this.config = { ...this.config, ...config };
387
+ }
388
+ /**
389
+ * 设置时间同步服务
390
+ *
391
+ * 用于后期绑定时间同步服务实例
392
+ *
393
+ * @param timeSyncService - 时间同步服务实例
394
+ */
395
+ setTimeSyncService(timeSyncService) {
396
+ this.timeSyncService = timeSyncService;
397
+ }
398
+ }
399
+
400
+ /**
401
+ * 请求拦截器
402
+ *
403
+ * 在请求发出前对请求进行处理,包括:
404
+ * - 时间同步
405
+ * - 设置默认请求头
406
+ * - 添加用户 ID
407
+ * - Token 加密和添加
408
+ * - 调用自定义拦截器
409
+ *
410
+ * @module interceptors/request
411
+ */
412
+ /**
413
+ * 设置请求拦截器
414
+ *
415
+ * 为 Axios 实例添加请求拦截器,处理请求前的通用逻辑
416
+ *
417
+ * 拦截器执行顺序:
418
+ * 1. 确保时间同步(如果配置了 TimeSyncService)
419
+ * 2. 设置默认 Content-Type
420
+ * 3. 添加用户 ID 到请求头
421
+ * 4. 加密并添加 Token 到请求头
422
+ * 5. 调用自定义请求拦截器
423
+ *
424
+ * @param instance - Axios 实例
425
+ * @param options - 请求配置选项
426
+ * @param timeSyncService - 时间同步服务实例(可选)
427
+ * @param encryptionService - 加密服务实例(可选)
428
+ *
429
+ * @example
430
+ * ```ts
431
+ * const instance = axios.create({ baseURL: 'https://api.example.com' });
432
+ *
433
+ * setRequestInterceptor(
434
+ * instance,
435
+ * {
436
+ * baseURL: 'https://api.example.com',
437
+ * getToken: () => localStorage.getItem('token'),
438
+ * getUserId: () => localStorage.getItem('userId'),
439
+ * },
440
+ * timeSyncService,
441
+ * encryptionService,
442
+ * );
443
+ * ```
444
+ */
445
+ function setRequestInterceptor(instance, options, timeSyncService, encryptionService) {
446
+ // 获取 header 名称,使用默认值
447
+ const tokenHeaderName = options.tokenHeaderName || 'X-BeLink-Token';
448
+ const userIdHeaderName = options.userIdHeaderName || 'X-BeLink-UserId';
449
+ instance.interceptors.request.use(async (config) => {
450
+ // 步骤 1: 确保时间同步
451
+ // 在请求发出前检查并同步服务器时间,确保后续 Token 加密使用正确的时间戳
452
+ if (timeSyncService) {
453
+ await timeSyncService.ensureSync();
454
+ }
455
+ // 步骤 2: 设置默认 Content-Type
456
+ // 如果请求未指定 Content-Type,默认使用 JSON 格式
457
+ if (!config.headers['Content-Type']) {
458
+ config.headers['Content-Type'] = 'application/json';
459
+ }
460
+ // 步骤 3: 添加用户 ID
461
+ // 调用用户配置的 getUserId 函数获取用户 ID,添加到请求头
462
+ if (options.getUserId) {
463
+ const userId = options.getUserId();
464
+ if (userId) {
465
+ config.headers[userIdHeaderName] = userId;
466
+ }
467
+ }
468
+ // 步骤 4: 添加 Token
469
+ // 根据是否配置了加密服务,决定是否对 Token 进行加密
470
+ if (options.getToken && encryptionService) {
471
+ // 有加密服务:获取 Token 并加密后添加到请求头
472
+ const token = options.getToken();
473
+ if (token) {
474
+ config.headers[tokenHeaderName] = encryptionService.encryptToken(token);
475
+ }
476
+ }
477
+ else if (options.getToken) {
478
+ // 无加密服务:直接使用原始 Token
479
+ const token = options.getToken();
480
+ if (token) {
481
+ config.headers[tokenHeaderName] = token;
482
+ }
483
+ }
484
+ // 步骤 5: 调用自定义请求拦截器
485
+ // 允许用户进一步自定义请求配置
486
+ if (options.onRequest) {
487
+ return options.onRequest(config);
488
+ }
489
+ return config;
490
+ });
491
+ }
492
+
493
+ /**
494
+ * 响应拦截器
495
+ *
496
+ * 在收到响应后对响应进行处理,包括:
497
+ * - 提取响应数据
498
+ * - 调用自定义响应拦截器
499
+ * - 统一错误处理
500
+ *
501
+ * @module interceptors/response
502
+ */
503
+ /**
504
+ * 设置响应拦截器
505
+ *
506
+ * 为 Axios 实例添加响应拦截器,处理响应后的通用逻辑
507
+ *
508
+ * 成功响应处理:
509
+ * 1. 提取响应数据(response.data)
510
+ * 2. 调用自定义响应拦截器(如果配置)
511
+ * 3. 返回处理后的数据
512
+ *
513
+ * 错误响应处理:
514
+ * 1. 调用自定义错误处理(如果配置)
515
+ * 2. 检查业务错误(success === false)
516
+ * 3. 抛出错误
517
+ *
518
+ * @param instance - Axios 实例
519
+ * @param options - 请求配置选项
520
+ *
521
+ * @example
522
+ * ```ts
523
+ * const instance = axios.create({ baseURL: 'https://api.example.com' });
524
+ *
525
+ * setResponseInterceptor(instance, {
526
+ * baseURL: 'https://api.example.com',
527
+ * onResponse: (data) => {
528
+ * console.log('响应数据:', data);
529
+ * return data;
530
+ * },
531
+ * onError: (error) => {
532
+ * if (error.response?.status === 401) {
533
+ * // 处理未授权错误
534
+ * window.location.href = '/login';
535
+ * }
536
+ * throw error;
537
+ * },
538
+ * });
539
+ * ```
540
+ */
541
+ function setResponseInterceptor(instance, options) {
542
+ instance.interceptors.response.use(
543
+ // 成功响应处理
544
+ (response) => {
545
+ // 提取响应数据(Axios 的响应数据在 response.data 中)
546
+ const data = response.data;
547
+ // 调用自定义响应拦截器
548
+ // 允许用户对响应数据进行进一步处理
549
+ if (options.onResponse) {
550
+ return options.onResponse(data);
551
+ }
552
+ return data;
553
+ },
554
+ // 错误响应处理
555
+ (error) => {
556
+ // 优先调用自定义错误处理
557
+ // 如果用户配置了 onError,由用户自行处理错误
558
+ if (options.onError) {
559
+ return options.onError(error);
560
+ }
561
+ // 获取响应数据(如果有)
562
+ const responseData = error?.response?.data;
563
+ // 检查业务错误
564
+ // 业务错误:HTTP 状态码可能是 200,但响应体中 success === false
565
+ if (responseData && typeof responseData === 'object' && !responseData.success) {
566
+ // 创建业务错误对象
567
+ const bizError = new Error(responseData.message || '接口请求失败');
568
+ // 附加原始响应信息,方便调试
569
+ bizError.response = error.response;
570
+ bizError.data = responseData;
571
+ throw bizError;
572
+ }
573
+ // 其他错误(网络错误、超时等)直接抛出
574
+ throw error;
575
+ });
576
+ }
577
+
578
+ /**
579
+ * BeLinkHttp 请求客户端
580
+ *
581
+ * 单例模式的 HTTP 请求客户端
582
+ * 支持时间同步和 Token 加密
583
+ *
584
+ * @module BeLinkHttp
585
+ *
586
+ * @example
587
+ * ```ts
588
+ * import { beLinkHttp } from '@be-link/http';
589
+ *
590
+ * // 初始化
591
+ * beLinkHttp.init({
592
+ * baseURL: 'https://api.example.com',
593
+ * timeSync: { syncUrl: 'https://api.example.com/api/time' },
594
+ * encryption: { key: '...', iv: '...' },
595
+ * getToken: () => localStorage.getItem('token'),
596
+ * });
597
+ *
598
+ * // 发起请求
599
+ * const data = await beLinkHttp.get('/api/users');
600
+ * ```
601
+ */
602
+ /**
603
+ * BeLinkHttp 单例类
604
+ *
605
+ * 提供统一的 HTTP 请求接口,支持:
606
+ * - 时间同步
607
+ * - Token 加密
608
+ * - 请求/响应拦截
609
+ */
610
+ class BeLinkHttp {
611
+ constructor() {
612
+ /** Axios 实例 */
613
+ this.instance = null;
614
+ /** 时间同步服务 */
615
+ this.timeSyncService = null;
616
+ /** 加密服务 */
617
+ this.encryptionService = null;
618
+ /** 是否已初始化 */
619
+ this.initialized = false;
620
+ }
621
+ /**
622
+ * 初始化请求客户端
623
+ *
624
+ * 必须在使用其他方法之前调用此方法进行初始化
625
+ *
626
+ * @param options - 请求配置选项
627
+ *
628
+ * @example
629
+ * ```ts
630
+ * beLinkHttp.init({
631
+ * baseURL: 'https://api.example.com',
632
+ * timeout: 30000,
633
+ * timeSync: {
634
+ * syncUrl: 'https://api.example.com/api/time',
635
+ * },
636
+ * encryption: {
637
+ * key: 'your-aes-key-16ch',
638
+ * iv: 'your-aes-iv-16ch',
639
+ * },
640
+ * getToken: () => localStorage.getItem('token'),
641
+ * getUserId: () => localStorage.getItem('userId'),
642
+ * });
643
+ * ```
644
+ */
645
+ init(options) {
646
+ const { baseURL, timeout = 30000, headers = {}, timeSync, encryption } = options;
647
+ // 创建 Axios 实例
648
+ this.instance = axios.create({
649
+ baseURL,
650
+ timeout,
651
+ headers: {
652
+ 'Content-Type': 'application/json',
653
+ ...headers,
654
+ },
655
+ });
656
+ // 创建时间同步服务
657
+ if (timeSync?.syncUrl) {
658
+ this.timeSyncService = new TimeSyncService(timeSync);
659
+ }
660
+ // 创建加密服务
661
+ if (encryption?.key && encryption?.iv) {
662
+ this.encryptionService = new EncryptionService(encryption, this.timeSyncService || undefined);
663
+ }
664
+ // 设置拦截器
665
+ setRequestInterceptor(this.instance, options, this.timeSyncService || undefined, this.encryptionService || undefined);
666
+ setResponseInterceptor(this.instance, options);
667
+ this.initialized = true;
668
+ }
669
+ /**
670
+ * 检查是否已初始化
671
+ */
672
+ checkInitialized() {
673
+ if (!this.initialized || !this.instance) {
674
+ throw new Error('[BeLinkHttp] 请先调用 init() 方法进行初始化');
675
+ }
676
+ }
677
+ /**
678
+ * GET 请求
679
+ *
680
+ * @param url - 请求 URL
681
+ * @param config - 可选的 Axios 请求配置
682
+ * @returns Promise 响应数据
683
+ *
684
+ * @example
685
+ * ```ts
686
+ * const users = await beLinkHttp.get('/api/users');
687
+ * const user = await beLinkHttp.get('/api/users/1', { params: { include: 'profile' } });
688
+ * ```
689
+ */
690
+ async get(url, config) {
691
+ this.checkInitialized();
692
+ return this.instance.get(url, config);
693
+ }
694
+ /**
695
+ * POST 请求
696
+ *
697
+ * @param url - 请求 URL
698
+ * @param data - 请求体数据
699
+ * @param config - 可选的 Axios 请求配置
700
+ * @returns Promise 响应数据
701
+ *
702
+ * @example
703
+ * ```ts
704
+ * const result = await beLinkHttp.post('/api/users', { name: '张三', age: 25 });
705
+ * ```
706
+ */
707
+ async post(url, data, config) {
708
+ this.checkInitialized();
709
+ return this.instance.post(url, data, config);
710
+ }
711
+ /**
712
+ * PUT 请求
713
+ *
714
+ * @param url - 请求 URL
715
+ * @param data - 请求体数据
716
+ * @param config - 可选的 Axios 请求配置
717
+ * @returns Promise 响应数据
718
+ *
719
+ * @example
720
+ * ```ts
721
+ * const result = await beLinkHttp.put('/api/users/1', { name: '李四' });
722
+ * ```
723
+ */
724
+ async put(url, data, config) {
725
+ this.checkInitialized();
726
+ return this.instance.put(url, data, config);
727
+ }
728
+ /**
729
+ * PATCH 请求
730
+ *
731
+ * @param url - 请求 URL
732
+ * @param data - 请求体数据
733
+ * @param config - 可选的 Axios 请求配置
734
+ * @returns Promise 响应数据
735
+ *
736
+ * @example
737
+ * ```ts
738
+ * const result = await beLinkHttp.patch('/api/users/1', { age: 26 });
739
+ * ```
740
+ */
741
+ async patch(url, data, config) {
742
+ this.checkInitialized();
743
+ return this.instance.patch(url, data, config);
744
+ }
745
+ /**
746
+ * DELETE 请求
747
+ *
748
+ * @param url - 请求 URL
749
+ * @param config - 可选的 Axios 请求配置
750
+ * @returns Promise 响应数据
751
+ *
752
+ * @example
753
+ * ```ts
754
+ * await beLinkHttp.delete('/api/users/1');
755
+ * ```
756
+ */
757
+ async delete(url, config) {
758
+ this.checkInitialized();
759
+ return this.instance.delete(url, config);
760
+ }
761
+ /**
762
+ * 通用请求方法
763
+ *
764
+ * @param config - Axios 请求配置
765
+ * @returns Promise 响应数据
766
+ *
767
+ * @example
768
+ * ```ts
769
+ * const result = await beLinkHttp.request({
770
+ * method: 'post',
771
+ * url: '/api/users',
772
+ * data: { name: '张三' },
773
+ * headers: { 'X-Custom': 'value' },
774
+ * });
775
+ * ```
776
+ */
777
+ async request(config) {
778
+ this.checkInitialized();
779
+ return this.instance.request(config);
780
+ }
781
+ /**
782
+ * 获取时间同步服务实例
783
+ *
784
+ * @returns 时间同步服务实例,未初始化时返回 null
785
+ */
786
+ getTimeSyncService() {
787
+ return this.timeSyncService;
788
+ }
789
+ /**
790
+ * 获取加密服务实例
791
+ *
792
+ * @returns 加密服务实例,未初始化时返回 null
793
+ */
794
+ getEncryptionService() {
795
+ return this.encryptionService;
796
+ }
797
+ /**
798
+ * 获取 Axios 实例
799
+ *
800
+ * @returns Axios 实例,未初始化时返回 null
801
+ */
802
+ getAxiosInstance() {
803
+ return this.instance;
804
+ }
805
+ /**
806
+ * 重置客户端
807
+ *
808
+ * 清除所有配置和实例,需要重新调用 init() 初始化
809
+ */
810
+ reset() {
811
+ this.instance = null;
812
+ this.timeSyncService = null;
813
+ this.encryptionService = null;
814
+ this.initialized = false;
815
+ }
816
+ }
817
+ /**
818
+ * BeLinkHttp 单例实例
819
+ *
820
+ * @example
821
+ * ```ts
822
+ * import { beLinkHttp } from '@be-link/http';
823
+ *
824
+ * // 初始化
825
+ * beLinkHttp.init({
826
+ * baseURL: 'https://api.example.com',
827
+ * });
828
+ *
829
+ * // 使用
830
+ * const data = await beLinkHttp.get('/api/users');
831
+ * ```
832
+ */
833
+ const beLinkHttp = new BeLinkHttp();
834
+
835
+ export { BeLinkHttp, EncryptionService, TimeSyncService, beLinkHttp, beLinkHttp as default, setRequestInterceptor, setResponseInterceptor };