uni-app三部曲之二: 封装http请求

1.引言

前面一篇文章写了使用Pinia进行全局状态管理。

这篇文章主要介绍一下封装http请求,发送数据请求到服务端进行数据的获取。

感谢:

1.yudao-mall-uniapp: 芋道商城,基于 Vue + Uniapp 实现,支持分销、拼团、砍价、秒杀、优惠券、积分、会员等级、小程序直播、页面 DIY 等功能,100% 开源

2.3.x文档 | luch-request

3.Day1-01-uni-app小兔鲜儿导学视频_哔哩哔哩_bilibili

2.token过期后的重新获取思路

在进行登录后,通过本地缓存,存储获取到的accessToken与refreshToken,accessToken的过期时间为30分钟,refreshToken过期时间为30天。在每次发送请求时,通过http的请求拦截器,放入accessToken进入header中,后端进行校验,当accessToken过期后,后端返回的封装中,code为401,此时应该用refreshToken无感知刷新accessToken继续本次的请求,当refreshToken也过期后,就需要用户重新进行登录。

3.代码

代码主要介绍三个部分,第一部分是自定义http的请求拦截器与响应拦截器,第二部分是封装http的请求,第三部分是如何发送具体的请求。

1.自定义拦截器

请求拦截器主要定义发送请求时的参数,响应拦截器主要处理返回时各种情况。具体可查看文档

import { getRefreshToken, getAccessToken, setAccessToken } from '@/utils/auth'
import { platform } from '@/utils/platform'
import { useUserStore } from '@/store'
import Request from 'luch-request'
import * as authApi from '@/api/auth'

const options = {
  // 显示操作成功消息 默认不显示
  showSuccess: false,
  // 成功提醒 默认使用后端返回值
  successMsg: '',
  // 显示失败消息 默认显示
  showError: true,
  // 失败提醒 默认使用后端返回信息
  errorMsg: '',
  // 显示请求时loading模态框 默认显示
  showLoading: true,
  // loading提醒文字
  loadingMsg: '加载中',
  // 需要授权才能请求 默认放开
  auth: false,
  // ...
}

// Loading全局实例
const LoadingInstance = {
  target: null,
  count: 0,
}

/**
 * 关闭loading
 */
function closeLoading() {
  if (LoadingInstance.count > 0) LoadingInstance.count--
  if (LoadingInstance.count === 0) uni.hideLoading()
}
/**
 * @description 请求基础配置 可直接使用访问自定义请求
 */
const http = new Request({
  // 请求基准地址
  baseURL: import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL,
  timeout: 8000,
  header: {
    Accept: '*/*',
    'Content-Type': 'application/json;charset=UTF-8',
    platform,
  },
  // #ifdef APP-PLUS
  sslVerify: false,
  // #endif
  // #ifdef H5
  // 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+)
  withCredentials: false,
  // #endif
  custom: options,
})

/**
 * @description 请求拦截器
 */
