Files
aiot-uniapp/src/http/http.ts

196 lines
6.5 KiB
TypeScript
Raw Normal View History

import type { IDoubleTokenRes } from '@/api/types/login'
import type { CustomRequestOptions, IResponse } from '@/http/types'
import { nextTick } from 'vue'
import { LOGIN_PAGE } from '@/router/config'
import { useTokenStore } from '@/store/token'
import { isDoubleTokenMode } from '@/utils'
import { ResultEnum } from './tools/enum'
// 刷新 token 状态管理
let refreshing = false // 防止重复刷新 token 标识
let taskQueue: { resolve: (value: any) => void, reject: (reason?: any) => void, options: CustomRequestOptions }[] = [] as { resolve: (value: any) => void, reject: (reason?: any) => void, options: CustomRequestOptions }[] // 刷新 token 请求队列
export function http<T>(options: CustomRequestOptions) {
let requestTask: UniApp.RequestTask | undefined
const promise = new Promise<T>((resolve, reject) => {
requestTask = uni.request({
2023-12-23 11:39:46 +08:00
...options,
2024-02-01 15:21:36 +08:00
dataType: 'json',
2024-03-07 19:09:34 +08:00
// #ifndef MP-WEIXIN
2024-02-01 15:21:36 +08:00
responseType: 'json',
2024-03-07 19:09:34 +08:00
// #endif
2023-12-23 11:39:46 +08:00
// 响应成功
success: async (res) => {
2023-12-23 11:39:46 +08:00
// 状态码 2xx参考 axios 的设计
if (res.statusCode >= 200 && res.statusCode < 300) {
// 2.1 处理业务逻辑错误
const { code, message, msg, data } = res.data as IResponse<T>
// 0和200当做成功都很普遍这里直接兼容两者见 ResultEnum
if (code !== ResultEnum.Success0 && code !== ResultEnum.Success200) {
throw new Error(`请求错误[${code}]${message || msg}`)
}
return resolve(data as T)
}
const resData: IResData<T> = res.data as IResData<T>
if ((res.statusCode === 401) || (resData.code === 401)) {
const tokenStore = useTokenStore()
if (!isDoubleTokenMode) {
// 未启用双token策略清理用户信息跳转到登录页
tokenStore.logout()
uni.navigateTo({ url: LOGIN_PAGE })
return reject(res)
}
/* -------- 无感刷新 token ----------- */
const { refreshToken } = tokenStore.tokenInfo as IDoubleTokenRes || {}
// token 失效的,且有刷新 token 的,才放到请求队列里
if ((res.statusCode === 401 || resData.code === 401) && refreshToken) {
taskQueue.push({ resolve, reject, options })
}
// 如果有 refreshToken 且未在刷新中,发起刷新 token 请求
if ((res.statusCode === 401 || resData.code === 401) && refreshToken && !refreshing) {
refreshing = true
try {
// 发起刷新 token 请求(使用 store 的 refreshToken 方法)
await tokenStore.refreshToken()
// 刷新 token 成功
refreshing = false
nextTick(() => {
// 关闭其他弹窗
uni.hideToast()
uni.showToast({
title: 'token 刷新成功',
icon: 'none',
})
})
// 将任务队列的所有任务重新请求
taskQueue.forEach((task) => {
http<T>(task.options).promise.then(task.resolve, task.reject)
})
}
catch (refreshErr) {
console.error('刷新 token 失败:', refreshErr)
refreshing = false
// 刷新 token 失败,跳转到登录页
nextTick(() => {
// 关闭其他弹窗
uni.hideToast()
uni.showToast({
title: '登录已过期,请重新登录',
icon: 'none',
})
})
// 清除用户信息
await tokenStore.logout()
// 跳转到登录页
setTimeout(() => {
uni.navigateTo({ url: LOGIN_PAGE })
}, 2000)
}
finally {
// 不管刷新 token 成功与否,都清空任务队列
taskQueue = []
}
}
}
else {
2023-12-23 11:39:46 +08:00
// 其他错误 -> 根据后端错误信息轻提示
!options.hideErrorToast
&& uni.showToast({
icon: 'none',
title: (res.data as IResData<T>).msg || '请求错误',
})
2023-12-23 11:39:46 +08:00
reject(res)
}
},
// 响应失败
fail(err: UniApp.RequestSuccessCallbackResult | UniApp.GeneralCallbackResult) {
console.log(`🚀 - fail - err:`, err)
// 如果是请求取消,则不显示错误提示
if (err.errMsg === 'request:fail abort') {
return reject(new Error('Request cancelled'))
}
2023-12-23 11:39:46 +08:00
uni.showToast({
icon: 'none',
title: '网络错误,换个网络试试',
})
reject(err)
},
})
})
return { promise, requestTask: requestTask! }
2023-12-23 11:39:46 +08:00
}
2024-05-04 23:29:21 +08:00
2024-05-03 14:53:11 +08:00
/**
* GET
* @param url
* @param query query参数
2025-04-12 14:05:58 +08:00
* @param header json格式
2024-05-03 14:53:11 +08:00
* @returns
*/
export function httpGet<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
2024-05-03 14:53:11 +08:00
return http<T>({
url,
query,
method: 'GET',
2025-04-12 11:02:48 +08:00
header,
...options,
2024-05-03 14:53:11 +08:00
})
}
2023-12-23 11:39:46 +08:00
2024-05-03 14:53:11 +08:00
/**
* POST
* @param url
* @param data body参数
* @param query query参数post请求也支持query
2025-04-12 14:05:58 +08:00
* @param header json格式
2024-05-03 14:53:11 +08:00
* @returns
*/
export function httpPost<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
2024-05-03 14:53:11 +08:00
return http<T>({
url,
query,
data,
method: 'POST',
2025-04-12 11:02:48 +08:00
header,
...options,
2024-03-27 17:20:05 +08:00
})
}
/**
* PUT
*/
export function httpPut<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
return http<T>({
url,
data,
query,
method: 'PUT',
header,
...options,
})
}
/**
* DELETE query
*/
export function httpDelete<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
return http<T>({
url,
query,
method: 'DELETE',
header,
...options,
})
}
2024-05-04 23:29:21 +08:00
// 支持与 axios 类似的API调用
2024-05-04 23:29:21 +08:00
http.get = httpGet
http.post = httpPost
http.put = httpPut
http.delete = httpDelete
// 支持与 alovaJS 类似的API调用
http.Get = httpGet
http.Post = httpPost
http.Put = httpPut
http.Delete = httpDelete