You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

950 lines
28 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { ref } from 'vue'
import { appLog, flushLogs } from '@/utils/appLog.js'
// 公网统一入口POST http://<gateway-host>/public
// 这里留一个可配置的占位符,实际项目中建议从配置文件或环境变量读取
// const baseURL = 'http://padapi.queuingsystem.cn/public'
const baseURL = 'https://api-dsb.dingtax.cn/dsb/api/tax-appoint/dx/third/request/public'
// 签名计算也走统一入口,通过约定的签名 tag/path 转发到内部 /api/auth
const SIGN_TAG = 'pad.auth'
const SIGN_PATH = '/sign'
const REFRESH_TAG = 'pad.auth'
const REFRESH_PATH = '/refresh'
// 全局 loading 状态,可在页面上直接使用
export const loading = ref(false)
// 生成 traceId时间戳 + 随机数(满足文档“可追踪”的要求即可)
const genTraceId = () => {
const ts = new Date()
.toISOString()
.replace(/[-:TZ.]/g, '')
const rand = Math.floor(Math.random() * 10000)
.toString()
.padStart(4, '0')
return `${ts}${rand}`
}
// 生成随机 nonce用于签名防重放
const genNonce = () => {
return `n-${Math.random().toString(36).slice(2, 10)}`
}
// 深度排序对象 key保证签名参数序列化一致
const sortKeysDeep = (value) => {
if (Array.isArray(value)) {
return value.map((item) => sortKeysDeep(item))
}
if (value && typeof value === 'object') {
const sorted = {}
Object.keys(value)
.sort()
.forEach((key) => {
sorted[key] = sortKeysDeep(value[key])
})
return sorted
}
return value
}
const clonePayloadPart = (value) => {
if (value === undefined || value === null) {
return {}
}
try {
return JSON.parse(JSON.stringify(value))
} catch (error) {
console.log('[request][clone-payload-error]', error, value)
return {}
}
}
const isBizSuccessCode = (code) => code === 200 || code === '200'
const getBizCode = (err) => {
if (!err) return null
if (err.code !== undefined && err.code !== null && err.code !== '') {
return Number(err.code)
}
if (err.data && err.data.code !== undefined && err.data.code !== null) {
return Number(err.data.code)
}
return null
}
const isUnauthorizedError = (err) => {
if (!err) return false
if (Number(err.statusCode) === 401) return true
return getBizCode(err) === 401
}
const getErrorMessage = (err, fallback = '请求失败') => {
if (!err) return fallback
return (
err.msg ||
err.message ||
err.error ||
(err.data && (err.data.msg || err.data.message || err.data.error)) ||
fallback
)
}
let refreshPromise = null
let sessionRestorePromise = null
let needSessionRestore = false
let sessionGateResolve = null
let sessionGatePromise = Promise.resolve()
const closeSessionGate = () => {
if (!sessionGateResolve) {
sessionGatePromise = new Promise((resolve) => {
sessionGateResolve = resolve
})
}
}
const openSessionGate = () => {
if (sessionGateResolve) {
sessionGateResolve()
sessionGateResolve = null
}
sessionGatePromise = Promise.resolve()
}
/** 后台恢复续会话完成前,业务请求在此等待,避免与刷新 token 并发导致误登出 */
export const waitSessionReady = () => sessionGatePromise
const isRefreshAuthFailure = (err) => {
if (!err) return false
if (isUnauthorizedError(err)) return true
const msg = getErrorMessage(err, '')
if (/refresh token 不存在|access token 为空|无效.*token|token.*无效|未授权/i.test(msg)) {
return true
}
return false
}
const isLoginPage = () => {
try {
const pages = getCurrentPages()
if (!pages || !pages.length) return false
const route = String(pages[pages.length - 1].route || '')
return route.indexOf('login') !== -1
} catch (error) {
return false
}
}
const clearAuthAndGoLogin = () => {
appLog('warn', '[auth] clearAuthAndGoLogin: 登录态失效,准备跳转登录页')
flushLogs()
uni.removeStorageSync('token')
uni.removeStorageSync('refresh_token')
uni.removeStorageSync('refreshToken')
uni.removeStorageSync('userInfo')
if (isLoginPage()) {
appLog('info', '[auth] 已在登录页,跳过 reLaunch')
return
}
uni.showToast({
title: '登录已过期,请重新登录',
icon: 'none'
})
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/login'
})
}, 500)
}
const isNetworkError = (err) => {
if (!err) return true
if (err.errMsg && /fail|timeout/i.test(String(err.errMsg))) return true
if (err.message && /fail|timeout|network/i.test(String(err.message))) return true
return false
}
/** 标记需要在下次前台时续会话(锁屏/切后台) */
export const markSessionNeedsRestore = () => {
needSessionRestore = true
closeSessionGate()
appLog('info', '[session] markSessionNeedsRestore: 已标记,等待回到前台续会话')
flushLogs()
}
/**
* 解锁或回到前台时续会话:
* 1. 先用本地 access_token 校验 /profile短时锁屏通常仍有效
* 2. 仅 401 时再刷新 token
* 3. 刷新/校验鉴权失败才登出;签名或网络问题保留登录态
*/
export const restoreSession = async () => {
if (sessionRestorePromise) {
appLog('info', '[session] restoreSession: 已有续会话进行中,复用')
return sessionRestorePromise
}
sessionRestorePromise = (async () => {
try {
const token = uni.getStorageSync('token')
const refreshToken =
uni.getStorageSync('refresh_token') ||
uni.getStorageSync('refreshToken')
appLog(
'info',
'[session] restoreSession: 开始',
`hasToken=${Boolean(token)}`,
`hasRefreshToken=${Boolean(refreshToken)}`,
`onLoginPage=${isLoginPage()}`
)
if (!token && !refreshToken) {
appLog('warn', '[session] restoreSession: 本地无登录态,跳过')
return false
}
const tryProfile = async () => {
appLog('info', '[session] restoreSession: 校验 profile')
await request({
tag: 'pad.auth',
path: '/profile',
method: 'GET',
loading: false,
quiet: true,
skipSessionGate: true,
})
}
try {
if (token) {
await tryProfile()
appLog('info', '[session] restoreSession: 现有 token 仍有效')
flushLogs()
if (isLoginPage()) {
appLog('info', '[session] restoreSession: 当前在登录页,跳转首页')
uni.reLaunch({
url: '/pages/index/index',
})
}
return true
}
} catch (profileErr) {
if (!isUnauthorizedError(profileErr)) {
if (isNetworkError(profileErr)) {
appLog('warn', '[session] restoreSession: profile 网络异常,保留本地登录态')
return false
}
appLog('warn', '[session] restoreSession: profile 非鉴权失败,保留本地登录态', profileErr)
return false
}
appLog('warn', '[session] restoreSession: profile 401尝试刷新 token')
}
if (!refreshToken) {
appLog('error', '[session] restoreSession: 无 refresh_token无法续会话')
clearAuthAndGoLogin()
return false
}
appLog('info', '[session] restoreSession: 主动刷新 token')
await refreshAccessToken()
await tryProfile()
appLog('info', '[session] restoreSession: 续会话成功')
flushLogs()
if (isLoginPage()) {
appLog('info', '[session] restoreSession: 当前在登录页,跳转首页')
uni.reLaunch({
url: '/pages/index/index',
})
}
return true
} catch (error) {
appLog('error', '[session] restoreSession: 续会话失败', error)
const msg = getErrorMessage(error, '')
if (/signature is blank/i.test(msg)) {
appLog('warn', '[session] restoreSession: 刷新签名失败,保留本地登录态')
return false
}
if (isUnauthorizedError(error) || isRefreshAuthFailure(error)) {
appLog('warn', '[session] restoreSession: 鉴权失败,清理登录态')
clearAuthAndGoLogin()
return false
}
if (isNetworkError(error)) {
appLog('warn', '[session] restoreSession: 网络异常,保留本地登录态')
return false
}
appLog('warn', '[session] restoreSession: 未知异常,保留本地登录态', error)
return false
} finally {
openSessionGate()
sessionRestorePromise = null
}
})()
return sessionRestorePromise
}
/** 仅在从后台恢复时续会话,避免首次冷启动重复打断登录页 */
export const restoreSessionIfNeeded = async () => {
if (!needSessionRestore) {
appLog('info', '[session] restoreSessionIfNeeded: 无需续会话')
openSessionGate()
return true
}
appLog('info', '[session] restoreSessionIfNeeded: 触发续会话')
needSessionRestore = false
return restoreSession()
}
// 组装接口要求的公共请求头
const buildHeaders = (withToken = true) => {
const headers = {
'Content-Type': 'application/json'
}
// 如有额外网关签名、appId 等,统一在这里追加
// headers['X-App-Id'] = 'xxx'
// headers['X-Sign'] = 'xxx'
if (withToken) {
const token = uni.getStorageSync('token')
if (token) {
headers.Authorization = `Bearer ${token}`
}
}
return headers
}
// 统一处理 HTTP 状态码和业务状态码(文档规范)
const handleResponse = (res, meta = {}) => {
const { statusCode, data } = res
const apiLabel = `${meta.tag || ''}${meta.path || ''}` || 'unknown'
const methodLabel = meta.method || ''
const traceLabel = meta.traceId || ''
// 1. HTTP 层异常
if (statusCode !== 200) {
let msg = '网络请求异常'
if (statusCode === 400) msg = '请求参数错误'
if (statusCode === 401) msg = '未授权访问'
if (statusCode === 403) msg = '权限不足'
if (statusCode === 404) msg = '资源不存在'
if (statusCode >= 500) msg = '服务器内部错误'
appLog(
statusCode === 401 ? 'warn' : 'error',
'[request][http-error]',
`${methodLabel} ${apiLabel}`,
`http=${statusCode}`,
msg,
data && data.msg,
data && data.code,
traceLabel ? `traceId=${traceLabel}` : ''
)
// 401 交给上层静默刷新,避免先弹出错误打断体验
if (statusCode !== 401 && !meta.quiet) {
uni.showToast({
title: `${msg} (${statusCode})`,
icon: 'none'
})
}
console.log('[request][http-error]', res)
throw Object.assign({}, res, { code: statusCode === 401 ? 401 : (data && data.code) })
}
// 2. 业务层:文档约定 code=200 成功,其它为错误
if (data && isBizSuccessCode(data.code)) {
// 返回真正的业务数据data 为 null 时退回完整响应,便于从 msg 等字段解析
if (data.data !== undefined && data.data !== null) {
return data.data
}
return data
}
const bizCode = data && data.code
const bizMsg = (data && data.msg) || '请求失败'
appLog(
Number(bizCode) === 401 ? 'warn' : 'error',
'[request][biz-error]',
`${methodLabel} ${apiLabel}`,
`code=${bizCode}`,
bizMsg,
traceLabel ? `traceId=${traceLabel}` : ''
)
if (Number(bizCode) !== 401 && !meta.quiet) {
uni.showToast({
title: `${bizMsg}${bizCode ? ` (${bizCode})` : ''}`,
icon: 'none'
})
}
console.log('[request][biz-error]', data)
throw Object.assign({}, data || {}, { code: bizCode, statusCode })
}
/**
* 统一 PAD 公网入口请求封装
*
* 签名流程:
* 1. 前端先准备业务参数(不含 signature
* 2. 调用签名服务(同样走 /public传入 { tag, path, query, body, timestamp, nonce }
* 3. 后端用 secret key 计算 HMAC-SHA256返回 signature
* 4. 前端携带 signature/timestamp/nonce 调用真正网关入口 /public
*
* @param {Object} options
* - tag: 路由标签(必填),如 'pad.auth'
* - path: map.path 子路径(必填),如 '/login'
* - method: 业务含义上的 method必填如 'GET' / 'POST',会写入 map.head.method
* - query: 对应 map.query 对象
* - body: 对应 map.body 对象
* - traceId: 可选,自定义链路号;不传则自动生成
* - withToken: 是否携带 token登录等接口传 false
* - skipSign: 是否跳过签名(登录 /login 时传 true不调 /api/sign、不校验签名
* - loading: 是否展示 loading
* - loadingText: loading 文案
*/
const request = (options) => {
return runRequest(options)
}
const runRequest = async (options) => {
const {
tag,
path,
method = 'GET',
query = {},
body = {},
traceId,
withToken = true,
skipSign = false,
skipSessionGate = false,
quiet = false,
loading: showLoading = true,
loadingText = '加载中...',
_retry401 = false,
_skip401Retry = false
} = options
const isRefreshRequest = tag === REFRESH_TAG && path === REFRESH_PATH
if (!skipSign && !isRefreshRequest && !skipSessionGate) {
await waitSessionReady()
}
if (!tag) {
throw new Error('request 需要传入 tag')
}
if (!path) {
throw new Error('request 需要传入 path')
}
const finalTraceId = traceId || genTraceId()
const normalizedMethod = String(method || 'GET').toUpperCase()
const normalizedQuery = sortKeysDeep(clonePayloadPart(query))
const normalizedBody = sortKeysDeep(clonePayloadPart(body))
// 登录等 skipSign 场景:不携带 token
const useToken = skipSign ? false : withToken
const token = useToken ? uni.getStorageSync('token') : ''
const authHeader = token ? `Bearer ${token}` : ''
const doRequest = (payload, withTokenHeader = useToken) => {
const reqMeta = {
tag,
path,
method: normalizedMethod,
traceId: finalTraceId,
quiet
}
return new Promise((resolve, reject) => {
uni.request({
url: baseURL,
method: 'POST',
data: payload,
header: buildHeaders(withTokenHeader),
success: (res) => {
try {
const result = handleResponse(res, reqMeta)
resolve(result)
} catch (e) {
console.log('[request][handleResponse-error]', e)
reject(e)
}
},
fail: (err) => {
appLog(
'error',
'[request][network-fail]',
`${normalizedMethod} ${tag}${path}`,
err && err.errMsg,
finalTraceId ? `traceId=${finalTraceId}` : ''
)
if (!quiet) {
uni.showToast({
title: '网络请求失败',
icon: 'none'
})
}
console.log('[request][network-fail]', err)
reject(err)
},
complete: () => {
if (showLoading) {
loading.value = false
uni.hideLoading()
}
}
})
})
}
// 401 自动刷新 token并发请求共用一次刷新
const with401Retry = (executor) => {
return executor().catch(async (err) => {
const isRefreshRequest = tag === REFRESH_TAG && path === REFRESH_PATH
if (!_retry401 && !_skip401Retry && !isRefreshRequest && isUnauthorizedError(err)) {
appLog(
'warn',
'[request][401]',
`tag=${tag}`,
`path=${path}`,
'触发静默刷新并重试'
)
try {
await refreshAccessToken()
// 刷新后重试一次(重走签名流程)
appLog('info', '[request][401]', `tag=${tag}`, `path=${path}`, '刷新成功,重试原请求')
return await request({
...options,
_retry401: true,
loading: false
})
} catch (refreshErr) {
appLog('error', '[request][401]', '刷新失败', refreshErr)
if (isRefreshAuthFailure(refreshErr)) {
clearAuthAndGoLogin()
} else {
appLog('warn', '[request][401]', '刷新失败(非鉴权原因),保留登录态')
}
throw err
}
}
if (_retry401 && isUnauthorizedError(err)) {
appLog('error', '[request][401]', `tag=${tag}`, `path=${path}`, '重试后仍 401')
clearAuthAndGoLogin()
}
throw err
})
}
if (showLoading) {
loading.value = true
uni.showLoading({
title: loadingText,
mask: true
})
}
// 登录等接口:跳过 /api/sign直接请求业务网关head 中不含 signature/timestamp/nonce
if (skipSign) {
const payload = {
tag,
traceId: finalTraceId,
map: {
traceId: finalTraceId,
head: {
method: normalizedMethod,
contentType: 'application/json',
...(authHeader ? { Authorization: authHeader } : {})
},
path,
query: normalizedQuery,
body: normalizedBody
}
}
return with401Retry(() => doRequest(payload))
}
// 常规流程:先走统一入口调用签名服务,再携带 signature/timestamp/nonce 调业务接口
const timestamp = Date.now()
const nonce = genNonce()
const tokenForSign = uni.getStorageSync('token')
const signPayload = {
tag: SIGN_TAG,
traceId: finalTraceId,
map: {
traceId: finalTraceId,
head: {
method: 'POST',
contentType: 'application/json',
timestamp,
nonce,
// 调用签名服务时在 head 中显式传入 token
...(tokenForSign ? { Authorization: `Bearer ${tokenForSign}` } : {})
},
path: SIGN_PATH,
query: {},
// 签名参数需与目标业务请求一致:{ tag, path, query, body, timestamp, nonce }
body: sortKeysDeep({
tag,
path,
query: normalizedQuery,
body: normalizedBody,
timestamp,
nonce
})
}
}
if (isRefreshRequest) {
appLog(
'info',
'[auth][refresh-sign]',
'准备调用 /sign',
`baseURL=${baseURL}`,
`traceId=${finalTraceId}`,
`hasAccessToken=${Boolean(tokenForSign)}`,
`accessTokenLen=${tokenForSign ? String(tokenForSign).length : 0}`,
`timestamp=${timestamp}`,
`nonce=${nonce}`,
'signBody=',
{
tag,
path,
query: normalizedQuery,
bodyKeys: Object.keys(normalizedBody || {}),
hasRefreshToken: Boolean(normalizedBody && normalizedBody.refreshToken),
refreshTokenLen: normalizedBody && normalizedBody.refreshToken
? String(normalizedBody.refreshToken).length
: 0,
timestamp,
nonce,
}
)
flushLogs()
}
return with401Retry(() => new Promise((resolve, reject) => {
uni.request({
// 签名服务也通过公网统一入口 /public由 SIGN_TAG + SIGN_PATH 路由到内部 /api/sign
url: baseURL,
method: 'POST',
data: signPayload,
header: buildHeaders(useToken),
success: (signRes) => {
try {
const signData = signRes.data || {}
const signOk =
signRes.statusCode === 200 && isBizSuccessCode(signData.code)
if (isRefreshRequest) {
const dataKeys =
signData.data && typeof signData.data === 'object'
? Object.keys(signData.data)
: []
const rawSig =
(signData.data && signData.data.signature) ||
signData.signature ||
''
appLog(
'info',
'[auth][refresh-sign]',
'/sign 返回',
`http=${signRes.statusCode}`,
`code=${signData.code}`,
`msg=${signData.msg || signData.message || signData.error || ''}`,
`signOk=${signOk}`,
`dataKeys=${JSON.stringify(dataKeys)}`,
`hasSignature=${Boolean(rawSig)}`,
`signatureLen=${rawSig ? String(rawSig).length : 0}`,
`signaturePrefix=${rawSig ? String(rawSig).slice(0, 12) : ''}`,
'signData=',
signData
)
flushLogs()
}
if (!signOk) {
const signErr = Object.assign({}, signRes, {
code: signData.code,
msg:
signData.error ||
signData.msg ||
signData.message ||
'获取签名失败',
data: signData
})
// 401不弹 Toast交给 with401Retry 静默刷新后重试
if (isUnauthorizedError(signErr)) {
appLog(
'warn',
'[request][sign]',
`业务=${tag}${path}`,
'签名接口返回 401准备静默刷新'
)
} else {
appLog(
'error',
'[request][sign]',
`业务=${tag}${path}`,
'获取签名失败',
signErr.msg,
signData.code
)
if (!quiet) {
uni.showToast({
title: getErrorMessage(signErr, '获取签名失败'),
icon: 'none'
})
}
}
console.log('[request][sign-response-error]', signRes)
console.log('[request][sign-request-body]', {
tag,
path,
method: normalizedMethod,
query: normalizedQuery,
body: normalizedBody,
})
throw signErr
}
const signature =
(signData.data && signData.data.signature) || signData.signature
if (!signature) {
appLog(
'error',
'[request][sign]',
`业务=${tag}${path}`,
'签名结果为空',
`traceId=${finalTraceId}`,
'signData=',
signData
)
console.log('[request][sign-empty]', signRes)
throw new Error('签名结果为空')
}
const payload = {
tag,
traceId: finalTraceId,
map: {
traceId: finalTraceId,
head: {
method: normalizedMethod,
contentType: 'application/json',
signature,
timestamp,
nonce,
...(authHeader ? { Authorization: authHeader } : {})
},
path,
query: normalizedQuery,
body: normalizedBody
}
}
if (isRefreshRequest) {
const head = payload.map.head || {}
appLog(
'info',
'[auth][refresh-req]',
'即将调用 /refresh',
`baseURL=${baseURL}`,
`traceId=${finalTraceId}`,
`path=${payload.map.path}`,
`hasSignature=${Boolean(head.signature)}`,
`signatureLen=${head.signature ? String(head.signature).length : 0}`,
`signaturePrefix=${head.signature ? String(head.signature).slice(0, 12) : ''}`,
`timestamp=${head.timestamp}`,
`nonce=${head.nonce}`,
`hasAuthorization=${Boolean(head.Authorization)}`,
`bodyHasRefreshToken=${Boolean(
payload.map.body && payload.map.body.refreshToken
)}`,
'headKeys=',
Object.keys(head)
)
flushLogs()
}
doRequest(payload)
.then((result) => {
if (isRefreshRequest) {
appLog(
'info',
'[auth][refresh-req]',
'/refresh 业务成功',
`hasAccessToken=${Boolean(
result && (result.access_token || result.token)
)}`,
`hasRefreshToken=${Boolean(
result && (result.refresh_token || result.refreshToken)
)}`
)
flushLogs()
}
resolve(result)
})
.catch((err) => {
if (isRefreshRequest) {
appLog(
'error',
'[auth][refresh-req]',
'/refresh 业务失败',
`http=${err && err.statusCode}`,
`code=${getBizCode(err)}`,
`msg=${getErrorMessage(err, '')}`,
'err=',
err
)
flushLogs()
}
reject(err)
})
} catch (err) {
if (showLoading) {
loading.value = false
uni.hideLoading()
}
if (!isUnauthorizedError(err)) {
appLog(
'error',
'[request][sign-process-error]',
`业务=${tag}${path}`,
getErrorMessage(err, '签名流程异常'),
`traceId=${finalTraceId}`
)
}
console.log('[request][sign-process-error]', err)
reject(err)
}
},
fail: (err) => {
if (showLoading) {
loading.value = false
uni.hideLoading()
}
if (isRefreshRequest) {
appLog(
'error',
'[auth][refresh-sign]',
'/sign 网络失败',
err && err.errMsg,
`traceId=${finalTraceId}`
)
flushLogs()
}
appLog(
'error',
'[request][sign-network-fail]',
`业务=${tag}${path}`,
err && err.errMsg,
`traceId=${finalTraceId}`
)
if (!quiet) {
uni.showToast({
title: '获取签名接口失败',
icon: 'none'
})
}
console.log('[request][sign-network-fail]', err)
reject(err)
}
})
}))
}
const refreshAccessToken = () => {
if (refreshPromise) {
appLog('info', '[auth] refreshAccessToken: 复用进行中的刷新请求')
return refreshPromise
}
refreshPromise = (async () => {
const refreshToken =
uni.getStorageSync('refresh_token') ||
uni.getStorageSync('refreshToken')
if (!refreshToken) {
appLog('error', '[auth] refreshAccessToken: refresh token 不存在')
throw new Error('refresh token 不存在')
}
appLog(
'info',
'[auth] refreshAccessToken: 开始刷新 token签名网关',
`baseURL=${baseURL}`,
`hasAccessToken=${Boolean(uni.getStorageSync('token'))}`,
`refreshTokenLen=${String(refreshToken).length}`
)
flushLogs()
// /sign 需要 Bearer短时后台时旧 access_token 通常仍可用来签名
// 若 access 已失效,签名会 401由上层决定是否登出
const tokenData = await runRequest({
tag: REFRESH_TAG,
path: REFRESH_PATH,
method: 'POST',
body: { refreshToken },
withToken: true,
quiet: true,
loading: false,
skipSessionGate: true,
_skip401Retry: true,
})
const newAccessToken =
tokenData?.access_token || tokenData?.token || ''
const newRefreshToken =
tokenData?.refresh_token || tokenData?.refreshToken || ''
if (!newAccessToken) {
appLog(
'error',
'[auth] refreshAccessToken: 刷新成功但 access token 为空',
'tokenDataKeys=',
tokenData && typeof tokenData === 'object' ? Object.keys(tokenData) : [],
'tokenData=',
tokenData
)
throw new Error('刷新成功但 access token 为空')
}
uni.setStorageSync('token', newAccessToken)
if (newRefreshToken) {
uni.setStorageSync('refresh_token', newRefreshToken)
}
appLog(
'info',
'[auth] refreshAccessToken: 刷新成功',
`accessTokenLen=${String(newAccessToken).length}`,
`gotNewRefreshToken=${Boolean(newRefreshToken)}`
)
flushLogs()
return newAccessToken
})().catch((err) => {
appLog(
'error',
'[auth] refreshAccessToken: 最终失败',
`code=${getBizCode(err)}`,
`msg=${getErrorMessage(err, '')}`,
err
)
flushLogs()
throw err
}).finally(() => {
refreshPromise = null
})
return refreshPromise
}
export default request