axios-annotations

1.3.0 • Public • Published

Axios Annotations

HTTP client library uses Axios without Typescript.

Quick Overview

Basic Usage

开发环境不支持装饰器。

import Service from "axios-annotations/core/service";
import {config} from "axios-annotations/core/config"

config.protocol = "http";
config.host = "localhost";
config.port = 8080;
config.prefix = "/api";

export default class TestService extends Service {
    /**
     * new TestService().get("a","b",null);
     * <br>
     * http://localhost:8080/api/path?p1=a&p2=b
     * @param data1
     * @param data2
     * @returns {AxiosPromise<any>}
     */
    get(required1, required2, optional1) {
        return this.requestWith("GET", "/path")
            .param("p1", true)
            .param("p2", true)
            .param("p3", false)
            .send({
                p1: required1,
                p2: required2,
                p3: optional1
            });
    }

    post(data1, data2) {
        return this.requestWith("POST", "/path2")
            .param("p1", true)
            .body("p2")
            .send({
                p1: data1,
                p2: data2
            });
    }

    path(id) {
        return this.requestWith("GET", "/path3/{id}")
            .send({
                id
            });
    }

    basic() {
        return this.request("POST", "/path3?p1=a&p2=b", {field1: 'c', field: 'd'},
            {
                headers: {
                    'Content-Type': 'application/json'
                }
            }
        );
    }
}

Basic Usage With Decorators

使用装饰器
可能需要插件支持:
@babel/plugin-proposal-decorators
@babel/plugin-proposal-class-properties
添加配置:

{
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {
        "legacy": true
      }
    ],
    [
      "@babel/plugin-proposal-class-properties",
      {
        "loose": true
      }
    ]
  ]
}

vue-cli等脚手架已默认支持装饰器。
方法只需要返回参数,并注解参数类型。

import Service from "axios-annotations/core/service";
import RequestConfig from "axios-annotations/decorator/request-config";
import RequestParam from "axios-annotations/decorator/request-param";
import RequestMapping from "axios-annotations/decorator/request-mapping";
import RequestBody from "axios-annotations/decorator/request-body";
import RequestHeader from "axios-annotations/decorator/request-header";
import IgnoreResidualParams from "axios-annotations/decorator/ignore-residual-params";

@RequestMapping("/api")
export default class TestService extends Service {
    @RequestMapping("/path", "GET")
    @RequestParam("p1", true)
    @RequestParam("p2", true)
    @RequestParam("p3", false)
    get(p1, p2, p3) {
        return {p1, p2, p3};
    }

    @RequestMapping("/path2", "POST")
    @RequestParam("p1", true)
    @RequestBody("p2")
    @RequestHeader("Content-Type", "text/plain")
    post(p1, str2) {
        const defaultValue = {p1: "0x123456"};
        return Object.assign(defaultValue, {p1, p2: str2});
    }

    @RequestMapping("/path", "GET")
    @IgnoreResidualParams(false)
    getDefault() {
        return {
            p1: "p1",
            p2: "p2",
            p3: "p3"
        }
    }
}

如果不爽部分IDE的non-promise inspection info下划线,也可以给方法加上async

QueryString Encoding

key-values pair转查询串算法,运行环境不支持URLSearchParams时使用默认算法,也可以自定义。
使用第三方库,qs,querystring,url-search-params-polyfill等,不同运行环境下可能有差异。

import qs from "qs";
import URLSearchParamsParser from "axios-annotations/core/parser";

if (typeof URLSearchParams === "undefined") {
    URLSearchParamsParser.encode = function (encoder) {
        return qs.stringify(encoder);
    }
}

Configuration

Custom Config

import Config from "axios-annotations/core/config";
import RequestConfig from "axios-annotations/decorator/request-config";
import RequestMapping from "axios-annotations/decorator/request-mapping";

const config = new Config();
config.host = "localhost";
config.port = 8086;
config.protocol = "http";
config.prefix = "/api";

@RequestConfig(config)
@RequestMapping("/test")
export default class TestService extends Service {
    @RequestMapping("/{id}", "GET") 
    foo(id) {
        return {id};
    }
}

Default Config

