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