diff --git a/src/pages/tabbar/my.vue b/src/pages/tabbar/my.vue index 0aba858..7b8e245 100644 --- a/src/pages/tabbar/my.vue +++ b/src/pages/tabbar/my.vue @@ -87,6 +87,7 @@ import { hooks, router, constant } from "@@/index" import { actCancel, userApi } from "@@/api" import { isDevEnv } from "@/sheep/utils/app" import { APPENV } from "@/sheep/constant" +import { ensureBizLogin } from "@/sheep/utils/request" import ModifyUser from "@@/components/modify-user.vue" @@ -185,20 +186,11 @@ const login = { } }, - // 诗画环境登录 + // 诗画环境登录:走统一 ensureBizLogin,避免与请求拦截器各打一遍 /shjnsso shihuaLogin: async () => { const appInfo = await (window as any).CP2.getAppInfo() if (appInfo.code == 0) return false - - const userInfo = await (window as any).CP2.getUserInfo() - if (userInfo.code == 0) { - uni.showToast({ title: "获取诗画用户信息失败", icon: "none" }) - return false - } else { - const { accountId, sessionId } = userInfo.data - return login.doLogin(1, "", accountId, sessionId) - // return login.doLogin(1, "", "67186e49d63fea141aad6772", "67186e49d63fea141aad6776") - } + return await ensureBizLogin() }, // 浙政钉登录 diff --git a/src/sheep/utils/request.ts b/src/sheep/utils/request.ts index eebbed1..a1e55cd 100644 --- a/src/sheep/utils/request.ts +++ b/src/sheep/utils/request.ts @@ -16,6 +16,10 @@ let isRefreshing = false // 是否正在刷新 let failedQueue: any[] = [] // 队列存储发送失败的请求 /** App.onLaunch 与页面 ensureBizLogin 并发时复用同一次浙政钉登录,避免双重 showLoading/getAuthCode */ let zzdLoginPromise: Promise<"ok" | "sms" | "failed"> | null = null +/** 拦截器 refreshToken、「我的」checkAppInfo、页面 ensureBizLogin 并发时复用同一次自动登录 */ +let autoLoginPromise: Promise | null = null +/** 诗画 /shjnsso 并发互斥(与浙政钉 zzdLoginPromise 对称) */ +let shLoginPromise: Promise | null = null /** * 清除本地登录相关缓存(等价于「重装清 WebView」里最关键的几项)。 @@ -289,16 +293,21 @@ async function refreshToken(): Promise { return refreshTokenPromise } -// 根据应用环境自动登录 返回登录结果 +// 根据应用环境自动登录 返回登录结果(全局互斥,避免清缓存后并发打多遍单点) const autoLogin = async (): Promise => { - const appInfo = await getAppInfo() - // 浙政钉环境 - if (appInfo == APPENV.ZZD) return await loginByZzd() - // 诗画环境 - if (appInfo == APPENV.SH) return await loginBySh() - - uni.showToast({ title: "app环境错误", icon: "none" }) - return false + if (autoLoginPromise) return autoLoginPromise + autoLoginPromise = (async () => { + try { + const appInfo = await getAppInfo() + if (appInfo == APPENV.ZZD) return await loginByZzd() + if (appInfo == APPENV.SH) return await loginBySh() + uni.showToast({ title: "app环境错误", icon: "none" }) + return false + } finally { + autoLoginPromise = null + } + })() + return autoLoginPromise } function isZzdNeedSmsVerify(data: unknown): data is { needSmsVerify: true; bindSessionId: string; displayName?: string } { @@ -420,60 +429,68 @@ export async function tryZzdEntryLogin(): Promise<"ok" | "sms" | "failed"> { return await zzdLoginCore() } -// 诗画登录 +// 诗画登录(互斥:避免「我的」与请求拦截器各打一遍 /shjnsso) async function loginBySh(): Promise { - try { - uni.showLoading({ title: "登录中...", mask: true }) - sanitizeBizSession() - - const userInfo = await (window as any).CP2.getUserInfo() - if (userInfo.code == 0 || !userInfo.data.accountId || !userInfo.data.sessionId) { - uni.showToast({ title: "获取诗画用户信息失败", icon: "none" }) - return false - } - - const { accountId, sessionId } = userInfo.data - const response = await userApi.authsh({ accountId, sessionId }) - // const response = await userApi.authsh({ accountId: "67e790d6a95d5a7b507e53dd", sessionId: "68215b03a95d5a4899d20cb4" }) // 测试 - - if (response.code == 0) { - const at = response.data?.accessToken - // 统一成可被 getTokenValue 识别的结构,避免只落了字符串导致后续读不到 - if (typeof at === "string") { - uni.setStorageSync(TOKEN_KEY, { accessToken: { tokenValue: at } }) - } else { - uni.setStorageSync(TOKEN_KEY, { accessToken: at }) + if (shLoginPromise) return shLoginPromise + shLoginPromise = (async () => { + try { + uni.showLoading({ title: "登录中...", mask: true }) + sanitizeBizSession() + const tokenStorage = uni.getStorageSync(TOKEN_KEY) + if (hasUsableBizToken(tokenStorage)) { + return true } - const { data: userData } = await userApi.userInfo() - uni.setStorageSync(USER_INFO_KEY, userData) - uni.showToast({ title: "登录成功", icon: "none" }) - return true - } else { - clearBizSession() - if (response.msg == "token已失效") { - uni.showToast({ title: "用户凭证已失效,请退出并重新进入智慧后勤应用", icon: "none", duration: 6000 }) + + const userInfo = await (window as any).CP2.getUserInfo() + if (userInfo.code == 0 || !userInfo.data.accountId || !userInfo.data.sessionId) { + uni.showToast({ title: "获取诗画用户信息失败", icon: "none" }) return false } - // 设备未信任:后端 R.failed(-1, msg) 业务码在 code,data 为 null - if (response.code == -1) { - uni.setStorageSync(TOKEN_KEY, { data: -1 }) - } + const { accountId, sessionId } = userInfo.data + const response = await userApi.authsh({ accountId, sessionId }) - uni.showToast({ title: response.msg || "登录失败", icon: "none" }) + if (response.code == 0) { + const at = response.data?.accessToken + // 统一成可被 getTokenValue 识别的结构,避免只落了字符串导致后续读不到 + if (typeof at === "string") { + uni.setStorageSync(TOKEN_KEY, { accessToken: { tokenValue: at } }) + } else { + uni.setStorageSync(TOKEN_KEY, { accessToken: at }) + } + const { data: userData } = await userApi.userInfo() + uni.setStorageSync(USER_INFO_KEY, userData) + uni.showToast({ title: "登录成功", icon: "none" }) + return true + } else { + clearBizSession() + if (response.msg == "token已失效") { + uni.showToast({ title: "用户凭证已失效,请退出并重新进入智慧后勤应用", icon: "none", duration: 6000 }) + return false + } + + // 设备未信任:后端 R.failed(-1, msg) 业务码在 code,data 为 null + if (response.code == -1) { + uni.setStorageSync(TOKEN_KEY, { data: -1 }) + } + + uni.showToast({ title: response.msg || "登录失败", icon: "none" }) + return false + } + } catch (error) { + clearBizSession() + uni.showToast({ title: "登录错误", icon: "none" }) return false + } finally { + try { + uni.hideLoading() + } catch { + /* ignore */ + } + shLoginPromise = null } - } catch (error) { - clearBizSession() - uni.showToast({ title: "登录错误", icon: "none" }) - return false - } finally { - try { - uni.hideLoading() - } catch { - /* ignore */ - } - } + })() + return shLoginPromise } /** @@ -496,6 +513,8 @@ export async function ensureBizLogin(): Promise { export async function recoverBizSession(): Promise { clearBizSession({ clearRole: true }) zzdLoginPromise = null + shLoginPromise = null + autoLoginPromise = null refreshTokenPromise = null isRefreshing = false return await autoLogin()