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

news2024/11/16 3:47:13

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请求,通过测试,发现能满足实际需用需求。

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

k8s核心操作_Deployment的扩缩容能力_Deployment自愈和故障转移能力---分布式云原生部署架构搭建022

然后我们上面说了k8s中的deployment的多副本能力 然后,我们再来看 k8s中的deployment的扩缩容能力 可以看到,对于扩容,要使用 kubectl scale 命令 对于缩容 要使用kubectl scale 命令都是使用这个命令对吧 来试试,可以看到上面命令 首先看看 kubectl get pod 可以看到有…

u-boot的主要目录结构

arch 各种芯片架构的相关代码,u-boot入口代码 board 各种单板相关的代码,主要包含存储器驱动等 board/hi3516a hi3516a单板相关的代码 arch/xxx/lib 各种体系结构的相关的代码,如ARM include 头文件 include/configs 各种单板的配置文件 common 各种功能实…

springboot篮球馆管理系统-计算机毕业设计源码21945

目 录 摘要 1 绪论 1.1选题背景 1.2研究意义 1.3论文结构与章节安排 2 篮球馆管理系统系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用例分析 2.4 …

SSO单点登录-1-同浏览器进行单点登录

前端同域 客户端前端同域&#xff0c;则cookie可以存在相同的域名或顶级域名下&#xff0c;一个客户端登录成功后&#xff0c;将token信息保存到域名下的cookie中其他不同客户端访问时&#xff0c;因为域名或者顶级域名相同&#xff0c;也能取到域名下的cookie中的token信息并…

华三m-lag三层转发+VRRP配置案例

目录 一、相关理论介绍 1.1 华三M-LAG介绍 1.2 DRCP协议 1.3 keepalive机制 1.4 MAD机制 1.5 一致性检查功能 二、M-LAG系统建立及工作过程 三、实验组网案例 3.1 组网需求 3.2 组网拓扑 3.3 设备接口及地址规划 四、具体配置命令 4.1 S6850-1的配置 4.2 S6850-2…

python爬虫和用腾讯云API接口进行翻译并存入excel,通过本机的Windows任务计划程序定时运行Python脚本!

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a;定时爬取外网的某个页面&#xff0c;并将需要的部分翻译为中文存入excel 接下了的&#xff0c;没学过的最好看一下 基本爬虫的学习 【爬虫】requests 结合 BeautifulSoup抓取网页数据_requests beauti…

Visual Studio 安装Python 环境

前言 我在使用Visual Studio作为Python工具的时候&#xff0c;碰到了一些环境问题和依赖包安装问题&#xff0c;现将问题和方法总结出来&#xff0c;供大家学习和参考。 一、Python 安装 Python官网&#xff1a; Welcome to Python.org 可以下载左侧的稳定发布版本 下载完成之…

昇思25天学习打卡营第21天 | Diffusion扩散模型

内容介绍&#xff1a; 扩散模型&#xff08;Diffusion Models&#xff09;有很多种理解&#xff0c;本文的介绍是基于denoising diffusion probabilistic model &#xff08;DDPM&#xff09;&#xff0c;DDPM已经在&#xff08;无&#xff09;条件图像/音频/视频生成领域取得…

The Quinfall昆法尔卡顿延迟高怎么办?快速降低昆法尔延迟

The Quinfall昆法尔中&#xff0c;玩家可以选择数十种不同的职业&#xff0c;体验从战士到法师&#xff0c;从猎人到工匠的丰富人生。每个职业都有其独特的技能和装备&#xff0c;玩家可以根据自己的喜好和游戏风格来自由搭配。而游戏中的战斗系统更是丰富多彩&#xff0c;无论…

【深度学习实战(44)】Anchor based and Anchor free(无锚VS有锚)

1 anchor-based 深度学习目标检测通常都被建模成对一些候选区域进行分类和回归的问题。在单阶段检测器中&#xff0c;这些候选区域就是通过滑窗方式产生的 anchor&#xff1b;在两阶段检测器中&#xff0c;候选区域是 RPN 生成的 proposal&#xff0c;但是 RPN 本身仍然是对滑窗…

gen_cross_contour_xld 为每个输入点生成一个十字形状的XLD轮廓。

gen_cross_contour_xld Name 名称 gen_cross_contour_xld — Generate one XLD contour in the shape of a cross for each input point. 为每个输入点生成一个十字形状的XLD轮廓。 Signature 签名 gen_cross_contour_xld( : Cross : Row, Col, Size, Angle : ) Descripti…

SQL创建一个actor_name表

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 对于如下表ac…

微信投票小程序源码系统 带礼物功能 完整的源代码包以及搭建部署教程

系统概述 随着移动互联网的快速发展&#xff0c;微信小程序以其便捷性、轻量化及强大的社交属性&#xff0c;已成为众多企业和个人进行品牌宣传、活动推广的重要工具。其中&#xff0c;微信投票小程序更是因其独特的互动性和参与感&#xff0c;深受用户喜爱。然而&#xff0c;…

matlab 花瓣线绘制

matlab 花瓣线绘制 clc,clear,close all; % 创建一个范围内的 x 和 y 值 x linspace(-1.5, 1.5, 100); y linspace(-1.5, 1.5, 100);% 创建一个网格来表示 x 和 y 值的组合 [X, Y] meshgrid(x, y);% 计算方程的左边和右边的值 LHS1 X.^2 Y.^2; RHS1 X.^4 Y.^4;LHS2 X.…

内网信息收集:手动、脚本和工具查IP、端口

1.手动查IP和端口 2.工具查IP 3.工具查端口 我们在内网中拿下目标机器后&#xff0c;需要进行一系列的信息收集&#xff0c;以下为总结的收集方法 1.手动信息收集&#xff1a; 以下命令在CS执行时命令前须加shell,如&#xff1a;shell ipconfig 1.收集IP网卡&#xff1a; ip…

node-gyp 重新安装,解决编译遇到的问题【超详细图解】

一、报错信息 npm ERR! gyp info it worked if it ends with ok npm ERR! gyp info using node-gyp10.0.1 npm ERR! gyp info using node18.19.0 | darwin | arm64 npm ERR! gyp info find Python using Python version 3.12.2 found at "/opt/homebrew/opt/python3.12/…

js实现移动蒙版层

移动蒙版层 可在整个页面拖动方块&#xff0c;但方块不能超出页面 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">…

人工智能算法工程师(中级)课程1-Opencv视觉处理之基本操作

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能算法工程师(中级)课程1-Opencv视觉处理之基本操作。OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习软件库。它提供了各种视觉处理函数&#xff0c;并…

Linux 创建新虚拟机的全过程图解

一、创建新虚拟机 1.选择自定义 2.直接下一步 3.选择稍后安装 4.设置虚拟机名和安装位置 5.配置处理器&#xff08;处理器数量&#xff1a;4、每个处理器的内核&#xff1a;2&#xff09; 6. 内存选择 7.网络类型 8. IO控制器类型-默认推荐 9.磁盘类型-默认推荐 10.选择虚拟磁…

xcode中对项目或者文件文件夹重命名操作

提起揭秘答案&#xff1a;选中文件后&#xff0c;按下回车键就可以了 如果在项目中对新建的文件夹或者文件名称不满意或者输入错误了&#xff0c;想要修改一下名称该怎么办&#xff1f;如果是在文件或文件夹上右键是没有rename选项的&#xff1a; 其实想要重命名&#xff0c;很…