0.2.8 • Published 3 years ago

@segma/api-tools v0.2.8

Weekly downloads
-
License
ISC
Repository
-
Last release
3 years ago

@segma/api-tools

简介

这是一个与构建 api 请求相关的工具包,包含了定制 axios 实例、认证 token 的管理、基于 vue 的认证 mixin、构建可取消、可 mock 数据的 api 请求等功能。

仓库地址

https://github.com/RanSatious/segma-api

快速开始

安装

# 切换仓库
npm config set registry http://npm.segma.tech/
npm i @segma/api-tools

使用

import { ApiFactory, getToken, setToken, clearToken, AuthChecker, initBuilder } from '@segma/api-tools';

ApiFactory

我们通常使用 axios 来请求后端接口,但在实际使用时,往往需要对 axios 实例进行一些定制化的配置来适应后端接口的一些通用的约定,如返回数据格式、验证机制、错误处理等等。

这些通用的约定一般是作为团队的代码规范,不会随着项目而变化,所以在以往的实践中,前端需要在每个项目里去编写这样一份相同的配置,这是重复性的工作,需要进行改进。

于是,api-tools 提供了 ApiFactory 函数,可以快速生成一个已经拥有默认配置的 axios 实例。

快速使用

import { ApiFactory, SegmaStrategy } from '@segma/api-tools';

const axios = ApiFactory({
    tip: console.log,
    auth: SegmaStrategy,
    axiosConfig: {
        baseURL: process.env.VUE_APP_BASE_API,
    },
});

async function request() {
    let result = await axios.get('http://localhost:7000/api');
    console.log(result);
}

接口定义

import { AxiosInstance } from 'axios';

declare function ApiFactory(config: IApiConfig): AxiosInstance;

interface IApiResult {
    // 错误码
    code: number | string;
    // 返回提示信息
    message: string;
    // 跟踪id
    traceId?: string | null;
    // 导致错误的可能原因
    possibleReason?: string | null;
    // 建议采取的解决问题的措施
    suggestMeasure?: string | null;
    // 返回数据
    data?: unknown;
}

interface IApiConfig {
    // 提示接口错误消息的函数
    tip: (message: string, code?: number | string, result?: IApiResult) => void;
    // axios 配置
    // 会与默认的 axios 配置进行合并
    axiosConfig?: AxiosRequestConfig;
    // 认证策略
    auth?: IAuthStrategy;
}

interface IAuthStrategy {
    // 在请求前调用
    onAuth: (config: AxiosRequestConfig) => Promise<void>;
    // 在请求返回401时调用
    onUnauthorized: (error?: AxiosError) => void;
}

默认配置

import qs from 'qs';

const defaultConfig: IApiConfig = {
    tip: console.log,
    axiosConfig: {
        baseURL: '',
        headers: { 'X-Requested-With': 'XMLHttpRequest' },
        transformResponse: (data: any) => {
            if (typeof data === 'string' && data[0] === '{') {
                return JSON.parse(data);
            } else {
                return data;
            }
        },
        paramsSerializer: function (params) {
            return qs.stringify(params, { arrayFormat: 'brackets' });
        },
    },
};

SegmaStrategy

Segma 的认证策略

const SegmaStrategy: IAuthStrategy = {
    async onAuth(config) {
        config.headers['Authorization'] = await getToken();
    },
    onUnauthorized(error) {
        let redirect = process.env.VUE_APP_AUTH_REDIRECT_URI;
        let clientId = process.env.VUE_APP_AUTH_CLIENT_ID;
        if (redirect && clientId) {
            setTimeout(() => {
                logout(`${redirect}?state=xyz&client_id=${clientId}&redirect_uri=${encodeURIComponent(window.location.href)}`);
            }, 500);
        }
    },
};

QingtuiStrategy

轻推的认证策略

const QingtuiStrategy: IAuthStrategy = {
    async onAuth(config) {
        config.headers['Authorization'] = await getToken();
    },
    onUnauthorized(error) {
        let redirect = process.env.VUE_APP_AUTH_REDIRECT_URI;
        if (redirect) {
            setTimeout(() => {
                logout(`${redirect}?uri=${encodeURIComponent(window.location.href)}`);
            }, 500);
        }
    },
};

