axios-annotations 2.2.0 → 2.3.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/LICENSE +21 -21
- package/README.md +944 -944
- package/lib/core/expect.d.ts +4 -4
- package/lib/core/expect.js +15 -15
- package/lib/core/service.d.ts +2 -2
- package/lib/plugins/auth/queue.js +21 -13
- package/package.json +16 -16
package/README.md
CHANGED
|
@@ -1,944 +1,944 @@
|
|
|
1
|
-
# Axios Annotations
|
|
2
|
-
|
|
3
|
-
Quick Configuration Framework without Typescript using axios.<br/>
|
|
4
|
-
|
|
5
|
-
声明式`API`配置工具。
|
|
6
|
-
|
|
7
|
-
## Quick Overview
|
|
8
|
-
|
|
9
|
-
+ Step 1:继承服务类`Service`
|
|
10
|
-
+ Step 2:注解路径和参数
|
|
11
|
-
+ Step 3:构建服务实例,调用接口
|
|
12
|
-
|
|
13
|
-
### Basic Usage
|
|
14
|
-
|
|
15
|
-
备胎,如果开发环境不支持装饰器。
|
|
16
|
-
|
|
17
|
-
```javascript
|
|
18
|
-
import {config, Service} from "axios-annotations"
|
|
19
|
-
|
|
20
|
-
config.protocol = "http";
|
|
21
|
-
config.host = "localhost";
|
|
22
|
-
config.port = 8080;
|
|
23
|
-
config.prefix = "/api";
|
|
24
|
-
|
|
25
|
-
export default class TestService extends Service {
|
|
26
|
-
/**
|
|
27
|
-
* new TestService().get("a","b",null);
|
|
28
|
-
* <br>
|
|
29
|
-
* http://localhost:8080/api/path?p1=a&p2=b
|
|
30
|
-
* @param data1
|
|
31
|
-
* @param data2
|
|
32
|
-
* @returns {AxiosPromise<any>}
|
|
33
|
-
*/
|
|
34
|
-
get(required1, required2, optional1) {
|
|
35
|
-
return this.requestWith("GET", "/path")
|
|
36
|
-
.param("p1", true)
|
|
37
|
-
.param("p2", true)
|
|
38
|
-
.param("p3", false)
|
|
39
|
-
.send({
|
|
40
|
-
p1: required1,
|
|
41
|
-
p2: required2,
|
|
42
|
-
p3: optional1
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
post(data1, data2) {
|
|
47
|
-
return this.requestWith("POST", "/path2")
|
|
48
|
-
.param("p1", true)
|
|
49
|
-
.body("p2")
|
|
50
|
-
.send({
|
|
51
|
-
p1: data1,
|
|
52
|
-
p2: data2
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
path(id) {
|
|
57
|
-
return this.requestWith("GET", "/path3/{id}")
|
|
58
|
-
.send({
|
|
59
|
-
id
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
basic() {
|
|
64
|
-
return this.request("POST", "/path3?p1=a&p2=b", {field1: 'c', field: 'd'},
|
|
65
|
-
{
|
|
66
|
-
headers: {
|
|
67
|
-
'Content-Type': 'application/json'
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
剩下自由发挥,自行管理服务实例。
|
|
76
|
-
|
|
77
|
-
```javascript
|
|
78
|
-
const ApiCommon = {
|
|
79
|
-
test: new TestService()
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// 调用接口
|
|
83
|
-
ApiCommon.test.get("a","b",null);
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
### Basic Usage With Decorators
|
|
89
|
-
|
|
90
|
-
使用装饰器
|
|
91
|
-
<br>
|
|
92
|
-
可能需要插件支持:
|
|
93
|
-
<br>
|
|
94
|
-
`@babel/plugin-proposal-decorators`
|
|
95
|
-
<br>
|
|
96
|
-
`@babel/plugin-proposal-class-properties`
|
|
97
|
-
<br>
|
|
98
|
-
添加配置:
|
|
99
|
-
|
|
100
|
-
```json
|
|
101
|
-
{
|
|
102
|
-
"plugins": [
|
|
103
|
-
[
|
|
104
|
-
"@babel/plugin-proposal-decorators",
|
|
105
|
-
{
|
|
106
|
-
"legacy": true
|
|
107
|
-
}
|
|
108
|
-
],
|
|
109
|
-
[
|
|
110
|
-
"@babel/plugin-proposal-class-properties",
|
|
111
|
-
{
|
|
112
|
-
"loose": true
|
|
113
|
-
}
|
|
114
|
-
]
|
|
115
|
-
]
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
tsconfig.json / jsconfig.json
|
|
119
|
-
```json
|
|
120
|
-
{
|
|
121
|
-
"compilerOptions": {
|
|
122
|
-
"experimentalDecorators": true,
|
|
123
|
-
"emitDecoratorMetadata": true
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
`vue-cli`等脚手架已默认支持装饰器,微信小程序说明请拉到末尾。
|
|
129
|
-
<br>接口方法只需要处理和返回参数,并注解参数类型,框架根据注解分拆参数并注入`HTTP`请求。
|
|
130
|
-
|
|
131
|
-
```javascript
|
|
132
|
-
import {
|
|
133
|
-
Service,
|
|
134
|
-
RequestConfig,
|
|
135
|
-
RequestParam,
|
|
136
|
-
RequestMapping,
|
|
137
|
-
RequestBody,
|
|
138
|
-
RequestHeader,
|
|
139
|
-
IgnoreResidualParams
|
|
140
|
-
} from "axios-annotations";
|
|
141
|
-
|
|
142
|
-
@RequestMapping("/api")
|
|
143
|
-
export default class TestService extends Service {
|
|
144
|
-
@RequestMapping("/path", "GET")
|
|
145
|
-
@RequestParam("p1", true)
|
|
146
|
-
@RequestParam("p2", true)
|
|
147
|
-
@RequestParam("p3", false)
|
|
148
|
-
get(p1, p2, p3) {
|
|
149
|
-
return {p1, p2, p3};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
@RequestMapping("/path2", "POST")
|
|
153
|
-
@RequestParam("p1", true)
|
|
154
|
-
@RequestBody("p2")
|
|
155
|
-
@RequestHeader("Content-Type", "text/plain")
|
|
156
|
-
post(p1, str2) {
|
|
157
|
-
const defaultValue = {p1: "0x123456"};
|
|
158
|
-
return Object.assign(defaultValue, {p1, p2: str2});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
@RequestMapping("/path", "GET")
|
|
162
|
-
@IgnoreResidualParams(false)
|
|
163
|
-
getDefault() {
|
|
164
|
-
return {
|
|
165
|
-
p1: "p1",
|
|
166
|
-
p2: "p2",
|
|
167
|
-
p3: "p3"
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
调用接口:
|
|
174
|
-
|
|
175
|
-
```javascript
|
|
176
|
-
const ApiCommon = {
|
|
177
|
-
test: new TestService()
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
// 调用API
|
|
181
|
-
ApiCommon.test.get("a","b",null);
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
如果不爽部分IDE的`non-promise inspection info`下划线,也可以给方法加上`async`。
|
|
187
|
-
|
|
188
|
-
### 代码提示
|
|
189
|
-
|
|
190
|
-
> Expect<ResponseType, PromiseType = AxiosPromise<ResponseType>>
|
|
191
|
-
|
|
192
|
-
`Typescript`不支持装饰器修改方法返回值,但是可以绕开检查。<br/>
|
|
193
|
-
|
|
194
|
-
Using a method decorator can not change the function return type.<br/>
|
|
195
|
-
|
|
196
|
-
使用`Expect`绕过类型检查,获得代码提示功能。<br/>
|
|
197
|
-
|
|
198
|
-
Using `Expect` to bypass type-checking and get code intelligence.
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
import {Expect} from "axios-annotations";
|
|
202
|
-
|
|
203
|
-
// ...
|
|
204
|
-
|
|
205
|
-
export default class TestService extends Service {
|
|
206
|
-
@RequestBody()
|
|
207
|
-
@RequestMapping("/message", "POST")
|
|
208
|
-
@RequestHeader("Content-Type", "text/plain")
|
|
209
|
-
postMessage(message: string) {
|
|
210
|
-
return Expect<{
|
|
211
|
-
data: string;
|
|
212
|
-
success: boolean;
|
|
213
|
-
}>({
|
|
214
|
-
body: message
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// call method, the return value can be deconstructed by IDE
|
|
220
|
-
|
|
221
|
-
const res: AxiosResponse<{
|
|
222
|
-
data:string;
|
|
223
|
-
success:boolean;
|
|
224
|
-
}> = await new TestService().postMessage("foo");
|
|
225
|
-
|
|
226
|
-
console.log(res.data.data);
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
### QueryString Encoding
|
|
232
|
-
|
|
233
|
-
`key-values pair`转查询串算法,运行环境不支持`URLSearchParams`时使用默认算法,也可以自定义。
|
|
234
|
-
<br>
|
|
235
|
-
使用第三方库,`qs`,`querystring`,`url-search-params-polyfill`等,不同运行环境下可能有差异。
|
|
236
|
-
|
|
237
|
-
```javascript
|
|
238
|
-
import qs from "qs";
|
|
239
|
-
import {URLSearchParamsParser} from "axios-annotations";
|
|
240
|
-
|
|
241
|
-
if (typeof URLSearchParams === "undefined") {
|
|
242
|
-
URLSearchParamsParser.encode = function (encoder) {
|
|
243
|
-
return qs.stringify(encoder);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
## Configuration
|
|
249
|
-
|
|
250
|
-
### Custom Config
|
|
251
|
-
|
|
252
|
-
配置服务链接,框架自带默认配置对象`config`,建议自行创建,使用`@RequestConfig`注入`Service`。
|
|
253
|
-
|
|
254
|
-
```javascript
|
|
255
|
-
import {
|
|
256
|
-
Config,
|
|
257
|
-
RequestConfig,
|
|
258
|
-
RequestMapping
|
|
259
|
-
} from "axios-annotations";
|
|
260
|
-
|
|
261
|
-
const config = new Config({
|
|
262
|
-
host: "localhost",
|
|
263
|
-
port: 8086,
|
|
264
|
-
protocol: "http",
|
|
265
|
-
prefix: "/api",
|
|
266
|
-
plugins: []
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
@RequestConfig(config)
|
|
270
|
-
@RequestMapping("/test")
|
|
271
|
-
export default class TestService extends Service {
|
|
272
|
-
@RequestMapping("/{id}", "GET")
|
|
273
|
-
foo(id) {
|
|
274
|
-
return {id};
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
### Default Config
|
|
280
|
-
|
|
281
|
-
All Services inject this by default.
|
|
282
|
-
|
|
283
|
-
```javascript
|
|
284
|
-
import {config} from "axios-annotations";
|
|
285
|
-
|
|
286
|
-
config.host = "192.168.137.1";
|
|
287
|
-
config.port = 8080;
|
|
288
|
-
// ...
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### Register Config
|
|
292
|
-
|
|
293
|
-
注册配置,用途:<br>
|
|
294
|
-
|
|
295
|
-
+ 不需要 `export` 导出,使用 `Config.forName(name:string)` 获取。
|
|
296
|
-
+ 部分特殊请求可能需要绕开自身配置,使用`@RequestWith(configName: string)`注解方法,请求将使用指定配置进行构建。
|
|
297
|
-
|
|
298
|
-
```javascript
|
|
299
|
-
new Config({
|
|
300
|
-
protocol: "http",
|
|
301
|
-
host: "localhost",
|
|
302
|
-
port: 9999,
|
|
303
|
-
prefix: "/auth"
|
|
304
|
-
}).register("withoutAuth");
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
```javascript
|
|
308
|
-
@RequestConfig(new Config({
|
|
309
|
-
protocol: "http",
|
|
310
|
-
host: "localhost",
|
|
311
|
-
port: 8888,
|
|
312
|
-
prefix: "/prefix"
|
|
313
|
-
}))
|
|
314
|
-
@RequestMapping("/oauth")
|
|
315
|
-
class AuthService extends Service {
|
|
316
|
-
@PostMapping("/login")
|
|
317
|
-
@RequestWith("withoutAuth")
|
|
318
|
-
login() {
|
|
319
|
-
// http://localhost:9999/auth/oauth/login
|
|
320
|
-
return {usename: "0x123456", password: "123456"};
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
@GetMapping("/foo")
|
|
324
|
-
bar() {
|
|
325
|
-
// http://localhost:8888/prefix/oauth/foo
|
|
326
|
-
return {};
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
## Abort
|
|
332
|
-
小程序端第三方库可能没有实现请求取消接口。
|
|
333
|
-
### 静态注入
|
|
334
|
-
|
|
335
|
-
静态注入的`AbortController`取消请求为一次性,仅调试用途。
|
|
336
|
-
|
|
337
|
-
```javascript
|
|
338
|
-
const controller = new AbortController();
|
|
339
|
-
// ...
|
|
340
|
-
class AuthService extends Service {
|
|
341
|
-
// ...
|
|
342
|
-
@RequestConfig({
|
|
343
|
-
signal: controller.signal
|
|
344
|
-
})
|
|
345
|
-
bar() {
|
|
346
|
-
// ....
|
|
347
|
-
return {};
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// 取消请求
|
|
352
|
-
controller.abort()
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
或者:
|
|
356
|
-
|
|
357
|
-
```javascript
|
|
358
|
-
const controller = new AbortController();
|
|
359
|
-
|
|
360
|
-
// ...
|
|
361
|
-
class AuthService extends Service {
|
|
362
|
-
// ...
|
|
363
|
-
@AbortSource(controller)
|
|
364
|
-
bar() {
|
|
365
|
-
// ....
|
|
366
|
-
return {};
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
new AuthService().bar().then(() => {
|
|
371
|
-
|
|
372
|
-
}).catch(e => {
|
|
373
|
-
console.log(axios.isCancel(e));
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
// 取消请求
|
|
377
|
-
controller.abort()
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
兼容旧版 `CancelToken` `(deprecated)`:
|
|
381
|
-
```javascript
|
|
382
|
-
const CancelToken = axios.CancelToken;
|
|
383
|
-
|
|
384
|
-
const controller = new AbortControllerAdapter(CancelToken);
|
|
385
|
-
|
|
386
|
-
// ...
|
|
387
|
-
class AuthService extends Service {
|
|
388
|
-
// ...
|
|
389
|
-
@AbortSource(controller)
|
|
390
|
-
bar() {
|
|
391
|
-
// ....
|
|
392
|
-
return {};
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
new AuthService().bar().then(() => {
|
|
398
|
-
// ...
|
|
399
|
-
}).catch(e => {
|
|
400
|
-
console.log(axios.isCancel(e));
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
// 取消请求
|
|
404
|
-
controller.signal.onabort = () => {
|
|
405
|
-
console.log("aborted");
|
|
406
|
-
};
|
|
407
|
-
controller.abort("cancel test")
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
### 动态创建中断源
|
|
411
|
-
不确定中断时机,自由发挥。
|
|
412
|
-
```javascript
|
|
413
|
-
// 自定义中断逻辑
|
|
414
|
-
class AbortSourceManager {
|
|
415
|
-
// ...
|
|
416
|
-
|
|
417
|
-
create () {
|
|
418
|
-
return AbortController();
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
abortAll () {
|
|
422
|
-
// ...
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const manager = new AbortSourceManager();
|
|
427
|
-
|
|
428
|
-
// ...
|
|
429
|
-
class AuthService extends Service {
|
|
430
|
-
// ...
|
|
431
|
-
@RequestConfig((...args)=>{
|
|
432
|
-
console.log(args);
|
|
433
|
-
const controller = manager.create();
|
|
434
|
-
return {
|
|
435
|
-
// ...
|
|
436
|
-
signal: controller.signal
|
|
437
|
-
};
|
|
438
|
-
})
|
|
439
|
-
bar() {
|
|
440
|
-
// ....
|
|
441
|
-
return {};
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
## Plugin
|
|
448
|
-
|
|
449
|
-
### Custom Plugin
|
|
450
|
-
|
|
451
|
-
插件函数接收配置对象为参数,出于扩展性考虑,通常由高阶函数返回。
|
|
452
|
-
|
|
453
|
-
插件在`Config`对象的`axios`实例创建时注入,建议在`Config`构造函数配置。
|
|
454
|
-
|
|
455
|
-
```typescript
|
|
456
|
-
import {Config} from "axios-annotations"
|
|
457
|
-
import type {AxiosInstance} from "axios";
|
|
458
|
-
|
|
459
|
-
export function ToastPlugin(fnToast) {
|
|
460
|
-
return function (config: Config, axios: AxiosInstance) {
|
|
461
|
-
axios.interceptors.response.use(function (e) {
|
|
462
|
-
return Promise.resolve(e);
|
|
463
|
-
}, function (e) {
|
|
464
|
-
fnToast(e);
|
|
465
|
-
return Promise.reject(e);
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
axios.interceptors.request.use(function (e) {
|
|
469
|
-
return Promise.resolve(e);
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
配置插件:
|
|
476
|
-
|
|
477
|
-
```javascript
|
|
478
|
-
new Config({
|
|
479
|
-
plugins: [
|
|
480
|
-
ToastPlugin(function (e) {
|
|
481
|
-
if (typeof wx !== "undefined") {
|
|
482
|
-
wx.showToast({
|
|
483
|
-
icon: "none",
|
|
484
|
-
title: `[${e.response.status}]` + ' ' + e.config.url
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
})
|
|
488
|
-
]
|
|
489
|
-
})
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
### <text style="color:red;">Auth Plugin</text>
|
|
495
|
-
|
|
496
|
-
可选的内置插件,适配需要身份认证的服务,以`Spring Security`作为后端实现为例。
|
|
497
|
-
<br>
|
|
498
|
-
Basic Usage for Auth Plugin.
|
|
499
|
-
<br>
|
|
500
|
-
Take the case of `Spring Security OAtuh2.0`。
|
|
501
|
-
|
|
502
|
-
```javascript
|
|
503
|
-
// DevServer Proxy Config
|
|
504
|
-
const authCfg = new Config({
|
|
505
|
-
host: "localhost",
|
|
506
|
-
port: 8080,
|
|
507
|
-
protocol: "http",
|
|
508
|
-
prefix: "/api"
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
@RequestConfig(authCfg)
|
|
512
|
-
@RequestMapping("/oauth")
|
|
513
|
-
export default class OAuth2Service extends Service {
|
|
514
|
-
@GetMapping("/token")
|
|
515
|
-
@RequestParam("grant_type", true)
|
|
516
|
-
@RequestParam("scope", false)
|
|
517
|
-
@RequestParam("client_id", false)
|
|
518
|
-
@RequestParam("client_secret", false)
|
|
519
|
-
@RequestParam("username", false)
|
|
520
|
-
@RequestParam("password", false)
|
|
521
|
-
@RequestBody()
|
|
522
|
-
token() {
|
|
523
|
-
return {
|
|
524
|
-
grant_type: "password",
|
|
525
|
-
scope: "all",
|
|
526
|
-
client_id: "client_1",
|
|
527
|
-
client_secret: "123456",
|
|
528
|
-
username: "admin",
|
|
529
|
-
password: "123456"
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
@GetMapping("/token")
|
|
534
|
-
@RequestParam("grant_type", true)
|
|
535
|
-
@RequestParam("refresh_token", true)
|
|
536
|
-
@RequestParam("scope", false)
|
|
537
|
-
@RequestParam("client_id", true)
|
|
538
|
-
@RequestParam("client_secret", true)
|
|
539
|
-
refreshToken(session) {
|
|
540
|
-
return {
|
|
541
|
-
grant_type: "refresh_token",
|
|
542
|
-
refresh_token: session.refresh_token,
|
|
543
|
-
scope: "all",
|
|
544
|
-
client_id: "client_1",
|
|
545
|
-
client_secret: "123456"
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
Implement Authorizer.
|
|
552
|
-
<br/>
|
|
553
|
-
实现`Authorizer`类。至少需要实现方法`refreshSession`、`onAuthorizedDenied`。如果需要调用`invalidateSession`,还需要重载`onSessionInvalidated`。
|
|
554
|
-
|
|
555
|
-
```javascript
|
|
556
|
-
import {Authorizer} from "axios-annotations/plugins/auth";
|
|
557
|
-
|
|
558
|
-
export default class OAuth2Authorizer extends Authorizer {
|
|
559
|
-
async refreshSession(session) {
|
|
560
|
-
// access_token invalid, could refresh access_token with refresh_token through 'password' grant type
|
|
561
|
-
// access_token 过期,如果使用 password 方式认证, 可使用 refresh_token 进行刷新
|
|
562
|
-
const oauthService = new OAuth2Service();
|
|
563
|
-
let res;
|
|
564
|
-
try {
|
|
565
|
-
res = await oauthService.refreshToken(session);
|
|
566
|
-
} catch (e) {
|
|
567
|
-
throw e;
|
|
568
|
-
}
|
|
569
|
-
if (!res || !res.data) {
|
|
570
|
-
throw new Error("Seession Unknow Error");
|
|
571
|
-
}
|
|
572
|
-
const nextSession = res.data;
|
|
573
|
-
return nextSession;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
async onAuthorizedDenied(error) {
|
|
577
|
-
// refresh_token invalid (HTTP 401 default),you should re-loign or logout here.
|
|
578
|
-
// refresh_token 过期触发该回调,在此进行重新登录或注销操作
|
|
579
|
-
|
|
580
|
-
// try logout, clean session.
|
|
581
|
-
// await this.invalidateSession();
|
|
582
|
-
// return;
|
|
583
|
-
|
|
584
|
-
const res = await new OAuth2Service().token();
|
|
585
|
-
if (res && res.data) {
|
|
586
|
-
const nextSession = res.data;
|
|
587
|
-
|
|
588
|
-
// save session manually if try re-login
|
|
589
|
-
await this.storageSession(nextSession);
|
|
590
|
-
return nextSession;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
throw error;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// 调用 invalidateSession() 清除持久化的认证信息后触发该回调
|
|
597
|
-
onSessionInvalidated() {
|
|
598
|
-
// session cleaned, redirect to login page.
|
|
599
|
-
router.redirect("/login");
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
如果是浏览器环境,持久化的认证信息默认存储在`sessionStorage`,默认键值`$_SESSION`,可以通过`window.sessionStorage.getItem("$_SESSION")`验证。
|
|
605
|
-
<br>
|
|
606
|
-
|
|
607
|
-
假如是`React Native`环境则需要自行实现持久化方案:
|
|
608
|
-
|
|
609
|
-
```javascript
|
|
610
|
-
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
611
|
-
import {SessionStorage} from "axios-annotations/plugins/auth";
|
|
612
|
-
|
|
613
|
-
export default class RNSessionStorage extends SessionStorage {
|
|
614
|
-
async set(key, value) {
|
|
615
|
-
const jsonValue = JSON.stringify(value);
|
|
616
|
-
// key: default "$_SESSION"
|
|
617
|
-
await AsyncStorage.setItem(key, jsonValue);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
async get(key) {
|
|
621
|
-
// key: default "$_SESSION"
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
async remove(key) {
|
|
625
|
-
// key: default "$_SESSION"
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
然后替换掉`Authorizer`存储器,如果通过重载`getSession`和`storageSession`实现验证信息存取,可以忽略掉`sessionStorage`和`sessionKey`,此时`sessionStorage`和`sessionKey`不会被调用。
|
|
631
|
-
|
|
632
|
-
```javascript
|
|
633
|
-
export default class OAuth2Authorizer extends Authorizer {
|
|
634
|
-
constructor() {
|
|
635
|
-
super();
|
|
636
|
-
this.sessionStorage = new RNSessionStorage();
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
在需要鉴权的服务配置上设置插件:
|
|
642
|
-
|
|
643
|
-
```javascript
|
|
644
|
-
// config.js
|
|
645
|
-
import {Config} from "axios-annotations";
|
|
646
|
-
import {AuthorizationPlugin} from "axios-annotations/plugins/auth";
|
|
647
|
-
|
|
648
|
-
const _authorizer = new OAuth2Authorizer();
|
|
649
|
-
|
|
650
|
-
const config = new Config({
|
|
651
|
-
host: "localhost",
|
|
652
|
-
port: 8080,
|
|
653
|
-
protocol: "http",
|
|
654
|
-
prefix: "/api",
|
|
655
|
-
plugins: [
|
|
656
|
-
AuthorizationPlugin(_authorizer)
|
|
657
|
-
]
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
// export it in order to save or read the grant result
|
|
661
|
-
// 导出authorizer对象,方便读取或保存认证信息
|
|
662
|
-
export const authorizer = _authorizer;
|
|
663
|
-
|
|
664
|
-
// service.js
|
|
665
|
-
@RequestConfig(config)
|
|
666
|
-
@RequestMapping("/test")
|
|
667
|
-
export default class TestService extends Service {
|
|
668
|
-
// ...
|
|
669
|
-
}
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
首次登录,需要手动保存认证信息。
|
|
673
|
-
<br>
|
|
674
|
-
but you may store grant information yourself when the first time login succeed.
|
|
675
|
-
|
|
676
|
-
```javascript
|
|
677
|
-
import {authorizer} from "/path/config.js";
|
|
678
|
-
|
|
679
|
-
// ... Login Page
|
|
680
|
-
{
|
|
681
|
-
methods: {
|
|
682
|
-
registerApi().then(async session => {
|
|
683
|
-
await authorizer.sessionStorage.storageSession(session.data);
|
|
684
|
-
// redirect to other page ...
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
```
|
|
689
|
-
|
|
690
|
-
## API
|
|
691
|
-
|
|
692
|
-
### Service
|
|
693
|
-
|
|
694
|
-
#### request(method, path, data?, config?): AxiosPromise
|
|
695
|
-
|
|
696
|
-
+ method : string `GET / POST / DELETE...`
|
|
697
|
-
+ path : string `相对路径`
|
|
698
|
-
+ data : Object `请求体`
|
|
699
|
-
+ config : Object `AxiosRequestConfig`
|
|
700
|
-
|
|
701
|
-
#### requestWith(method, path): RequestController
|
|
702
|
-
|
|
703
|
-
+ method : string `GET /POST / DELETE...`
|
|
704
|
-
+ path : string `相对路径`
|
|
705
|
-
|
|
706
|
-
> #### RequestController
|
|
707
|
-
> + param: (key, required?) : RequestController
|
|
708
|
-
> + key : string `标记查询串参数`
|
|
709
|
-
> + required : boolean `默认false,空字符串,null,undefined 将忽略`
|
|
710
|
-
> + header: (header, header) : RequestController
|
|
711
|
-
> + header : string `url 附加参数键值`
|
|
712
|
-
> + header : string | function `字符串,或者接收 send 方法参数的函数,该函数应返回合法值。`
|
|
713
|
-
> + body: (key) : RequestController
|
|
714
|
-
> + key : string `标记参数中请求体`
|
|
715
|
-
> + config: (cfg) : RequestController
|
|
716
|
-
> + cfg : `AxiosRequestConfig`
|
|
717
|
-
>
|
|
718
|
-
> + send: (data) : AxiosPromise<any>
|
|
719
|
-
> + data : object `参数键值对`
|
|
720
|
-
> + with: (name) : RequestController
|
|
721
|
-
> + name : string `config name : 已注册配置名称`
|
|
722
|
-
|
|
723
|
-
### Decorators
|
|
724
|
-
|
|
725
|
-
#### RequestMapping(path, method?)
|
|
726
|
-
|
|
727
|
-
+ path : string `相对路径`
|
|
728
|
-
+ method : string `默认GET,注解服务类时忽略该参数`
|
|
729
|
-
|
|
730
|
-
> 注解方法时,可以使用简化形式:
|
|
731
|
-
> <br>
|
|
732
|
-
> GetMapping(path)
|
|
733
|
-
> <br>
|
|
734
|
-
> PostMapping(path)
|
|
735
|
-
> <br>
|
|
736
|
-
> PatchMapping(path)
|
|
737
|
-
> <br>
|
|
738
|
-
> PutMapping(path)
|
|
739
|
-
> <br>
|
|
740
|
-
> DeleteMapping(path)
|
|
741
|
-
|
|
742
|
-
#### RequestParam(name, required?)
|
|
743
|
-
|
|
744
|
-
+ name : string `方法返回值属性`
|
|
745
|
-
+ required : boolean `是否必要参数`
|
|
746
|
-
|
|
747
|
-
#### RequestHeader(header, value)
|
|
748
|
-
|
|
749
|
-
+ header : string `请求头`
|
|
750
|
-
+ value : string `字符串或函数`
|
|
751
|
-
|
|
752
|
-
> 使用函数。
|
|
753
|
-
> ```javascript
|
|
754
|
-
> class TestService extends Service {
|
|
755
|
-
> @RequestHeader("Authorization", (token) => {
|
|
756
|
-
> return `Basic ${token}`;
|
|
757
|
-
> })
|
|
758
|
-
> @RequestMapping("/login", "GET")
|
|
759
|
-
> foo(token) {
|
|
760
|
-
> return {};
|
|
761
|
-
> }
|
|
762
|
-
> }
|
|
763
|
-
> ```
|
|
764
|
-
|
|
765
|
-
#### RequestBody(name)
|
|
766
|
-
|
|
767
|
-
+ name : string `方法返回值属性,默认为 body,不能与 RequestParam name 参数重复,如果重复 RequestBody 请使用别名`,
|
|
768
|
-
```javascript
|
|
769
|
-
class TestService extends Service {
|
|
770
|
-
@RequestMapping("/foo", "POST")
|
|
771
|
-
@RequestHeader("Content-Type", "text/plain")
|
|
772
|
-
@RequestParam("str", true)
|
|
773
|
-
@RequestParam("strRepeat", true)
|
|
774
|
-
foo(str) {
|
|
775
|
-
return {
|
|
776
|
-
p2: str,
|
|
777
|
-
strRepeat: str // 如果请求体与查询串冲突
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
```
|
|
782
|
-
|
|
783
|
-
#### IgnoreResidualParams(ignore?)
|
|
784
|
-
+ ignore : boolean `拼接 QueryString 时是否忽略没有声明的参数`
|
|
785
|
-
|
|
786
|
-
#### RequestConfig(config)
|
|
787
|
-
+ axiosConfig: Config - class decorator,注解类,传入框架配置对象,注意不是 AxiosConfig。
|
|
788
|
-
+ axiosConfig: AxiosConfig | (...args:any[]) => AxiosConfig - method decorator,注解方法,注解方法时可根据请求参数构造配置对象。
|
|
789
|
-
|
|
790
|
-
```javascript
|
|
791
|
-
// GET /foo?p1=p1&p2=p2&p3=p3
|
|
792
|
-
class TestService extends Service {
|
|
793
|
-
@RequestMapping("/foo", "GET")
|
|
794
|
-
@RequestParam("p1", true)
|
|
795
|
-
foo(token) {
|
|
796
|
-
return {
|
|
797
|
-
p1: "p1",
|
|
798
|
-
p2: "p2",
|
|
799
|
-
p3: "p3"
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
```
|
|
804
|
-
该注解对请求体没有影响。
|
|
805
|
-
```javascript
|
|
806
|
-
// GET /foo?p1=p1
|
|
807
|
-
class TestService extends Service {
|
|
808
|
-
@RequestMapping("/foo", "GET")
|
|
809
|
-
@RequestParam("p1", true)
|
|
810
|
-
@IgnoreResidualParams()
|
|
811
|
-
foo(token) {
|
|
812
|
-
return {
|
|
813
|
-
p1: "p1",
|
|
814
|
-
p2: "p2",
|
|
815
|
-
p3: "p3"
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
```
|
|
820
|
-
|
|
821
|
-
### Authorizer (Optional Plugin)
|
|
822
|
-
|
|
823
|
-
鉴权插件。
|
|
824
|
-
|
|
825
|
-
+ sessionKey : string `键值名称`
|
|
826
|
-
```javascript
|
|
827
|
-
authorizer.sessionKey = "$_SESSION"; // default value
|
|
828
|
-
```
|
|
829
|
-
+ getSession : Promise<Session> `获取授权信息`
|
|
830
|
-
```javascript
|
|
831
|
-
function onLogin(){
|
|
832
|
-
authorizer.getSession().then(session => {
|
|
833
|
-
// fetch other info / redirect to other page
|
|
834
|
-
store.dispatch('SAVE_USER_INFO' , info);
|
|
835
|
-
});
|
|
836
|
-
}
|
|
837
|
-
```
|
|
838
|
-
+ storageSession(session: Session): Promise<void> `存储授权信息`
|
|
839
|
-
+ checkResponse(response:AxiosResponse) : boolean `检查授权是否过期`
|
|
840
|
-
```javascript
|
|
841
|
-
class OAuth2Authorizer extends Authorizer {
|
|
842
|
-
checkResponse(response){
|
|
843
|
-
return response.stauts === 401; // default implement
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
```
|
|
847
|
-
|
|
848
|
-
+ withAuthentication(request: AxiosRequestConfig, session: Session) : void `请求附加认证信息,默认附加请求头`
|
|
849
|
-
```javascript
|
|
850
|
-
class OAuth2Authorizer extends Authorizer {
|
|
851
|
-
withAuthentication(request, session){
|
|
852
|
-
request.headers.Authorization = 'Bearer 0x123456';
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
```
|
|
856
|
-
|
|
857
|
-
## 运行环境
|
|
858
|
-
|
|
859
|
-
### 微信小程序配置
|
|
860
|
-
|
|
861
|
-
更新开发工具以支持装饰器语法。
|
|
862
|
-
|
|
863
|
-
小程序`Typescript`环境不支持装饰器编译,但是`Javascript`环境可以。把涉及到`API`配置的`*.ts`文件扩展名改为`*.js`,绕过蹩脚的`TS`编译,本地配置勾选上`将JS编译成ES5`,正常引入即可。<br/>
|
|
864
|
-
|
|
865
|
-
> 开发工具BUG:`TS`环境`npm`构建失败
|
|
866
|
-
>
|
|
867
|
-
> 找到`project.config.json`文件,`setting`下面添加如下配置:
|
|
868
|
-
>
|
|
869
|
-
> ```json
|
|
870
|
-
> {
|
|
871
|
-
> "packNpmManually": true,
|
|
872
|
-
> "packNpmRelationList": [
|
|
873
|
-
> {
|
|
874
|
-
> "packageJsonPath": "./package.json",
|
|
875
|
-
> "miniprogramNpmDistDir": "./miniprogram/"
|
|
876
|
-
> }
|
|
877
|
-
> ]
|
|
878
|
-
> }
|
|
879
|
-
> ```
|
|
880
|
-
>
|
|
881
|
-
> 开发工具`项目`→`重新打开此项目`,然后构建`npm`。
|
|
882
|
-
|
|
883
|
-
**安装第三方`axios`实现:**
|
|
884
|
-
|
|
885
|
-
+ 备胎1,使用适配器,`axios-miniprogram-adapter`
|
|
886
|
-
|
|
887
|
-
`axios`需要降级,版本再高就得报错:
|
|
888
|
-
|
|
889
|
-
```shell
|
|
890
|
-
npm install axios@0.26.1
|
|
891
|
-
npm install axios-miniprogram-adapter
|
|
892
|
-
```
|
|
893
|
-
|
|
894
|
-
开发工具如果编译报错 `module is not defined`, 在`app.js`头部补充缺失组件的声明:
|
|
895
|
-
|
|
896
|
-
```javascript
|
|
897
|
-
import {
|
|
898
|
-
Config,
|
|
899
|
-
URLSearchParamsParser,
|
|
900
|
-
AbortControllerAdapter,
|
|
901
|
-
Service,
|
|
902
|
-
AbortSource,
|
|
903
|
-
DeleteMapping,
|
|
904
|
-
GetMapping,
|
|
905
|
-
IgnoreResidualParams,
|
|
906
|
-
PatchMapping,
|
|
907
|
-
PostMapping,
|
|
908
|
-
PutMapping,
|
|
909
|
-
RequestBody,
|
|
910
|
-
RequestConfig,
|
|
911
|
-
RequestHeader,
|
|
912
|
-
RequestMapping,
|
|
913
|
-
RequestParam,
|
|
914
|
-
RequestWith
|
|
915
|
-
} from "axios-annotations";
|
|
916
|
-
```
|
|
917
|
-
+ 备胎2,使用第三方实现,不限于`axios-miniprogram`
|
|
918
|
-
|
|
919
|
-
```shell
|
|
920
|
-
npm install axios-miniprogram
|
|
921
|
-
```
|
|
922
|
-
|
|
923
|
-
实现`AxiosStaticInstanceProvider`并配置,如果`IDE`警告`provide`返回类型,可忽略掉:
|
|
924
|
-
|
|
925
|
-
```javascript
|
|
926
|
-
import mpAxios from 'axios-miniprogram';
|
|
927
|
-
|
|
928
|
-
class ThirdAxiosStaticInstanceProvider extends AxiosStaticInstanceProvider {
|
|
929
|
-
provide() {
|
|
930
|
-
return mpAxios;
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
const config = new Config({
|
|
935
|
-
plugins: [],
|
|
936
|
-
protocol: "http",
|
|
937
|
-
host: "localhost",
|
|
938
|
-
port: 8888,
|
|
939
|
-
prefix: "/test",
|
|
940
|
-
axiosProvider: new ThirdAxiosStaticInstanceProvider(),
|
|
941
|
-
});
|
|
942
|
-
```
|
|
943
|
-
|
|
944
|
-
|
|
1
|
+
# Axios Annotations
|
|
2
|
+
|
|
3
|
+
Quick Configuration Framework without Typescript using axios.<br/>
|
|
4
|
+
|
|
5
|
+
声明式`API`配置工具。
|
|
6
|
+
|
|
7
|
+
## Quick Overview
|
|
8
|
+
|
|
9
|
+
+ Step 1:继承服务类`Service`
|
|
10
|
+
+ Step 2:注解路径和参数
|
|
11
|
+
+ Step 3:构建服务实例,调用接口
|
|
12
|
+
|
|
13
|
+
### Basic Usage
|
|
14
|
+
|
|
15
|
+
备胎,如果开发环境不支持装饰器。
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
import {config, Service} from "axios-annotations"
|
|
19
|
+
|
|
20
|
+
config.protocol = "http";
|
|
21
|
+
config.host = "localhost";
|
|
22
|
+
config.port = 8080;
|
|
23
|
+
config.prefix = "/api";
|
|
24
|
+
|
|
25
|
+
export default class TestService extends Service {
|
|
26
|
+
/**
|
|
27
|
+
* new TestService().get("a","b",null);
|
|
28
|
+
* <br>
|
|
29
|
+
* http://localhost:8080/api/path?p1=a&p2=b
|
|
30
|
+
* @param data1
|
|
31
|
+
* @param data2
|
|
32
|
+
* @returns {AxiosPromise<any>}
|
|
33
|
+
*/
|
|
34
|
+
get(required1, required2, optional1) {
|
|
35
|
+
return this.requestWith("GET", "/path")
|
|
36
|
+
.param("p1", true)
|
|
37
|
+
.param("p2", true)
|
|
38
|
+
.param("p3", false)
|
|
39
|
+
.send({
|
|
40
|
+
p1: required1,
|
|
41
|
+
p2: required2,
|
|
42
|
+
p3: optional1
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
post(data1, data2) {
|
|
47
|
+
return this.requestWith("POST", "/path2")
|
|
48
|
+
.param("p1", true)
|
|
49
|
+
.body("p2")
|
|
50
|
+
.send({
|
|
51
|
+
p1: data1,
|
|
52
|
+
p2: data2
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
path(id) {
|
|
57
|
+
return this.requestWith("GET", "/path3/{id}")
|
|
58
|
+
.send({
|
|
59
|
+
id
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
basic() {
|
|
64
|
+
return this.request("POST", "/path3?p1=a&p2=b", {field1: 'c', field: 'd'},
|
|
65
|
+
{
|
|
66
|
+
headers: {
|
|
67
|
+
'Content-Type': 'application/json'
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
剩下自由发挥,自行管理服务实例。
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
const ApiCommon = {
|
|
79
|
+
test: new TestService()
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// 调用接口
|
|
83
|
+
ApiCommon.test.get("a","b",null);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
### Basic Usage With Decorators
|
|
89
|
+
|
|
90
|
+
使用装饰器
|
|
91
|
+
<br>
|
|
92
|
+
可能需要插件支持:
|
|
93
|
+
<br>
|
|
94
|
+
`@babel/plugin-proposal-decorators`
|
|
95
|
+
<br>
|
|
96
|
+
`@babel/plugin-proposal-class-properties`
|
|
97
|
+
<br>
|
|
98
|
+
添加配置:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"plugins": [
|
|
103
|
+
[
|
|
104
|
+
"@babel/plugin-proposal-decorators",
|
|
105
|
+
{
|
|
106
|
+
"legacy": true
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
[
|
|
110
|
+
"@babel/plugin-proposal-class-properties",
|
|
111
|
+
{
|
|
112
|
+
"loose": true
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
tsconfig.json / jsconfig.json
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"compilerOptions": {
|
|
122
|
+
"experimentalDecorators": true,
|
|
123
|
+
"emitDecoratorMetadata": true
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`vue-cli`等脚手架已默认支持装饰器,微信小程序说明请拉到末尾。
|
|
129
|
+
<br>接口方法只需要处理和返回参数,并注解参数类型,框架根据注解分拆参数并注入`HTTP`请求。
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
import {
|
|
133
|
+
Service,
|
|
134
|
+
RequestConfig,
|
|
135
|
+
RequestParam,
|
|
136
|
+
RequestMapping,
|
|
137
|
+
RequestBody,
|
|
138
|
+
RequestHeader,
|
|
139
|
+
IgnoreResidualParams
|
|
140
|
+
} from "axios-annotations";
|
|
141
|
+
|
|
142
|
+
@RequestMapping("/api")
|
|
143
|
+
export default class TestService extends Service {
|
|
144
|
+
@RequestMapping("/path", "GET")
|
|
145
|
+
@RequestParam("p1", true)
|
|
146
|
+
@RequestParam("p2", true)
|
|
147
|
+
@RequestParam("p3", false)
|
|
148
|
+
get(p1, p2, p3) {
|
|
149
|
+
return {p1, p2, p3};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@RequestMapping("/path2", "POST")
|
|
153
|
+
@RequestParam("p1", true)
|
|
154
|
+
@RequestBody("p2")
|
|
155
|
+
@RequestHeader("Content-Type", "text/plain")
|
|
156
|
+
post(p1, str2) {
|
|
157
|
+
const defaultValue = {p1: "0x123456"};
|
|
158
|
+
return Object.assign(defaultValue, {p1, p2: str2});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@RequestMapping("/path", "GET")
|
|
162
|
+
@IgnoreResidualParams(false)
|
|
163
|
+
getDefault() {
|
|
164
|
+
return {
|
|
165
|
+
p1: "p1",
|
|
166
|
+
p2: "p2",
|
|
167
|
+
p3: "p3"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
调用接口:
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
const ApiCommon = {
|
|
177
|
+
test: new TestService()
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// 调用API
|
|
181
|
+
ApiCommon.test.get("a","b",null);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
如果不爽部分IDE的`non-promise inspection info`下划线,也可以给方法加上`async`。
|
|
187
|
+
|
|
188
|
+
### 代码提示
|
|
189
|
+
|
|
190
|
+
> Expect<ResponseType, PromiseType = AxiosPromise<ResponseType>>
|
|
191
|
+
|
|
192
|
+
`Typescript`不支持装饰器修改方法返回值,但是可以绕开检查。<br/>
|
|
193
|
+
|
|
194
|
+
Using a method decorator can not change the function return type.<br/>
|
|
195
|
+
|
|
196
|
+
使用`Expect`绕过类型检查,获得代码提示功能。<br/>
|
|
197
|
+
|
|
198
|
+
Using `Expect` to bypass type-checking and get code intelligence.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import {Expect} from "axios-annotations";
|
|
202
|
+
|
|
203
|
+
// ...
|
|
204
|
+
|
|
205
|
+
export default class TestService extends Service {
|
|
206
|
+
@RequestBody()
|
|
207
|
+
@RequestMapping("/message", "POST")
|
|
208
|
+
@RequestHeader("Content-Type", "text/plain")
|
|
209
|
+
postMessage(message: string) {
|
|
210
|
+
return Expect<{
|
|
211
|
+
data: string;
|
|
212
|
+
success: boolean;
|
|
213
|
+
}>({
|
|
214
|
+
body: message
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// call method, the return value can be deconstructed by IDE
|
|
220
|
+
|
|
221
|
+
const res: AxiosResponse<{
|
|
222
|
+
data:string;
|
|
223
|
+
success:boolean;
|
|
224
|
+
}> = await new TestService().postMessage("foo");
|
|
225
|
+
|
|
226
|
+
console.log(res.data.data);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
### QueryString Encoding
|
|
232
|
+
|
|
233
|
+
`key-values pair`转查询串算法,运行环境不支持`URLSearchParams`时使用默认算法,也可以自定义。
|
|
234
|
+
<br>
|
|
235
|
+
使用第三方库,`qs`,`querystring`,`url-search-params-polyfill`等,不同运行环境下可能有差异。
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
import qs from "qs";
|
|
239
|
+
import {URLSearchParamsParser} from "axios-annotations";
|
|
240
|
+
|
|
241
|
+
if (typeof URLSearchParams === "undefined") {
|
|
242
|
+
URLSearchParamsParser.encode = function (encoder) {
|
|
243
|
+
return qs.stringify(encoder);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Configuration
|
|
249
|
+
|
|
250
|
+
### Custom Config
|
|
251
|
+
|
|
252
|
+
配置服务链接,框架自带默认配置对象`config`,建议自行创建,使用`@RequestConfig`注入`Service`。
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
import {
|
|
256
|
+
Config,
|
|
257
|
+
RequestConfig,
|
|
258
|
+
RequestMapping
|
|
259
|
+
} from "axios-annotations";
|
|
260
|
+
|
|
261
|
+
const config = new Config({
|
|
262
|
+
host: "localhost",
|
|
263
|
+
port: 8086,
|
|
264
|
+
protocol: "http",
|
|
265
|
+
prefix: "/api",
|
|
266
|
+
plugins: []
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
@RequestConfig(config)
|
|
270
|
+
@RequestMapping("/test")
|
|
271
|
+
export default class TestService extends Service {
|
|
272
|
+
@RequestMapping("/{id}", "GET")
|
|
273
|
+
foo(id) {
|
|
274
|
+
return {id};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Default Config
|
|
280
|
+
|
|
281
|
+
All Services inject this by default.
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
import {config} from "axios-annotations";
|
|
285
|
+
|
|
286
|
+
config.host = "192.168.137.1";
|
|
287
|
+
config.port = 8080;
|
|
288
|
+
// ...
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Register Config
|
|
292
|
+
|
|
293
|
+
注册配置,用途:<br>
|
|
294
|
+
|
|
295
|
+
+ 不需要 `export` 导出,使用 `Config.forName(name:string)` 获取。
|
|
296
|
+
+ 部分特殊请求可能需要绕开自身配置,使用`@RequestWith(configName: string)`注解方法,请求将使用指定配置进行构建。
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
new Config({
|
|
300
|
+
protocol: "http",
|
|
301
|
+
host: "localhost",
|
|
302
|
+
port: 9999,
|
|
303
|
+
prefix: "/auth"
|
|
304
|
+
}).register("withoutAuth");
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
@RequestConfig(new Config({
|
|
309
|
+
protocol: "http",
|
|
310
|
+
host: "localhost",
|
|
311
|
+
port: 8888,
|
|
312
|
+
prefix: "/prefix"
|
|
313
|
+
}))
|
|
314
|
+
@RequestMapping("/oauth")
|
|
315
|
+
class AuthService extends Service {
|
|
316
|
+
@PostMapping("/login")
|
|
317
|
+
@RequestWith("withoutAuth")
|
|
318
|
+
login() {
|
|
319
|
+
// http://localhost:9999/auth/oauth/login
|
|
320
|
+
return {usename: "0x123456", password: "123456"};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
@GetMapping("/foo")
|
|
324
|
+
bar() {
|
|
325
|
+
// http://localhost:8888/prefix/oauth/foo
|
|
326
|
+
return {};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Abort
|
|
332
|
+
小程序端第三方库可能没有实现请求取消接口。
|
|
333
|
+
### 静态注入
|
|
334
|
+
|
|
335
|
+
静态注入的`AbortController`取消请求为一次性,仅调试用途。
|
|
336
|
+
|
|
337
|
+
```javascript
|
|
338
|
+
const controller = new AbortController();
|
|
339
|
+
// ...
|
|
340
|
+
class AuthService extends Service {
|
|
341
|
+
// ...
|
|
342
|
+
@RequestConfig({
|
|
343
|
+
signal: controller.signal
|
|
344
|
+
})
|
|
345
|
+
bar() {
|
|
346
|
+
// ....
|
|
347
|
+
return {};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// 取消请求
|
|
352
|
+
controller.abort()
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
或者:
|
|
356
|
+
|
|
357
|
+
```javascript
|
|
358
|
+
const controller = new AbortController();
|
|
359
|
+
|
|
360
|
+
// ...
|
|
361
|
+
class AuthService extends Service {
|
|
362
|
+
// ...
|
|
363
|
+
@AbortSource(controller)
|
|
364
|
+
bar() {
|
|
365
|
+
// ....
|
|
366
|
+
return {};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
new AuthService().bar().then(() => {
|
|
371
|
+
|
|
372
|
+
}).catch(e => {
|
|
373
|
+
console.log(axios.isCancel(e));
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// 取消请求
|
|
377
|
+
controller.abort()
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
兼容旧版 `CancelToken` `(deprecated)`:
|
|
381
|
+
```javascript
|
|
382
|
+
const CancelToken = axios.CancelToken;
|
|
383
|
+
|
|
384
|
+
const controller = new AbortControllerAdapter(CancelToken);
|
|
385
|
+
|
|
386
|
+
// ...
|
|
387
|
+
class AuthService extends Service {
|
|
388
|
+
// ...
|
|
389
|
+
@AbortSource(controller)
|
|
390
|
+
bar() {
|
|
391
|
+
// ....
|
|
392
|
+
return {};
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
new AuthService().bar().then(() => {
|
|
398
|
+
// ...
|
|
399
|
+
}).catch(e => {
|
|
400
|
+
console.log(axios.isCancel(e));
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// 取消请求
|
|
404
|
+
controller.signal.onabort = () => {
|
|
405
|
+
console.log("aborted");
|
|
406
|
+
};
|
|
407
|
+
controller.abort("cancel test")
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### 动态创建中断源
|
|
411
|
+
不确定中断时机,自由发挥。
|
|
412
|
+
```javascript
|
|
413
|
+
// 自定义中断逻辑
|
|
414
|
+
class AbortSourceManager {
|
|
415
|
+
// ...
|
|
416
|
+
|
|
417
|
+
create () {
|
|
418
|
+
return AbortController();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
abortAll () {
|
|
422
|
+
// ...
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const manager = new AbortSourceManager();
|
|
427
|
+
|
|
428
|
+
// ...
|
|
429
|
+
class AuthService extends Service {
|
|
430
|
+
// ...
|
|
431
|
+
@RequestConfig((...args)=>{
|
|
432
|
+
console.log(args);
|
|
433
|
+
const controller = manager.create();
|
|
434
|
+
return {
|
|
435
|
+
// ...
|
|
436
|
+
signal: controller.signal
|
|
437
|
+
};
|
|
438
|
+
})
|
|
439
|
+
bar() {
|
|
440
|
+
// ....
|
|
441
|
+
return {};
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
## Plugin
|
|
448
|
+
|
|
449
|
+
### Custom Plugin
|
|
450
|
+
|
|
451
|
+
插件函数接收配置对象为参数,出于扩展性考虑,通常由高阶函数返回。
|
|
452
|
+
|
|
453
|
+
插件在`Config`对象的`axios`实例创建时注入,建议在`Config`构造函数配置。
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import {Config} from "axios-annotations"
|
|
457
|
+
import type {AxiosInstance} from "axios";
|
|
458
|
+
|
|
459
|
+
export function ToastPlugin(fnToast) {
|
|
460
|
+
return function (config: Config, axios: AxiosInstance) {
|
|
461
|
+
axios.interceptors.response.use(function (e) {
|
|
462
|
+
return Promise.resolve(e);
|
|
463
|
+
}, function (e) {
|
|
464
|
+
fnToast(e);
|
|
465
|
+
return Promise.reject(e);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
axios.interceptors.request.use(function (e) {
|
|
469
|
+
return Promise.resolve(e);
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
配置插件:
|
|
476
|
+
|
|
477
|
+
```javascript
|
|
478
|
+
new Config({
|
|
479
|
+
plugins: [
|
|
480
|
+
ToastPlugin(function (e) {
|
|
481
|
+
if (typeof wx !== "undefined") {
|
|
482
|
+
wx.showToast({
|
|
483
|
+
icon: "none",
|
|
484
|
+
title: `[${e.response.status}]` + ' ' + e.config.url
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
})
|
|
488
|
+
]
|
|
489
|
+
})
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
### <text style="color:red;">Auth Plugin</text>
|
|
495
|
+
|
|
496
|
+
可选的内置插件,适配需要身份认证的服务,以`Spring Security`作为后端实现为例。
|
|
497
|
+
<br>
|
|
498
|
+
Basic Usage for Auth Plugin.
|
|
499
|
+
<br>
|
|
500
|
+
Take the case of `Spring Security OAtuh2.0`。
|
|
501
|
+
|
|
502
|
+
```javascript
|
|
503
|
+
// DevServer Proxy Config
|
|
504
|
+
const authCfg = new Config({
|
|
505
|
+
host: "localhost",
|
|
506
|
+
port: 8080,
|
|
507
|
+
protocol: "http",
|
|
508
|
+
prefix: "/api"
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
@RequestConfig(authCfg)
|
|
512
|
+
@RequestMapping("/oauth")
|
|
513
|
+
export default class OAuth2Service extends Service {
|
|
514
|
+
@GetMapping("/token")
|
|
515
|
+
@RequestParam("grant_type", true)
|
|
516
|
+
@RequestParam("scope", false)
|
|
517
|
+
@RequestParam("client_id", false)
|
|
518
|
+
@RequestParam("client_secret", false)
|
|
519
|
+
@RequestParam("username", false)
|
|
520
|
+
@RequestParam("password", false)
|
|
521
|
+
@RequestBody()
|
|
522
|
+
token() {
|
|
523
|
+
return {
|
|
524
|
+
grant_type: "password",
|
|
525
|
+
scope: "all",
|
|
526
|
+
client_id: "client_1",
|
|
527
|
+
client_secret: "123456",
|
|
528
|
+
username: "admin",
|
|
529
|
+
password: "123456"
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
@GetMapping("/token")
|
|
534
|
+
@RequestParam("grant_type", true)
|
|
535
|
+
@RequestParam("refresh_token", true)
|
|
536
|
+
@RequestParam("scope", false)
|
|
537
|
+
@RequestParam("client_id", true)
|
|
538
|
+
@RequestParam("client_secret", true)
|
|
539
|
+
refreshToken(session) {
|
|
540
|
+
return {
|
|
541
|
+
grant_type: "refresh_token",
|
|
542
|
+
refresh_token: session.refresh_token,
|
|
543
|
+
scope: "all",
|
|
544
|
+
client_id: "client_1",
|
|
545
|
+
client_secret: "123456"
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
Implement Authorizer.
|
|
552
|
+
<br/>
|
|
553
|
+
实现`Authorizer`类。至少需要实现方法`refreshSession`、`onAuthorizedDenied`。如果需要调用`invalidateSession`,还需要重载`onSessionInvalidated`。
|
|
554
|
+
|
|
555
|
+
```javascript
|
|
556
|
+
import {Authorizer} from "axios-annotations/plugins/auth";
|
|
557
|
+
|
|
558
|
+
export default class OAuth2Authorizer extends Authorizer {
|
|
559
|
+
async refreshSession(session) {
|
|
560
|
+
// access_token invalid, could refresh access_token with refresh_token through 'password' grant type
|
|
561
|
+
// access_token 过期,如果使用 password 方式认证, 可使用 refresh_token 进行刷新
|
|
562
|
+
const oauthService = new OAuth2Service();
|
|
563
|
+
let res;
|
|
564
|
+
try {
|
|
565
|
+
res = await oauthService.refreshToken(session);
|
|
566
|
+
} catch (e) {
|
|
567
|
+
throw e;
|
|
568
|
+
}
|
|
569
|
+
if (!res || !res.data) {
|
|
570
|
+
throw new Error("Seession Unknow Error");
|
|
571
|
+
}
|
|
572
|
+
const nextSession = res.data;
|
|
573
|
+
return nextSession;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
async onAuthorizedDenied(error) {
|
|
577
|
+
// refresh_token invalid (HTTP 401 default),you should re-loign or logout here.
|
|
578
|
+
// refresh_token 过期触发该回调,在此进行重新登录或注销操作
|
|
579
|
+
|
|
580
|
+
// try logout, clean session.
|
|
581
|
+
// await this.invalidateSession();
|
|
582
|
+
// return;
|
|
583
|
+
|
|
584
|
+
const res = await new OAuth2Service().token();
|
|
585
|
+
if (res && res.data) {
|
|
586
|
+
const nextSession = res.data;
|
|
587
|
+
|
|
588
|
+
// save session manually if try re-login
|
|
589
|
+
await this.storageSession(nextSession);
|
|
590
|
+
return nextSession;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
throw error;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// 调用 invalidateSession() 清除持久化的认证信息后触发该回调
|
|
597
|
+
onSessionInvalidated() {
|
|
598
|
+
// session cleaned, redirect to login page.
|
|
599
|
+
router.redirect("/login");
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
如果是浏览器环境,持久化的认证信息默认存储在`sessionStorage`,默认键值`$_SESSION`,可以通过`window.sessionStorage.getItem("$_SESSION")`验证。
|
|
605
|
+
<br>
|
|
606
|
+
|
|
607
|
+
假如是`React Native`环境则需要自行实现持久化方案:
|
|
608
|
+
|
|
609
|
+
```javascript
|
|
610
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
611
|
+
import {SessionStorage} from "axios-annotations/plugins/auth";
|
|
612
|
+
|
|
613
|
+
export default class RNSessionStorage extends SessionStorage {
|
|
614
|
+
async set(key, value) {
|
|
615
|
+
const jsonValue = JSON.stringify(value);
|
|
616
|
+
// key: default "$_SESSION"
|
|
617
|
+
await AsyncStorage.setItem(key, jsonValue);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async get(key) {
|
|
621
|
+
// key: default "$_SESSION"
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
async remove(key) {
|
|
625
|
+
// key: default "$_SESSION"
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
然后替换掉`Authorizer`存储器,如果通过重载`getSession`和`storageSession`实现验证信息存取,可以忽略掉`sessionStorage`和`sessionKey`,此时`sessionStorage`和`sessionKey`不会被调用。
|
|
631
|
+
|
|
632
|
+
```javascript
|
|
633
|
+
export default class OAuth2Authorizer extends Authorizer {
|
|
634
|
+
constructor() {
|
|
635
|
+
super();
|
|
636
|
+
this.sessionStorage = new RNSessionStorage();
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
在需要鉴权的服务配置上设置插件:
|
|
642
|
+
|
|
643
|
+
```javascript
|
|
644
|
+
// config.js
|
|
645
|
+
import {Config} from "axios-annotations";
|
|
646
|
+
import {AuthorizationPlugin} from "axios-annotations/plugins/auth";
|
|
647
|
+
|
|
648
|
+
const _authorizer = new OAuth2Authorizer();
|
|
649
|
+
|
|
650
|
+
const config = new Config({
|
|
651
|
+
host: "localhost",
|
|
652
|
+
port: 8080,
|
|
653
|
+
protocol: "http",
|
|
654
|
+
prefix: "/api",
|
|
655
|
+
plugins: [
|
|
656
|
+
AuthorizationPlugin(_authorizer)
|
|
657
|
+
]
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// export it in order to save or read the grant result
|
|
661
|
+
// 导出authorizer对象,方便读取或保存认证信息
|
|
662
|
+
export const authorizer = _authorizer;
|
|
663
|
+
|
|
664
|
+
// service.js
|
|
665
|
+
@RequestConfig(config)
|
|
666
|
+
@RequestMapping("/test")
|
|
667
|
+
export default class TestService extends Service {
|
|
668
|
+
// ...
|
|
669
|
+
}
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
首次登录,需要手动保存认证信息。
|
|
673
|
+
<br>
|
|
674
|
+
but you may store grant information yourself when the first time login succeed.
|
|
675
|
+
|
|
676
|
+
```javascript
|
|
677
|
+
import {authorizer} from "/path/config.js";
|
|
678
|
+
|
|
679
|
+
// ... Login Page
|
|
680
|
+
{
|
|
681
|
+
methods: {
|
|
682
|
+
registerApi().then(async session => {
|
|
683
|
+
await authorizer.sessionStorage.storageSession(session.data);
|
|
684
|
+
// redirect to other page ...
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
## API
|
|
691
|
+
|
|
692
|
+
### Service
|
|
693
|
+
|
|
694
|
+
#### request(method, path, data?, config?): AxiosPromise
|
|
695
|
+
|
|
696
|
+
+ method : string `GET / POST / DELETE...`
|
|
697
|
+
+ path : string `相对路径`
|
|
698
|
+
+ data : Object `请求体`
|
|
699
|
+
+ config : Object `AxiosRequestConfig`
|
|
700
|
+
|
|
701
|
+
#### requestWith(method, path): RequestController
|
|
702
|
+
|
|
703
|
+
+ method : string `GET /POST / DELETE...`
|
|
704
|
+
+ path : string `相对路径`
|
|
705
|
+
|
|
706
|
+
> #### RequestController
|
|
707
|
+
> + param: (key, required?) : RequestController
|
|
708
|
+
> + key : string `标记查询串参数`
|
|
709
|
+
> + required : boolean `默认false,空字符串,null,undefined 将忽略`
|
|
710
|
+
> + header: (header, header) : RequestController
|
|
711
|
+
> + header : string `url 附加参数键值`
|
|
712
|
+
> + header : string | function `字符串,或者接收 send 方法参数的函数,该函数应返回合法值。`
|
|
713
|
+
> + body: (key) : RequestController
|
|
714
|
+
> + key : string `标记参数中请求体`
|
|
715
|
+
> + config: (cfg) : RequestController
|
|
716
|
+
> + cfg : `AxiosRequestConfig`
|
|
717
|
+
>
|
|
718
|
+
> + send: (data) : AxiosPromise<any>
|
|
719
|
+
> + data : object `参数键值对`
|
|
720
|
+
> + with: (name) : RequestController
|
|
721
|
+
> + name : string `config name : 已注册配置名称`
|
|
722
|
+
|
|
723
|
+
### Decorators
|
|
724
|
+
|
|
725
|
+
#### RequestMapping(path, method?)
|
|
726
|
+
|
|
727
|
+
+ path : string `相对路径`
|
|
728
|
+
+ method : string `默认GET,注解服务类时忽略该参数`
|
|
729
|
+
|
|
730
|
+
> 注解方法时,可以使用简化形式:
|
|
731
|
+
> <br>
|
|
732
|
+
> GetMapping(path)
|
|
733
|
+
> <br>
|
|
734
|
+
> PostMapping(path)
|
|
735
|
+
> <br>
|
|
736
|
+
> PatchMapping(path)
|
|
737
|
+
> <br>
|
|
738
|
+
> PutMapping(path)
|
|
739
|
+
> <br>
|
|
740
|
+
> DeleteMapping(path)
|
|
741
|
+
|
|
742
|
+
#### RequestParam(name, required?)
|
|
743
|
+
|
|
744
|
+
+ name : string `方法返回值属性`
|
|
745
|
+
+ required : boolean `是否必要参数`
|
|
746
|
+
|
|
747
|
+
#### RequestHeader(header, value)
|
|
748
|
+
|
|
749
|
+
+ header : string `请求头`
|
|
750
|
+
+ value : string `字符串或函数`
|
|
751
|
+
|
|
752
|
+
> 使用函数。
|
|
753
|
+
> ```javascript
|
|
754
|
+
> class TestService extends Service {
|
|
755
|
+
> @RequestHeader("Authorization", (token) => {
|
|
756
|
+
> return `Basic ${token}`;
|
|
757
|
+
> })
|
|
758
|
+
> @RequestMapping("/login", "GET")
|
|
759
|
+
> foo(token) {
|
|
760
|
+
> return {};
|
|
761
|
+
> }
|
|
762
|
+
> }
|
|
763
|
+
> ```
|
|
764
|
+
|
|
765
|
+
#### RequestBody(name)
|
|
766
|
+
|
|
767
|
+
+ name : string `方法返回值属性,默认为 body,不能与 RequestParam name 参数重复,如果重复 RequestBody 请使用别名`,
|
|
768
|
+
```javascript
|
|
769
|
+
class TestService extends Service {
|
|
770
|
+
@RequestMapping("/foo", "POST")
|
|
771
|
+
@RequestHeader("Content-Type", "text/plain")
|
|
772
|
+
@RequestParam("str", true)
|
|
773
|
+
@RequestParam("strRepeat", true)
|
|
774
|
+
foo(str) {
|
|
775
|
+
return {
|
|
776
|
+
p2: str,
|
|
777
|
+
strRepeat: str // 如果请求体与查询串冲突
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
#### IgnoreResidualParams(ignore?)
|
|
784
|
+
+ ignore : boolean `拼接 QueryString 时是否忽略没有声明的参数`
|
|
785
|
+
|
|
786
|
+
#### RequestConfig(config)
|
|
787
|
+
+ axiosConfig: Config - class decorator,注解类,传入框架配置对象,注意不是 AxiosConfig。
|
|
788
|
+
+ axiosConfig: AxiosConfig | (...args:any[]) => AxiosConfig - method decorator,注解方法,注解方法时可根据请求参数构造配置对象。
|
|
789
|
+
|
|
790
|
+
```javascript
|
|
791
|
+
// GET /foo?p1=p1&p2=p2&p3=p3
|
|
792
|
+
class TestService extends Service {
|
|
793
|
+
@RequestMapping("/foo", "GET")
|
|
794
|
+
@RequestParam("p1", true)
|
|
795
|
+
foo(token) {
|
|
796
|
+
return {
|
|
797
|
+
p1: "p1",
|
|
798
|
+
p2: "p2",
|
|
799
|
+
p3: "p3"
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
该注解对请求体没有影响。
|
|
805
|
+
```javascript
|
|
806
|
+
// GET /foo?p1=p1
|
|
807
|
+
class TestService extends Service {
|
|
808
|
+
@RequestMapping("/foo", "GET")
|
|
809
|
+
@RequestParam("p1", true)
|
|
810
|
+
@IgnoreResidualParams()
|
|
811
|
+
foo(token) {
|
|
812
|
+
return {
|
|
813
|
+
p1: "p1",
|
|
814
|
+
p2: "p2",
|
|
815
|
+
p3: "p3"
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
### Authorizer (Optional Plugin)
|
|
822
|
+
|
|
823
|
+
鉴权插件。
|
|
824
|
+
|
|
825
|
+
+ sessionKey : string `键值名称`
|
|
826
|
+
```javascript
|
|
827
|
+
authorizer.sessionKey = "$_SESSION"; // default value
|
|
828
|
+
```
|
|
829
|
+
+ getSession : Promise<Session> `获取授权信息`
|
|
830
|
+
```javascript
|
|
831
|
+
function onLogin(){
|
|
832
|
+
authorizer.getSession().then(session => {
|
|
833
|
+
// fetch other info / redirect to other page
|
|
834
|
+
store.dispatch('SAVE_USER_INFO' , info);
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
```
|
|
838
|
+
+ storageSession(session: Session): Promise<void> `存储授权信息`
|
|
839
|
+
+ checkResponse(response:AxiosResponse) : boolean `检查授权是否过期`
|
|
840
|
+
```javascript
|
|
841
|
+
class OAuth2Authorizer extends Authorizer {
|
|
842
|
+
checkResponse(response){
|
|
843
|
+
return response.stauts === 401; // default implement
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
+ withAuthentication(request: AxiosRequestConfig, session: Session) : void `请求附加认证信息,默认附加请求头`
|
|
849
|
+
```javascript
|
|
850
|
+
class OAuth2Authorizer extends Authorizer {
|
|
851
|
+
withAuthentication(request, session){
|
|
852
|
+
request.headers.Authorization = 'Bearer 0x123456';
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
## 运行环境
|
|
858
|
+
|
|
859
|
+
### 微信小程序配置
|
|
860
|
+
|
|
861
|
+
更新开发工具以支持装饰器语法。
|
|
862
|
+
|
|
863
|
+
小程序`Typescript`环境不支持装饰器编译,但是`Javascript`环境可以。把涉及到`API`配置的`*.ts`文件扩展名改为`*.js`,绕过蹩脚的`TS`编译,本地配置勾选上`将JS编译成ES5`,正常引入即可。<br/>
|
|
864
|
+
|
|
865
|
+
> 开发工具BUG:`TS`环境`npm`构建失败
|
|
866
|
+
>
|
|
867
|
+
> 找到`project.config.json`文件,`setting`下面添加如下配置:
|
|
868
|
+
>
|
|
869
|
+
> ```json
|
|
870
|
+
> {
|
|
871
|
+
> "packNpmManually": true,
|
|
872
|
+
> "packNpmRelationList": [
|
|
873
|
+
> {
|
|
874
|
+
> "packageJsonPath": "./package.json",
|
|
875
|
+
> "miniprogramNpmDistDir": "./miniprogram/"
|
|
876
|
+
> }
|
|
877
|
+
> ]
|
|
878
|
+
> }
|
|
879
|
+
> ```
|
|
880
|
+
>
|
|
881
|
+
> 开发工具`项目`→`重新打开此项目`,然后构建`npm`。
|
|
882
|
+
|
|
883
|
+
**安装第三方`axios`实现:**
|
|
884
|
+
|
|
885
|
+
+ 备胎1,使用适配器,`axios-miniprogram-adapter`
|
|
886
|
+
|
|
887
|
+
`axios`需要降级,版本再高就得报错:
|
|
888
|
+
|
|
889
|
+
```shell
|
|
890
|
+
npm install axios@0.26.1
|
|
891
|
+
npm install axios-miniprogram-adapter
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
开发工具如果编译报错 `module is not defined`, 在`app.js`头部补充缺失组件的声明:
|
|
895
|
+
|
|
896
|
+
```javascript
|
|
897
|
+
import {
|
|
898
|
+
Config,
|
|
899
|
+
URLSearchParamsParser,
|
|
900
|
+
AbortControllerAdapter,
|
|
901
|
+
Service,
|
|
902
|
+
AbortSource,
|
|
903
|
+
DeleteMapping,
|
|
904
|
+
GetMapping,
|
|
905
|
+
IgnoreResidualParams,
|
|
906
|
+
PatchMapping,
|
|
907
|
+
PostMapping,
|
|
908
|
+
PutMapping,
|
|
909
|
+
RequestBody,
|
|
910
|
+
RequestConfig,
|
|
911
|
+
RequestHeader,
|
|
912
|
+
RequestMapping,
|
|
913
|
+
RequestParam,
|
|
914
|
+
RequestWith
|
|
915
|
+
} from "axios-annotations";
|
|
916
|
+
```
|
|
917
|
+
+ 备胎2,使用第三方实现,不限于`axios-miniprogram`
|
|
918
|
+
|
|
919
|
+
```shell
|
|
920
|
+
npm install axios-miniprogram
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
实现`AxiosStaticInstanceProvider`并配置,如果`IDE`警告`provide`返回类型,可忽略掉:
|
|
924
|
+
|
|
925
|
+
```javascript
|
|
926
|
+
import mpAxios from 'axios-miniprogram';
|
|
927
|
+
|
|
928
|
+
class ThirdAxiosStaticInstanceProvider extends AxiosStaticInstanceProvider {
|
|
929
|
+
provide() {
|
|
930
|
+
return mpAxios;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const config = new Config({
|
|
935
|
+
plugins: [],
|
|
936
|
+
protocol: "http",
|
|
937
|
+
host: "localhost",
|
|
938
|
+
port: 8888,
|
|
939
|
+
prefix: "/test",
|
|
940
|
+
axiosProvider: new ThirdAxiosStaticInstanceProvider(),
|
|
941
|
+
});
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
|