手写VUE后台管理系统10 - 封装Axios实现异常统一处理

news/2024/5/20 0:50:12 标签: vue3, vite, ts, axios

目录

    • 前后端交互约定
    • 安装
    • 创建Axios实例
    • 拦截器
    • 封装请求方法
    • 业务异常处理


在这里插入图片描述

axios 是一个易用、简洁且高效的http库
axios 中文文档:http://www.axios-js.com/zh-cn/docs/


前后端交互约定

在本项目中,前后端交互统一使用 application/json;charset=UTF-8 的请求方式,后端返回对象统一为如下格式

ts">export interface ResponseBody<T = any> {
    status: boolean,	// 业务处理状态,true表示正常,false表示异常
    code: string		// 业务处理状态码
    message: string,	// 提示信息
    data?: T			// 业务处理返回数据
}

安装

yarn add axios

创建Axios实例

src 目录下创建 http 目录,http 请求相关的文件都放置于该目录

创建 axios.ts 文件,用于定义 axios

ts">// axios.ts
const instance: AxiosInstance = axios.create({
    baseURL: import.meta.env.VITE_APP_BASE_API,
    timeout: 60000,
    headers: { 'Content-Type': 'application/json;charset=UTF-8' },
})

其中,import.meta.env.VITE_APP_BASE_API.env 中配置的环境变量,不同环境使用不同的请求地址

拦截器

通过 instance.interceptors.request.use 实现前置拦截器,发起请求前执行,用于对请求对象进行加工处理。

下面这段代码主要做的事情就是将 token 设置到请求头中。

ts">// axios.ts
async function requestHandler(config: InternalAxiosRequestConfig & RequestConfigExtra): Promise<InternalAxiosRequestConfig> {
    if (config.modulePrefix) {
        config.url = config.modulePrefix + config.url
    }
    const token = useAuthorization()
    if (token.value && config.token !== false) {
        config.headers.set("Authorization", token.value)
    }
    console.log("execute http request:" + config.url)
    return config
}

instance.interceptors.request.use(requestHandler)

RequestConfigExtra 为自定义参数,在本项目中定义如下,可根据需要自行扩展。

ts">export interface RequestConfigExtra {
    // 模块前缀
    modulePrefix?: string,
    // 发起请求时,是否需要在请求头中附加 token
    token?: boolean,
    // 成功处理函数,默认为 false,如果为 false,则什么都不做,如果为 true,则自动提示成功信息,如果为 Function,则自定义处理结果
    success?: boolean | ((response: ResponseBody<any>) => void),
    // 失败处理函数,默认为 true,如果为 false,则什么都不做,如果为 true,则自动提示失败信息,如果为 Function,则自定义处理结果
    error?: boolean | ((response: ResponseBody<any>) => void),
}

通过 instance.interceptors.response.use 实现后置拦截器,对后端返回数据进行处理。

ts">function responseHandler(response: any): ResponseBody<any> | AxiosResponse<any> | Promise<any> | any {
    return response.data
}

function errorHandler(errorInfo: AxiosError): Promise<any> {
    if (errorInfo.response) {
        const { data, status, statusText } = errorInfo.response as AxiosResponse<ResponseBody>
        if (status === 401) {
            const token = useAuthorization()
            token.value = null
            message.error(data?.message || statusText)
            router.push({path: '/login', query: { 
                redirect: router.currentRoute.value.fullPath
            }})
        } else {
            message.error(data?.message || statusText)
        } 
    }
    return Promise.reject(errorInfo)
}

instance.interceptors.response.use(responseHandler, errorHandler)

errorHandler 方法对系统异常进行了统一处理,如果后端返回的 status 不是 200,则会执行该处理方法,如果返回 401,表示没有通过登录鉴权,自动跳转登录页,如果是其它异常码,则提示错误信息。

封装请求方法

这里对 restful 常用的四种请求方式进行进一步的封装

ts">// axios.ts
function instancePromise<R = any, T = any>(options: AxiosRequestConfig<T> & RequestConfigExtra): Promise<ResponseBody<R>> {
    return new Promise((resolve, reject) => {
        instance.request<any, ResponseBody<R>>(options)
            .then((res) => {
                try {
                    resolve(responseBodyHandle(res, options))
                } catch (err) {
                    reject(err || new Error('response handle error!'))
                }
            })
            .catch((e: Error | AxiosError) => {
                reject(e)
            })
    })
}