http.interceptors.request.use(
  (config) => {
    // 自定义处理【loading 加载中】:如果需要显示 loading,则显示 loading
    if (config.custom.showLoading) {
      LoadingInstance.count++
      LoadingInstance.count === 1 &&
        uni.showLoading({
          title: config.custom.loadingMsg,
          mask: true,
          fail: () => {
            uni.hideLoading()
          },
        })
    }
    // 添加 token 请求头标识
    const token = getAccessToken()
    if (token) {
      config.header.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error) => {
    return Promise.reject(error)
  },
)

/**
 * @description 响应拦截器
 */
http.interceptors.response.use(
  (response) => {
    // 自定处理【loading 加载中】:如果需要显示 loading,则关闭 loading
    response.config.custom.showLoading && closeLoading()
    // 返回结果:包括 code + data + msg
    const resData = response.data
    const code = resData.code
    if (code === 200) {
      return Promise.resolve(response.data)
    } else if (code === 401) {
      return refreshToken(response.config)
    } else {
      uni.showToast({
        title: resData.message || '出错啦!',
        icon: 'none',
        mask: true,
      })
    }
  },
  (error) => {
    let errorMessage = '网络请求出错'
    if (error !== undefined) {
      switch (error.statusCode) {
        case 400:
          errorMessage = '请求错误'
          break
        case 401:
          errorMessage = '请登录'
          // 正常情况下,后端不会返回 401 错误,所以这里不处理 handleAuthorized
          break
        case 403:
          errorMessage = '拒绝访问'
          break
        case 404:
          errorMessage = '请求出错'
          break
        case 408:
          errorMessage = '请求超时'
          break
        case 429:
          errorMessage = '请求频繁, 请稍后再访问'
          break
        case 500:
          errorMessage = '服务器开小差啦,请稍后再试~'
          break
        case 501:
          errorMessage = '服务未实现'
          break
        case 502:
          errorMessage = '网络错误'
          break
        case 503:
          errorMessage = '服务不可用'
          break
        case 504:
          errorMessage = '网络超时'
          break
        case 505:
          errorMessage = 'HTTP 版本不受支持'
          break
      }
      if (error.errMsg.includes('timeout')) errorMessage = '请求超时'
      // #ifdef H5
      if (error.errMsg.includes('Network'))
        errorMessage = window.navigator.onLine ? '服务器异常' : '请检查您的网络连接'
      // #endif
    }

    if (error && error.config) {
      if (error.config.custom.showError === false) {
        uni.showToast({
          title: error.data?.msg || errorMessage,
          icon: 'none',
          mask: true,
        })
      }
      error.config.custom.showLoading && closeLoading()
    }

    return false
  },
)

// Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现
let requestList = [] // 请求队列
let isRefreshToken = false // 是否正在刷新中
const refreshToken = async (config) => {
  // 如果当前已经是 refresh-token 的 URL 地址,并且还是 401 错误,说明是刷新令牌失败了,直接返回 Promise.reject(error)
  if (config.url.indexOf('/auth/refresh-token') >= 0) {
    isRefreshToken = false
    uni.navigateTo({ url: '/pages/login/index' })
    return Promise.reject(new Error('error'))
  }

  console.log('过期', config)
  // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
  if (!isRefreshToken) {
    isRefreshToken = true
    // 1. 如果获取不到刷新令牌,则只能执行登出操作
    const refreshToken = getRefreshToken()
    if (!refreshToken) {
      return handleAuthorized()
    }
    // 2. 进行刷新访问令牌
    const refreshTokenData = reactive({
      refreshToken: getRefreshToken(),
      clientId: import.meta.env.VITE_CLIENT_ID,
    })
    const res = await authApi.refreshToken(refreshTokenData)
    console.log(res)
    setAccessToken(res.data.accessToken)
    try {
      // 2.1 刷新成功,则回放队列的请求 + 当前请求
      config.header.Authorization = 'Bearer ' + getAccessToken()
      requestList.forEach((cb) => {
        cb()
      })
      requestList = []
      return request(options)
    } catch (e) {
      // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
      // 2.2 刷新失败,只回放队列的请求
      requestList.forEach((cb) => {
        cb()
      })
      // 提示是否要登出。即不回放当前请求!不然会形成递归
      return handleAuthorized()
    } finally {
      requestList = []
      isRefreshToken = false
    }
  } else {
    // 添加到队列,等待刷新获取到新的令牌
    return new Promise((resolve) => {
      console.log('重试', config)
      requestList.push(() => {
        config.header.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改
        resolve(request(options))
      })
    })
  }
}

/**
 * 处理 401 未登录的错误
 */
const handleAuthorized = () => {
  const userStore = useUserStore()
  userStore.userLogout()
  isRefreshToken = false
  // 是否进入登录页
  uni.showModal({
    title: '提示',
    content: '重新登录?',
    success: function (res) {
      if (res.confirm) {
        uni.navigateTo({ url: '/pages/login/index' })
      }
    },
  })

  // 登录超时
  return new Promise<IResData<boolean>>((resolve, reject) => {
    const res: IResData<boolean> = {
      code: 401,
      message: '请重新登录',
      data: false,
    }
    reject(res)
  })
}

const request = (config) => {
  return http.middleware(config)
}

export default request
auth.ts

/**
 * 存储用户身份信息令牌
 */

export const CACHE_KEY = {
  ACCESS_TOKEN: 'access_token',
  REFRESH_TOKEN: 'refresh_token',
}

// 存储访问令牌
export const setAccessToken = (accessToken: string) => {
  uni.setStorageSync(CACHE_KEY.ACCESS_TOKEN, accessToken)
}

// 存储刷新令牌
export const setRefreshToken = (refreshToken: string) => {
  uni.setStorageSync(CACHE_KEY.REFRESH_TOKEN, refreshToken)
}

// 获取访问令牌
export const getAccessToken = () => {
  return uni.getStorageSync(CACHE_KEY.ACCESS_TOKEN)
}

// 获取刷新令牌
export const getRefreshToken = () => {
  return uni.getStorageSync(CACHE_KEY.REFRESH_TOKEN)
}

// 清理本地所有缓存
export const clearStorage = () => {
  uni.clearStorageSync()
}

2.封装http请求

/**
 * 封装不同类型的restful请求
 */

import request from './request'


// 全局要用的类型放到这里

type IResData<T> = {
  code: number
  message: string
  data: T
}

export default {
  get: async <T = any>(options: any) => {
    const res = await request({ method: 'GET', ...options })
    return res as unknown as IResData<T>
  },
  post: async <T = any>(option: any) => {
    const res = await request({ method: 'POST', ...option })
    return res as unknown as IResData<T>
  },
  postOriginal: async (option: any) => {
    const res = await request({ method: 'POST', ...option })
    return res
  },
  delete: async <T = any>(option: any) => {
    const res = await request({ method: 'DELETE', ...option })
    return res as unknown as IResData<T>
  },
  put: async <T = any>(option: any) => {
    const res = await request({ method: 'PUT', ...option })
    return res as unknown as IResData<T>
  },
  download: async <T = any>(option: any) => {
    const res = await request({ method: 'GET', responseType: 'blob', ...option })
    return res as unknown as Promise<T>
  },
  upload: async <T = any>(option: any) => {
    option.headersType = 'multipart/form-data'
    const res = await request({ method: 'POST', ...option })
    return res as unknown as Promise<T>
  },
}

3.定义请求

import http from '@/service/http'

/** 用户登录 */
export const login = (data: LoginReqVO) => {
  return http.post({ url: '/auth/login', data })
}

4.写在最后

在本项目开始,使用了uni.request来发送http请求,通过uni-app的拦截器配置请求拦截器,后面学习研究的时候发现了luch-request,通过文档然后参考yudao-mall-uniapp项目,封装http请求,通过测试,发现能满足实际需用需求。

当然,本篇文章写的比较简陋,水平有限,欢迎共同探讨指教。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/786702.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java项目总结数据库

1.什么是数据库 用于存储和管理数据的仓库 2.数据库的特点 1.持久化存储数据。确实数据库就是一个文件系统。 2.便于存储和管理数据 3.使用统一的方式操作数据库 --SQL 3.MqSql服务启动 4.登录和退出 这里的ip值IP地址 5.客户端与服务器关系 6.目录结构 7.SQL 1.什么是SQL&…

跟《经济学人》学英文:2024年6月29日这期 A new lab and a new paper reignite an old AI debate

A new lab and a new paper reignite an old AI debate Two duelling visions of the technological future 对技术未来的两个对立的愿景 reignite&#xff1a;美 [ˌriɪɡˈnaɪt] 重新点燃&#xff1b;重新激起 duel&#xff1a;美 [ˈduːəl] 决斗&#xff1b;对决&…

自动群发消息插件常用源代码科普!

随着网络技术的快速发展&#xff0c;自动群发消息插件成为了众多企业和个人提高效率、加强沟通的重要工具。 然而&#xff0c;开发一个高效且稳定的自动群发消息插件并非易事&#xff0c;需要深入理解并熟练掌握相关的源代码。 本文将从五个方面&#xff0c;通过具体的源代码…

解决 NullReferenceException: Object reference not set to an instance of an object

在 Unity 中 利用 URDF Importer import UR5e_gripper 的 URDF file 时出现错误&#xff1a; NullReferenceException: Object reference not set to an instance of an object。 理论上是有个Object 是 Null&#xff0c;当我再次检查URDF后仍觉得路径没有写错。 于是我 把Mesh…

什么是O2O?线上线下怎么完美结合?

现如今互联网技术飞速发展&#xff0c;智能手机普及。O2O&#xff08;Online To Offline&#xff09;模式已经成为一种新的商业模式&#xff0c;人们的生活和消费习惯也逐渐被改变。经常听到企业提到“O2O”&#xff0c;它究竟是什么呢&#xff1f;对企业有着什么魅力呢&#x…

Zynq系列FPGA实现SDI视频编解码+UDP以太网传输,基于GTX高速接口,提供3套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本博已有的以太网方案本博已有的FPGA图像缩放方案1G/2.5G Ethernet PCS/PMA or SGMII架构以太网通信方案AXI 1G/2.5G Ethernet Subsystem架构以太网通信方案本方案的缩放应用本方案在Xilinx--Kintex系列…

什么,有狗快跑!慢着,这次手把手教你怎么过安全狗!(sql注入篇)

前言 在记忆里上次绕安全狗还是在上次&#xff0c;开开心心把自己之前绕过狗的payload拿出来&#xff0c;发现全部被拦截了&#xff0c;事情一下子就严肃起来了&#xff0c;这就开整。 环境 本次环境如下sqli-lab的sql注入靶场 网站安全狗APACHE版V4.0版本的最高防护等级绕过…

大数据之Hadoop平台的搭建

实验环境 三台虚拟机 master slave1 slave2 服务器集群单节点&#xff0c;机器最低配置&#xff1a;双核 CPU、8GB 内存、100G 硬盘运行环境CentOS 7.4服务和组件服务和组件根据实验需求安装 1实验过程 1.1实验任务一&#xff1a;配置 Linux 系统基础环境 1.1.1步骤一&a…

信友队 货仓选址

题目ID&#xff1a;9731 必做题 100分 时间限制: 200ms 空间限制: 65535kB 题目描述 时间&#xff1a;0.2s 空间&#xff1a;64M 【题目描述】 在一条数轴上有 N 家商店&#xff0c;他们的坐标分别为 A[1]~A[N]。现在需要在数轴上建立一家货仓&#xff0c;每天清晨&#x…

前端水印使用指南

一、背景 用户在使用系统的时候&#xff0c;有些数据是有权限的用户才能查看&#xff0c;有权限查看数据的用户在查看数据的时候&#xff0c;把数据截图发给了没有权限查看的用户&#xff0c;然后数据泄露了&#xff0c;当截图多次流转后就逐渐不知道最初是谁截的图&#xff0…

Presto报错:[Presto requires an Oracle or OpenJDK JVM (found Red Hat, Inc.)]

启动前: 已经搭建了jdk环境hadoop的jdk环境配置没有问题 启动Presto时&#xff0c;报错 解决方案: 可能是presto自身变量配置没生效在presto路径下找到bin目录, 进入启动脚本launcher 在 exec "$(dirname “ 0 " ) / l a u n c h e r . p y " " 0"…

格式工厂转换视频分辨率

1、下载和安装 http://www.pcfreetime.com/formatfactory/CN/index.html 2、打开视频 3、设置分辨率等参数 也可以选择保持原分辨率 4、执行导出 5、打开输出所在位置

nvm 管理多版本 node

1、下载 先不安装node 下载 nvm 1.1.10-setup.zip 解压&#xff1a;nvm&#xff1a;https://nvm.uihtm.com/ 新建nodejs/node、nodejs/nvm文件夹用于存放node版本和nvm安装路径 安装nvm&#xff1a;上述链接有安装教程 查看是否安装成功&#xff1a;重新打开cmd 输入 nvm nv…

SenseVoice - 阿里最新开源精准多语言语音识别与情感辨识模型 本地一键整合包下载

阿里巴巴近期发布了开源语音大模型项目FunAudioLLM&#xff0c;该项目包含了两个核心模型&#xff1a;SenseVoice和CosyVoice。可以精准多语言识别并且进行语音克隆 本地一键包下载地址&#xff1a; SenseVoice - 精准多语言语音识别与情感辨识模型 本地一键整合包下载 SenseVo…

本地部署 SenseVoice - 阿里开源语音大模型

本地部署 SenseVoice - 阿里开源语音大模型 1. 创建虚拟环境2. 克隆代码3. 安装依赖模块4. 启动 WebUI5. 访问 WebUI 1. 创建虚拟环境 conda create -n sensevoice python3.11 -y conda activate sensevoice 2. 克隆代码 git clone https://github.com/FunAudioLLM/SenseVoic…

二阶线性微分方程

假设一个质量 m 连接在弹簧和阻尼器上&#xff0c;系统受到外力 F(t) 的作用。设 x(t) 为质量的位移&#xff0c;系统的运动方程可以用牛顿第二定律表示为&#xff1a; 这是一个典型的二阶线性非齐次微分方程&#xff1a;其中&#xff1a; m 是质量&#xff08;Fma&#xff09…

“El-Table二次封装“这样做【高级前端必备技能之一】

&#x1f525; 前言 这篇文章给大家分享一个高级自定义列表组件从0到1的开发过程&#xff0c;这个列表组件的主要功能有&#xff0c;列表拖拽排序&#xff0c;右侧操作按钮统一使用Tooltip展示&#xff0c;操作表头增加自定列表icon&#xff0c;点击icon可以对列表展示数据进行…

玩具营销是如何拿捏成年人钱包?

好像现在的成年人逐渐热衷于偏向年轻化&#xff0c;问问题会好奇“尊嘟假嘟”&#xff0c;饭量上的“儿童套餐”&#xff0c;娃娃机前排长队......而最突出的莫过于各类各式的玩具不断收割当代年轻人&#xff0c;除去常给大朋友们小朋友们送去玩具福利的“麦、肯”双门&#xf…

nvm安装使用 nrm使用

因维护老项目及开发新项目同时进行&#xff0c;需要使用不同版本的node进行运行&#xff0c;所以用nvm进行多个版本的node维护&#xff0c;通过nrm进行镜像源管理切换 简介 Node.js 是一种基于 Chrome V8 引擎的 JavaScript 运行环境&#xff0c;用于构建高性能的网络应用程序…

Linux--线程的控制

目录 0.前言 1.pthread库 2.关于控制线程的接口 2.1.创建线程&#xff08;pthread_create&#xff09; 2.2.线程等待&#xff08;pthread_join&#xff09; 代码示例1&#xff1a; ​编辑 ***一些问题*** 2. 3.创建多线程 3.线程的终止 &#xff08;pthread_exit /…