diff --git a/src/http/http.ts b/src/http/http.ts index 98c7324..bc87f09 100644 --- a/src/http/http.ts +++ b/src/http/http.ts @@ -1,8 +1,18 @@ -import type { CustomRequestOptions } from '@/http/types' +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: (() => void)[] = [] // 刷新 token 请求队列 export function http(options: CustomRequestOptions) { // 1. 返回 Promise 对象 - return new Promise>((resolve, reject) => { + return new Promise((resolve, reject) => { uni.request({ ...options, dataType: 'json', @@ -10,17 +20,77 @@ export function http(options: CustomRequestOptions) { responseType: 'json', // #endif // 响应成功 - success(res) { + success: async (res) => { // 状态码 2xx,参考 axios 的设计 if (res.statusCode >= 200 && res.statusCode < 300) { - // 2.1 提取核心数据 res.data - resolve(res.data as IResData) + // 2.1 处理业务逻辑错误 + const { code, data, message, msg } = res.data as IResponse + // 0和200当做成功都很普遍,这里直接兼容两者,见 ResultEnum + if (code !== ResultEnum.Success0 && code !== ResultEnum.Success200) { + throw new Error(`请求错误[${code}]:${message || msg}`) + } + return resolve(data as T) } - else if (res.statusCode === 401) { - // 401错误 -> 清理用户信息,跳转到登录页 - // userStore.clearUserInfo() - // uni.navigateTo({ url: '/pages/login/login' }) - reject(res) + const resData: IResData = res.data as IResData + 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(http(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 => task()) + } + 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 { // 其他错误 -> 根据后端错误信息轻提示 @@ -106,6 +176,7 @@ export function httpDelete(url: string, query?: Record, header?: }) } +// 支持与 axios 类似的API调用 http.get = httpGet http.post = httpPost http.put = httpPut diff --git a/src/pages/about/components/request-openapi.vue b/src/pages/about/components/request-openapi.vue index 9ce6bae..64fb095 100644 --- a/src/pages/about/components/request-openapi.vue +++ b/src/pages/about/components/request-openapi.vue @@ -6,10 +6,11 @@ const loading = ref(false) const error = ref(null) const data = ref() +// openapi 请求示例 async function getUserInfo() { try { loading.value = true - const res = await (await infoUsingGet({})).promise + const res = await infoUsingGet({}) console.log(res) data.value = res error.value = null @@ -22,7 +23,9 @@ async function getUserInfo() { loading.value = false } } -const { data: data2, loading: loading2, run, cancel } = useRequest(() => listAllUsingGet({}), { + +// openapi + useRequest 请求示例 +const { data: data2, loading: loading2, run } = useRequest(() => listAllUsingGet({}), { immediate: false, }) @@ -50,9 +53,6 @@ const { data: data2, loading: loading2, run, cancel } = useRequest(() => listAll - 请求数据如下 diff --git a/src/pages/about/components/request.vue b/src/pages/about/components/request.vue index f36f759..18f8582 100644 --- a/src/pages/about/components/request.vue +++ b/src/pages/about/components/request.vue @@ -8,14 +8,23 @@ import { getFooAPI } from '@/api/foo' // } const initialData = undefined +// 直接请求示例 +async function reqFooAPI() { + try { + const res = await getFooAPI('菲鸽') + console.log('直接请求示例res', res) + } + catch (err) { + console.log(err) + } +} +reqFooAPI() + +// 直接useRequest请求示例 const { loading, error, data, run } = useRequest(() => getFooAPI('菲鸽'), { immediate: true, initialData, }) -function reqFooAPI() { - run() - cancel() -} function reset() { data.value = initialData @@ -35,12 +44,6 @@ function reset() { - - diff --git a/src/service/listAll.ts b/src/service/listAll.ts index 2caddd9..92ba293 100644 --- a/src/service/listAll.ts +++ b/src/service/listAll.ts @@ -12,7 +12,6 @@ export async function listAllUsingGet({ }: { options?: CustomRequestOptions; }) { - await sleep(2000); // 方便测试 cancel 功能 return request('/user/listAll', { method: 'GET', ...(options || {}), diff --git a/src/store/token.ts b/src/store/token.ts index 36c1349..363dc2b 100644 --- a/src/store/token.ts +++ b/src/store/token.ts @@ -103,7 +103,7 @@ export const useTokenStore = defineStore( */ const login = async (loginForm: ILoginForm) => { try { - const res = await _login(loginForm).promise + const res = await _login(loginForm) console.log('普通登录-res: ', res) await _postLogin(res) uni.showToast({ @@ -133,7 +133,7 @@ export const useTokenStore = defineStore( // 获取微信小程序登录的code const code = await getWxCode() console.log('微信登录-code: ', code) - const res = await _wxLogin(code).promise + const res = await _wxLogin(code) console.log('微信登录-res: ', res) await _postLogin(res) uni.showToast({ @@ -193,7 +193,7 @@ export const useTokenStore = defineStore( } const refreshToken = tokenInfo.value.refreshToken - const res = await _refreshToken(refreshToken).promise + const res = await _refreshToken(refreshToken) console.log('刷新token-res: ', res) setTokenInfo(res) return res diff --git a/src/store/user.ts b/src/store/user.ts index 60e9574..3f6d693 100644 --- a/src/store/user.ts +++ b/src/store/user.ts @@ -42,7 +42,7 @@ export const useUserStore = defineStore( * 获取用户信息 */ const fetchUserInfo = async () => { - const res = await getUserInfo().promise + const res = await getUserInfo() setUserInfo(res) return res }