token manager

token 的管理主要与 axios 实例,AuthChecker 一起使用,做到与业务代码基本解耦,日常的开发不需要关心 token 如何处理。

快速使用

import { getToken, setToken, clearToken } from '@segma/api-tools';

// 获取 token
// 注意是异步操作
let token;
getToken().then(result => {
    token = result;
});

// 设置 token
setToken('token_value');

// 清理 token
clearToken();

接口定义

// 对应 strategy 的 get 操作
declare const getToken: () => Promise<string>;
// 对应 strategy 的 set 操作
declare const setToken: (token?: string | null) => void;
// 对应 strategy 的 remove 操作
declare const clearToken: () => void;

管理策略

一个 token 的管理策略包含了 get/set/remove 3 种操作,具体参见下面的接口定义,你可以自由添加、选择 token 的管理策略,默认提供 localstorage 的策略。

interface IStrategy {
    get: (name: string) => string | null;
    set: (name: string, value: string) => void;
    remove: (name: string) => void;
}
declare const addStrategy: (name: string, payload: IStrategy, force?: boolean) => void;
declare const selectStrategy: (name?: string) => IStrategy;
export { addStrategy, selectStrategy };

虽然提供了变更管理策略的能力,但请谨慎使用。

todo

  • 自定义 token 的名称,现在固定为 auth_token。

AuthChecker

通过 AuthChecker 快速创建一个可以获取、保存、检查 token 是否存在的 mixin。

快速使用

// 创建 mixin
// src/plugins/mixins/auth.js
import { AuthChecker } from '@segma/api-tools';

export default AuthChecker();
// 使用 mixin
// 在需要验证 token 的布局、视图甚至组件文件中,引入 mixin
// src/components/layout/Default.vue
import auth from '../../plugins/mixins/auth';

export default {
    name: 'DefaultLayout',
    mixins: [auth],
};

接口定义

interface IAuthConfig {
    // 是否在获取不到 token 时自动登出
    autoLogout: boolean;
    // logout 的具体操作
    logout: Function;
}

默认配置

import { SegmaStrategy } from '../api';

const defaultConfig: IAuthConfig = {
    autoLogout: true,
    logout: () => {
        SegmaStrategy.onUnauthorized();
    },
};

api builder

通过 ApiFactory 可以快速构建出满足前后端约定的 axios 实例,但是在实际的开发过程中,下面的一些场景仍会造成一些麻烦:

  • 请求报错,检查了很久,才发现是请求参数写错了
  • 后端接口迟迟不给出来,接口联调陷入僵局
  • 每个请求都需要重复地去控制加载状态
  • 网络波动,第一次请求的数据比第二次请求的数据还要后返回,测试提了 bug

api builder 可以轻松解决以上所有痛点:

  • (规划中)请求预处理,包括并不限于日志输出、参数格式化等
  • 开放接口的 mock 能力,轻松切换 mock 与真实请求
  • 监测接口的状态变化,减少重复代码的编写
  • 接口的可取消能力

初始化

初始化的目的主要是为了指定 axios 实例,并返回 buildApi 函数来构建请求。

快速使用

// 初始化
import { initBuilder } from '@segma/api-tools';

const buildApi = initBuilder({
    axios,
    log: console.log,
});

接口定义

import { AxiosInstance } from 'axios';

declare function initBuilder(config: BuilderConfig): (config: ApiBuilderConfig) => ApiFunction;

interface BuilderConfig {
    // axios 实例
    axios: AxiosInstance;
    // 日志函数
    log: (name: string, ...args: any[]) => void;
}

默认配置

import Axios from 'axios';

let defaultBuilderConfig: BuilderConfig = {
    axios: Axios,
    log: console.log,
};

与 ApiFactory 集成

import { ApiFactory, initBuilder } from '@segma/api-tools';

const buildApi = initBuilder({
    axios: ApiFactory({
        tip: console.log,
        auth: true,
        redirect: process.env.VUE_APP_AUTH_REDIRECT_URI,
    }),
    log: console.log,
});

构建 api

首先完成初始化操作

快速使用

