uniapp 网络请求封装 + token刷新

3,303 阅读1分钟

大家好😊,我叫天黑黑,默默在掘金潜水很长时间了,今天刚刚提测了小程序,下午在掘金划划水(嘘...),突发奇想,想写一篇热乎的文章(第一次写文章...😋)

前两周被通知要做一个钉钉小程序,由于公司的技术栈是vue,就选择了uni-app 作为小程序的前端框架

项目目录

image.png

...
├──src
│   ├──api                    // api文件夹
│       ├──modules            // 分模块的请求地址
│          ...         
│       ├──index.js           // modules的整合
│       ├──request.js         // 请求封装
|   ...
│   ├──config                 // 根据不同环境配置的请求地址
....

项目中按模块划分了请求,只要在 modules 文件夹中新增模块的请求js文件(例如:登录模块:新增 modules/login.js )

在 api/index.js 对 modules 进行统一整合。

api/inedx.js

const modulesFiles = require.context('./modules', true, /\.js$/)
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
  const value = modulesFiles(modulePath)
  modules = {
            ...modules,
            ...value.default
	 }
  return modules
}, {})

export default {
  ...modules
}

使用webpack中的 require.context 获取上下文,不用写过多的import来导入模块

例:base.js

/**
 * 请求配置项:默认值
 * url = '',
 * method = 'GET',
 * BASE_API = env_config.BASE_API,
 * headers =  {'Content-Type': 'application/x-www-form-urlencoded'}, // 默认
 * dataType = 'json', // 支持json、text、base64
 * timeout = 30000 // 默认 30000
 * loading = true,
 * loadingText = '加载中...',
 * token = true
 **/

import HTTP from '../request.js';

const BASE_API =  {
    // 获取token
    API_TOKEN() {
        return HTTP({
            url: `/api/auth/token`,
            token: false,
            loading: false
        })
    },
    // 获取用户信息
    API_USER_INFO(data) {
        return HTTP({
            url: '/api/auth/userinfo',
            loading: false
        }, data)
    }
}
export default BASE_API

config/

config文件夹,是通过 process.env.NODE_ENV 来配置不同环境的全局变量,例如 BaseURL、小程序ID等...

api/request.js

这是对 dd.httpRequest 的二次封装,如下:

const env_config = require('../config/index.js')
import store from '../store/index.js'

let isRefresh = false // 是否在请求新的token
const http = (option, data) => {
    let {
        url = '',
        method = 'GET',
        BASE_API = env_config.BASE_API,
        headers =  {'Content-Type': 'application/x-www-form-urlencoded'}, // 默认
        dataType = 'json', // 支持json、text、base64
        timeout = 3000000, // 默认 30000
        loading = true,
        loadingText = '加载中...',
        token = true
    } = option
    // 1.2 Promise
    return new Promise(async (resolve, reject) => {
        // 是否需要 请求loading
        if(loading) {
            dd.showLoading({
              content: loadingText,
            });
        }
        // 是否需要token
        if(token) {
            let access_token = dd.getStorageSync({ key: 'access_token' }).data
            if(!access_token) {
                // 具体获取token的请求,在store中
                await store.dispatch('getAccessToken').then(res => {
                         access_token = res.token
                })
            }
            headers = {
                ...headers,
                "Authorization": `Bearer ${access_token}`
            }
        }
        method = method.toUpperCase(); 
        dd.httpRequest({
            url: BASE_API + url,
            method: method,
            headers: headers,
            data: data,
            timeout: timeout,
            dataType: dataType,
            success: function(res) {
                if(res.status === 200) {
                    resolve(res.data)
                } else {
                    dd.showToast({
                        type: 'fail',
                        content: res.data.msg,
                        duration: 3000
                    });
                }
            },
            fail: async function(res) {
                // 如果接口返回 401 无权限,再请求一次token,并在resolve中返回本次请求
                if(res.status === 401) {
                        if(!isRefresh) {
                            isRefresh = true
                            await store.dispatch('getAccessToken').then(res => {
                                if(res.code === '0') {
                                    resolve(http(option, data))
                                } else {
                                    dd.showToast({
                                        type: 'fail',
                                        content: res.msg,
                                        duration: 3000,
                                        success:() => {   
                                            dd.navigateTo({
						url: '/pages/noToken'
                                            })
					}
                                    });
				}
                            }).catch(err => {				
                                dd.showToast({
                                        type: 'fail',
                                        content: err.msg,
                                        duration: 3000,
                                        success:() => {
                                            dd.navigateTo({
                                                url: '/pages/noToken'
                                            })
                                        }
                                });
                            }).finally(() => {
                                isRefresh = false
                            })
                        }
                    } else {
                        reject(res)
                    }
		},
		complete: function(res) {
                    dd.hideLoading();
		}
            });
	})
};

export default http

这里用的钉钉小程序的api,可以换成uniapp的api,同样适用;

在main.js中:

import http from './api/index.js';
Vue.prototype.$HTTP = http

如何使用

在项目中使用如下:

const data = {
    userId: 'test'
}
this.$HTTP.API_USER_INFO(data).then(res => {
    ...
}).catch(() => {
    ...
}).finally(() => {
    ...
})