export function doGet<R = any, T = any>(url: string, params?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        params,
        method: RequestEnum.GET,
        ...config,
    }
    return instancePromise<R, T>(options)
}

export function doPost<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        data,
        method: RequestEnum.POST,
        ...config,
    }
    return instancePromise<R, T>(options)
}

export function doPut<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        data,
        method: RequestEnum.PUT,
        ...config,
    }
    return instancePromise<R, T>(options)
}

export function doDelete<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        data,
        method: RequestEnum.DELETE,
        ...config,
    }
    return instancePromise<R, T>(options)
}

业务异常处理

在拦截器中,我们对系统异常进行了统一处理,在实际项目中,更多的情况是前端请求没有通过后端的业务校验,后端返回错误信息,前端进行提示。

创建一个业务异常类

ts">export class ResponseBodyError extends Error {
    code: string
    message: string
    cause: any

    constructor({ code, message, cause }: { code: string, message: string, cause?: any }) {
        super();
        this.code = code;
        this.message = message;
        this.cause = cause
    }
}

实现业务异常统一处理,通过在请求时定义 RequestConfigExtra 中的参数来实现对后端返回结果的提示。

比如:查询请求,设置为 {success:false, error: true},如果请求失败,提示错误信息,如果请求成功,不作处理。业务请求,设置为 {success: true, error: true},如果请求失败,提示错误信息,如果处理成功,提示操作成功。

ts">function responseBodyHandle<R = any, T = any>(response: ResponseBody<R>, options: AxiosRequestConfig<T> & RequestConfigExtra): any {
    const { status, message:msg, code, data } = response
    const { success, error } = options
    if (status === true) {
        if (success === true) {
            message.success(msg ?? "操作成功")
        } else if (isFunction(success)) {
            success(response)
        }
        return response
    } else {
        if (isFunction(error)) {
            error(response)
        } else if (error !== false) {
            message.error(msg ?? "操作失败")
        }
        throw new ResponseBodyError({ code, msg })
    }
}

完整代码如下:

ts">// axios.ts
/**
 * 创建 Axios 实例
 */
const instance: AxiosInstance = axios.create({
    baseURL: import.meta.env.VITE_APP_BASE_API,
    timeout: 60000,
    headers: { 'Content-Type': 'application/json;charset=UTF-8' },
})

/**
 * 前置拦截器
 */
async function requestHandler(config: InternalAxiosRequestConfig & RequestConfigExtra): Promise<InternalAxiosRequestConfig> {
    if (config.modulePrefix) {
        config.url = config.modulePrefix + config.url
    }
    const token = useAuthorization()
    if (token.value && config.token !== false) {
        config.headers.set(authorizationHeader, authorizationValue())
    }
    console.log("execute http request:" + config.url)
    return config
}

/**
 * 后置拦截器
 */
function responseHandler(response: any): ResponseBody<any> | AxiosResponse<any> | Promise<any> | any {
    return response.data
}

/**
 * 系统异常统一处理函数
 */
function errorHandler(errorInfo: AxiosError): Promise<any> {
    if (errorInfo.response) {
        const { data, status, statusText } = errorInfo.response as AxiosResponse<ResponseBody>
        if (status === 401) {
            const token = useAuthorization()
            token.value = null
            message.error(data?.message || statusText)
            router.push({path: '/login', query: { 
                redirect: router.currentRoute.value.fullPath
            }})
        } else {
            message.error(data?.message || statusText)
        } 
    }
    return Promise.reject(errorInfo)
}

instance.interceptors.request.use(requestHandler)
instance.interceptors.response.use(responseHandler, errorHandler)

/**
 * 业务异常统一处理函数
 */
