当前位置: 首页 > article >正文

TS二次封装axios学习总结

1.put请求

put请求一般用的很少,主要有两点区别。

1.PUT请求:如果两个请求相同,后一个请求会把第一个请求覆盖掉。(所以PUT用来改资源)
Post请求:后一个请求不会把第一个请求覆盖掉。(所以Post用来增资源)
2.PUT通常指定了资源的存放位置,而POST则没有,POST的数据存放位置由服务器自己决定。

2.ts内置高级对象Record

用法:getHourCheckinList(params?: Record<string, any>){}
Record 是 TS 内置的一个高级类型,是通过映射类型的语法来生成索引类型的,Record<string, any> 也就是 key 为 string 类型,value 为任意类型的索引类型,可以代替 object 来用,用来指定object内部的key value的数据类型。
其他高级内置对象

http.ts 二次封装axios:

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import { ElMessage } from 'element-plus'
import { loginApi } from '@/apis'
import type { ApiResponse, RefreshParams, RefreshData, HttpResponse } from '../../@types/api'

// 定义全局请求配置接口
interface HttpClientOptions {
  baseURL?: string
  headers?: Record<string, string>
  timeout?: number
}

// 定义特殊错误码的处理类型
type CodeHandler = (error: ApiResponse['err']) => void

// 定义重试配置接口
interface RetryConfig {
  maxRetries: number;          // 最大重试次数
  retryDelay: number;          // 重试延迟时间
  retryAbleStatus: number[];   // 可重试的状态码
}

// 通用请求类
class HttpClient {
  private instance: AxiosInstance
  private isRefreshing = false // 用于防止同时刷新多个 token
  private refreshSubscribers: Array<(token: string) => void> = []
  private codeHandlers: Record<number, CodeHandler> = {} // 用于存储特殊错误码的处理器
  private customHeaders: Record<string, string | number> = {} // 存储自定义请求头
  private retryConfig: RetryConfig = {
    maxRetries: 3,
    retryDelay: 1000,
    retryAbleStatus: [408, 500, 502, 503, 504]
  }

  // 添加设置自定义请求头的方法
  public setCustomHeaders(headers: Record<string, string | number>) {
    this.customHeaders = { ...this.customHeaders, ...headers };
  }

  constructor(options: HttpClientOptions) {
    this.instance = axios.create({
      baseURL: options.baseURL,
      headers: options.headers || {},       // 默认请求头
      timeout: options.timeout || 15000,     // 请求超时时间(默认10秒)
      withCredentials: false // 不携带 cookie,避免 CORS 问题
    })

    // 请求拦截器
    this.instance.interceptors.request.use(
      (config: InternalAxiosRequestConfig) => {
        // 获取 token
        const token = localStorage.getItem('token')
        // 如果有 token,添加到请求头
        if (token) {
          config.headers.Authorization = `${token}`
        }

        // 添加通用请求头
        config.headers['Content-Type'] = 'application/json'
        config.headers.Accept = 'application/json'

        // 从 localStorage 获取 tenant-id 和 hotel-id
        const tenantId = localStorage.getItem('tenant-id')
        const hotelId = localStorage.getItem('hotel-id')
        
        if (tenantId) {
            config.headers['tenant-id'] = tenantId
        }
        if (hotelId) {
            config.headers['hotel-id'] = hotelId
        }

        // 添加自定义请求头
        Object.assign(config.headers, this.customHeaders)
        // 登录请求不需要带 trace-id
        if (config.url?.includes('/api/admin/login')) {
          return config
        }

        // 从本地缓存获取 Trace-Id
        const traceId = localStorage.getItem('trace-id')
        if (traceId) {
          config.headers['trace-id'] = traceId
        }

        return config
      },
      (error) => {
        ElMessage.error(error)
        return Promise.reject(error)
      }
    )

    // 响应拦截器
    this.instance.interceptors.response.use(
      (response: AxiosResponse<ApiResponse>) => {
        const apiResponse = response.data;

        // 保存新的 Trace-Id
        const newTraceId = response.headers['trace-id']
        if (newTraceId) {
          localStorage.setItem('trace-id', newTraceId)
        }

        // 查 API 响应的状态并处理
        if (!apiResponse.ok) {
          // 如果需要处理 API 错误,以在这里加入逻辑
          ElMessage.error(response.data.err?.msg || '操作失败')
          return Promise.reject(apiResponse.err);
        }

        // 返回原始的 AxiosResponse 类型,包裹 apiResponse
        return {
          ...response,
          data: apiResponse, // 保持 data 字段为 ApiResponse 类型
        };
      },
      async (error) => {
        // 即使请求失败也要保存 Trace-Id
        const newTraceId = error.response?.headers?.['trace-id']
        if (newTraceId) {
          localStorage.setItem('trace-id', newTraceId)
        }

        const { response } = error;

        if (response?.status === 401) {
          return this.handle401Error(error);
        }

        console.error('请求错误:', response?.data || error.message);
        return Promise.reject(error);
      }
    );
  }

  // 注册特殊错误码的处理器
  public registerCodeHandler(code: number, handler: CodeHandler) {
    this.codeHandlers[code] = handler
  }