All Services inject this by default.

import {config} from "axios-annotations/core/config";

config.host = "192.168.137.1";
config.port = 8080;
// ...

Register Config

注册配置,用途:

  • 不需要 export 导出,使用 Config.forName(name:string) 获取。
  • 部分特殊请求可能需要绕开自身配置,使用@RequestWith(config)注解方法,请求将使用指定配置进行构建。
new Config("http", "localhost", 9999, "/auth").register("withoutPlugins");
@RequestConfig(new Config("http", "localhost", 8888, "/prefix"))
@RequestMapping("/oauth") 
class AuthService extends Service {
    @PostMapping("/login")
    @RequestWith("withoutPlugins") 
    login() {
        // http://localhost:9999/auth/oauth/login
        return {usename: "0x123456", password: "123456"};
    }
    
    @GetMapping("/foo")
    bar() {
        // http://localhost:8888/prefix/oauth/foo
        return {};
    }
}

Abort

注意:小程序端可能没有实现请求取消接口。

静态注入

const controller = new AbortController();
// ...
class AuthService extends Service {
    // ...
    @RequestConfig({
      signal: controller.signal
    })
    bar() {
        // ....
        return {};
    }
}

// 取消请求
controller.abort()

或者:

const controller = new AbortController();

// ...
class AuthService extends Service {
  // ...
  @AbortSource(controller) bar() {
    // ....
    return {};
  }
}

new AuthService().bar().then(() => {

}).catch(e => {
  console.log(axios.isCancel(e));
});

// 取消请求
controller.abort()

兼容旧版 CancelToken (deprecated):

const CancelToken = axios.CancelToken;

const controller = new AbortControllerAdapter(CancelToken);

// ...
class AuthService extends Service {
  // ...
  @AbortSource(controller) bar() {
    // ....
    return {};
  }
}


new AuthService().bar().then(() => {

}).catch(e => {
  console.log(axios.isCancel(e));
});

// 取消请求
controller.abort("cancel test")

动态注入中断源

不确定中断时机。

// 自定义中断逻辑
class AbortSourceManager {
    // ...
    
    create () {
        return AbortController();
    }
    
    abortAll () {
        // ...
    }
}

const manager = new AbortSourceManager();

// ...
class AuthService extends Service {
    // ...
    @RequestConfig((...args)=>{
        const controller = manager.create();
        return {
          // ...
          signal: controller.signal
        };
    })
    bar() {
        // ....
        return {};
    }
}

Plugin

Custom Plugin

插件函数接收配置对象为参数,出于扩展性考虑,通常由高阶函数返回。

import {config} from "axios-annotations/core/config"

function ToastPlugin(fnToast) {
    return function (config) {
        config.axios.interceptors.response.use(function (e) {
            return Promise.resolve(e);
        }, function (e) {
            fnToast(e);
            return Promise.reject(e);
        });

        config.axios.interceptors.request.use(function (e) {
            return Promise.resolve(e);
        });
    }
}

config.plugins = [
    ToastPlugin(function (e) {
        if (typeof wx !== "undefined") {
            wx.showToast({
                icon: "none",
                title: `[${e.response.status}]` + ' ' + e.config.url
            });
        }
    })
];

Auth Plugin

可选的内置插件。
Basic Usage for Auth Plugin.
Take the case of Spring Security OAtuh2.0

// DevServer Proxy Config
const authCfg = new Config();
authCfg.host = "localhost";
authCfg.port = 8080;
authCfg.protocol = "http";
authCfg.prefix = "/api";

@RequestConfig(authCfg)
@RequestMapping("/oauth")
export default class OAuth2Service extends Service {
    @GetMapping("/token")
    @RequestParam("grant_type", true)
    @RequestParam("scope", false)
    @RequestParam("client_id", false)
    @RequestParam("client_secret", false)
    @RequestParam("username", false)
    @RequestParam("password", false)
    @RequestBody() 
    token() {
        return {
            grant_type: "password",
            scope: "all",
            client_id: "client_1",
            client_secret: "123456",
            username: "admin",
            password: "123456"
        };
    }

