@polyv/request-core 2.0.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/README.md +171 -0
- package/ajax.d.ts +60 -0
- package/ajax.js +222 -0
- package/axios-interceptor.d.ts +10 -0
- package/axios-interceptor.js +62 -0
- package/config.d.ts +3 -0
- package/config.js +2 -0
- package/index.d.ts +7 -0
- package/index.js +7 -0
- package/interface/index.d.ts +118 -0
- package/interface/index.js +1 -0
- package/package.json +20 -0
- package/plugins/index.d.ts +56 -0
- package/plugins/index.js +1 -0
- package/plugins/plugin-controller.d.ts +24 -0
- package/plugins/plugin-controller.js +86 -0
- package/request-error.d.ts +12 -0
- package/request-error.js +14 -0
- package/utils.d.ts +10 -0
- package/utils.js +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# 保利威业务请求库
|
|
2
|
+
|
|
3
|
+
保利威前端项目请求库,用于前端项目的请求封装。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm install @polyv/request-core --save
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 使用
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { PolyvRequest } from '@polyv/request-core';
|
|
15
|
+
|
|
16
|
+
// 实例化请求器
|
|
17
|
+
const requester = new PolyvRequest({
|
|
18
|
+
baseUrl: '//api.polyv.net',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
(async () => {
|
|
22
|
+
// 执行请求
|
|
23
|
+
const data = await requester.get('/getDetail', {
|
|
24
|
+
channelId: 'xxxx'
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log(data);
|
|
28
|
+
})();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
使用 Typescript:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// 自定义请求选项
|
|
35
|
+
interface CustomRequestOptions {
|
|
36
|
+
test?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 实例化请求器
|
|
40
|
+
const requester = new PolyvRequest<CustomRequestOptions>({
|
|
41
|
+
baseUrl: '//api.polyv.net',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
interface ChannelDetail {
|
|
45
|
+
channelName: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
(async () => {
|
|
49
|
+
// 执行请求
|
|
50
|
+
const data = await requester.get<ChannelDetail>('/getDetail', {
|
|
51
|
+
channelId: 'xxxx'
|
|
52
|
+
}, {
|
|
53
|
+
test: true
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log(data.channelName);
|
|
57
|
+
})();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 实例化参数
|
|
61
|
+
|
|
62
|
+
| 参数名 | 用途 | 类型 | 默认值 |
|
|
63
|
+
| - | - | - | - |
|
|
64
|
+
| baseUrl | 接口请求地址前缀 | `string \| function` | `''` |
|
|
65
|
+
| requestPlugins | 请求插件 | `RequestPlugin[]` | `[]` |
|
|
66
|
+
| timeout | 超时时间,毫秒 | `number` | `10000` |
|
|
67
|
+
| requestType | 请求方式 | `RequestType` | `'form'` |
|
|
68
|
+
| responseType | 响应格式 | `ResponseType` | `'json'` |
|
|
69
|
+
| withCredentials | 跨域请求时是否提供凭据 | `boolean` | `false` |
|
|
70
|
+
|
|
71
|
+
## 发送请求
|
|
72
|
+
|
|
73
|
+
### 发送 GET 请求
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// 后端接口返回的 Response
|
|
77
|
+
interface ChannelDetailResponse {
|
|
78
|
+
code: number;
|
|
79
|
+
data: {
|
|
80
|
+
/** 频道名称 */
|
|
81
|
+
channelName: string;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 接口入参参数
|
|
86
|
+
interface ChannelDetailRequestParams {
|
|
87
|
+
/** 频道号 */
|
|
88
|
+
channelId: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const res = await requester.get<ChannelDetailResponse, ChannelDetailRequestParams>(
|
|
92
|
+
'/getDetail',
|
|
93
|
+
{
|
|
94
|
+
channelId: 'xxxx'
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
console.log('code', res.code);
|
|
99
|
+
console.log('channelName', res.data.channelName);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 发送 POST 请求
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
interface Response {
|
|
106
|
+
code: number;
|
|
107
|
+
message: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface RequestData {
|
|
111
|
+
userId: number;
|
|
112
|
+
name: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const res = await requester.post<Response, RequestData>(
|
|
116
|
+
'/save-name',
|
|
117
|
+
{
|
|
118
|
+
userId: 123,
|
|
119
|
+
name: '小明'
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
// 如果需要额外入参地址参数
|
|
123
|
+
params: {
|
|
124
|
+
channelId: 'xxx'
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (res.code === 200) {
|
|
130
|
+
console.log('提交成功');
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 请求选项
|
|
135
|
+
|
|
136
|
+
请求器实例的 `get`、`post` 方法的第三个入参为请求选项,详细类型查看 `RequestOptions` 类型。
|
|
137
|
+
|
|
138
|
+
## 请求插件
|
|
139
|
+
|
|
140
|
+
`@polyv/request-plugin-xxx` 为请求库的插件,使用方式如下:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { PolyvRequest } from '@polyv/request-core';
|
|
144
|
+
import { GlobalParamsRequestPlugin } from '@polyv/request-plugin-global-params';
|
|
145
|
+
import { StatusCodeRequestPlugin } from '@polyv/request-plugin-status-code';
|
|
146
|
+
|
|
147
|
+
const requester = new PolyvRequest({
|
|
148
|
+
requestPlugins: [
|
|
149
|
+
// 创建插件并传入 requestPlugins
|
|
150
|
+
new GlobalParamsRequestPlugin({
|
|
151
|
+
globalParams: {
|
|
152
|
+
viewerId: 'xxx',
|
|
153
|
+
channelId: 'xxx',
|
|
154
|
+
},
|
|
155
|
+
}),
|
|
156
|
+
new StatusCodeRequestPlugin(),
|
|
157
|
+
]
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 插件概览
|
|
162
|
+
|
|
163
|
+
| 插件名 | 用途 |
|
|
164
|
+
| - | - |
|
|
165
|
+
| [@polyv/request-plugin-global-params](https://npm-registry.polyv.net/-/web/detail/@polyv/request-plugin-global-params) | 全局 params 入参插件 |
|
|
166
|
+
| [@polyv/request-plugin-authorize-token](https://npm-registry.polyv.net/-/web/detail/@polyv/request-plugin-authorize-token) | 请求令牌插件 |
|
|
167
|
+
| [@polyv/request-plugin-authorize-app-sign](https://npm-registry.polyv.net/-/web/detail/@polyv/request-plugin-authorize-app-sign) | 请求签名插件 |
|
|
168
|
+
| [@polyv/request-plugin-status-code](https://npm-registry.polyv.net/-/web/detail/@polyv/request-plugin-status-code) | 状态码处理插件 |
|
|
169
|
+
| [@polyv/request-plugin-aes-decrypt](https://npm-registry.polyv.net/-/web/detail/@polyv/request-plugin-aes-decrypt) | AES 解密响应内容 |
|
|
170
|
+
| [@polyv/request-plugin-aes](https://npm-registry.polyv.net/-/web/detail/@polyv/request-plugin-aes) | AES 业务请求加密和解密 |
|
|
171
|
+
| [@polyv/request-plugin-sm2](https://npm-registry.polyv.net/-/web/detail/@polyv/request-plugin-sm2) | SM2 业务请求加密和解密 |
|
package/ajax.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { PolyvRequestConfig, RequestOptions } from './interface';
|
|
2
|
+
/**
|
|
3
|
+
* API 请求封装
|
|
4
|
+
*/
|
|
5
|
+
export declare class PolyvRequest<Options extends RequestOptions = RequestOptions> {
|
|
6
|
+
/** 是否已被销毁(不再允许执行 ajax 调用) */
|
|
7
|
+
private __isDestroyed;
|
|
8
|
+
/** 请求域名前缀 */
|
|
9
|
+
private __baseUrl;
|
|
10
|
+
private __timeout;
|
|
11
|
+
private __requestType;
|
|
12
|
+
private __responseType;
|
|
13
|
+
private __withCredentials;
|
|
14
|
+
/** 请求器 */
|
|
15
|
+
private __xhrRequest;
|
|
16
|
+
private __pluginCtrl;
|
|
17
|
+
constructor(options?: PolyvRequestConfig<Options>);
|
|
18
|
+
/**
|
|
19
|
+
* 修改请求前缀
|
|
20
|
+
* @param baseUrl 新的请求前缀
|
|
21
|
+
* @returns this
|
|
22
|
+
*/
|
|
23
|
+
setBaseUrl(baseUrl: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* 生成请求地址,插入 baseUrl
|
|
26
|
+
*/
|
|
27
|
+
generateUrl(options: Options): string;
|
|
28
|
+
/** 发起请求 */
|
|
29
|
+
protected _request<R = unknown>(options: Options): Promise<R>;
|
|
30
|
+
private __beforeSend;
|
|
31
|
+
/**
|
|
32
|
+
* 请求拦截
|
|
33
|
+
*/
|
|
34
|
+
private __interceptRequest;
|
|
35
|
+
/**
|
|
36
|
+
* 响应拦截
|
|
37
|
+
*/
|
|
38
|
+
private __interceptResponse;
|
|
39
|
+
/**
|
|
40
|
+
* 过滤请求中的 params 和 data,将 undefined 的字段移除
|
|
41
|
+
* @param target 目标对象
|
|
42
|
+
*/
|
|
43
|
+
private __filterParamsData;
|
|
44
|
+
/** 发起 get 请求 */
|
|
45
|
+
get<R = unknown, P = object>(url: string, params: P, options?: Options): Promise<R>;
|
|
46
|
+
/** 发起 post 请求 */
|
|
47
|
+
post<R = unknown, D = object>(url: string, data: D, options?: Options): Promise<R>;
|
|
48
|
+
/** 发起 delete 请求 */
|
|
49
|
+
delete<R = unknown, D = object>(url: string, data: D, options?: Options): Promise<R>;
|
|
50
|
+
/** 发起 put 请求 */
|
|
51
|
+
put<R = unknown, D = object>(url: string, data: D, options?: Options): Promise<R>;
|
|
52
|
+
/**
|
|
53
|
+
* 解析响应头
|
|
54
|
+
* @param headerStr 响应头字符串
|
|
55
|
+
* @returns 响应头对象
|
|
56
|
+
*/
|
|
57
|
+
private __parseResponseHeaders;
|
|
58
|
+
destroy(): void;
|
|
59
|
+
}
|
|
60
|
+
export default PolyvRequest;
|
package/ajax.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
2
|
+
import { Request } from '@just4/request';
|
|
3
|
+
import { xhrAdapter } from '@just4/request/adapter/xhr';
|
|
4
|
+
import { startsWithProtocol } from '@polyv/utils/es/net';
|
|
5
|
+
import { PluginController } from './plugins/plugin-controller';
|
|
6
|
+
import { buildFormData, hasFileValue } from './utils';
|
|
7
|
+
import { DEFAULT_REQUEST_TYPE, DEFAULT_RESPONSE_TYPE } from './config';
|
|
8
|
+
let xhrRequest;
|
|
9
|
+
/**
|
|
10
|
+
* API 请求封装
|
|
11
|
+
*/
|
|
12
|
+
export class PolyvRequest {
|
|
13
|
+
/** 是否已被销毁(不再允许执行 ajax 调用) */
|
|
14
|
+
__isDestroyed = false;
|
|
15
|
+
/** 请求域名前缀 */
|
|
16
|
+
__baseUrl = '';
|
|
17
|
+
__timeout;
|
|
18
|
+
__requestType;
|
|
19
|
+
__responseType;
|
|
20
|
+
__withCredentials;
|
|
21
|
+
/** 请求器 */
|
|
22
|
+
__xhrRequest;
|
|
23
|
+
__pluginCtrl;
|
|
24
|
+
constructor(options = {}) {
|
|
25
|
+
const { baseUrl } = options;
|
|
26
|
+
if (baseUrl) {
|
|
27
|
+
this.setBaseUrl(baseUrl);
|
|
28
|
+
}
|
|
29
|
+
this.__timeout = options.timeout || 10000;
|
|
30
|
+
this.__requestType = options.requestType || DEFAULT_REQUEST_TYPE;
|
|
31
|
+
this.__responseType = options.responseType || DEFAULT_RESPONSE_TYPE;
|
|
32
|
+
this.__withCredentials = options.withCredentials ?? false;
|
|
33
|
+
if (!xhrRequest) {
|
|
34
|
+
xhrRequest = new Request(xhrAdapter);
|
|
35
|
+
}
|
|
36
|
+
this.__xhrRequest = xhrRequest;
|
|
37
|
+
const requestPlugins = (options.requestPlugins || []);
|
|
38
|
+
this.__pluginCtrl = new PluginController(requestPlugins);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 修改请求前缀
|
|
42
|
+
* @param baseUrl 新的请求前缀
|
|
43
|
+
* @returns this
|
|
44
|
+
*/
|
|
45
|
+
setBaseUrl(baseUrl) {
|
|
46
|
+
this.__baseUrl = baseUrl;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 生成请求地址,插入 baseUrl
|
|
50
|
+
*/
|
|
51
|
+
generateUrl(options) {
|
|
52
|
+
if (options.requestUrl) {
|
|
53
|
+
return options.requestUrl;
|
|
54
|
+
}
|
|
55
|
+
const url = options.url || '';
|
|
56
|
+
let baseUrl = '';
|
|
57
|
+
if (typeof this.__baseUrl === 'string') {
|
|
58
|
+
baseUrl = this.__baseUrl;
|
|
59
|
+
}
|
|
60
|
+
// if (typeof this.__baseUrl === 'function') {
|
|
61
|
+
// baseUrl = this.__baseUrl();
|
|
62
|
+
// }
|
|
63
|
+
if (!baseUrl || startsWithProtocol(url)) {
|
|
64
|
+
return url;
|
|
65
|
+
}
|
|
66
|
+
return baseUrl + url;
|
|
67
|
+
}
|
|
68
|
+
/** 发起请求 */
|
|
69
|
+
async _request(options) {
|
|
70
|
+
if (this.__isDestroyed) {
|
|
71
|
+
return Promise.reject(new Error('Ajax has been destroyed.'));
|
|
72
|
+
}
|
|
73
|
+
let _options = { ...options };
|
|
74
|
+
// 处理请求地址
|
|
75
|
+
const orignRequestUrl = this.generateUrl(_options);
|
|
76
|
+
_options.requestUrl = orignRequestUrl;
|
|
77
|
+
// 超时时间
|
|
78
|
+
_options.timeout = _options.timeout || this.__timeout;
|
|
79
|
+
// 请求方式
|
|
80
|
+
_options.requestType = _options.requestType || this.__requestType;
|
|
81
|
+
_options.responseType = _options.responseType || this.__responseType;
|
|
82
|
+
_options.withCredentials = _options.withCredentials || this.__withCredentials;
|
|
83
|
+
// 处理插件中的请求拦截
|
|
84
|
+
_options = await this.__interceptRequest(_options);
|
|
85
|
+
// 处理后的请求地址
|
|
86
|
+
const requestUrl = _options.requestUrl || orignRequestUrl;
|
|
87
|
+
// 做最后一步处理
|
|
88
|
+
_options = this.__beforeSend(_options);
|
|
89
|
+
let result;
|
|
90
|
+
try {
|
|
91
|
+
// 调用请求库
|
|
92
|
+
const justRes = await this.__xhrRequest.send(requestUrl, {
|
|
93
|
+
..._options,
|
|
94
|
+
});
|
|
95
|
+
const headers = this.__parseResponseHeaders(justRes.xhr.getAllResponseHeaders());
|
|
96
|
+
// 请求结果
|
|
97
|
+
result = {
|
|
98
|
+
data: justRes.data,
|
|
99
|
+
headers,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
const error = e;
|
|
104
|
+
const justRes = error.result;
|
|
105
|
+
result = {
|
|
106
|
+
data: justRes.data,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (!result) {
|
|
110
|
+
throw Error('result is undefined');
|
|
111
|
+
}
|
|
112
|
+
// 处理插件中的响应拦截
|
|
113
|
+
result = await this.__interceptResponse(result, _options);
|
|
114
|
+
return result.data;
|
|
115
|
+
}
|
|
116
|
+
__beforeSend(options) {
|
|
117
|
+
const data = options.data || {};
|
|
118
|
+
// 处理 formData 提交
|
|
119
|
+
if (options.method === 'POST' && options.requestType === 'form' && typeof data === 'object') {
|
|
120
|
+
const useFormData = options.useFormData ?? hasFileValue(data);
|
|
121
|
+
if (useFormData) {
|
|
122
|
+
options.data = buildFormData(data);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return options;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 请求拦截
|
|
129
|
+
*/
|
|
130
|
+
async __interceptRequest(options) {
|
|
131
|
+
let newOptions = options;
|
|
132
|
+
// 处理插件中的请求拦截
|
|
133
|
+
newOptions = await this.__pluginCtrl.interceptPluginRequest(newOptions);
|
|
134
|
+
return newOptions;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 响应拦截
|
|
138
|
+
*/
|
|
139
|
+
async __interceptResponse(result, options) {
|
|
140
|
+
let newResult = result;
|
|
141
|
+
// 处理插件中的响应拦截
|
|
142
|
+
newResult = await this.__pluginCtrl.interceptPluginResponse(newResult, options);
|
|
143
|
+
return newResult;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 过滤请求中的 params 和 data,将 undefined 的字段移除
|
|
147
|
+
* @param target 目标对象
|
|
148
|
+
*/
|
|
149
|
+
__filterParamsData(target) {
|
|
150
|
+
const obj = {};
|
|
151
|
+
for (const key in target) {
|
|
152
|
+
const val = target[key];
|
|
153
|
+
if (typeof val !== 'undefined') {
|
|
154
|
+
obj[key] = val;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return obj;
|
|
158
|
+
}
|
|
159
|
+
/** 发起 get 请求 */
|
|
160
|
+
get(url, params, options) {
|
|
161
|
+
return this._request(Object.assign({
|
|
162
|
+
url,
|
|
163
|
+
method: 'GET',
|
|
164
|
+
params: this.__filterParamsData(params),
|
|
165
|
+
}, options));
|
|
166
|
+
}
|
|
167
|
+
/** 发起 post 请求 */
|
|
168
|
+
post(url, data, options) {
|
|
169
|
+
return this._request(Object.assign({
|
|
170
|
+
url,
|
|
171
|
+
method: 'POST',
|
|
172
|
+
data: this.__filterParamsData(data),
|
|
173
|
+
}, options));
|
|
174
|
+
}
|
|
175
|
+
/** 发起 delete 请求 */
|
|
176
|
+
delete(url, data, options) {
|
|
177
|
+
return this._request(Object.assign({
|
|
178
|
+
url,
|
|
179
|
+
method: 'DELETE',
|
|
180
|
+
data: this.__filterParamsData(data),
|
|
181
|
+
}, options));
|
|
182
|
+
}
|
|
183
|
+
/** 发起 put 请求 */
|
|
184
|
+
put(url, data, options) {
|
|
185
|
+
return this._request(Object.assign({
|
|
186
|
+
url,
|
|
187
|
+
method: 'PUT',
|
|
188
|
+
data: this.__filterParamsData(data),
|
|
189
|
+
}, options));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 解析响应头
|
|
193
|
+
* @param headerStr 响应头字符串
|
|
194
|
+
* @returns 响应头对象
|
|
195
|
+
*/
|
|
196
|
+
__parseResponseHeaders(headerStr) {
|
|
197
|
+
const headers = {};
|
|
198
|
+
// 如果字符串为空,则返回空对象
|
|
199
|
+
if (!headerStr) {
|
|
200
|
+
return headers;
|
|
201
|
+
}
|
|
202
|
+
// 按行分割头信息
|
|
203
|
+
const headerPairs = headerStr.trim().split('\r\n');
|
|
204
|
+
headerPairs.forEach(headerPair => {
|
|
205
|
+
// 查找第一个冒号的位置
|
|
206
|
+
const index = headerPair.indexOf(':');
|
|
207
|
+
// 如果找不到冒号,则跳过此行
|
|
208
|
+
if (index > 0) {
|
|
209
|
+
// 提取键(转为小写)和值(去除前后空格)
|
|
210
|
+
const key = headerPair.substring(0, index).trim().toLowerCase();
|
|
211
|
+
const value = headerPair.substring(index + 1).trim();
|
|
212
|
+
// 保存到结果对象
|
|
213
|
+
headers[key] = value;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
return headers;
|
|
217
|
+
}
|
|
218
|
+
destroy() {
|
|
219
|
+
this.__isDestroyed = true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
export default PolyvRequest;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
import { PolyvRequestConfig, RequestType } from './interface';
|
|
3
|
+
export type SetupAxiosInterceptorConfig = Pick<PolyvRequestConfig, 'baseUrl' | 'timeout' | 'requestPlugins'>;
|
|
4
|
+
declare module 'axios' {
|
|
5
|
+
interface AxiosRequestConfig {
|
|
6
|
+
/** 请求类型 */
|
|
7
|
+
requestType?: RequestType;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export declare function setupAxiosInterceptor(axios: AxiosInstance, config?: PolyvRequestConfig): void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { PluginController } from './plugins/plugin-controller';
|
|
2
|
+
import { DEFAULT_REQUEST_TYPE } from './config';
|
|
3
|
+
export function setupAxiosInterceptor(axios, config = {}) {
|
|
4
|
+
const pluginController = new PluginController(config.requestPlugins || []);
|
|
5
|
+
// 处理默认配置
|
|
6
|
+
if (config.baseUrl) {
|
|
7
|
+
axios.defaults.baseURL = config.baseUrl;
|
|
8
|
+
}
|
|
9
|
+
if (config.timeout) {
|
|
10
|
+
axios.defaults.timeout = config.timeout;
|
|
11
|
+
}
|
|
12
|
+
if (config.responseType) {
|
|
13
|
+
axios.defaults.responseType = config.responseType;
|
|
14
|
+
}
|
|
15
|
+
// 注入请求拦截器
|
|
16
|
+
axios.interceptors.request.use(async (options) => {
|
|
17
|
+
const axiosHeaders = options.headers;
|
|
18
|
+
const headers = axiosHeaders.toJSON();
|
|
19
|
+
const requestOptions = {
|
|
20
|
+
...options,
|
|
21
|
+
params: options.params,
|
|
22
|
+
data: options.data,
|
|
23
|
+
headers,
|
|
24
|
+
requestType: options.requestType || config.requestType || DEFAULT_REQUEST_TYPE,
|
|
25
|
+
};
|
|
26
|
+
const newOptions = await pluginController.interceptPluginRequest(requestOptions);
|
|
27
|
+
options.params = newOptions.params;
|
|
28
|
+
options.data = newOptions.data;
|
|
29
|
+
// 将新的请求头重新设置会 axios 中
|
|
30
|
+
const newHeaders = newOptions.headers;
|
|
31
|
+
if (newHeaders) {
|
|
32
|
+
for (const key in newHeaders) {
|
|
33
|
+
options.headers.set(key, newHeaders[key]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// 处理请求选项
|
|
37
|
+
if (newOptions.requestType && options.method?.toLocaleUpperCase() === 'POST') {
|
|
38
|
+
const reqTypes = {
|
|
39
|
+
json: 'application/json;charset=UTF-8',
|
|
40
|
+
form: 'application/x-www-form-urlencoded'
|
|
41
|
+
};
|
|
42
|
+
options.headers.setAccept('*/*');
|
|
43
|
+
options.headers.setContentType(reqTypes[newOptions.requestType]);
|
|
44
|
+
}
|
|
45
|
+
return options;
|
|
46
|
+
});
|
|
47
|
+
// 注入响应拦截器
|
|
48
|
+
axios.interceptors.response.use(async (response) => {
|
|
49
|
+
let headers = response.headers;
|
|
50
|
+
if (response.headers && typeof response.headers.toJSON === 'function') {
|
|
51
|
+
headers = response.headers.toJSON();
|
|
52
|
+
}
|
|
53
|
+
const result = {
|
|
54
|
+
data: response.data,
|
|
55
|
+
headers,
|
|
56
|
+
};
|
|
57
|
+
const requestResult = response.config;
|
|
58
|
+
const newResult = await pluginController.interceptPluginResponse(result, requestResult);
|
|
59
|
+
response.data = newResult.data;
|
|
60
|
+
return response;
|
|
61
|
+
});
|
|
62
|
+
}
|
package/config.d.ts
ADDED
package/config.js
ADDED
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { RequestPlugin } from '../plugins';
|
|
2
|
+
export type BaseUrlFn = () => string;
|
|
3
|
+
export interface PolyvRequestConfig<Options extends RequestOptions = RequestOptions> {
|
|
4
|
+
/**
|
|
5
|
+
* 接口请求地址前缀
|
|
6
|
+
* @default ''
|
|
7
|
+
*/
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
/**
|
|
10
|
+
* 超时时间,单位:毫秒
|
|
11
|
+
* @default 10000
|
|
12
|
+
*/
|
|
13
|
+
timeout?: number;
|
|
14
|
+
/**
|
|
15
|
+
* 请求方式,form 或 json。POST 或 PUT 时有效,默认为 form
|
|
16
|
+
*/
|
|
17
|
+
requestType?: RequestType;
|
|
18
|
+
/**
|
|
19
|
+
* 响应格式,默认为 json
|
|
20
|
+
*/
|
|
21
|
+
responseType?: ResponseType;
|
|
22
|
+
/**
|
|
23
|
+
* 请求插件
|
|
24
|
+
*/
|
|
25
|
+
requestPlugins?: RequestPlugin<Options>[];
|
|
26
|
+
/**
|
|
27
|
+
* 跨域请求时是否提供凭据,默认:false
|
|
28
|
+
*/
|
|
29
|
+
withCredentials?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 请求方法。
|
|
33
|
+
*/
|
|
34
|
+
export type RequestMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
35
|
+
/**
|
|
36
|
+
* 请求格式。
|
|
37
|
+
*/
|
|
38
|
+
export type RequestType = 'form' | 'json';
|
|
39
|
+
/**
|
|
40
|
+
* 响应格式。
|
|
41
|
+
*/
|
|
42
|
+
export type ResponseType = 'json' | 'text' | 'blob' | 'arraybuffer';
|
|
43
|
+
/**
|
|
44
|
+
* 请求公用选项
|
|
45
|
+
*/
|
|
46
|
+
export interface RequestBasicOptions {
|
|
47
|
+
/**
|
|
48
|
+
* 请求地址
|
|
49
|
+
*/
|
|
50
|
+
url?: string;
|
|
51
|
+
/**
|
|
52
|
+
* 完整的请求地址
|
|
53
|
+
*/
|
|
54
|
+
requestUrl?: string;
|
|
55
|
+
/**
|
|
56
|
+
* 请求方式
|
|
57
|
+
*/
|
|
58
|
+
method?: RequestMethod;
|
|
59
|
+
/**
|
|
60
|
+
* URL 参数。
|
|
61
|
+
*/
|
|
62
|
+
params?: object;
|
|
63
|
+
/**
|
|
64
|
+
* 请求体数据。
|
|
65
|
+
*/
|
|
66
|
+
data?: object | string;
|
|
67
|
+
/**
|
|
68
|
+
* 自定义请求头。
|
|
69
|
+
*/
|
|
70
|
+
headers?: object;
|
|
71
|
+
/**
|
|
72
|
+
* 超时时间,单位:毫秒
|
|
73
|
+
* @default 10000
|
|
74
|
+
*/
|
|
75
|
+
timeout?: number;
|
|
76
|
+
/**
|
|
77
|
+
* 请求方式,form 或 json。POST 或 PUT 时有效,默认为 json
|
|
78
|
+
*/
|
|
79
|
+
requestType?: RequestType;
|
|
80
|
+
/**
|
|
81
|
+
* 响应格式,默认为 json
|
|
82
|
+
*/
|
|
83
|
+
responseType?: ResponseType;
|
|
84
|
+
/**
|
|
85
|
+
* 跨域请求时是否提供凭据
|
|
86
|
+
*/
|
|
87
|
+
withCredentials?: boolean;
|
|
88
|
+
}
|
|
89
|
+
export interface RequestCustomOptions {
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 请求选项
|
|
93
|
+
*/
|
|
94
|
+
export interface RequestOptions extends RequestBasicOptions, RequestCustomOptions {
|
|
95
|
+
/**
|
|
96
|
+
* 使用 formData 提交
|
|
97
|
+
*/
|
|
98
|
+
useFormData?: boolean;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 请求结果
|
|
102
|
+
*/
|
|
103
|
+
export interface RequestResult {
|
|
104
|
+
/**
|
|
105
|
+
* 接口响应数据
|
|
106
|
+
*/
|
|
107
|
+
data?: unknown;
|
|
108
|
+
/**
|
|
109
|
+
* 响应头对象
|
|
110
|
+
*/
|
|
111
|
+
headers?: Record<string, string>;
|
|
112
|
+
}
|
|
113
|
+
export type UniversalParams = {
|
|
114
|
+
[key: string]: unknown;
|
|
115
|
+
};
|
|
116
|
+
export type RequiredField<T, K extends keyof T> = T & {
|
|
117
|
+
[P in K]-?: NonNullable<T[P]>;
|
|
118
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@polyv/request-core",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"main": "./index.js",
|
|
5
|
+
"peerDependencies": {
|
|
6
|
+
"axios": ">=1.0.0"
|
|
7
|
+
},
|
|
8
|
+
"peerDependenciesMeta": {
|
|
9
|
+
"axios": {
|
|
10
|
+
"optional": true
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@just4/request": "^0.6.0",
|
|
15
|
+
"@polyv/utils": "^2.6.0",
|
|
16
|
+
"md5": "2.3.0",
|
|
17
|
+
"sha256": "0.2.0"
|
|
18
|
+
},
|
|
19
|
+
"types": "./index.d.ts"
|
|
20
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { RequestOptions, RequestResult } from '../interface';
|
|
2
|
+
import { PluginController } from './plugin-controller';
|
|
3
|
+
/**
|
|
4
|
+
* 请求插件
|
|
5
|
+
*/
|
|
6
|
+
export interface RequestPlugin<Options extends RequestOptions = RequestOptions> {
|
|
7
|
+
/**
|
|
8
|
+
* 安装插件钩子
|
|
9
|
+
* @param pluginController 插件控制器
|
|
10
|
+
*/
|
|
11
|
+
installPlugin?: (pluginController: PluginController) => void;
|
|
12
|
+
/**
|
|
13
|
+
* 是否使用当前插件,不传默认为 true
|
|
14
|
+
* @param options 请求选项
|
|
15
|
+
* @returns 是否使用
|
|
16
|
+
*/
|
|
17
|
+
usePlugin?: (options: Options) => Promise<boolean> | boolean;
|
|
18
|
+
/**
|
|
19
|
+
* 插入 params 参数,仅插入参数
|
|
20
|
+
* @param options 请求选项
|
|
21
|
+
* @returns 请求选项
|
|
22
|
+
*/
|
|
23
|
+
interceptIncludeParams?: (options: Options) => Promise<object | void> | object | void;
|
|
24
|
+
/**
|
|
25
|
+
* 请求授权
|
|
26
|
+
* @param options 请求选项
|
|
27
|
+
* @returns 请求选项
|
|
28
|
+
*/
|
|
29
|
+
interceptAuthorizeRequest?: (options: Options) => Promise<Options | void> | Options | void;
|
|
30
|
+
/**
|
|
31
|
+
* 请求加密
|
|
32
|
+
* @param options 请求选项
|
|
33
|
+
* @returns 请求选项
|
|
34
|
+
*/
|
|
35
|
+
interceptEncryptRequest?: (options: Options) => Promise<Options | void> | Options | void;
|
|
36
|
+
/**
|
|
37
|
+
* 请求拦截器
|
|
38
|
+
* @param options 请求选项
|
|
39
|
+
* @returns 请求选项
|
|
40
|
+
*/
|
|
41
|
+
interceptRequest?: (options: Options) => Promise<Options | void> | Options | void;
|
|
42
|
+
/**
|
|
43
|
+
* 响应解密
|
|
44
|
+
* @param result 请求结果
|
|
45
|
+
* @param options 请求选项
|
|
46
|
+
* @returns 请求结果
|
|
47
|
+
*/
|
|
48
|
+
interceptDecryptResponse?: (result: RequestResult, options: Options) => Promise<RequestResult | void> | RequestResult | void;
|
|
49
|
+
/**
|
|
50
|
+
* 响应拦截器
|
|
51
|
+
* @param result 请求结果
|
|
52
|
+
* @param options 请求选项
|
|
53
|
+
* @returns 请求结果
|
|
54
|
+
*/
|
|
55
|
+
interceptResponse?: (result: RequestResult, options: Options) => Promise<RequestResult | void> | RequestResult | void;
|
|
56
|
+
}
|
package/plugins/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { RequestOptions, RequestResult } from "../interface";
|
|
2
|
+
import { RequestPlugin } from "./index";
|
|
3
|
+
export declare class PluginController {
|
|
4
|
+
/** 请求插件 */
|
|
5
|
+
private __requestPlugins;
|
|
6
|
+
constructor(plugins: RequestPlugin[]);
|
|
7
|
+
installPlugin(plugin: RequestPlugin): void;
|
|
8
|
+
private __getUsePlugin;
|
|
9
|
+
/**
|
|
10
|
+
* 遍历请求插件中的某个方法
|
|
11
|
+
* @param options 请求选项
|
|
12
|
+
* @param fnName 插件方法名
|
|
13
|
+
* @param callback 处理回调
|
|
14
|
+
*/
|
|
15
|
+
private __forEachRequestPlugin;
|
|
16
|
+
/**
|
|
17
|
+
* 请求拦截
|
|
18
|
+
*/
|
|
19
|
+
interceptPluginRequest(options: RequestOptions): Promise<RequestOptions>;
|
|
20
|
+
/**
|
|
21
|
+
* 响应拦截
|
|
22
|
+
*/
|
|
23
|
+
interceptPluginResponse(result: RequestResult, options: RequestOptions): Promise<RequestResult>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export class PluginController {
|
|
2
|
+
/** 请求插件 */
|
|
3
|
+
__requestPlugins = [];
|
|
4
|
+
constructor(plugins) {
|
|
5
|
+
plugins.forEach(plugin => {
|
|
6
|
+
this.installPlugin(plugin);
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
installPlugin(plugin) {
|
|
10
|
+
this.__requestPlugins.push(plugin);
|
|
11
|
+
if (plugin.installPlugin) {
|
|
12
|
+
plugin.installPlugin(this);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async __getUsePlugin(options, requestPlugin) {
|
|
16
|
+
if (!requestPlugin.usePlugin) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
const use = await requestPlugin.usePlugin(options);
|
|
20
|
+
return !!use;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 遍历请求插件中的某个方法
|
|
24
|
+
* @param options 请求选项
|
|
25
|
+
* @param fnName 插件方法名
|
|
26
|
+
* @param callback 处理回调
|
|
27
|
+
*/
|
|
28
|
+
async __forEachRequestPlugin(options, fnName, callback) {
|
|
29
|
+
for (let i = 0; i < this.__requestPlugins.length; i++) {
|
|
30
|
+
const requestPlugin = this.__requestPlugins[i];
|
|
31
|
+
const usePlugin = await this.__getUsePlugin(options, requestPlugin);
|
|
32
|
+
if (usePlugin && requestPlugin[fnName]) {
|
|
33
|
+
await callback(requestPlugin);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 请求拦截
|
|
39
|
+
*/
|
|
40
|
+
async interceptPluginRequest(options) {
|
|
41
|
+
let newOptions = options;
|
|
42
|
+
// 处理插件中的 includeParams
|
|
43
|
+
await this.__forEachRequestPlugin(options, 'interceptIncludeParams', async (requestPlugin) => {
|
|
44
|
+
const includeParams = await requestPlugin.interceptIncludeParams(newOptions);
|
|
45
|
+
if (includeParams) {
|
|
46
|
+
newOptions.params = {
|
|
47
|
+
...(newOptions.params || {}),
|
|
48
|
+
...includeParams,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
// 处理插件中的请求授权
|
|
53
|
+
await this.__forEachRequestPlugin(options, 'interceptAuthorizeRequest', async (requestPlugin) => {
|
|
54
|
+
const r = await requestPlugin.interceptAuthorizeRequest(newOptions);
|
|
55
|
+
newOptions = r || newOptions;
|
|
56
|
+
});
|
|
57
|
+
// 处理插件中的请求加密
|
|
58
|
+
await this.__forEachRequestPlugin(options, 'interceptEncryptRequest', async (requestPlugin) => {
|
|
59
|
+
const r = await requestPlugin.interceptEncryptRequest(newOptions);
|
|
60
|
+
newOptions = r || newOptions;
|
|
61
|
+
});
|
|
62
|
+
// 处理插件中的请求
|
|
63
|
+
await this.__forEachRequestPlugin(options, 'interceptRequest', async (requestPlugin) => {
|
|
64
|
+
const r = await requestPlugin.interceptRequest(newOptions);
|
|
65
|
+
newOptions = r || newOptions;
|
|
66
|
+
});
|
|
67
|
+
return newOptions;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 响应拦截
|
|
71
|
+
*/
|
|
72
|
+
async interceptPluginResponse(result, options) {
|
|
73
|
+
let newResult = result;
|
|
74
|
+
// 处理插件中的解密
|
|
75
|
+
await this.__forEachRequestPlugin(options, 'interceptDecryptResponse', async (requestPlugin) => {
|
|
76
|
+
const r = await requestPlugin.interceptDecryptResponse(newResult, options);
|
|
77
|
+
newResult = r || newResult;
|
|
78
|
+
});
|
|
79
|
+
// 处理插件中的响应
|
|
80
|
+
await this.__forEachRequestPlugin(options, 'interceptResponse', async (requestPlugin) => {
|
|
81
|
+
const r = await requestPlugin.interceptResponse(newResult, options);
|
|
82
|
+
newResult = r || newResult;
|
|
83
|
+
});
|
|
84
|
+
return newResult;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface RequestErrorOptions {
|
|
2
|
+
code: number;
|
|
3
|
+
message: string;
|
|
4
|
+
}
|
|
5
|
+
export default class RequestError extends Error {
|
|
6
|
+
/**
|
|
7
|
+
* 错误码
|
|
8
|
+
*/
|
|
9
|
+
code: number;
|
|
10
|
+
constructor(options: RequestErrorOptions);
|
|
11
|
+
}
|
|
12
|
+
export declare function createRequestError(options: RequestErrorOptions): RequestError;
|
package/request-error.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default class RequestError extends Error {
|
|
2
|
+
/**
|
|
3
|
+
* 错误码
|
|
4
|
+
*/
|
|
5
|
+
code;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
const { code, message } = options;
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function createRequestError(options) {
|
|
13
|
+
return new RequestError(options);
|
|
14
|
+
}
|
package/utils.d.ts
ADDED
package/utils.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 是否存在 File 值
|
|
3
|
+
* @param data
|
|
4
|
+
*/
|
|
5
|
+
export function hasFileValue(data) {
|
|
6
|
+
for (const key in data) {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
const val = data[key];
|
|
9
|
+
if (val instanceof File) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 构建formData对象
|
|
17
|
+
* @param data 数据
|
|
18
|
+
*/
|
|
19
|
+
export function buildFormData(data) {
|
|
20
|
+
const form = new FormData();
|
|
21
|
+
for (const key in data) {
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
+
const val = data[key];
|
|
24
|
+
form.append(key, val);
|
|
25
|
+
}
|
|
26
|
+
return form;
|
|
27
|
+
}
|