草庐IT

配置项目请求地址和axios以及实现token过期无痛刷新

王帆 2023-03-28 原文

配置请求地址:config->index.js

一个项目里通常有一个config->index.js,该文件包含了当前项目的请求地址,以及项目的版本信息。

// 请求地址
const API_URL_DEV = 'http://xxx.xxx.xxx.net:81/xxx' // 测试接口
const API_URL_TRIAL = 'http://xxx.xxx.xxx.net:81/xxx' // 体验版接口
const API_URL_PROD = 'http://xxx.xxx.xxx.net:81/xxx' // 线上接口

上面就是api请求的baseUrl,在后续配置axios的时候会用到,在项目开发中,有时候会切换开发版和体验版,就是更改此处的地址。

微信小程序里需要区分一件事,那就是当前的运行环境,是否是开发环境。
微信小程序官方提供了 wx.getAccountInfoSync()可以获取小程序版本的appid以及小程序的版本信息,根据envVersion来判断当前小程序是线下环境还是线上环境。

  let {
    miniProgram: {
      envVersion,
      version
    }
  } = wx.getAccountInfoSync()  // 获取小程序版本信息

将获取版本信息的操作封装成一个函数,根据运行环境配置小程序版本信息和apiUrl

function getparamsByEnv() {
  /**
   * 官方提供:wx.getAccountInfoSync()获取小程序版本信息,以及appid
   * 模拟当前环境
   * 线下的是 'develop' 线上是 'prod'
   */

  let {
    miniProgram: {
      envVersion,
      version
    }
  } = wx.getAccountInfoSync()  // 获取小程序版本信息
  
  // 返回的参数
  let params = {
    apiUrl: '',
    version: version,
    tester: true
  }
  
  switch (envVersion) {
    case 'develop':
      params.apiUrl = API_URL_DEV  // 此处对应上面配置的线下baseUrl接口
      params.version = '1.0.2'
      break
    case 'trial':
      params.apiUrl = API_URL_TRIAL // 此处对应上面配置的体验版baseUrl接口
      params.version = '1.0.2'
      break
    default:
      params.apiUrl = API_URL_PROD  // 此处对应上面配置的线上baseUrl接口
      params.tester = false
      break
  }
  return params   // 配置完成后返回params
}

完整的config.js配置

// 请求地址
const API_URL_DEV = 'http://xxx.xxx.xxx.net:81/xxx' // 测试接口
const API_URL_TRIAL = 'http://xxx.xxx.xxx.net:81/xxx' // 体验版接口
const API_URL_PROD = 'http://xxx.xxx.xxx.net:81/xxx' // 线上接口


const envParams = getparamsByEnv()

export default envParams.apiUrl  // 导出apiUrl

export const version = envParams.version  // 导出版本信息

export const tester = envParams.tester  // 导出tester

/**
 * 区分小程序当前运行环境
 * @return {Boolean} 是否是开发环境
 */
function getparamsByEnv() {
  /**
   * 官方提供:wx.getAccountInfoSync()获取小程序版本信息,以及appid
   * 模拟当前环境
   * 线下的是 'develop' 线上是 'prod'
   */

  let {
    miniProgram: {
      envVersion,
      version
    }
  } = wx.getAccountInfoSync()  // 获取小程序版本信息
  let params = {
    apiUrl: '',
    version: version,
    tester: true
  }
  switch (envVersion) {
    case 'develop':
      params.apiUrl = API_URL_DEV
      params.version = '1.0.2'
      break
    case 'trial':
      params.apiUrl = API_URL_TRIAL
      params.version = '1.0.2'
      break
    default:
      params.apiUrl = API_URL_PROD
      params.tester = false
      break
  }
  return params
}

配置request.js

在这个文件其实就是配置axios,需要引入axios以及上面配置的config.js文件

import axios from 'axios'
import mpAdapter from 'axios-miniprogram-adapter'
axios.defaults.adapter = mpAdapter
import API_URL from '../config/index'   // apiUrl

axios有三个配置项,分别是:baseUrl请求拦截器响应拦截器,下面分别就这些配置项进行配置。