    @GetMapping("/token")
    @RequestParam("grant_type", true)
    @RequestParam("refresh_token", true)
    @RequestParam("scope", false)
    @RequestParam("client_id", true)
    @RequestParam("client_secret", true) 
    refreshToken(session) {
        return {
            grant_type: "refresh_token",
            refresh_token: session.refresh_token,
            scope: "all",
            client_id: "client_1",
            client_secret: "123456"
        };
    }
}

Implement Authorizer.
实现Authorizer类。至少需要实现方法refreshSessiononAuthorizedDenied。如果需要调用invalidateSession,还需要重载onSessionInvalidated

import Authorizer from "axios-annotations/plugins/auth/authorizer";

export default class OAuth2Authorizer extends Authorizer {
    async refreshSession(session) {
        // access_token invalid, could refresh access_token with refresh_token through 'password' grant type
        // access_token 过期,如果使用 password 方式认证, 可使用 refresh_token 进行刷新
        const oauthService = new OAuth2Service();
        let res;
        try {
            res = await oauthService.refreshToken(session);
        } catch (e) {
            throw e;
        }
        if (!res || !res.data) {
            throw new Error("Seession Unknow Error");
        }
        const nextSession = res.data;
        return nextSession;
    }

    async onAuthorizedDenied(error) {
        // refresh_token invalid,you should re-loign or logout here.
        // refresh_token 过期触发该回调,在此进行重新登录或注销操作

        // try logout, clean session.
        // await this.invalidateSession();
        // return;

        const res = await new OAuth2Service().token();
        if (res && res.data) {
            const nextSession = res.data;

            // save session manually if try re-login
            await this.storageSession(nextSession);
            return nextSession;
        }

        throw error;
    }

    onSessionInvalidated() {
        // session cleaned, redirect to login page.
        router.redirect("/login");
    }
}

认证信息默认存储在sessionStorage
Implement SessionStorage if store mode changed.

import AsyncStorage from '@react-native-async-storage/async-storage';
import SessionStorage from "axios-annotations/plugins/auth/storage";

export default class RNSessionStorage extends SessionStorage {
    async set(key, value) {
        const jsonValue = JSON.stringify(value);
        await AsyncStorage.setItem(key, jsonValue);
    }

    async get(key) {
        // omit...
    }

    async remove(key) {
        // omit...
    }
}

替换掉Authorizer存储器,如果通过重载getSessionstorageSession实现验证信息存储,可以忽略掉sessionStoragesessionKey,此时sessionStoragesessionKey不会被调用。

export default class OAuth2Authorizer extends Authorizer {
    constructor() {
        super();
        this.sessionStorage = new RNSessionStorage();
    }
}

在默认配置上设置插件。

// config.js
import AuthorizationPlugin from "axios-annotations/plugins/auth/index";

// default config
const config = new Config();
config.host = "localhost";
config.port = 8080;
config.protocol = "http";
config.prefix = "/api";

const _authorizer = new OAuth2Authorizer();

config.plugins = [
    AuthorizationPlugin(_authorizer)
];

// export it in order to save or read the grant result
export const authorizer = _authorizer;

// service.js
// the request will be authorized or not
@RequestConfig(config)
@RequestMapping("/test")
export default class TestService extends Service {
    // ...
}

首次登录,需要手动保存认证信息。
but you may store grant information yourself when the first time login succeed.

import {authorizer} from "/path/config.js";

// ... Login Page
{
    methods: {
        registerApi().then(async session => {
            await authorizer.sessionStorage.storageSession(session.data);
            // redirect to other page ...
        });
    }
}

API

Service

request(method, path, data?, config?): AxiosPromise

  • method : string GET / POST / DELETE...
  • path : string 相对路径
  • data : Object 请求体
  • config : Object AxiosRequestConfig

requestWith(method, path): RequestController

  • method : string GET /POST / DELETE...
  • path : string 相对路径

RequestController

  • param: (key, required?) : RequestController

    • key : string 标记查询串参数
    • required : boolean 默认false,空字符串,null,undefined 将忽略
  • header: (header, header) : RequestController

    • header : string url 附加参数键值
    • header : string | function 字符串,或者接收 send 方法参数的函数,该函数应返回合法值。
  • body: (key) : RequestController

    • key : string 标记参数中请求体
  • config: (cfg) : RequestController

    • cfg : AxiosRequestConfig
  • send: (data) : AxiosPromise

    • data : object 参数键值对
  • with: (name) : RequestController

    • name : string config name : 已注册配置名称