function responseBodyHandle<R = any, T = any>(response: ResponseBody<R>, options: AxiosRequestConfig<T> & RequestConfigExtra): any {
    const { status, message:msg, code, data } = response
    const { success, error } = options
    if (status === true) {
        if (success === true) {
            message.success(msg ?? "操作成功")
        } else if (isFunction(success)) {
            success(response)
        }
        return response
    } else {
        if (isFunction(error)) {
            error(response)
        } else if (error !== false) {
            message.error(msg ?? "操作失败")
        }
        throw new ResponseBodyError({ code, msg })
    }
}

function instancePromise<R = any, T = any>(options: AxiosRequestConfig<T> & RequestConfigExtra): Promise<ResponseBody<R>> {
    return new Promise((resolve, reject) => {
        instance.request<any, ResponseBody<R>>(options)
            .then((res) => {
                try {
                    resolve(responseBodyHandle(res, options))
                } catch (err) {
                    reject(err || new Error('response handle error!'))
                }
            })
            .catch((e: Error | AxiosError) => {
                reject(e)
            })
    })
}

export function doGet<R = any, T = any>(url: string, params?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        params,
        method: RequestEnum.GET,
        ...config,
    }
    return instancePromise<R, T>(options)
}

export function doPost<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        data,
        method: RequestEnum.POST,
        ...config,
    }
    return instancePromise<R, T>(options)
}

export function doPut<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        data,
        method: RequestEnum.PUT,
        ...config,
    }
    return instancePromise<R, T>(options)
}

export function doDelete<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
    const options = {
        url,
        data,
        method: RequestEnum.DELETE,
        ...config,
    }
    return instancePromise<R, T>(options)
}

http://www.niftyadmin.cn/n/5255375.html

相关文章

DevEco Studio IDE 创建项目时候配置环境

DevEco Studio IDE 创建项目时候配置环境 一、安装环境 操作系统: Windows 10 专业版 IDE:DevEco Studio 3.1 SDK:HarmonyOS 3.1 二、在配置向导的时候意外关闭配置界面该如何二次配置IDE环境。 打开IDE的界面是这样的。 点击Create Project进行环境配置。 点击OK后出现如…

第一章 Django基本使用

第一章 Django 基本使用 第二章 Django URL路由系统 第三章 Django 视图系统 第四章 Django 模板系统 第五章 Django 数据模型系统(基本使用) 第六章 Django 数据模型系统(多表操作) 第七章 Django 用户认证与会话技术 第八章 Django CSRF防护 文章目录 Django介绍django是什么…

win10脚本 | 使用 Word 自动化对象模型找出指定路径下含有特定内容的.docx

场景 今年的实验日志被我放在这样一个文件夹下&#xff0c;每个月下是每天具体的.docx文件&#xff0c;里面记录了我的一些实验操作步骤。现在我需要补充一个实验&#xff0c;用到一个名为chatunitest的插件&#xff0c;但是这是很久之前做的事情了&#xff0c;我无法判断是哪…

电脑和手机中的日历提醒怎么进行同步

作为一名忙碌的现代人&#xff0c;我常常需要在电脑和手机上记录各种日程和提醒。然而&#xff0c;我发现电脑和手机“日历提醒无法同步”是一个令人头疼的问题。如果我在电脑中添加了一个提醒&#xff0c;但是我没有把它同步到我的手机上&#xff0c;那么当我离开电脑时&#…

探索 HTML 语义化:让你的网页更有意义(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Mysql8和Oracle实际项目中递归查询树形结构

背景&#xff1a; 项目升级&#xff0c;引入MySQL数据库&#xff0c;之前一直用的是Oracle数据&#xff0c;在做用户登录单位维护的时候&#xff0c;需要返回该用户所属单位下的所有子单位。下边是模拟项目数据实践的过程。 数据准备&#xff1a; 准备一张单位表&#xff0c…

C++学习笔记(十一)------has_a和use_a关系

文章目录 前言 一、has_a关系 1.1 has_a概念 1.2 has_a中构造和析构的顺序 1.3 has_a对象的内存情况 二、use_a关系&#xff08;友元关系&#xff09; 1.友元函数&#xff1a; 2.友元类 3 使用多文件编程的方式重新编辑上述代码 总结 前言 随着技术的革新&#xff0c;出现各种各…

Spring-Java配置版本

依赖 <!--spring-context涵盖了aop,beans,core,expression--> <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.18</version> </dependency>实体类 // Comp…