  // 处理 401 错误
  private async handle401Error(originalError: any) {
    if (!this.isRefreshing) {
      this.isRefreshing = true
      try {
        // 从 localStorage 取刷新令牌相关信息
        const userId = parseInt(localStorage.getItem('userId') || '0')
        const refreshToken = localStorage.getItem('refreshToken')

        if (!userId || !refreshToken) {
          throw new Error('刷新令牌信息不存在')
        }

        // 调用刷新令牌接口
        const refreshParams: RefreshParams = {
          user_id: userId,
          refresh_token: refreshToken
        }
        const res = await loginApi.refreshToken(refreshParams) as HttpResponse<RefreshData>

        if (res.data.ok) {
          // 更新存储的令牌
          const { token, refresh_token } = res.data.data
          localStorage.setItem('token', token)
          localStorage.setItem('refreshToken', refresh_token)

          // 通知所有等待的请求
          this.refreshSubscribers.forEach(callback => callback(token))
          this.refreshSubscribers = []

          // 重试原始请求
          return this.retryOriginalRequest(originalError, token)
        } else {
          throw new Error(res.data.err?.msg || '刷新令牌失败')
        }
      } catch (error) {
        // 清除所有认证信息
        localStorage.removeItem('token')
        localStorage.removeItem('refreshToken')
        localStorage.removeItem('userId')
        
        // 显示错误消息
        ElMessage.error('登录已过期,请重新登录')
        
        // 跳转到登录页
        window.location.href = '/login';
        return Promise.reject(error)
      } finally {
        this.isRefreshing = false
      }
    } else {
      // 如果已经在刷新中,将请求加入队列
      return new Promise((resolve) => {
        this.refreshSubscribers.push((token: string) => {
          resolve(this.retryOriginalRequest(originalError, token))
        })
      })
    }
  }

  // 通用重试机制
  private async retryRequest(config: AxiosRequestConfig, retryCount = 0): Promise<any> {
    try {
      return await this.instance.request(config)
    } catch (error: any) {
      // 判断是否可以重试
      const canRetry = this.retryConfig.retryAbleStatus.includes(error.response?.status)
        && retryCount < this.retryConfig.maxRetries;

      if (canRetry) {
        // 计算延迟时间
        const delay = this.retryConfig.retryDelay * Math.pow(2, retryCount)
        console.log(`请求失败,${delay}ms 后进行第 ${retryCount + 1} 次重试`)
        // 等待延迟时间
        await new Promise(resolve => setTimeout(resolve, delay))

        // 递归重试
        return this.retryRequest(config, retryCount + 1)
      }

      // 不能重试则抛出错误
      throw error
    }
  }

  // token 刷新重试机制
  private retryOriginalRequest(error: any, token: string) {
    const config = error.config
    config.headers.Authorization = `${token}`
    return this.instance.request(config)
  }

  public get<T>(url: string, params?: Record<string, any>, config?: AxiosRequestConfig): Promise<T> {
    return this.instance.get(url, { params, ...config })
  }


  // POST 请求
  public post<T>(url: string, data?: Record<string, any>, config?: AxiosRequestConfig): Promise<T> {
    return this.instance.post(url, data, config)
  }

  // PUT 请求
  public put<T>(url: string, data?: Record<string, any>, config?: AxiosRequestConfig): Promise<T> {
    return this.instance.put(url, data, config)
  }

  // DELETE 请求
  public delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    return this.instance.delete(url, config)
  }

  // PATCH 请求
  public patch<T>(url: string, data?: Record<string, any>, config?: AxiosRequestConfig): Promise<T> {
    return this.instance.patch(url, data, config)
  }
}

export default HttpClient

api.ts 统一设置请求头

import HttpClient from './http'
// 根据环境变量设置基础 URL
const baseURL = import.meta.env.VITE_API_URL

const apiClient = new HttpClient({
    baseURL,
    timeout: 15000,  // 添加超时设置
    headers: {      // 添加通用请求头
        'Content-Type': 'application/json'
    },
})

// 添加设置请求头的方法
export const setApiHeaders = (tenantId: string | number, hotelId: string | number) => {
    // 保存到本地存储
    localStorage.setItem('tenant-id', tenantId.toString());
    localStorage.setItem('hotel-id', hotelId.toString());

    // 设置到请求头
    apiClient.setCustomHeaders({
        'tenant-id': tenantId,
        'hotel-id': hotelId
    });
}

export { apiClient }


http://www.kler.cn/a/563639.html

相关文章:

  • 开源模型应用落地-DeepSeek-R1-Distill-Qwen-7B-Docker助力-模型部署 “光速” 指南
  • Linux下原子操作`__atomic_store_n`和`__atomic_load_n`的消耗问题
  • 验证码介绍及生成与验证(HTML + JavaScript实现)
  • Python爬取某云热歌榜:解析动态加载的歌曲数据
  • AI安全相关漏洞
  • 朴素贝叶斯分类
  • Maven+SSM+SpringBoot+Mybatis-Plus
  • 50周学习go语言:第四周 函数与错误处理深度解析
  • Python 网络爬虫实战全解析:案例驱动的技术探索
  • python编写liunx服务器登陆自动巡检脚本
  • Faster-RCNN与DETR:遥感影像目标检测的对比与选择
  • React加TypeScript最新部署完整版
  • 火语言RPA--Excel获取Sheet页列表
  • Redis 基本数据类型及其适用场景与案例
  • Yalmip工具箱(2)——建模
  • 知识图谱的自主学习技术:从数据驱动到知识驱动
  • 面试八股文--数据库基础知识总结(2) MySQL
  • 【LeetCode 热题100】 240. 搜索二维矩阵 II的算法思路及python代码
  • SQL_优化
  • 深度剖析数据中台架构图,铸造数字文明的基石