import { initBuilder } from '@segma/api-tools';

const buildApi = initBuilder();

const api = buildApi({
    name: 'test',
    axios: () => ({
        url: 'http://localhost:7000/api',
        method: 'GET',
    }),
    isMock: false,
});

const request = async () => {
    let result = await api.test({ page: 1 });
    console.log(result);
};

request();

接口定义

import { AxiosRequestConfig } from 'axios';

declare function initBuilder(config: BuilderConfig): (config: ApiBuilderConfig) => ApiFunction;

interface ApiBuilderConfig {
    // 接口名称,供内置的日志功能使用
    name: string;
    // 返回 axios 请求参数的函数
    // 详情可参考:https://github.com/axios/axios#request-config
    axios: (params?: unknown) => AxiosRequestConfig;
    // 返回 mock 数据的方法
    // 接收 axios 方法返回的 AxiosRequestConfig 作为参数
    mock: (config?: AxiosRequestConfig) => Promise<any>;
    // 是否 mock 数据
    isMock: boolean;
    // 接口是否可以执行取消操作,为 true 时,返回的接口函数会多出 cancel/getToken 2个方法
    cancel?: boolean;
    // 是否监测接口的状态变化,为 true 时,返回的接口函数会多出 callback 1个方法
    callback?: boolean;
}

interface ApiFunction extends Function {
    cancel?: (message: string) => void;
    getToken?: () => Function | null;
    callback?: Function;
}

mock 请求

import { initBuilder } from '@segma/api-tools';

const buildApi = initBuilder();

const api = buildApi({
    name: 'test',
    mock: async () => 'mock result',
    isMock: true,
});

注意事项

  • mock 需要返回一个 Promise 或是一个 async 函数
  • 可搭配 mockjs 使用
  • isMock 为 true 时,mock 必须设置,axios 则不用;为 false 时,axios 必须设置,mock 则不用

取消请求

import { initBuilder } from '@segma/api-tools';

const buildApi = initBuilder();

const api = buildApi({
    name: 'test',
    axios: () => ({
        url: 'http://localhost:7000/api/wait',
        method: 'GET',
    }),
    isMock: false,
    cancel: true,
});

const request = async () => {
    api.cancel();
    let result = await api();
    console.log(result);
};

request();

方法说明

  • cancel(): void

取消当前正在进行的请求,需要注意取消只是客户端单方面的行为,不会影响到服务端。

  • getToken(): Function | null

获取内部的取消函数,可能为空,主要用于判断接口是否处于请求状态。

let loading = false;
api.callback(status => {
    loading = status === 'start' || (status === 'error' && api.getToken());
});

注意事项

  • 不管是 mock 还是真实请求,都支持取消
  • 不能在会导致数据变更的接口(大部分 POST 请求,PUT、DELETE 请求)上执行取消操作

监测接口状态

import { initBuilder } from '@segma/api-tools';

const buildApi = initBuilder();

const api = buildApi({
    name: 'test',
    axios: () => ({
        url: 'http://localhost:7000/api/wait',
        method: 'GET',
    }),
    isMock: false,
    cancel: true,
    // 监测接口状态的开关
    callback: true,
});

let loading = false;
api.callback(status => {
    loading = status === 'start' || (status === 'error' && api.getToken());
});

const request = async () => {
    api.cancel();
    let result = await api();
    console.log(result);
};

// 对比以前的写法,不用再去使用 try/catch/finally 来控制加载状态,代码变得简洁
// 当然在串联多个异步操作时,仍可以使用以前的写法
const oldRequest = async () => {
    try {
        loading = true;
        let result = await api();
        console.log(result);
    } catch (error) {
        // handle error
    } finally {
        loading = false;
    }
};

request();

方法说明

  • callback((status: 'start' | 'success' | 'error', ...args: any[]) => void): void

接收一个回调函数作为参数,回调函数的第一个参数 status 表示了接口当前的状态,只会是 start/success/error 这 3 个其中之一;后续参数会根据 status 的值而变化:

  • start:无后续参数
  • success:后续参数为接口返回的结果
  • error:后续参数为接口返回的错误

todo

  • 整合 ApiFactory 与 api builder