引言
在 UniApp 结合 TypeScript 和 Vue3 的项目开发中,请求拦截器起着至关重要的作用。它能够在请求发送前和响应接收后对数据进行统一处理,极大地提高了代码的可维护性和功能性。本文将详细解析上述代码中请求拦截器的实现及其在 UniApp-Ts-Vue3 项目中的应用,让您更直观地理解其工作流程。
一、请求拦截器的作用
请求拦截器主要用于在请求发送到服务器之前对请求进行预处理,以及在接收到服务器响应后对响应进行后处理。在我们的代码中,它实现了以下几个关键功能:
(一)基础 URL 补全
if (!options.url.startsWith('https://')) {
options.url = baseURLhttp + options.url;
}
当请求的 URL 不是以 https:// 开头时,会自动将基础 URL中的 baseURLhttp(项目中发起https的请求头) (比如:https://11.12.55.666666:8080)拼接在前面。这确保了所有请求都使用正确的基础路径,尤其在开发和上线不同环境切换时,能方便地统一管理请求地址。例如,若我们有一个相对路径的请求 '/api/user',经过拦截器处理后会变为:
'https://11.12.55.666666:8080/api/user'。
(二)设置请求超时
options.timeout = TIMEOUT
设置了请求的超时时间为 TIMEOUT
(30000 毫秒,即 30 秒)。这是一个很重要的设置,防止请求因为网络问题或服务器故障而长时间等待,提升用户体验。当请求超过 30 秒还未得到响应时,会触发 fail
回调,提示用户网络连接失败。
(三)添加小程序端请求头标识
options.header = {
'source - client':'miniapp',
...options.header, // 允许覆盖默认 header
}
在请求头中添加了 source - client
字段,值为 miniapp
,用于标识该请求来自小程序端。同时,通过 ...options.header
保留了原有的请求头信息,这样既添加了自定义标识,又不会影响其他可能需要的请求头设置。例如,在服务器端可以根据这个标识对来自小程序的请求进行特定的处理。
(四)添加 token 请求头标识
const memberStore = useMemberStore()
const token = memberStore.profile?.token
if (token) {
options.header.token = token
}
从状态管理(这里使用的是 Pinia 库的 useMemberStore
)中获取用户的 token
,并将其添加到请求头的 token
字段中。这样,服务器可以通过验证 token
来确认用户身份,实现对受保护资源的访问控制。比如,用户在登录成功后,token
被存储在状态管理中,后续的请求通过拦截器自动带上 token
,无需在每个请求中手动添加。
二、拦截器的实现与注册
(一)拦截器定义
const httpInterceptor = {
// 拦截前触发
invoke(options: UniApp.RequestOptions) {
// 一系列处理逻辑
},
}
这里定义了一个名为 httpInterceptor
的拦截器对象,它包含一个 invoke
方法。invoke
方法在每次请求被拦截时触发,接收一个 options
参数,类型为 UniApp.RequestOptions
,这个参数包含了本次请求的所有配置信息,我们可以在这个方法中对这些配置进行修改。
(二)拦截器注册
uni.addInterceptor('request', httpInterceptor)
uni.addInterceptor('uploadFile', httpInterceptor)
通过 uni.addInterceptor
方法将 httpInterceptor
注册到 request
和 uploadFile
这两种请求类型上。这意味着无论是普通的 request
请求(如获取数据、提交表单等),还是 uploadFile
文件上传请求,都会经过 httpInterceptor
的处理。
三、封装请求函数
(一)函数定义与返回值
type Data<T> = {
categorys(categorys: any): unknown
code: string
msg: string
result: T
}
// 2.2 添加类型,支持泛型
export const http = <T>(options: UniApp.RequestOptions) => {
//1.返回Promise对象
return new Promise<Data<T>>((resolve, reject) => {
// 请求处理逻辑
})
}
定义了一个名为 http
的函数,它是对 uni.request
的封装。通过泛型 <T>
支持不同类型的响应数据。函数返回一个 Promise
对象,这样可以使用 async/await
语法来处理异步请求,使代码更简洁易读。Data<T>
类型定义了响应数据的结构,包含 code
(状态码)、msg
(错误信息)、result
(实际数据,其类型由泛型 T
决定)以及一个 categorys
函数(这里可能是特定业务需求的函数定义,具体功能需根据业务进一步分析)。
(二)请求成功处理
success(res) {
if (res.statusCode >= 200 && res.statusCode < 300) {
// 获取数据成功, 调用resolve
resolve(res.data as Data<T>)
} else if (res.statusCode === 401) {
// 401错误 -> 清理用户信息,跳转到登录页等,具体看你的需求逻辑
handleUnauthorized()
reject(res)
} else {
// 其他HTTP错误 -> 根据后端错误信息轻提示
const errMsg = getHttpErrorMsg(res.statusCode)
uni.showToast({ icon: 'none', title: errMsg })
reject(res)
}
}
在 uni.request
的 success
回调中,首先判断响应状态码 statusCode
。如果状态码在 200 到 299 之间,表示请求成功,将响应数据转换为 Data<T>
类型后调用 resolve
,将数据传递出去。若状态码为 401,表示用户未授权,调用 handleUnauthorized
函数清理用户信息并跳转到登录页面,同时调用 reject
拒绝 Promise,传递错误响应。对于其他状态码,通过 getHttpErrorMsg
函数获取对应的错误信息,并使用 uni.showToast
进行轻提示,然后拒绝 Promise。
(三)请求失败处理
fail(err) {
uni.showToast({
icon: 'none',
title: '网络连接失败',
})
reject(err)
}
当请求失败(如网络故障、超时等),在 fail
回调中使用 uni.showToast
提示用户网络连接失败,并调用 reject
拒绝 Promise,传递错误信息。
四、辅助函数
(一)处理 401 未授权
const handleUnauthorized = () => {
const memberStore = useMemberStore()
memberStore.clearProfile()
uni.navigateTo({ url: '/pages/login/login' })
uni.showToast({ icon: 'none', title: '登录已过期' })
}
handleUnauthorized
函数在用户遇到 401 未授权错误时被调用。它从状态管理中获取 memberStore
,调用 clearProfile
方法清理用户信息,然后使用 uni.navigateTo
跳转到登录页面,并通过 uni.showToast
提示用户登录已过期。
(二)获取 HTTP 错误信息
const getHttpErrorMsg = (code: number): string => {
const messages: Record<number, string> = {
400: '请求参数错误',
403: '禁止访问',
404: '资源不存在',
500: '服务器错误',
502: '网关错误'
}
return messages[code] || `请求失败(${code})`
}
getHttpErrorMsg
函数根据传入的 HTTP 状态码 code
返回对应的错误信息。它通过一个 Record
类型的 messages
对象存储常见状态码对应的错误描述。如果状态码在 messages
中存在,则返回对应的描述;否则返回通用的错误提示 请求失败(${code})
。
五、工作流程示意图
从发起,经过拦截器处理,到服务器响应,再经过拦截器处理返回给调用者的整个流程,包括基础 URL 补全、请求头添加、超时设置等在流程中的位置]
六、总结
通过上述代码和详细解析,我们深入了解了在 UniApp-Ts-Vue3 项目中如何构建和使用请求拦截器。请求拦截器不仅能简化请求处理逻辑,还能统一管理请求相关的配置和错误处理,提升项目的开发效率和稳定性。在实际项目中,我们可以根据具体业务需求进一步扩展和优化拦截器的功能,使其更好地服务于整个应用程序。
最后,附上完整代码:
/**
* 添加拦截器:
* 拦截 request 请求
* 拦截 uploadFile 文件上传
*
* TODO:
* 1. 非 http 开头需拼接地址
* 2. 请求超时
* 3. 添加小程序端请求头标识
* 4. 添加 token 请求头标识
*/
import { useMemberStore } from '@/stores'
const baseURLhttp = 'https://11.12.55.666666:8080'; //后期注意要申请SSL证书,上线小程序的请求必须是https类型的
const TIMEOUT = 30000; // 请求超时 默认 30秒 超时
//request请求拦截
const httpInterceptor = {
// 拦截前触发
invoke(options: UniApp.RequestOptions) {
//1. 补全基础路径(非 https 开头时)
if (!options.url.startsWith('https://')) {
options.url = baseURLhttp + options.url;
}
//设置超时时间
options.timeout = TIMEOUT
//3.添加小程序请求头
options.header = {
'source-client': 'miniapp',
...options.header, // 允许覆盖默认 header
}
//4.自动添加 token 信息到请求头
const memberStore = useMemberStore()
const token = memberStore.profile?.token
if (token) {
options.header.token = token
}
//输出options
// console.log(options)
},
}
// 拦截 request 请求
uni.addInterceptor('request', httpInterceptor)
// 拦截 uploadFile 文件上传
uni.addInterceptor('uploadFile', httpInterceptor)
/**
* 封装请求函数
* @param UniApp.RequestOptions
* @returns Promise
* 1. 返回 Promise 对象
* 2. 获取数据成功
* 2.1 提取核心数据 res.data
* 2.2 添加类型,支持泛型
* 3. 获取数据失败
* 3.1 401错误 -> 清理用户信息,跳转到登录页
* 3.2 其他错误 -> 根据后端错误信息轻提示
* 3.3 网络错误 -> 提示用户换网络
*/
type Data<T> = {
categorys(categorys: any): unknown
code: string
msg: string
result: T
}
// 2.2 添加类型,支持泛型
export const http = <T>(options: UniApp.RequestOptions) => {
//1.返回Promise对象
return new Promise<Data<T>>((resolve, reject) => {
uni.request({
...options,
//2.请求成功
success(res) {
// if (res.statusCode == 0 || (res.statusCode >= 200 && res.statusCode < 300)) {
if (res.statusCode >= 200 && res.statusCode < 300) {
// 获取数据成功, 调用resolve
resolve(res.data as Data<T>)
} else if (res.statusCode === 401) {
// 401错误 -> 清理用户信息,跳转到登录页
handleUnauthorized()
reject(res)
} else {
// 其他HTTP错误 -> 根据后端错误信息轻提示
const errMsg = getHttpErrorMsg(res.statusCode)
uni.showToast({ icon: 'none', title: errMsg })
reject(res)
}
},
// 响应失败
fail(err) {
uni.showToast({
icon: 'none',
title: '网络连接失败',
})
reject(err)
},
})
})
}
/**
* 处理 401 未授权
*/
const handleUnauthorized = () => {
const memberStore = useMemberStore()
memberStore.clearProfile()
uni.navigateTo({ url: '/pages/login/login' })
uni.showToast({ icon: 'none', title: '登录已过期' })
}
/**
* 获取 HTTP 错误信息
*/
const getHttpErrorMsg = (code: number): string => {
const messages: Record<number, string> = {
400: '请求参数错误',
403: '禁止访问',
404: '资源不存在',
500: '服务器错误',
502: '网关错误'
}
return messages[code] || `请求失败(${code})`
}