From e857c008b4a65a27f88747d44e1ab83828a37255 Mon Sep 17 00:00:00 2001 From: FengHui Date: Fri, 15 May 2026 14:16:31 +0800 Subject: [PATCH] 11 --- pages/login/id-card-login.vue | 4 + pages/login/sms-verify.vue | 8 +- pages/login/wx-login.vue | 8 ++ stores/useUserStore.js | 84 ++++++++++++++++++++ utils/request.js | 140 +++++++++++++++++++++++----------- 5 files changed, 197 insertions(+), 47 deletions(-) diff --git a/pages/login/id-card-login.vue b/pages/login/id-card-login.vue index 8525908..e1ea427 100644 --- a/pages/login/id-card-login.vue +++ b/pages/login/id-card-login.vue @@ -61,6 +61,10 @@ const handleIdCardLogin = async (idCard) => { if (res.token) { // 登录成功,存储token并获取用户信息 + if (res.refreshToken) { + userStore.setRefreshToken(res.refreshToken) + } + userStore.setLoginTime(Date.now()) await userStore.loginSetToken(res.token) // 获取用户详细信息 diff --git a/pages/login/sms-verify.vue b/pages/login/sms-verify.vue index 5ce1577..a6f9756 100644 --- a/pages/login/sms-verify.vue +++ b/pages/login/sms-verify.vue @@ -336,8 +336,12 @@ const submitVerification = async () => { const res = await $api.createRequest('/app/appLoginPhone', requestParams, 'post'); if (res.token && res.code === 200) { - // 登录成功,存储token - await userStore.loginSetToken(res.token).then((resume) => { + // 登录成功,存储token + if (res.refreshToken) { + userStore.setRefreshToken(res.refreshToken) + } + userStore.setLoginTime(Date.now()) + await userStore.loginSetToken(res.token).then((resume) => { // 更新用户类型到缓存 if (res.isCompanyUser !== undefined) { const userInfo = uni.getStorageSync('userInfo') || {}; diff --git a/pages/login/wx-login.vue b/pages/login/wx-login.vue index 2d45d61..6682767 100644 --- a/pages/login/wx-login.vue +++ b/pages/login/wx-login.vue @@ -373,6 +373,10 @@ const wxLogin = () => { // 调用后端接口进行登录 $api.createRequest('/app/appLogin', loginParams, 'post').then((resData) => { if (resData.token) { + if (resData.refreshToken) { + userStore.setRefreshToken(resData.refreshToken) + } + userStore.setLoginTime(Date.now()) userStore.loginSetToken(resData.token).then((resume) => { // 更新用户类型到缓存 if (resData.isCompanyUser !== undefined) { @@ -442,6 +446,10 @@ const testLogin = () => { $api.createRequest('/app/login', params, 'post').then((resData) => { uni.hideLoading(); + if (resData.refreshToken) { + userStore.setRefreshToken(resData.refreshToken) + } + userStore.setLoginTime(Date.now()) userStore.loginSetToken(resData.token).then((resume) => { // 更新用户类型到缓存 if (resData.isCompanyUser !== undefined) { diff --git a/stores/useUserStore.js b/stores/useUserStore.js index 9388388..23f0eb8 100644 --- a/stores/useUserStore.js +++ b/stores/useUserStore.js @@ -49,9 +49,12 @@ const useUserStore = defineStore("user", () => { const userInfo = ref({}); const role = ref({}); const token = ref('') + const refreshToken = ref('') + const loginTime = ref(0) const resume = ref({}) const Completion = ref('0%') const seesionId = ref(uni.getStorageSync('seesionId') || '') + const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1000 const login = (value) => { hasLogin.value = true; @@ -67,11 +70,15 @@ const useUserStore = defineStore("user", () => { const logOut = (showLoginModal = true) => { hasLogin.value = false; token.value = '' + refreshToken.value = '' + loginTime.value = 0 resume.value = {} userInfo.value = {} role.value = {} uni.removeStorageSync('userInfo') uni.removeStorageSync('token') + uni.removeStorageSync('refreshToken') + uni.removeStorageSync('loginTime') uni.removeStorageSync('Padmin-Token') // 如果需要显示登录弹窗,则通过事件通知页面显示微信登录弹窗 // if (showLoginModal) { @@ -122,6 +129,73 @@ const useUserStore = defineStore("user", () => { return getUserResume() } + const setRefreshToken = (refreshTokenVal) => { + refreshToken.value = refreshTokenVal + uni.setStorageSync('refreshToken', refreshTokenVal) + } + + const setLoginTime = (time) => { + loginTime.value = time + uni.setStorageSync('loginTime', time.toString()) + } + + const isLoginExpired = () => { + if (!loginTime.value) { + const storedTime = uni.getStorageSync('loginTime') + if (storedTime) { + loginTime.value = parseInt(storedTime) + } + } + if (!loginTime.value) return true + return Date.now() - loginTime.value > SEVEN_DAYS + } + + const refreshAccessToken = async () => { + if (isLoginExpired()) { + logOut(false) + return Promise.reject(new Error('登录已过期')) + } + + const storedRefreshToken = uni.getStorageSync('refreshToken') || refreshToken.value + if (!storedRefreshToken) { + logOut(false) + return Promise.reject(new Error('没有刷新token')) + } + + return new Promise((resolve, reject) => { + uni.request({ + url: 'http://ks.zhaopinzao8dian.com/api/ks/refreshToken', + method: 'POST', + data: { + refreshToken: storedRefreshToken + }, + success: (resData) => { + if (resData.statusCode === 200) { + const { code, msg, token: newToken } = resData.data + if (code === 200 && newToken) { + token.value = newToken + uni.setStorageSync('token', newToken) + resolve(newToken) + } else if (code === 401) { + logOut(false) + reject(new Error(msg || '认证失败')) + } else { + logOut(false) + reject(new Error(msg || '刷新token失败')) + } + } else { + logOut(false) + reject(new Error('网络请求失败')) + } + }, + fail: (err) => { + logOut(false) + reject(err) + } + }) + }) + } + const setUserInfo = (values) => { userInfo.value = values.data; resume.value = values.data; // 将用户信息同时存储到resume中 @@ -156,10 +230,14 @@ const useUserStore = defineStore("user", () => { const restoreUserInfo = () => { const cachedUserInfo = uni.getStorageSync('userInfo'); const cachedToken = uni.getStorageSync('token'); + const cachedRefreshToken = uni.getStorageSync('refreshToken'); + const cachedLoginTime = uni.getStorageSync('loginTime'); if (cachedUserInfo && cachedToken) { userInfo.value = cachedUserInfo; resume.value = cachedUserInfo; token.value = cachedToken; + refreshToken.value = cachedRefreshToken || ''; + loginTime.value = cachedLoginTime ? parseInt(cachedLoginTime) : 0; hasLogin.value = true; Completion.value = getResumeCompletionPercentage(cachedUserInfo); return true; @@ -172,10 +250,16 @@ const useUserStore = defineStore("user", () => { hasLogin, userInfo, token, + refreshToken, + loginTime, resume, login, logOut, loginSetToken, + setRefreshToken, + setLoginTime, + isLoginExpired, + refreshAccessToken, getUserResume, initSeesionId, seesionId, diff --git a/utils/request.js b/utils/request.js index e7f7f12..ac5db72 100644 --- a/utils/request.js +++ b/utils/request.js @@ -2,6 +2,9 @@ import config from "@/config.js" import { sm4Encrypt, sm4Decrypt } from '@/utils/crypto'; import useUserStore from '@/stores/useUserStore'; +let isRefreshing = false; +let refreshSubscribers = []; + const needToEncryptSet = new Set([ 'POST:/app/login', 'GET:/app/user/resume', @@ -36,6 +39,20 @@ const encryptPathPrefixes = [ '/refreshToken', ]; +const addRefreshSubscriber = (callback) => { + refreshSubscribers.push(callback); +}; + +const notifyRefreshSubscribers = (token) => { + refreshSubscribers.forEach((callback) => callback(token)); + refreshSubscribers = []; +}; + +const resetRefreshState = () => { + isRefreshing = false; + refreshSubscribers = []; +}; + const noEncryptSet = new Set([ 'DELETE:/app/job/applyJobCencal', ]); @@ -172,56 +189,89 @@ export function createRequest(url, data = {}, method = 'GET', loading = false, h header["Authorization"] = Authorization; } const requestData = isEncryptNeeded(method, url) ? encryptRequestData(data) : data; - return new Promise((resolve, reject) => { - uni.request({ - url: config.baseUrl + url, - method: method, - data: requestData, - header, - success: resData => { - // 响应拦截 - if (resData.statusCode === 200) { - const responseData = handleResponseData(resData.data) - console.log('[请求] 接口地址:', config.baseUrl + url) - console.log('[请求] 解密后数据:', responseData) - const { - code, - msg - } = responseData - if (code === 200) { - resolve(responseData) + + const doRequest = () => { + return new Promise((resolve, reject) => { + uni.request({ + url: config.baseUrl + url, + method: method, + data: requestData, + header, + success: resData => { + // 响应拦截 + if (resData.statusCode === 200) { + const responseData = handleResponseData(resData.data) + // console.log('[请求] 接口地址:', config.baseUrl + url) + // console.log('[请求] 解密后数据:', responseData) + const { + code, + msg + } = responseData + if (code === 200) { + resolve(responseData) + return + } + // 处理业务错误 + if (responseData?.code === 401 || responseData?.code === 402) { + handleTokenExpired(resolve, reject, doRequest, loading) + return + } + // 显示具体的错误信息 + const errorMsg = msg || '请求出现异常,请联系工作人员' + const err = new Error(errorMsg) + err.error = resData + reject(err) return } - // 处理业务错误 - if (responseData?.code === 401 || responseData?.code === 402) { - useUserStore().logOut() - } - // 显示具体的错误信息 - const errorMsg = msg || '请求出现异常,请联系工作人员' - // uni.showToast({ - // title: errorMsg, - // icon: 'none' - // }) - const err = new Error(errorMsg) + // HTTP状态码不是200的情况 + const err = new Error('网络请求失败,请检查网络连接') err.error = resData reject(err) - return + }, + fail: (err) => { + reject(err) + }, + complete: () => { + if (loading) { + uni.hideLoading(); + } } - // HTTP状态码不是200的情况 - const err = new Error('网络请求失败,请检查网络连接') - err.error = resData - reject(err) - }, - fail: (err) => { - reject(err) - }, - complete: () => { - if (loading) { - uni.hideLoading(); - } - } - }); - }) + }); + }) + } + + return doRequest() +} + +const handleTokenExpired = (resolve, reject, retryRequest, loading) => { + if (isRefreshing) { + addRefreshSubscriber((token) => { + if (token) { + retryRequest().then(resolve).catch(reject) + } else { + reject(new Error('刷新token失败')) + } + }) + return + } + + isRefreshing = true + + useUserStore().refreshAccessToken().then((newToken) => { + notifyRefreshSubscribers(newToken) + retryRequest().then(resolve).catch(reject) + }).catch((error) => { + resetRefreshState() + uni.showToast({ + title: '登录过期,请重新登录', + icon: 'none' + }) + reject(error) + }).finally(() => { + if (loading) { + uni.hideLoading() + } + }) }