配置请求baseUrl
引入config.jsapiUrl,在axiosbaseUrl中配置

const service = axios.create({
  baseURL: API_URL, // url = base url + request url
})

配置请求拦截器service.interceptors.request.use
在请求拦截器内通常会做一些配置,比如:在请求头中携带token,在请求的时候弹出“加载中”告诉用户正在发生什么等等,通常只要发送请求,都会经过请求拦截器。

let ajaxTimes = 0; // 状态
// 请求拦截器
service.interceptors.request.use(function (config) {
    // config就是请求的配置信息,里面包含baseUrl地址,headers请求头,url请求接口等信息
    // 在发送请求前,可以对config做一些配置操作(加盐)
    
    // 获取token
    if(wx.getStorageSync('token')){
        // 如果token存在,那么在请求头上带上token
        config.headers['access_token'] = wx.getStorageSync('token')
    }
    
    //  请求的时候,添加一个弹框,告诉用户正在发生什么
    // config.url != 'xxx/xxx' 请求某个接口不弹出提示
    if (!ajaxTimes++ && config.url != '/lottery/activity/get-prize'){
        wx.showLoading({ title: '加载中···' })  // 其实把wx.showLoading写在请求拦截器里都会生效,这里只是做了一个ajaxTimes的判断,优化了提示弹框
    }
    
    return config;  // 返回配置信息-也就是请求头之类的
    
},function (error){
    // 处理错误请求
    return Promise.reject(error)
})

配置响应拦截器service.interceptors.response.use
只要请求接口,接口响应并返回,都会走响应拦截器,可以在响应拦截器内做一些配置,比如请求日志、token过期处理、账户封禁处理、服务器错误处理、响应成功处理等等。

