feat:小程序修改
This commit is contained in:
1 parent
5f267c1e76
commit
f4adf95e7a
8 files changed
+203
-69
No files matched your search
+20
-18
@@ -1,20 +1,22 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<script>
|
<script>
|
||||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
var coverSupport =
|
||||||
CSS.supports('top: constant(a)'))
|
"CSS" in window && typeof CSS.supports === "function" && (CSS.supports("top: env(a)") || CSS.supports("top: constant(a)"))
|
||||||
document.write(
|
document.write(
|
||||||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||||
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
(coverSupport ? ", viewport-fit=cover" : "") +
|
||||||
</script>
|
'" />',
|
||||||
<title></title>
|
)
|
||||||
<!--preload-links-->
|
</script>
|
||||||
<!--app-context-->
|
<title></title>
|
||||||
</head>
|
<!--preload-links-->
|
||||||
<body>
|
<!--app-context-->
|
||||||
<div id="app"><!--app-html--></div>
|
</head>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<body>
|
||||||
</body>
|
<div id="app"><!--app-html--></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+2
-1
@@ -97,7 +97,8 @@
|
|||||||
"path": "pages/root/web-view",
|
"path": "pages/root/web-view",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
"navigationBarTitleText": "加载中...",
|
"navigationBarTitleText": "美好商城",
|
||||||
|
"navigationStyle": "default",
|
||||||
"disableScroll": true
|
"disableScroll": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ const doSubmit = async () => {
|
|||||||
uni.setStorageSync(tokenKey, {
|
uni.setStorageSync(tokenKey, {
|
||||||
accessToken: res.data.access_token,
|
accessToken: res.data.access_token,
|
||||||
refreshToken: res.data.refresh_token,
|
refreshToken: res.data.refresh_token,
|
||||||
accessTokenExpiresIn: new Date(res.data.expires_in + Date.now()).toISOString(),
|
// OAuth expires_in 的单位是秒,Date.now() 的单位是毫秒,必须先乘 1000。
|
||||||
|
accessTokenExpiresIn: new Date(Date.now() + Number(res.data.expires_in) * 1000).toISOString(),
|
||||||
refreshTokenExpiresIn: "",
|
refreshTokenExpiresIn: "",
|
||||||
} as GlobalSetting.TokenStorage)
|
} as GlobalSetting.TokenStorage)
|
||||||
|
|
||||||
|
|||||||
+15
-3
@@ -82,6 +82,7 @@ import { isEmpty } from "radash"
|
|||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
import { filePath } from "@@/utils/file"
|
import { filePath } from "@@/utils/file"
|
||||||
import { getAppInfo } from "@@/utils/app"
|
import { getAppInfo } from "@@/utils/app"
|
||||||
|
import { normalizeTokenStorage } from "@@/utils/request"
|
||||||
import * as hooks from "@@/hooks"
|
import * as hooks from "@@/hooks"
|
||||||
import * as router from "@@/router"
|
import * as router from "@@/router"
|
||||||
import * as constant from "@@/constant"
|
import * as constant from "@@/constant"
|
||||||
@@ -136,7 +137,11 @@ const storage = {
|
|||||||
|
|
||||||
// 保存token到存储
|
// 保存token到存储
|
||||||
saveToken: (tokenData: any) => {
|
saveToken: (tokenData: any) => {
|
||||||
uni.setStorageSync(STORAGE_KEYS.TOKEN, tokenData)
|
// SSO 接口可能返回嵌套 token,统一落成请求层使用的扁平结构。
|
||||||
|
const normalizedToken = normalizeTokenStorage(tokenData)
|
||||||
|
if (!normalizedToken) return false
|
||||||
|
uni.setStorageSync(STORAGE_KEYS.TOKEN, normalizedToken)
|
||||||
|
return true
|
||||||
},
|
},
|
||||||
|
|
||||||
// 清除用户登录相关的存储
|
// 清除用户登录相关的存储
|
||||||
@@ -202,7 +207,11 @@ const login = {
|
|||||||
const response = await userApi.authsh({ accountId, sessionId })
|
const response = await userApi.authsh({ accountId, sessionId })
|
||||||
|
|
||||||
if (response.code == 0) {
|
if (response.code == 0) {
|
||||||
storage.saveToken({ accessToken: response.data.accessToken })
|
if (!storage.saveToken(response.data)) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: "登录返回数据异常", icon: "none" })
|
||||||
|
return
|
||||||
|
}
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
uni.showToast({ title: "登录成功", icon: "none" })
|
uni.showToast({ title: "登录成功", icon: "none" })
|
||||||
|
|
||||||
@@ -226,7 +235,10 @@ const login = {
|
|||||||
const res = await userApi.auth(token)
|
const res = await userApi.auth(token)
|
||||||
|
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
storage.saveToken(res.data)
|
if (!storage.saveToken(res.data)) {
|
||||||
|
uni.showToast({ title: "登录返回数据异常", icon: "none" })
|
||||||
|
return
|
||||||
|
}
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
uni.showToast({ title: "登录成功", icon: "none" })
|
uni.showToast({ title: "登录成功", icon: "none" })
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,33 @@ import { filePath } from "@@/utils/file"
|
|||||||
|
|
||||||
const show = ref(false)
|
const show = ref(false)
|
||||||
|
|
||||||
|
function normalizeMallUrl(rawUrl: string) {
|
||||||
|
const matched = rawUrl.match(/^(https:\/\/(?:[a-z0-9-]+\.)*nbcb\.com\.cn)(\/[^?#]*)?(\?[^#]*)?(#.*)?$/i)
|
||||||
|
if (!matched) throw new Error("商城登录地址不合法")
|
||||||
|
|
||||||
|
const [, origin, rawPathname = "/", search = "", hash = ""] = matched
|
||||||
|
const queryItems = search.slice(1).split("&").filter(Boolean)
|
||||||
|
const queryKeys = queryItems.map((item) => decodeURIComponent(item.split("=")[0] || ""))
|
||||||
|
const hasToken = queryItems.some((item) => /^token=.+/i.test(item))
|
||||||
|
let pathname = rawPathname
|
||||||
|
|
||||||
|
// 宁波银行正常登录页是 /h5/thirdApp;部分环境会错误返回带 token 的根路径。
|
||||||
|
if (pathname === "/" && hasToken) {
|
||||||
|
pathname = "/h5/thirdApp"
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[happyMall] 商城登录地址:", {
|
||||||
|
origin,
|
||||||
|
pathname,
|
||||||
|
queryKeys,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (pathname === "/" || !hasToken) {
|
||||||
|
throw new Error("商城登录地址异常,请联系管理员")
|
||||||
|
}
|
||||||
|
return `${origin}${pathname}${search}${hash}`
|
||||||
|
}
|
||||||
|
|
||||||
onLoad(async () => {
|
onLoad(async () => {
|
||||||
const isLogin = await hooks.useCheckLogin()
|
const isLogin = await hooks.useCheckLogin()
|
||||||
if (isLogin) {
|
if (isLogin) {
|
||||||
@@ -42,10 +69,11 @@ const thirdAppJump = async () => {
|
|||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
show.value = false
|
show.value = false
|
||||||
if (!res.data) return uni.showToast({ title: "url错误", icon: "none" })
|
if (!res.data) return uni.showToast({ title: "url错误", icon: "none" })
|
||||||
router.jump("/pages/root/web-view", { url: encodeURIComponent(res.data) }, 2)
|
const mallUrl = normalizeMallUrl(res.data)
|
||||||
|
router.jump("/pages/root/web-view", { url: encodeURIComponent(mallUrl) }, 2)
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
uni.showToast({ title: error, icon: "none" })
|
uni.showToast({ title: error?.message || String(error), icon: "none" })
|
||||||
} finally {
|
} finally {
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import storageConstant from "../constant/storage-constant"
|
import storageConstant from "../constant/storage-constant"
|
||||||
import { jump } from "../router/index"
|
import { jump } from "../router/index"
|
||||||
import { isDevEnv } from "../utils/app"
|
|
||||||
|
|
||||||
export const useToken = () => {
|
export const useToken = () => {
|
||||||
const tokenKey = storageConstant.TOKEN
|
const tokenKey = storageConstant.TOKEN
|
||||||
const tokenStorage = uni.getStorageSync(tokenKey)
|
const tokenStorage = uni.getStorageSync(tokenKey)
|
||||||
return tokenStorage?.access_token ? `Bearer ${tokenStorage.access_token}` : ""
|
// 登录与刷新接口历史上存在两种字段格式,上传等原生请求也必须读取同一个有效 token。
|
||||||
|
const rawToken = tokenStorage?.accessToken ?? tokenStorage?.access_token
|
||||||
|
const tokenValue = typeof rawToken === "string" ? rawToken : rawToken?.tokenValue
|
||||||
|
return tokenValue ? `Bearer ${tokenValue}` : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDefaultHeader = () => {
|
export const useDefaultHeader = () => {
|
||||||
@@ -18,17 +20,12 @@ export const useDefaultHeader = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useStaticCheckLogin = () => {
|
export const useStaticCheckLogin = () => {
|
||||||
const tokenKey = storageConstant.TOKEN
|
// 不能只判断缓存对象存在,刷新失败留下的空对象不代表已登录。
|
||||||
const tokenStorage = uni.getStorageSync(tokenKey)
|
return Boolean(useToken())
|
||||||
if (tokenStorage) return true
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCheckLogin = async (): Promise<boolean> => {
|
export const useCheckLogin = async (): Promise<boolean> => {
|
||||||
const tokenKey = storageConstant.TOKEN
|
if (useToken()) return true
|
||||||
const tokenStorage = uni.getStorageSync(tokenKey)
|
|
||||||
|
|
||||||
if (tokenStorage) return true
|
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: "提示",
|
title: "提示",
|
||||||
content: "您还未登录,请先登录",
|
content: "您还未登录,请先登录",
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ export const useTabbarStore = defineStore("tabbarStore", {
|
|||||||
if (!firstTabbar) {
|
if (!firstTabbar) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jump(firstTabbar.pagePath, {}, 3)
|
jump(firstTabbar.pagePath, {})
|
||||||
},
|
},
|
||||||
setCurrentRoute(route: string) {
|
setCurrentRoute(route: string) {
|
||||||
if (route === this.currentRoute) {
|
if (route === this.currentRoute) {
|
||||||
|
|||||||
+126
-33
@@ -12,13 +12,16 @@ const SKIP_AUTH_URLS = [
|
|||||||
"/thirdparty/thirdPartyOAth2",
|
"/thirdparty/thirdPartyOAth2",
|
||||||
"/sys/auth/refresh-token",
|
"/sys/auth/refresh-token",
|
||||||
"/auth/oauth2/token",
|
"/auth/oauth2/token",
|
||||||
|
"/third/third-mini-app-oath/auto-admin-login",
|
||||||
"/admin/user/forgot-password",
|
"/admin/user/forgot-password",
|
||||||
"/admin/stats/access/track",
|
"/admin/stats/access/track",
|
||||||
]
|
]
|
||||||
const SKIP_REFRESH_TOKEN = ["/sys/auth/refresh-token", "/auth/token/check_token"]
|
const SKIP_REFRESH_TOKEN = ["/sys/auth/refresh-token", "/auth/token/check_token"]
|
||||||
|
const AUTH_EXPIRED_CODES = [401, 424]
|
||||||
|
const REFRESH_AHEAD_MS = 60 * 1000
|
||||||
|
|
||||||
let refreshTokenLock = true
|
let refreshTokenPromise: Promise<GlobalSetting.TokenStorage | null> | null = null
|
||||||
let refreshTokenPromise: Promise<any> | null = null
|
let loginExpiredHandled = false
|
||||||
const BASIC_AUTH = "Basic " + base64.encode(import.meta.env.VITE_OAUTH2_APP_CLIENT)
|
const BASIC_AUTH = "Basic " + base64.encode(import.meta.env.VITE_OAUTH2_APP_CLIENT)
|
||||||
const buildErrorResponse = (error: any, fallbackMsg: string = "请求失败") => ({
|
const buildErrorResponse = (error: any, fallbackMsg: string = "请求失败") => ({
|
||||||
code: error?.statusCode || -1,
|
code: error?.statusCode || -1,
|
||||||
@@ -58,14 +61,14 @@ https.interceptors.request.use(async (config) => {
|
|||||||
// 判断是否需要设置认证头
|
// 判断是否需要设置认证头
|
||||||
if (!SKIP_AUTH_URLS.some((url) => config.url?.startsWith(url))) {
|
if (!SKIP_AUTH_URLS.some((url) => config.url?.startsWith(url))) {
|
||||||
if (!isEmpty(tokenStorage)) {
|
if (!isEmpty(tokenStorage)) {
|
||||||
// token存在,检查是否过期
|
// 提前一分钟刷新,避免 token 在请求传输或服务端处理过程中到期。
|
||||||
if (isTokenExpires(tokenStorage) && !SKIP_REFRESH_TOKEN.some((url) => config.url?.startsWith(url)) && refreshTokenLock) {
|
if (isTokenExpires(tokenStorage) && !SKIP_REFRESH_TOKEN.some((url) => config.url?.startsWith(url))) {
|
||||||
// 过期,刷新token
|
// 所有并发请求都会等待同一个 refreshTokenPromise,不能让后续请求继续携带旧 token。
|
||||||
const newTokenStorage = await refreshToken()
|
const newTokenStorage = await refreshToken()
|
||||||
if (newTokenStorage) {
|
if (newTokenStorage) {
|
||||||
tokenValue = getTokenValue(newTokenStorage)
|
tokenValue = getTokenValue(newTokenStorage)
|
||||||
if (tokenValue) config_header.Authorization = tokenValue
|
|
||||||
} else {
|
} else {
|
||||||
|
config.custom = { ...(config.custom || {}), authRefreshAttempted: true }
|
||||||
return Promise.reject({
|
return Promise.reject({
|
||||||
statusCode: 424,
|
statusCode: 424,
|
||||||
errMsg: "refresh token failed",
|
errMsg: "refresh token failed",
|
||||||
@@ -75,7 +78,12 @@ https.interceptors.request.use(async (config) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config_header["Authorization"] = tokenValue
|
if (tokenValue) {
|
||||||
|
config_header.Authorization = tokenValue
|
||||||
|
loginExpiredHandled = false
|
||||||
|
} else {
|
||||||
|
delete config_header.Authorization
|
||||||
|
}
|
||||||
}
|
}
|
||||||
config.header = config_header
|
config.header = config_header
|
||||||
return config
|
return config
|
||||||
@@ -92,17 +100,27 @@ https.interceptors.response.use(
|
|||||||
msg: null,
|
msg: null,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// 部分网关以 HTTP 200 + 业务码返回 token 过期,也要进入同一套刷新和重试流程。
|
||||||
|
if (
|
||||||
|
AUTH_EXPIRED_CODES.includes(Number(response.data?.code)) &&
|
||||||
|
!SKIP_AUTH_URLS.some((url) => response.config.url?.startsWith(url))
|
||||||
|
) {
|
||||||
|
return recoverExpiredRequest({
|
||||||
|
statusCode: Number(response.data.code),
|
||||||
|
data: response.data,
|
||||||
|
config: response.config,
|
||||||
|
})
|
||||||
|
}
|
||||||
return Promise.resolve(response.data)
|
return Promise.resolve(response.data)
|
||||||
},
|
},
|
||||||
async (error) => {
|
async (error) => {
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
// 处理 424 错误(token过期)
|
// HTTP 401/424 先刷新并重放原请求,只有刷新失败才要求重新登录。
|
||||||
if (error.statusCode === 424) {
|
if (
|
||||||
// 判断是否在登录页面
|
AUTH_EXPIRED_CODES.includes(Number(error.statusCode)) &&
|
||||||
uni.removeStorageSync(TOKEN_KEY)
|
!SKIP_AUTH_URLS.some((url) => error.config?.url?.startsWith(url))
|
||||||
uni.removeStorageSync(USER_INFO_KEY)
|
) {
|
||||||
uni.showToast({ title: "登录已过期,请重新登录", icon: "none" })
|
return recoverExpiredRequest(error)
|
||||||
router.jump("/pages/root/login")
|
|
||||||
}
|
}
|
||||||
// 显示错误信息
|
// 显示错误信息
|
||||||
else if (error?.data?.msg) {
|
else if (error?.data?.msg) {
|
||||||
@@ -112,24 +130,71 @@ https.interceptors.response.use(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const unwrapTokenValue = (value: any): string | null => {
|
||||||
|
if (typeof value === "string" && value.trim()) return value.trim()
|
||||||
|
if (value && typeof value.tokenValue === "string" && value.tokenValue.trim()) return value.tokenValue.trim()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// 从tokenStorage中读取tokenValue值
|
// 从tokenStorage中读取tokenValue值
|
||||||
const getTokenValue = (tokenStorage: GlobalSetting.TokenStorage): string | null => {
|
const getTokenValue = (tokenStorage: GlobalSetting.TokenStorage): string | null => {
|
||||||
return tokenStorage?.accessToken ? `Bearer ${tokenStorage?.accessToken}` : null
|
const value = unwrapTokenValue(tokenStorage?.accessToken) || unwrapTokenValue((tokenStorage as any)?.access_token)
|
||||||
|
return value ? `Bearer ${value}` : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRefreshTokenValue = (tokenStorage: GlobalSetting.TokenStorage): string | null => {
|
||||||
|
return unwrapTokenValue(tokenStorage?.refreshToken) || unwrapTokenValue((tokenStorage as any)?.refresh_token)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getExpiresAt = (tokenStorage: any, tokenKey: "accessToken" | "refreshToken", expiresKey: string): number => {
|
||||||
|
const raw = tokenStorage?.[expiresKey] || tokenStorage?.[tokenKey]?.expiresAt
|
||||||
|
if (!raw) return Number.NaN
|
||||||
|
return new Date(raw).getTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查tokenStorage中tokenValue是否过期
|
// 检查tokenStorage中tokenValue是否过期
|
||||||
const isTokenExpires = (tokenStorage: GlobalSetting.TokenStorage): boolean => {
|
const isTokenExpires = (tokenStorage: GlobalSetting.TokenStorage): boolean => {
|
||||||
const { accessTokenExpiresIn } = tokenStorage
|
const expiresAt = getExpiresAt(tokenStorage, "accessToken", "accessTokenExpiresIn")
|
||||||
const expiresDate = new Date(accessTokenExpiresIn).getTime()
|
// 老版本缓存没有过期时间时交给服务端 401/424 兜底,避免把仍可用的 token 直接踢下线。
|
||||||
return new Date().getTime() >= expiresDate
|
if (!Number.isFinite(expiresAt)) return false
|
||||||
|
return Date.now() + REFRESH_AHEAD_MS >= expiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshToken(): Promise<any> {
|
const expiresInSecondsToIso = (seconds: any): string => {
|
||||||
|
const value = Number(seconds)
|
||||||
|
return Number.isFinite(value) && value > 0 ? new Date(Date.now() + value * 1000).toISOString() : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将 OAuth 下划线格式和后台嵌套格式统一为项目使用的 TokenStorage。 */
|
||||||
|
export const normalizeTokenStorage = (data: any, previous?: GlobalSetting.TokenStorage): GlobalSetting.TokenStorage | null => {
|
||||||
|
const accessToken = unwrapTokenValue(data?.accessToken) || unwrapTokenValue(data?.access_token)
|
||||||
|
if (!accessToken) return null
|
||||||
|
|
||||||
|
const refreshToken =
|
||||||
|
unwrapTokenValue(data?.refreshToken) || unwrapTokenValue(data?.refresh_token) || (previous ? getRefreshTokenValue(previous) : null) || ""
|
||||||
|
const accessTokenExpiresIn =
|
||||||
|
data?.accessTokenExpiresIn || data?.accessToken?.expiresAt || expiresInSecondsToIso(data?.expires_in)
|
||||||
|
const refreshTokenExpiresIn =
|
||||||
|
data?.refreshTokenExpiresIn ||
|
||||||
|
data?.refreshToken?.expiresAt ||
|
||||||
|
expiresInSecondsToIso(data?.refresh_expires_in) ||
|
||||||
|
previous?.refreshTokenExpiresIn ||
|
||||||
|
""
|
||||||
|
|
||||||
|
return { accessToken, refreshToken, accessTokenExpiresIn, refreshTokenExpiresIn }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshToken(): Promise<GlobalSetting.TokenStorage | null> {
|
||||||
if (refreshTokenPromise) return refreshTokenPromise
|
if (refreshTokenPromise) return refreshTokenPromise
|
||||||
refreshTokenPromise = (async () => {
|
refreshTokenPromise = (async () => {
|
||||||
refreshTokenLock = false
|
|
||||||
try {
|
try {
|
||||||
const tokenStorage = uni.getStorageSync(TOKEN_KEY) as GlobalSetting.TokenStorage
|
const tokenStorage = uni.getStorageSync(TOKEN_KEY) as GlobalSetting.TokenStorage
|
||||||
|
const refreshTokenValue = getRefreshTokenValue(tokenStorage)
|
||||||
|
if (!refreshTokenValue) return null
|
||||||
|
|
||||||
|
const refreshExpiresAt = getExpiresAt(tokenStorage, "refreshToken", "refreshTokenExpiresIn")
|
||||||
|
if (Number.isFinite(refreshExpiresAt) && Date.now() >= refreshExpiresAt) return null
|
||||||
|
|
||||||
const res: any = await https.middleware({
|
const res: any = await https.middleware({
|
||||||
url: "/auth/oauth2/token",
|
url: "/auth/oauth2/token",
|
||||||
header: {
|
header: {
|
||||||
@@ -138,28 +203,56 @@ async function refreshToken(): Promise<any> {
|
|||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
},
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
params: { refresh_token: tokenStorage.refreshToken, grant_type: "refresh_token", scope: "server" },
|
params: { refresh_token: refreshTokenValue, grant_type: "refresh_token", scope: "server" },
|
||||||
})
|
})
|
||||||
const { data } = res || {}
|
const newTokenStorage = res?.code === 0 ? normalizeTokenStorage(res.data, tokenStorage) : null
|
||||||
if (data) {
|
if (!newTokenStorage) return null
|
||||||
tokenStorage.accessToken = data?.accessToken?.tokenValue
|
|
||||||
tokenStorage.refreshToken = data?.refreshToken?.tokenValue
|
uni.setStorageSync(TOKEN_KEY, newTokenStorage)
|
||||||
tokenStorage.accessTokenExpiresIn = data?.accessToken?.expiresAt
|
loginExpiredHandled = false
|
||||||
tokenStorage.refreshTokenExpiresIn = data?.refreshToken?.expiresAt
|
return newTokenStorage
|
||||||
uni.setStorageSync(TOKEN_KEY, tokenStorage)
|
|
||||||
return tokenStorage
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false
|
return null
|
||||||
} finally {
|
} finally {
|
||||||
refreshTokenLock = true
|
|
||||||
refreshTokenPromise = null
|
refreshTokenPromise = null
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
return refreshTokenPromise
|
return refreshTokenPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clearExpiredSession = () => {
|
||||||
|
// 多个并发请求同时失败时只提示和跳转一次。
|
||||||
|
if (loginExpiredHandled) return
|
||||||
|
loginExpiredHandled = true
|
||||||
|
uni.removeStorageSync(TOKEN_KEY)
|
||||||
|
uni.removeStorageSync(USER_INFO_KEY)
|
||||||
|
uni.showToast({ title: "登录已过期,请重新登录", icon: "none" })
|
||||||
|
router.jump("/pages/root/login")
|
||||||
|
}
|
||||||
|
|
||||||
|
const recoverExpiredRequest = async (error: any) => {
|
||||||
|
const config = error?.config || {}
|
||||||
|
const requestUrl = config.url || ""
|
||||||
|
const custom = config.custom || {}
|
||||||
|
const canRefresh = !SKIP_AUTH_URLS.some((url) => requestUrl.startsWith(url)) && !custom.authRefreshAttempted
|
||||||
|
|
||||||
|
if (canRefresh) {
|
||||||
|
const newTokenStorage = await refreshToken()
|
||||||
|
const authorization = newTokenStorage ? getTokenValue(newTokenStorage) : null
|
||||||
|
if (authorization) {
|
||||||
|
// 标记只重试一次,避免服务端持续返回 401/424 时形成请求死循环。
|
||||||
|
return https.middleware({
|
||||||
|
...config,
|
||||||
|
header: { ...(config.header || {}), Authorization: authorization },
|
||||||
|
custom: { ...custom, authRefreshAttempted: true },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearExpiredSession()
|
||||||
|
return buildErrorResponse(error, "登录已过期,请重新登录")
|
||||||
|
}
|
||||||
|
|
||||||
// 请求函数
|
// 请求函数
|
||||||
const request = (config: any) => {
|
const request = (config: any) => {
|
||||||
return https.middleware(config)
|
return https.middleware(config)
|
||||||
|
|||||||
Reference in new issue
Block a user