子应用共享http请求对象

2020-11-26 12:06:20 浏览数 (1)

背景

项目为旧单体项目, 改造为微前端项目。所以原项目存在大量共享工具及组件。当前方案是将这些共享代码根据功能,拆分为第三方包。这其中就包括http请求对象。而后端API并不参入前端业务的拆分,所以我们需要保证子应用与基座使用相同的请求配置。 这里记录相关的解决思路。

目标

我们希望子应用存在独立开发和嵌入基座的能力。

  • 独立开发: 使用本地请求对象
  • 嵌入基座:使用基座请求对象

目录

  • pkgs
    • http 公共请求封装
    • api 可公用的特定请求函数

这里我们将分为两个独立的包, http负责最基础的业务请求对象封装,例如登录拦截, token设置,接口兼容等, api 负责具体的业务请求,提供公共的请求方法。例如: 用户信息查询, 状态查询等.

http 包

目录

  • http
    • src
      • core.ts 请求封装
      • transform-res.ts 响应拦截
      • transform-req.ts 请求拦截
      • utils.ts 工具函数
      • index.ts 入口

核心

代码语言:javascript复制
import { AxiosInstance, AxiosRequestConfig }  from 'axios'
import qs from 'query-string'
import { deepCopy, getParams } from './utils'
const DEFAULT_CONF:AxiosRequestConfig = {
  timeout: 1000 * 6,
  withCredentials: true,
  headers: {
    'Content-Type': 'application/json; charset=utf-8'
  },
  baseURL: process.env.NODE ? '/DEV_PROXYY' : '',
  paramsSerializer
}


export interface iTimetamp{
  T:number
  [props:string]:any
}


/**
 * 刷新请求
 * @summary 通过添加多余的时间戳参数,防止浏览器缓存,刷新请求。
 */
export function paramsSerializer<T extends iTimetamp>(params:T):string{
  params = params.T ? { ...params, T: new Date().getTime() } : params
  return qs.stringify(params)
}


/**
 * 默认配置
 */
export function getDefaultConf():AxiosRequestConfig{
  return deepCopy<AxiosRequestConfig>(DEFAULT_CONF)
}


/**
 * 请求对象
 * @summary 
 * 使用单例模式,全局提供唯一请求对象
 * 封装常用请求方式
 * @props
 *  - _axios axios实例
 *  - CONF 当前全局配置
 * @maths
 *  - GET
 *  - POST
 *  - PUT
 *  
 *  - addResInterceptors response 拦截器
 *  - addReqInterceptors request 拦截器
 */
export class MicroHttp {
  static _http:MicroHttp
  _axios:AxiosInstance = {} as AxiosInstance // 单例兼容写法
  interceptors:AxiosInstance['interceptors'] = {} as AxiosInstance['interceptors']
  
  constructor(axiosObj:AxiosInstance){


    if(MicroHttp._http){
      return MicroHttp._http
    }
    
    this._axios = axiosObj
    this.interceptors = this._axios.interceptors
    MicroHttp._http = this
    
  }


  /**
   * 二次封装请求接口
   *  因为会使用中间件对返回数据做解包
   * 所以方法返回的类型直接使用了 【泛型T】而不是原方法默认的 【AxiosResponse<T>】
   */
  GET<Q, T>(url:string|[string, Q], conf?:AxiosRequestConfig):Promise<T>{
    const [_url, _conf] = getParams(url, conf)
    return MicroHttp._http._axios.get<T, T>(_url, _conf)
  }


  POST<Q, T>(url:string|[string, Q], data?:any, conf?:AxiosRequestConfig):Promise<T>{
    [url, conf] = getParams(url, conf)
    return MicroHttp._http._axios.post<T, T>(url, data, conf)
  }


  PUT<Q, T>(url:string|[string, Q], data?:any, conf?:AxiosRequestConfig):Promise<T>{
    [url, conf] = getParams(url, conf)
    return MicroHttp._http._axios.put<T, T>(url, data, conf)

这里重点是通过二次封装,提供了一个单例请求对象。

拦截器

这里的拦截器都是针对当前业务的特例处理函数, 之所以以单一函数插件的形式导出,是希望尽量降低底层包与业务的相关性

代码语言:javascript复制
// 请求拦截
// 设置请求头 token
export function setToken(getToken:() => string):AxiosTransformer{
  return (data:any, headers:any) => {
    headers.token = getToken()
    return JSON.stringify(data)
  }
}
// 响应拦截
// 因原请求接口返回数据存在格式不统一的请求,所以响应拦截的主要目的是统一响应数据格式
export class ReqData<T>{
  constructor(public _status:number, public _message:string, public data?:T) {
    this._status = _status
    this._message = _message
    this.data = data
  }
}


export interface IresData{
  code?:number
  status?:string
  message?:string
  [key:string]:any
}


export function resParse(response:string):IresData{
  // 存在请求成功后无返回
  try {
    return response ? JSON.parse(response) as IresData : {}
  } catch (error) {
    //适配 直接返回状态字符?
    console.error(`JSON 解析错误: ${error} => ${response}`)
    return {_message: typeof response === 'string' ? response : ''}
  }
}
....

其实单一的http,基本能满足基座与子应用请求对象的一致性。因为使用了单例模式, 子应嵌入基座时,基座与子应用使用同一依赖包,new MicroHttp(conf) 将返回同一请求对象。 也不需要通过传递请求对象保证配置的一致性

API 包

这个包主要提供多个应用或组件通用的请求函数, 是对http包的再次封装,与业务又强相关性。

目录

  • api
    • src
      • http.ts
      • index.ts
      • api-1
      • api-2
      • .....

http.ts

代码语言:javascript复制
import {
  MicroHttp, 
  resAdapter, 
  resListAdapter,
  setToken,
  getDefaultConf,
  resParse,
  resErr,
  resServerErr
} from '@micro/http'
import cookies from 'js-cookie'
const inspect = Symbol.for('nodejs.util.inspect.custom');


const conf = {
  ...getDefaultConf(), 
  baseURL: process.env.NODE_ENV !== 'production' ? '/proxyApi/' : '/',
  transformResponse: [ 
    resParse,
    resAdapter,
    resListAdapter
  ],
  transformRequest: [
    setToken(() => {
      return cookies.get('token') || ''
    })
  ]
}


let _http:MicroHttp
const HTTP = new Proxy({} as MicroHttp, {
  get(_, propKey:keyof MicroHttp | typeof inspect){


    if(propKey === inspect) {
      return `HTTP Proxy`
    }
    
    if (!_http) {
      throw new Error('调用 HTTP,需要先使用 createHTTP 或 setHTTP 初始化请求对象')
    }
    return _http[propKey]
  },
  set(_, propKey:keyof MicroHttp, value:any){
    _http[propKey] = value
    return true
  } 
})




function setHTTP(http:MicroHttp):void{
  _http = http
}


function createHTTP(axios: any): void {
  setHTTP(new MicroHttp(axios.create(conf)))
  HTTP.interceptors.response.use(resErr, resServerErr)


}


function getHTTP():MicroHttp {
  return _http
}


export {
  HTTP,
  createHTTP,
  setHTTP,
  g

使用

代码语言:javascript复制
import { createHTTP, HTTP } from '@micro/api'
import Axios from 'axios'
createHTTP(Axios)
HTTP.GET()

0 人点赞