跳转到内容

数据请求

我们需要使用更为优雅的方式,来处理 HTTP 相关的请求。使其能够支持链式调用,同时对返回数据统一处理,精简返回内容,使得在获取到最终结果处,可以尽可能简单和优雅。

接口请求封装

推荐使用 src/services/request.js 发送服务端接口的相关请求。request.js 是基于 request 的封装,便于统一处理 POST,GET 等请求参数,请求头,以及错误提示信息等。具体可以参看 request.js。它封装了全局 request 拦截器、response 拦截器、统一的错误处理、baseURL 设置等。

接口定义

  • 所有的接口名应定义在 src/services/RESTFULURL.js 文件里。可以使用数据服务层的解决方案 Nemo,自动化生成请求的代码。

接口配置地址

默认基础的请求地址在配置文件 .winrcappConfig 属性里,字段名为 API_HOME。当然也可以根据不同的服务接口地址进行拓展字段,比如下面的 API_UPLOAD

javascript
// .winrc.ts
export default {
  appConfig: {
    // 本地调试环境
    development: {
      API_HOME: 'https://api.github.com/',
      API_UPLOAD: 'https://api.github.com/upload',
      // vconsole 开关
      IS_OPEN_VCONSOLE: true
    },
    // 测试环境
    test: {
      API_HOME: 'https://test.github.com/',
      API_UPLOAD: 'https://test.github.com/upload',
      // vconsole 开关
      IS_OPEN_VCONSOLE: true
    },
    // 生产环境
    production: {
      API_HOME: 'https://production.github.com/',
      API_UPLOAD: 'https://production.github.com/upload',
      // vconsole 开关
      IS_OPEN_VCONSOLE: false
    }
  }
}

请求拦截

javascript
/**
 * requestInstance 实例全局请求扩展配置
 * 添加一个请求拦截器 
 */
export const httpRequest = {
  success: (config) => {
    // 以下代码,鉴权token,可根据具体业务增删。
    // demo示例:
    if (config['url'].indexOf('operatorQry') !== -1) {
      config.headers['accessToken'] = 'de4738c67e1bb450be71b660f0716aa4675860cec1ff9bc23d800efb40519cf3';
    }
    return config;
  },
  error: (error) => Promise.reject(error)
};

响应拦截

javascript
/**
 * requestInstance 实例全局请求响应处理
 * 添加一个返回拦截器 (于transformResponse之后处理)
 * 返回的数据类型默认是json,若是其他类型(text)就会出现问题,因此用try,catch捕获异常
 */
export const httpResponse = {
  success: (response) => {
    responseLog(response);
    return checkStatus(response);
  },
  error: (error) => {
    const { response, code } = error;
    // 接口请求异常统一处理
    if (code === 'ECONNABORTED') {
      // Timeout error
      console.log('Timeout error', code);
    }
    if (response) {
      // 请求已发出,但是不在2xx的范围
      // 对返回的错误进行一些处理
      return Promise.reject(checkStatus(response));
    } else {
      // 处理断网的情况
      // eg:请求超时或断网时,更新state的network状态
      // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
      // 关于断网组件中的刷新重新获取数据,会在断网组件中说明
      console.log('断网了~');
    }
  }
};

const codeMessage = {
  200: '服务器成功返回请求的数据。',
  201: '新建或修改数据成功。',
  202: '一个请求已经进入后台排队(异步任务)。',
  204: '删除数据成功。',
  400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
  401: '用户没有权限(令牌、用户名、密码错误)。',
  403: '用户得到授权,但是访问是被禁止的。',
  404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
  406: '请求的格式不可得。',
  410: '请求的资源被永久删除,且不会再得到的。',
  422: '当创建一个对象时,发生一个验证错误。',
  500: '服务器发生错误,请检查服务器。',
  502: '网关错误。',
  503: '服务不可用,服务器暂时过载或维护。',
  504: '网关超时。'
};

function responseLog(response) {
  if (process.env.NODE_ENV === 'development') {
    const randomColor = `rgba(${Math.round(Math.random() * 255)},${Math.round(Math.random() * 255)},${Math.round(
      Math.random() * 255
    )})`;
    console.log('%c┍------------------------------------------------------------------┑', `color:${randomColor};`);
    console.log('| 请求地址:', response.config.url);
    console.log('| 请求参数:', Qs.parse(response.config.data));
    console.log('| 返回数据:', response.data);
    console.log('%c┕------------------------------------------------------------------┙', `color:${randomColor};`);
  } else {
    console.log('| 请求地址:', response.config.url);
    console.log('| 请求参数:', Qs.parse(response.config.data));
    console.log('| 返回数据:', response.data);
  }
}

function checkStatus(response) {
  // 如果http状态码正常,则直接返回数据
  if (response) {
    const { status, statusText } = response;
    if ((status >= 200 && status < 300) || status === 304) {
      // 如果不需要除了data之外的数据,可以直接 return response.data
      return response.data;
    }
    return {
      status,
      msg: codeMessage[status] || statusText
    };
  }
  // 异常状态下,把错误信息返回去
  return {
    status: -404,
    msg: '网络异常'
  };
}

主体函数实现

js


/**
 * 基于 request 请求
 * @param url
 * @param method
 * @param timeout
 * @param prefix 用来拼接url地址
 * @param data
 * @param headers
 * @param dataType
 * @returns {Promise.<T>}
 */
export default function (
  url,
  { method = 'post', timeout = TIMEOUT, prefix = '', data = {}, headers = {}, dataType = 'json' }
) {
  const baseURL = autoMatchBaseUrl(prefix);

  const formatHeaders = {
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    ...headers
  };

  const defaultConfig = {
    baseURL,
    url: formatURL(url, data),
    method,
    params: data,
    data,
    timeout,
    headers: formatHeaders,
    responseType: dataType
  };

  if (method.toLowerCase() === 'get') {
    // 给 get 请求加上时间戳参数,避免从缓存中拿数据。
    defaultConfig.params = Object.assign(defaultConfig.params || {}, { _t: new Date().getTime() });
  }

  return request(defaultConfig.url, defaultConfig);
}

最后在 src/app.t[j]s 文件里添加以下代码即可。

js
import { TIMEOUT } from '@/constant';
import { RequestConfig } from 'winjs';
import { httpRequest, httpResponse } from './services/request';

export const request: RequestConfig = {
  timeout: TIMEOUT,
  requestInterceptors: [
    // 一个二元组,第一个元素是 request 拦截器,第二个元素是错误处理
    [
      (config) => {
        console.log('requestInterceptors 1', config);
        return httpRequest.success(config);
      },
      (error) => {
        console.log('error', error);
        return httpRequest.error(error);
      },
    ],
  ],
  responseInterceptors: [
    [
      (response) => {
        console.log('responseInterceptors 1', response);
        return httpResponse.success(response);
      },
      (error) => {
        console.log('responseInterceptors 2', error);
        return httpResponse.error(error);
      },
    ],
  ],
};

基于 MIT 许可发布