@be-link/http 1.1.0 → 1.2.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.
package/dist/index.esm.js CHANGED
@@ -1,6 +1,316 @@
1
1
  import axios from 'axios';
2
2
  import CryptoJS from 'crypto-js';
3
3
 
4
+ /**
5
+ * Axios 适配器
6
+ *
7
+ * 使用 Axios 实现 HttpAdapter 接口
8
+ *
9
+ * @module adapters/AxiosAdapter
10
+ */
11
+ /**
12
+ * Axios 适配器
13
+ *
14
+ * 将 Axios 封装为统一的 HttpAdapter 接口
15
+ */
16
+ class AxiosAdapter {
17
+ /**
18
+ * 创建 Axios 适配器实例
19
+ *
20
+ * @param config - 适配器配置
21
+ */
22
+ constructor(config) {
23
+ this.instance = axios.create({
24
+ baseURL: config.baseURL,
25
+ timeout: config.timeout || 30000,
26
+ headers: {
27
+ 'Content-Type': 'application/json',
28
+ ...(config.headers || {}),
29
+ },
30
+ });
31
+ }
32
+ /**
33
+ * 发起 HTTP 请求
34
+ *
35
+ * @param config - 请求配置
36
+ * @returns Promise 响应数据
37
+ */
38
+ async request(config) {
39
+ const axiosConfig = {
40
+ url: config.url,
41
+ method: config.method || 'GET',
42
+ data: config.data,
43
+ headers: config.headers,
44
+ timeout: config.timeout,
45
+ params: config.params,
46
+ };
47
+ const response = await this.instance.request(axiosConfig);
48
+ return {
49
+ data: response.data,
50
+ status: response.status,
51
+ headers: response.headers,
52
+ };
53
+ }
54
+ /**
55
+ * 获取 Axios 实例
56
+ *
57
+ * 用于直接访问 Axios 实例进行高级操作
58
+ *
59
+ * @returns Axios 实例
60
+ */
61
+ getAxiosInstance() {
62
+ return this.instance;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Taro 适配器
68
+ *
69
+ * 使用 Taro.request 实现 HttpAdapter 接口
70
+ * 适用于 Taro 小程序项目
71
+ *
72
+ * @module adapters/TaroAdapter
73
+ */
74
+ /**
75
+ * Taro 适配器
76
+ *
77
+ * 将 Taro.request 封装为统一的 HttpAdapter 接口
78
+ */
79
+ class TaroAdapter {
80
+ /**
81
+ * 创建 Taro 适配器实例
82
+ *
83
+ * @param config - 适配器配置
84
+ */
85
+ constructor(config) {
86
+ this.baseURL = config.baseURL;
87
+ this.timeout = config.timeout || 30000;
88
+ this.defaultHeaders = {
89
+ 'Content-Type': 'application/json',
90
+ ...(config.headers || {}),
91
+ };
92
+ this.taroRequest = config.taroRequest;
93
+ }
94
+ /**
95
+ * 发起 HTTP 请求
96
+ *
97
+ * @param config - 请求配置
98
+ * @returns Promise 响应数据
99
+ */
100
+ async request(config) {
101
+ const fullUrl = this.resolveUrl(config.url, config.params);
102
+ const res = await this.taroRequest({
103
+ url: fullUrl,
104
+ method: (config.method || 'GET'),
105
+ data: config.data,
106
+ header: {
107
+ ...this.defaultHeaders,
108
+ ...(config.headers || {}),
109
+ },
110
+ timeout: config.timeout || this.timeout,
111
+ });
112
+ return {
113
+ data: res.data,
114
+ status: res.statusCode,
115
+ headers: res.header,
116
+ };
117
+ }
118
+ /**
119
+ * 解析完整 URL
120
+ *
121
+ * 处理相对路径和查询参数
122
+ *
123
+ * @param url - 请求 URL
124
+ * @param params - 查询参数
125
+ * @returns 完整 URL
126
+ */
127
+ resolveUrl(url, params) {
128
+ // 如果是绝对路径,直接使用;否则拼接 baseURL
129
+ const fullUrl = url.startsWith('http') ? url : `${this.baseURL}${url}`;
130
+ // 如果没有查询参数,直接返回
131
+ if (!params || Object.keys(params).length === 0) {
132
+ return fullUrl;
133
+ }
134
+ // 序列化查询参数
135
+ const queryParts = [];
136
+ for (const key of Object.keys(params)) {
137
+ const value = params[key];
138
+ if (value !== undefined && value !== null) {
139
+ queryParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
140
+ }
141
+ }
142
+ if (queryParts.length === 0) {
143
+ return fullUrl;
144
+ }
145
+ const queryString = queryParts.join('&');
146
+ return `${fullUrl}${fullUrl.includes('?') ? '&' : '?'}${queryString}`;
147
+ }
148
+ }
149
+ /**
150
+ * 创建 Taro Fetch 包装器
151
+ *
152
+ * 将 Taro.request 包装为类似 fetch 的函数
153
+ * 用于 TimeSyncService 进行时间同步
154
+ *
155
+ * @param taroRequest - Taro.request 函数引用
156
+ * @returns FetchFunction 类型的函数
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * import Taro from '@tarojs/taro';
161
+ * import { createTaroFetch } from '@be-link/http';
162
+ *
163
+ * const fetchFn = createTaroFetch(Taro.request);
164
+ * const timeSyncService = new TimeSyncService(config, fetchFn);
165
+ * ```
166
+ */
167
+ function createTaroFetch(taroRequest) {
168
+ return async (url, options) => {
169
+ const res = await taroRequest({
170
+ url,
171
+ method: (options?.method?.toUpperCase() || 'GET'),
172
+ });
173
+ return {
174
+ ok: res.statusCode >= 200 && res.statusCode < 300,
175
+ status: res.statusCode,
176
+ json: async () => res.data,
177
+ };
178
+ };
179
+ }
180
+
181
+ /**
182
+ * 通用拦截器核心逻辑
183
+ *
184
+ * 提供与具体 HTTP 库无关的请求/响应处理函数
185
+ * 支持 Axios 和 Taro.request 适配器复用
186
+ *
187
+ * @module interceptors/core
188
+ */
189
+ /**
190
+ * 处理请求配置
191
+ *
192
+ * 在请求发出前对配置进行处理,包括:
193
+ * 1. 确保时间同步
194
+ * 2. 设置默认 Content-Type
195
+ * 3. 添加用户 ID 到请求头
196
+ * 4. 加密并添加 Token 到请求头
197
+ * 5. 调用自定义请求拦截器
198
+ *
199
+ * @param config - 请求配置
200
+ * @param options - 全局请求选项
201
+ * @param timeSyncService - 时间同步服务(可选)
202
+ * @param encryptionService - 加密服务(可选)
203
+ * @returns 处理后的请求配置
204
+ */
205
+ async function processRequest(config, options, timeSyncService, encryptionService) {
206
+ // 获取 header 名称,使用默认值
207
+ const tokenHeaderName = options.tokenHeaderName || 'X-BeLink-Token';
208
+ const userIdHeaderName = options.userIdHeaderName || 'X-BeLink-UserId';
209
+ // 确保 headers 对象存在
210
+ config.headers = config.headers || {};
211
+ // 步骤 1: 确保时间同步
212
+ // 在请求发出前检查并同步服务器时间,确保后续 Token 加密使用正确的时间戳
213
+ if (timeSyncService) {
214
+ await timeSyncService.ensureSync();
215
+ }
216
+ // 步骤 2: 设置默认 Content-Type
217
+ // 如果请求未指定 Content-Type,默认使用 JSON 格式
218
+ if (!config.headers['Content-Type']) {
219
+ config.headers['Content-Type'] = 'application/json';
220
+ }
221
+ // 步骤 3: 添加用户 ID
222
+ // 调用用户配置的 getUserId 函数获取用户 ID,添加到请求头
223
+ if (options.getUserId) {
224
+ const userId = options.getUserId();
225
+ if (userId) {
226
+ config.headers[userIdHeaderName] = userId;
227
+ }
228
+ }
229
+ // 步骤 4: 添加 Token
230
+ // 根据是否配置了加密服务,决定是否对 Token 进行加密
231
+ if (options.getToken) {
232
+ const token = options.getToken();
233
+ if (token) {
234
+ config.headers[tokenHeaderName] = encryptionService ? encryptionService.encryptToken(token) : token;
235
+ }
236
+ }
237
+ return config;
238
+ }
239
+ /**
240
+ * 处理响应数据
241
+ *
242
+ * 在收到响应后对数据进行处理,调用自定义响应拦截器
243
+ *
244
+ * @param data - 响应数据
245
+ * @param options - 全局请求选项
246
+ * @returns 处理后的响应数据
247
+ */
248
+ function processResponse(data, options) {
249
+ // 调用自定义响应拦截器
250
+ // 允许用户对响应数据进行进一步处理
251
+ if (options.onResponse) {
252
+ return options.onResponse(data);
253
+ }
254
+ return data;
255
+ }
256
+ /**
257
+ * 处理错误
258
+ *
259
+ * 在请求出错时进行处理,支持自定义错误处理和业务错误检查
260
+ *
261
+ * @param error - 错误对象
262
+ * @param options - 全局请求选项
263
+ * @throws 处理后的错误
264
+ */
265
+ function processError(error, options) {
266
+ // 优先调用自定义错误处理
267
+ // 如果用户配置了 onError,由用户自行处理错误
268
+ if (options.onError) {
269
+ const result = options.onError(error);
270
+ // 如果 onError 返回了值而不是抛出错误,我们继续抛出
271
+ throw result ?? error;
272
+ }
273
+ // 获取响应数据(如果有)
274
+ const responseData = error?.response?.data;
275
+ // 检查业务错误
276
+ // 业务错误:HTTP 状态码可能是 200,但响应体中 success === false
277
+ if (responseData && typeof responseData === 'object' && !responseData.success) {
278
+ // 创建业务错误对象
279
+ const bizError = new Error(responseData.message || '接口请求失败');
280
+ // 附加原始响应信息,方便调试
281
+ bizError.response = error.response;
282
+ bizError.data = responseData;
283
+ throw bizError;
284
+ }
285
+ // 其他错误(网络错误、超时等)直接抛出
286
+ throw error;
287
+ }
288
+ /**
289
+ * 检查响应状态并处理业务错误
290
+ *
291
+ * @param data - 响应数据
292
+ * @param status - HTTP 状态码
293
+ * @returns 处理后的响应数据
294
+ */
295
+ function checkResponseStatus(data, status) {
296
+ // 检查 HTTP 状态码
297
+ if (status < 200 || status >= 300) {
298
+ const error = new Error(`HTTP Error: ${status}`);
299
+ error.status = status;
300
+ error.data = data;
301
+ throw error;
302
+ }
303
+ // 检查业务错误(如果响应是标准格式)
304
+ const responseData = data;
305
+ if (responseData && typeof responseData === 'object' && 'success' in responseData && !responseData.success) {
306
+ const bizError = new Error(responseData.message || '接口请求失败');
307
+ bizError.data = responseData;
308
+ bizError.errorType = responseData.errorType;
309
+ throw bizError;
310
+ }
311
+ return data;
312
+ }
313
+
4
314
  /**
5
315
  * 时间同步服务
6
316
  *
@@ -67,8 +377,21 @@ function getPerformanceNow() {
67
377
  class TimeSyncService {
68
378
  /**
69
379
  * 创建时间同步服务实例
380
+ *
381
+ * @param config - 时间同步配置
382
+ * @param fetchFn - 自定义 fetch 函数(可选),用于支持 Taro 等非浏览器环境
383
+ *
384
+ * @example
385
+ * ```ts
386
+ * // 使用默认 fetch
387
+ * const timeSync = new TimeSyncService({ syncUrl: 'https://api.example.com/time' });
388
+ *
389
+ * // 使用 Taro.request
390
+ * import { createTaroFetch } from '@be-link/http';
391
+ * const timeSync = new TimeSyncService(config, createTaroFetch(Taro.request));
392
+ * ```
70
393
  */
71
- constructor(config) {
394
+ constructor(config, fetchFn) {
72
395
  /**
73
396
  * 同步时的服务器时间(内存存储)
74
397
  * 用于计算调整后的时间
@@ -89,6 +412,20 @@ class TimeSyncService {
89
412
  syncGapTime: 50 * 1000, // 默认 50 秒
90
413
  ...config,
91
414
  };
415
+ // 使用传入的 fetchFn 或默认的 fetch
416
+ this.fetchFn = fetchFn || this.defaultFetch.bind(this);
417
+ }
418
+ /**
419
+ * 默认的 fetch 实现
420
+ * 使用浏览器原生 fetch API
421
+ */
422
+ async defaultFetch(url, options) {
423
+ const response = await fetch(url, options);
424
+ return {
425
+ ok: response.ok,
426
+ status: response.status,
427
+ json: () => response.json(),
428
+ };
92
429
  }
93
430
  /**
94
431
  * 确保时间已同步
@@ -157,7 +494,8 @@ class TimeSyncService {
157
494
  try {
158
495
  // 记录请求发起时的 performance.now()
159
496
  const requestStartTime = getPerformanceNow();
160
- const response = await fetch(this.config.syncUrl, { method: 'POST' });
497
+ // 使用注入的 fetch 函数
498
+ const response = await this.fetchFn(this.config.syncUrl, { method: 'POST' });
161
499
  if (!response.ok) {
162
500
  console.error('[TimeSyncService] Sync failed with status:', response.status);
163
501
  this.clear();
@@ -175,11 +513,13 @@ class TimeSyncService {
175
513
  // 存储到内存(使用 performance.now())
176
514
  this.syncServerTime = serverTime + networkDelay;
177
515
  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()));
516
+ // 同时存储到 localStorage(用于调试和日志),仅在浏览器环境下
517
+ if (isBrowser()) {
518
+ const serverTimeKey = getStorageKey(this.config.storagePrefix, STORAGE_KEYS.SERVER_TIME);
519
+ const clientTimeKey = getStorageKey(this.config.storagePrefix, STORAGE_KEYS.CLIENT_TIME);
520
+ localStorage.setItem(serverTimeKey, String(this.syncServerTime));
521
+ localStorage.setItem(clientTimeKey, String(Date.now()));
522
+ }
183
523
  }
184
524
  catch (error) {
185
525
  console.error('[TimeSyncService] Failed to sync time:', error);
@@ -384,185 +724,16 @@ class EncryptionService {
384
724
  }
385
725
  }
386
726
 
387
- /**
388
- * 请求拦截器
389
- *
390
- * 在请求发出前对请求进行处理,包括:
391
- * - 时间同步
392
- * - 设置默认请求头
393
- * - 添加用户 ID
394
- * - Token 加密和添加
395
- * - 调用自定义拦截器
396
- *
397
- * @module interceptors/request
398
- */
399
- /**
400
- * 设置请求拦截器
401
- *
402
- * 为 Axios 实例添加请求拦截器,处理请求前的通用逻辑
403
- *
404
- * 拦截器执行顺序:
405
- * 1. 确保时间同步(如果配置了 TimeSyncService)
406
- * 2. 设置默认 Content-Type
407
- * 3. 添加用户 ID 到请求头
408
- * 4. 加密并添加 Token 到请求头
409
- * 5. 调用自定义请求拦截器
410
- *
411
- * @param instance - Axios 实例
412
- * @param options - 请求配置选项
413
- * @param timeSyncService - 时间同步服务实例(可选)
414
- * @param encryptionService - 加密服务实例(可选)
415
- *
416
- * @example
417
- * ```ts
418
- * const instance = axios.create({ baseURL: 'https://api.example.com' });
419
- *
420
- * setRequestInterceptor(
421
- * instance,
422
- * {
423
- * baseURL: 'https://api.example.com',
424
- * getToken: () => localStorage.getItem('token'),
425
- * getUserId: () => localStorage.getItem('userId'),
426
- * },
427
- * timeSyncService,
428
- * encryptionService,
429
- * );
430
- * ```
431
- */
432
- function setRequestInterceptor(instance, options, timeSyncService, encryptionService) {
433
- // 获取 header 名称,使用默认值
434
- const tokenHeaderName = options.tokenHeaderName || 'X-BeLink-Token';
435
- const userIdHeaderName = options.userIdHeaderName || 'X-BeLink-UserId';
436
- instance.interceptors.request.use(async (config) => {
437
- // 步骤 1: 确保时间同步
438
- // 在请求发出前检查并同步服务器时间,确保后续 Token 加密使用正确的时间戳
439
- if (timeSyncService) {
440
- await timeSyncService.ensureSync();
441
- }
442
- // 步骤 2: 设置默认 Content-Type
443
- // 如果请求未指定 Content-Type,默认使用 JSON 格式
444
- if (!config.headers['Content-Type']) {
445
- config.headers['Content-Type'] = 'application/json';
446
- }
447
- // 步骤 3: 添加用户 ID
448
- // 调用用户配置的 getUserId 函数获取用户 ID,添加到请求头
449
- if (options.getUserId) {
450
- const userId = options.getUserId();
451
- if (userId) {
452
- config.headers[userIdHeaderName] = userId;
453
- }
454
- }
455
- // 步骤 4: 添加 Token
456
- // 根据是否配置了加密服务,决定是否对 Token 进行加密
457
- if (options.getToken) {
458
- const token = options.getToken();
459
- if (token) {
460
- config.headers[tokenHeaderName] = encryptionService ? encryptionService.encryptToken(token) : token;
461
- }
462
- }
463
- // 步骤 5: 调用自定义请求拦截器
464
- // 允许用户进一步自定义请求配置
465
- if (options.onRequest) {
466
- return options.onRequest(config);
467
- }
468
- return config;
469
- });
470
- }
471
-
472
- /**
473
- * 响应拦截器
474
- *
475
- * 在收到响应后对响应进行处理,包括:
476
- * - 提取响应数据
477
- * - 调用自定义响应拦截器
478
- * - 统一错误处理
479
- *
480
- * @module interceptors/response
481
- */
482
- /**
483
- * 设置响应拦截器
484
- *
485
- * 为 Axios 实例添加响应拦截器,处理响应后的通用逻辑
486
- *
487
- * 成功响应处理:
488
- * 1. 提取响应数据(response.data)
489
- * 2. 调用自定义响应拦截器(如果配置)
490
- * 3. 返回处理后的数据
491
- *
492
- * 错误响应处理:
493
- * 1. 调用自定义错误处理(如果配置)
494
- * 2. 检查业务错误(success === false)
495
- * 3. 抛出错误
496
- *
497
- * @param instance - Axios 实例
498
- * @param options - 请求配置选项
499
- *
500
- * @example
501
- * ```ts
502
- * const instance = axios.create({ baseURL: 'https://api.example.com' });
503
- *
504
- * setResponseInterceptor(instance, {
505
- * baseURL: 'https://api.example.com',
506
- * onResponse: (data) => {
507
- * console.log('响应数据:', data);
508
- * return data;
509
- * },
510
- * onError: (error) => {
511
- * if (error.response?.status === 401) {
512
- * // 处理未授权错误
513
- * window.location.href = '/login';
514
- * }
515
- * throw error;
516
- * },
517
- * });
518
- * ```
519
- */
520
- function setResponseInterceptor(instance, options) {
521
- instance.interceptors.response.use(
522
- // 成功响应处理
523
- (response) => {
524
- // 提取响应数据(Axios 的响应数据在 response.data 中)
525
- const data = response.data;
526
- // 调用自定义响应拦截器
527
- // 允许用户对响应数据进行进一步处理
528
- if (options.onResponse) {
529
- return options.onResponse(data);
530
- }
531
- return data;
532
- },
533
- // 错误响应处理
534
- (error) => {
535
- // 优先调用自定义错误处理
536
- // 如果用户配置了 onError,由用户自行处理错误
537
- if (options.onError) {
538
- return options.onError(error);
539
- }
540
- // 获取响应数据(如果有)
541
- const responseData = error?.response?.data;
542
- // 检查业务错误
543
- // 业务错误:HTTP 状态码可能是 200,但响应体中 success === false
544
- if (responseData && typeof responseData === 'object' && !responseData.success) {
545
- // 创建业务错误对象
546
- const bizError = new Error(responseData.message || '接口请求失败');
547
- // 附加原始响应信息,方便调试
548
- bizError.response = error.response;
549
- bizError.data = responseData;
550
- throw bizError;
551
- }
552
- // 其他错误(网络错误、超时等)直接抛出
553
- throw error;
554
- });
555
- }
556
-
557
727
  /**
558
728
  * BeLinkHttp 请求客户端
559
729
  *
560
730
  * 单例模式的 HTTP 请求客户端
561
731
  * 支持时间同步和 Token 加密
732
+ * 支持 Axios 和 Taro.request 两种请求引擎
562
733
  *
563
734
  * @module BeLinkHttp
564
735
  *
565
- * @example
736
+ * @example Axios 模式(默认)
566
737
  * ```ts
567
738
  * import { beLinkHttp } from '@be-link/http';
568
739
  *
@@ -577,25 +748,49 @@ function setResponseInterceptor(instance, options) {
577
748
  * // 发起请求
578
749
  * const data = await beLinkHttp.get('/api/users');
579
750
  * ```
751
+ *
752
+ * @example Taro 模式
753
+ * ```ts
754
+ * import { beLinkHttp } from '@be-link/http';
755
+ * import Taro from '@tarojs/taro';
756
+ *
757
+ * // 初始化
758
+ * beLinkHttp.init({
759
+ * adapter: 'taro',
760
+ * taroRequest: Taro.request,
761
+ * baseURL: 'https://api.example.com',
762
+ * getToken: () => Taro.getStorageSync('token'),
763
+ * });
764
+ *
765
+ * // 发起请求(使用方式完全一致)
766
+ * const data = await beLinkHttp.get('/api/users');
767
+ * ```
580
768
  */
581
769
  /**
582
770
  * BeLinkHttp 单例类
583
771
  *
584
772
  * 提供统一的 HTTP 请求接口,支持:
773
+ * - 多适配器(Axios / Taro)
585
774
  * - 时间同步
586
775
  * - Token 加密
587
776
  * - 请求/响应拦截
588
777
  */
589
778
  class BeLinkHttp {
590
779
  constructor() {
591
- /** Axios 实例 */
592
- this.instance = null;
780
+ /** HTTP 适配器实例 */
781
+ this.adapter = null;
593
782
  /** 时间同步服务 */
594
783
  this.timeSyncService = null;
595
784
  /** 加密服务 */
596
785
  this.encryptionService = null;
786
+ /** 请求配置选项 */
787
+ this.options = null;
597
788
  /** 是否已初始化 */
598
789
  this.initialized = false;
790
+ /** 动态设置的 Token */
791
+ this._token = null;
792
+ /** 动态设置的 UserId */
793
+ this._userId = null;
599
794
  }
600
795
  /**
601
796
  * 初始化请求客户端
@@ -604,7 +799,7 @@ class BeLinkHttp {
604
799
  *
605
800
  * @param options - 请求配置选项
606
801
  *
607
- * @example
802
+ * @example Axios 模式
608
803
  * ```ts
609
804
  * beLinkHttp.init({
610
805
  * baseURL: 'https://api.example.com',
@@ -620,36 +815,61 @@ class BeLinkHttp {
620
815
  * getUserId: () => localStorage.getItem('userId'),
621
816
  * });
622
817
  * ```
818
+ *
819
+ * @example Taro 模式
820
+ * ```ts
821
+ * import Taro from '@tarojs/taro';
822
+ *
823
+ * beLinkHttp.init({
824
+ * adapter: 'taro',
825
+ * taroRequest: Taro.request,
826
+ * baseURL: 'https://api.example.com',
827
+ * getToken: () => Taro.getStorageSync('token'),
828
+ * });
829
+ * ```
623
830
  */
624
831
  init(options) {
625
- const { baseURL, timeout = 30000, headers = {}, timeSync, encryption } = options;
626
- // 创建 Axios 实例
627
- this.instance = axios.create({
628
- baseURL,
629
- timeout,
630
- headers: {
631
- 'Content-Type': 'application/json',
632
- ...headers,
633
- },
634
- });
832
+ this.options = options;
833
+ const { baseURL, timeout = 30000, headers = {}, timeSync, encryption, adapter = 'axios' } = options;
834
+ // 创建适配器
835
+ if (adapter === 'taro') {
836
+ if (!options.taroRequest) {
837
+ throw new Error('[BeLinkHttp] 使用 taro 适配器时必须传入 taroRequest');
838
+ }
839
+ this.adapter = new TaroAdapter({
840
+ baseURL,
841
+ timeout,
842
+ headers,
843
+ taroRequest: options.taroRequest,
844
+ });
845
+ }
846
+ else {
847
+ this.adapter = new AxiosAdapter({
848
+ baseURL,
849
+ timeout,
850
+ headers,
851
+ });
852
+ }
635
853
  // 创建时间同步服务
636
854
  if (timeSync?.syncUrl) {
637
- this.timeSyncService = new TimeSyncService(timeSync);
855
+ let fetchFn;
856
+ // 如果使用 Taro 适配器,创建 Taro Fetch 包装器
857
+ if (adapter === 'taro' && options.taroRequest) {
858
+ fetchFn = createTaroFetch(options.taroRequest);
859
+ }
860
+ this.timeSyncService = new TimeSyncService(timeSync, fetchFn);
638
861
  }
639
862
  // 创建加密服务
640
863
  if (encryption?.key && encryption?.iv) {
641
864
  this.encryptionService = new EncryptionService(encryption, this.timeSyncService);
642
865
  }
643
- // 设置拦截器
644
- setRequestInterceptor(this.instance, options, this.timeSyncService, this.encryptionService);
645
- setResponseInterceptor(this.instance, options);
646
866
  this.initialized = true;
647
867
  }
648
868
  /**
649
869
  * 检查是否已初始化
650
870
  */
651
871
  checkInitialized() {
652
- if (!this.initialized || !this.instance) {
872
+ if (!this.initialized || !this.adapter) {
653
873
  throw new Error('[BeLinkHttp] 请先调用 init() 方法进行初始化');
654
874
  }
655
875
  }
@@ -657,7 +877,7 @@ class BeLinkHttp {
657
877
  * GET 请求
658
878
  *
659
879
  * @param url - 请求 URL
660
- * @param config - 可选的 Axios 请求配置
880
+ * @param config - 可选的请求配置
661
881
  * @returns Promise 响应数据
662
882
  *
663
883
  * @example
@@ -667,15 +887,14 @@ class BeLinkHttp {
667
887
  * ```
668
888
  */
669
889
  async get(url, config) {
670
- this.checkInitialized();
671
- return this.instance.get(url, config);
890
+ return this.request({ ...config, url, method: 'GET' });
672
891
  }
673
892
  /**
674
893
  * POST 请求
675
894
  *
676
895
  * @param url - 请求 URL
677
896
  * @param data - 请求体数据
678
- * @param config - 可选的 Axios 请求配置
897
+ * @param config - 可选的请求配置
679
898
  * @returns Promise 响应数据
680
899
  *
681
900
  * @example
@@ -684,15 +903,14 @@ class BeLinkHttp {
684
903
  * ```
685
904
  */
686
905
  async post(url, data, config) {
687
- this.checkInitialized();
688
- return this.instance.post(url, data, config);
906
+ return this.request({ ...config, url, method: 'POST', data });
689
907
  }
690
908
  /**
691
909
  * PUT 请求
692
910
  *
693
911
  * @param url - 请求 URL
694
912
  * @param data - 请求体数据
695
- * @param config - 可选的 Axios 请求配置
913
+ * @param config - 可选的请求配置
696
914
  * @returns Promise 响应数据
697
915
  *
698
916
  * @example
@@ -701,15 +919,14 @@ class BeLinkHttp {
701
919
  * ```
702
920
  */
703
921
  async put(url, data, config) {
704
- this.checkInitialized();
705
- return this.instance.put(url, data, config);
922
+ return this.request({ ...config, url, method: 'PUT', data });
706
923
  }
707
924
  /**
708
925
  * PATCH 请求
709
926
  *
710
927
  * @param url - 请求 URL
711
928
  * @param data - 请求体数据
712
- * @param config - 可选的 Axios 请求配置
929
+ * @param config - 可选的请求配置
713
930
  * @returns Promise 响应数据
714
931
  *
715
932
  * @example
@@ -718,14 +935,13 @@ class BeLinkHttp {
718
935
  * ```
719
936
  */
720
937
  async patch(url, data, config) {
721
- this.checkInitialized();
722
- return this.instance.patch(url, data, config);
938
+ return this.request({ ...config, url, method: 'PATCH', data });
723
939
  }
724
940
  /**
725
941
  * DELETE 请求
726
942
  *
727
943
  * @param url - 请求 URL
728
- * @param config - 可选的 Axios 请求配置
944
+ * @param config - 可选的请求配置
729
945
  * @returns Promise 响应数据
730
946
  *
731
947
  * @example
@@ -734,19 +950,18 @@ class BeLinkHttp {
734
950
  * ```
735
951
  */
736
952
  async delete(url, config) {
737
- this.checkInitialized();
738
- return this.instance.delete(url, config);
953
+ return this.request({ ...config, url, method: 'DELETE' });
739
954
  }
740
955
  /**
741
956
  * 通用请求方法
742
957
  *
743
- * @param config - Axios 请求配置
958
+ * @param config - 请求配置
744
959
  * @returns Promise 响应数据
745
960
  *
746
961
  * @example
747
962
  * ```ts
748
963
  * const result = await beLinkHttp.request({
749
- * method: 'post',
964
+ * method: 'POST',
750
965
  * url: '/api/users',
751
966
  * data: { name: '张三' },
752
967
  * headers: { 'X-Custom': 'value' },
@@ -755,7 +970,25 @@ class BeLinkHttp {
755
970
  */
756
971
  async request(config) {
757
972
  this.checkInitialized();
758
- return this.instance.request(config);
973
+ try {
974
+ // 创建带有动态 token/userId 获取函数的 options
975
+ const optionsWithDynamicAuth = {
976
+ ...this.options,
977
+ getToken: () => this.getToken(),
978
+ getUserId: () => this.getUserId(),
979
+ };
980
+ // 处理请求(应用拦截器逻辑)
981
+ const processedConfig = await processRequest({ ...config }, optionsWithDynamicAuth, this.timeSyncService, this.encryptionService);
982
+ // 发起请求
983
+ const response = await this.adapter.request(processedConfig);
984
+ // 检查响应状态
985
+ const checkedData = checkResponseStatus(response.data, response.status);
986
+ // 处理响应
987
+ return processResponse(checkedData, this.options);
988
+ }
989
+ catch (error) {
990
+ return processError(error, this.options);
991
+ }
759
992
  }
760
993
  /**
761
994
  * 获取时间同步服务实例
@@ -776,10 +1009,90 @@ class BeLinkHttp {
776
1009
  /**
777
1010
  * 获取 Axios 实例
778
1011
  *
779
- * @returns Axios 实例,未初始化时返回 null
1012
+ * 仅在使用 axios 适配器时有效
1013
+ *
1014
+ * @returns Axios 实例,非 axios 适配器或未初始化时返回 null
780
1015
  */
781
1016
  getAxiosInstance() {
782
- return this.instance;
1017
+ if (this.adapter instanceof AxiosAdapter) {
1018
+ return this.adapter.getAxiosInstance();
1019
+ }
1020
+ return null;
1021
+ }
1022
+ /**
1023
+ * 获取当前适配器类型
1024
+ *
1025
+ * @returns 适配器类型,未初始化时返回 null
1026
+ */
1027
+ getAdapterType() {
1028
+ if (!this.initialized || !this.options) {
1029
+ return null;
1030
+ }
1031
+ return this.options.adapter || 'axios';
1032
+ }
1033
+ /**
1034
+ * 设置 Token
1035
+ *
1036
+ * 动态更新请求时使用的 Token,会覆盖 init 时传入的 getToken 返回值
1037
+ *
1038
+ * @param token - 新的 Token 值,传入 null 则清除
1039
+ *
1040
+ * @example
1041
+ * ```ts
1042
+ * // 登录后设置 Token
1043
+ * beLinkHttp.setToken('new-token-value');
1044
+ *
1045
+ * // 登出时清除 Token
1046
+ * beLinkHttp.setToken(null);
1047
+ * ```
1048
+ */
1049
+ setToken(token) {
1050
+ this._token = token;
1051
+ }
1052
+ /**
1053
+ * 设置 UserId
1054
+ *
1055
+ * 动态更新请求时使用的 UserId,会覆盖 init 时传入的 getUserId 返回值
1056
+ *
1057
+ * @param userId - 新的 UserId 值,传入 null 则清除
1058
+ *
1059
+ * @example
1060
+ * ```ts
1061
+ * // 登录后设置 UserId
1062
+ * beLinkHttp.setUserId('user-123');
1063
+ *
1064
+ * // 登出时清除 UserId
1065
+ * beLinkHttp.setUserId(null);
1066
+ * ```
1067
+ */
1068
+ setUserId(userId) {
1069
+ this._userId = userId;
1070
+ }
1071
+ /**
1072
+ * 获取当前 Token
1073
+ *
1074
+ * 优先返回通过 setToken 设置的值,否则调用 init 时传入的 getToken 函数
1075
+ *
1076
+ * @returns 当前 Token 值
1077
+ */
1078
+ getToken() {
1079
+ if (this._token !== null) {
1080
+ return this._token;
1081
+ }
1082
+ return this.options?.getToken?.() ?? null;
1083
+ }
1084
+ /**
1085
+ * 获取当前 UserId
1086
+ *
1087
+ * 优先返回通过 setUserId 设置的值,否则调用 init 时传入的 getUserId 函数
1088
+ *
1089
+ * @returns 当前 UserId 值
1090
+ */
1091
+ getUserId() {
1092
+ if (this._userId !== null) {
1093
+ return this._userId;
1094
+ }
1095
+ return this.options?.getUserId?.() ?? null;
783
1096
  }
784
1097
  /**
785
1098
  * 重置客户端
@@ -787,10 +1100,13 @@ class BeLinkHttp {
787
1100
  * 清除所有配置和实例,需要重新调用 init() 初始化
788
1101
  */
789
1102
  reset() {
790
- this.instance = null;
1103
+ this.adapter = null;
791
1104
  this.timeSyncService = null;
792
1105
  this.encryptionService = null;
1106
+ this.options = null;
793
1107
  this.initialized = false;
1108
+ this._token = null;
1109
+ this._userId = null;
794
1110
  }
795
1111
  }
796
1112
  /**
@@ -811,4 +1127,4 @@ class BeLinkHttp {
811
1127
  */
812
1128
  const beLinkHttp = new BeLinkHttp();
813
1129
 
814
- export { BeLinkHttp, beLinkHttp, beLinkHttp as default };
1130
+ export { AxiosAdapter, BeLinkHttp, TaroAdapter, beLinkHttp, createTaroFetch, beLinkHttp as default };