Decorators

RequestMapping(path, method?)

  • path : string 相对路径
  • method : string 默认GET,注解服务类时忽略该参数

注解方法时,可以使用简化形式:
GetMapping(path)
PostMapping(path)
PatchMapping(path)
PutMapping(path)
DeleteMapping(path)

RequestParam(name, required?)

  • name : string 方法返回值属性
  • required : boolean 是否必要参数

RequestHeader(header, value)

  • header : string 请求头
  • value : string 字符串或函数

使用函数。

   class TestService extends Service {
       @RequestHeader("Authorization", (token) => {
           return `Basic ${token}`;
       })
       @RequestMapping("/login", "GET")
       foo(token) {
           return {};
       }
   }

RequestBody(name)

  • name : string 方法返回值属性,默认为 body

IgnoreResidualParams(ignore?)

  • ignore : boolean 拼接 QueryString 时是否忽略没有声明的参数

RequestConfig(config)

  • axiosConfig: Config - class decorator,注解类,传入框架配置对象,注意不是 AxiosConfig。
  • axiosConfig: AxiosConfig | (...args:any[]) => AxiosConfig - method decorator,注解方法,注解方法时可根据请求参数构造配置对象。
// GET /foo?p1=p1&p2=p2&p3=p3
class TestService extends Service {
    @RequestMapping("/foo", "GET")
    @RequestParam("p1", true)
    foo(token) {
        return {
            p1: "p1",
            p2: "p2", 
            p3: "p3"
        };
    }
}

该注解对请求体没有影响。

// GET /foo?p1=p1
class TestService extends Service {
    @RequestMapping("/foo", "GET")
    @RequestParam("p1", true)
    @IgnoreResidualParams()
    foo(token) {
        return {
            p1: "p1",
            p2: "p2", 
            p3: "p3"
        };
    }
}

Authorizer (Optional Plugin)

  • sessionKey : string 键值名称

    authorizer.sessionKey = "$_SESSION"; // default value
  • getSession : Promise 获取授权信息

    function onLogin(){
      authorizer.getSession().then(session => {
          // fetch other info / redirect to other page
          store.dispatch('SAVE_USER_INFO' , info);
      });
    }  
  • storageSession(session: Session): Promise 存储授权信息

  • checkResponse(response:AxiosResponse) : boolean 检查授权是否过期

    class OAuth2Authorizer extends Authorizer {
      checkResponse(response){
          return response.stauts === 401; // default implement
      } 
    }
  • withAuthentication(request: AxiosRequestConfig, session: Session) : void 请求附加认证信息,默认附加请求头

    class OAuth2Authorizer extends Authorizer {
      withAuthentication(request, session){
          request.headers.Authorization = 'Bearer 0x123456';
      } 
    }

运行环境

微信小程序

  • axios需要降级:
npm install axios@0.21.0
npm install axios-miniprogram-adapter
  • 编译报错 module is not defined, 在app.js头部补充缺失组件的声明:
import "axios-annotations/core/service";
import "axios-annotations/decorator/request-mapping";
import "axios-annotations/decorator/get-mapping";
import "axios-annotations/decorator/post-mapping";
import "axios-annotations/decorator/put-mapping";
import "axios-annotations/decorator/delete-mapping";
import "axios-annotations/decorator/patch-mapping";
import "axios-annotations/decorator/request-param";
import "axios-annotations/decorator/request-body";
import "axios-annotations/decorator/request-header";
import "axios-annotations/decorator/request-config";
import "axios-annotations/decorator/request-with";
import "axios-annotations/decorator/ignore-residual-params";
  • 更新开发工具以支持装饰器语法。

Package Sidebar

Install

npm i axios-annotations

Weekly Downloads

0

Version

1.3.0

License

MIT

Unpacked Size

94.6 kB

Total Files

67

Last publish

Collaborators

  • sitorhy