// 添加响应拦截器
service.interceptors.response.use(function (response) {
  // response返回上方请求拦截器的config配置信息,data后台接口返回的数据,headers服务器响应头等信息
    
  // 由于微信小程序的 toast 和 loading 相关接口可以相互混用,所以需要取消混用提示,也是关联上方的“加载中”优化
  if (ajaxTimes > 0 && --ajaxTimes === 0) {
    wx.hideLoading({noConflict:true})  // // 取消混用提示
  }
  
  // 这里返回的response.data就是后台接口返回的数据
  const res = response.data
  // 如果返回的code不是200或201,则判断为错误
  if(res.code && res.code !== 200 && res.code !== 201){
      // 分别对这些错误进行对应的处理
      
      if(res.code === 1004){
          // 1004状态码表示token过期,需要重新获取token,并在获取token后,重新请求接口,清除token
          return reSetToken(response.config)  // 这里调用获取token的方法,后面会进行配置
          
      } else if(res.code === 9001){
          // 返回9001状态码处理,通过wx.login获取code
          if (res.message.indexOf('code been used') !== -1) {
            getApp().globalData.userInfo._getLoginCode()
          } else if (res.message.indexOf('invalid code') !== -1) {
            console.log('invalid code')
          }
          return Promise.reject(res)
          
      } else if (res.code === 400 || res.code === 500){
          // 返回400或500状态码,表示服务器错误或者接口请求异常
          wx.showToast({title: `${res.message}`,icon: 'none'})
          return Promise.reject(res)
          
      } else if(res.code == 30011){  // 账户封禁
          // 返回30011状态码,表示该账号已封禁,如果想对封禁的账户做一些操作可以将返回的信息携带并跳转到封禁页,封禁页展示封禁的原有和信息,用户可以在封禁页做封禁申诉等操作
          const info = res.message;
          wx.reLaunch({
            url: `/pages/account/index?info=${info}`,
          })
      }
      
      return Promise.reject(res) // 其他的错误状态码就直接reject
  } else {
      return res
  }   
}, function (error) {
   // 处理响应错误
   if (ajaxTimes > 0 && --ajaxTimes === 0) {
        wx.hideLoading({noConflict:true})
   }
   return Promise.reject(error);
}

token过期后重新获取token,在响应拦截器里说过,返回状态1004表示token过期,在这里封装重新获取token的方法,在响应拦截器状态1004处调用。

let tokenLoad = false
// 重新获取token
function reSetToken(params) {
    // 这里的params就是response.config的信息
    return new Promise(async (resolve,reject) => {
        // 用函数形式将resolve存入,等待刷新后再执行
        const app = getApp()  // 获取小程序全局唯一的 App 实例
        if(!app){
            return console.log("app undefined")
        }
        
        wx.queue.asyncWait("login_back", () => {
          service(params).then(res => {
            resolve(res)
          }, err => {
            reject(err)
          })
        },params.url)
        
        // tokenLoad = true什么都不做,false走重新发起登录的流程
        if(tokenLoad) {
            console.log("中断重新获取token")
        } else {
            // 将tokenLoad状态更改为true
            tokenLoad = true
            try {
                // 重新发起登录,调用发起登录的方法
                app.globalData.userInfo.Login().then(res => {
                    if(res === 'notBindPhone') {
                        app.awaitLoginDialog('request')
                    }
                    tokenLoad = false // 登录成功后将tokenLoad改为false,等待下次token过期重新发起登录
                }).catch(()=>{
                    tokenLoad = false
                })
                
            } catch (error) {
                // 重新获取token报错,说明用户没注册,需要唤醒注册弹窗,如果在tabbar页面,就不自动唤醒
                app.awaitLoginDialog('request')
                tokenLoad = false
                reject(error)
            }
        }
        
    })
}

完整的request.js配置

import axios from 'axios'
import mpAdapter from 'axios-miniprogram-adapter'
axios.defaults.adapter = mpAdapter
import API_URL from '../config/index'
const service = axios.create({
  baseURL: API_URL, // url = base url + request url
})

let ajaxTimes = 0  
// 请求拦截器
service.interceptors.request.use(function (config) {
  // config是请求的配置信息,内部有baseUrl地址,headers请求头,url请求接口等信息
  // console.log('请求拦截器',config); 

  // 发送请求之前你可以在这里对config做一些配置
  // 获取token
  if (wx.getStorageSync('token')) {
    // 如果token存在,那么在请求头上带上token
    config.headers['access_token'] = wx.getStorageSync('token')
  }

  // 请求的时候,添加一个弹框wx.showLoading告诉用户正在加载中
  // !ajaxTimes++是因为请求可能不止一次,请求的时候这种"加载中"的提醒优化,其实只需要显示一次即可,所以需要做一个判断,ajaxTimes == 0的时候提示一次,后续ajaxTimes++都不会提示,每次进新页面或刷新都会重置ajaxTimes == 0 ,这里做的操作其实就是为了优化,减少提示的次数。
  if (!ajaxTimes++ && config.url != '/lottery/activity/get-prize' && config.url != '/lottery/activity/time'){
    wx.showLoading({ title: '加载中···' })  // 其实把wx.showLoading写在请求拦截器里都会生效,这里只是做了一个ajaxTimes的判断,优化了提示弹框
  }

  return config;
}, function (error) {
  // 处理请求错误
  return Promise.reject(error);
});

// 添加一个响应拦截器
service.interceptors.response.use(function (response) {
  // response返回请求拦截器的config配置信息,data后台接口返回的数据,headers服务器响应头等信息
  // console.log('响应拦截器',response);
  if (ajaxTimes > 0 && --ajaxTimes === 0) {
    wx.hideLoading({noConflict:true})
  }

  // 这里返回的response.data就是后台接口返回的数据
  const res = response.data
  console.log('请求日志:',response);  // 请求日志
  // 如果自定义代码不是200或201,则判断为错误.
  if (res.code && res.code !== 200 && res.code !== 201 && res.code !== 2001 && res.code !== 2002) {
    if (res.code === 1004) {
      // 1004状态码表示token过期,这是需要重新获取token,并且在获取完token之后,重新请求接口,清除token
      return reSetToken(response.config)
    } else if (res.code === 9001) {
      if (res.message.indexOf('code been used') !== -1) {
        getApp().globalData.userInfo._getLoginCode()
      } else if (res.message.indexOf('invalid code') !== -1) {
        console.log('invalid code')
      }
      return Promise.reject(res)
    } else if (res.code === 400 || res.code === 500) {
      wx.showToast({title: `${res.message}`,icon: 'none'})
      return Promise.reject(res)
    } else if (res.code === 30011) { // 账户封禁
      const info = res.message;
      // 根据code判断该账户是否封禁,如果封禁,则携带信息跳转到封禁页
      wx.reLaunch({
        url: `/pages/account/index?info=${info}`,
      })
    }
    return Promise.reject(res)
  } else {
    return res
  }
}, function (error) {
  // 处理响应错误
  if (ajaxTimes > 0 && --ajaxTimes === 0) {
    wx.hideLoading({noConflict:true})
  }
  return Promise.reject(error);
});


let tokenLoad = false
// 重新获取token
function reSetToken(params) {
  // console.log('重新获取token:',params);
  return new Promise(async (resolve, reject) => {
    // 用函数形式将 resolve 存入,等待刷新后再执行
    const app = getApp()
    if (!app) {
      console.error("app undefind");
      return
    }
    wx.queue.asyncWait("login_back", () => {
      service(params).then(res => {
        resolve(res)
      }, err => {
        reject(err)
      })
    },params.url)

    // true什么都不做,false走重新发起登录流程
    if (tokenLoad) {
      console.log("中断重新获取token");
    } else {
      // 将tokenLoad状态更改为true
      tokenLoad = true
      try {
        // 重新发起登录
        app.globalData.userInfo.Login().then(res => {
          // console.log('发起登录',res);
          if (res === 'notBindPhone') {
            app.awaitLoginDialog('request')
          }
          tokenLoad = false  // 登录成功后将tokenLoad改为false,等待下次token过期重新发起登录
        }).catch(() => {
          tokenLoad = false
        })
      } catch (error) {
        // 重新获取token报错,说明用户没有注册。需要唤起注册弹窗,如果是在tabbar页面,就不自动唤起
        // console.log(error, '请求token失败报错')
        app.awaitLoginDialog('request')
        tokenLoad = false
        reject(error)
      }
    }
  })


}
export default service

getApp()为微信小程序的全局唯一App实例,用户的登录方法可以挂载到App实例当中,在需要唤醒登录处获取getApp()中挂载的登录方法即可唤醒登录。

有关配置项目请求地址和axios以及实现token过期无痛刷新的更多相关文章

  1. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  2. ruby-on-rails - Rails HTML 请求渲染 JSON - 2

    在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这

  3. ruby-on-rails - 独立 ruby​​ 脚本的配置文件 - 2

    我有一个在Linux服务器上运行的ruby​​脚本。它不使用rails或任何东西。它基本上是一个命令行ruby​​脚本,可以像这样传递参数:./ruby_script.rbarg1arg2如何将参数抽象到配置文件(例如yaml文件或其他文件)中?您能否举例说明如何做到这一点?提前谢谢你。 最佳答案 首先,您可以运行一个写入YAML配置文件的独立脚本:require"yaml"File.write("path_to_yaml_file",[arg1,arg2].to_yaml)然后,在您的应用中阅读它:require"yaml"arg

  4. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  5. ruby-on-rails - Rails 中的 NoMethodError::MailersController#preview undefined method `activation_token=' for nil:NilClass - 2

    似乎无法为此找到有效的答案。我正在阅读Rails教程的第10章第10.1.2节,但似乎无法使邮件程序预览正常工作。我发现处理错误的所有答案都与教程的不同部分相关,我假设我犯的错误正盯着我的脸。我已经完成并将教程中的代码复制/粘贴到相关文件中,但到目前为止,我还看不出我输入的内容与教程中的内容有什么区别。到目前为止,建议是在函数定义中添加或删除参数user,但这并没有解决问题。触发错误的url是http://localhost:3000/rails/mailers/user_mailer/account_activation.http://localhost:3000/rails/mai

  6. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  7. ruby - 从 Ruby 中的主机名获取 IP 地址 - 2

    我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge

  8. jquery - 我的 jquery AJAX POST 请求无需发送 Authenticity Token (Rails) - 2

    rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送

  9. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  10. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

随机推荐