|
@@ -4,6 +4,8 @@ const BASE_URL = config.API_BASE_URL
|
|
|
|
|
|
|
|
var _redirectingToLogin = false
|
|
var _redirectingToLogin = false
|
|
|
var _reloginLock = false
|
|
var _reloginLock = false
|
|
|
|
|
+// 续期在途期间收到的其他 401 请求,排队等待续期成功后统一重放(见 _handle401)
|
|
|
|
|
+var _pending401 = []
|
|
|
|
|
|
|
|
// === 请求去重(Dedup)全局安全网 ===
|
|
// === 请求去重(Dedup)全局安全网 ===
|
|
|
// 同 URL + method + body 在 DEDUP_WINDOW_MS 窗口内复用同一个 Promise,
|
|
// 同 URL + method + body 在 DEDUP_WINDOW_MS 窗口内复用同一个 Promise,
|
|
@@ -126,37 +128,62 @@ function _applyLoginData(data) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 401 处理:优先静默续期(wx.login code2Session)并重放原请求;续期失败则回退跳登录页
|
|
// 401 处理:优先静默续期(wx.login code2Session)并重放原请求;续期失败则回退跳登录页
|
|
|
|
|
+//
|
|
|
|
|
+// 关键:并发 401 必须「排队」而不是「清登录态」。
|
|
|
|
|
+// 首页冷启动一次会并发打出多个 401(family/member/list、membership/my、
|
|
|
|
|
+// onboarding/progress、home/app-library、guide-config/current ...)。
|
|
|
|
|
+// 早期实现在 _reloginLock 为真时直接调 _clearAuthAndRedirect(),于是:第 1 个 401
|
|
|
|
|
+// 刚启动 silentRelogin()(wx.login 是异步的),第 2~N 个 401 立刻把 token 从
|
|
|
|
|
+// storage 抹掉并预约 1500ms 后 reLaunch 到登录页 → 在途的续期还没回调就被打断,
|
|
|
|
|
+// 表现为「控制台只有 401、一条续期日志都没有、用户被弹回登录页」。
|
|
|
|
|
+// 续期失败与否只能由发起续期的那一个 401 判断,其它 401 只需排队等结果。
|
|
|
function _handle401(url, method, data, options, resolve, reject, resData) {
|
|
function _handle401(url, method, data, options, resolve, reject, resData) {
|
|
|
- // 续期接口自身 401 / 重试仍 401 时,避免递归续期死循环
|
|
|
|
|
|
|
+ // 已有续期在途:挂起本请求等待重放。绝不能在这里动登录态,否则会毁掉在途续期。
|
|
|
if (_reloginLock) {
|
|
if (_reloginLock) {
|
|
|
- _clearAuthAndRedirect()
|
|
|
|
|
- reject(resData)
|
|
|
|
|
|
|
+ console.warn('[API] 401 续期在途,挂起请求 ' + url)
|
|
|
|
|
+ _pending401.push({ url: url, method: method, data: data, options: options, resolve: resolve, reject: reject, resData: resData })
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
_reloginLock = true
|
|
_reloginLock = true
|
|
|
|
|
+ var queue = [{ url: url, method: method, data: data, options: options, resolve: resolve, reject: reject, resData: resData }]
|
|
|
|
|
+ _pending401 = queue
|
|
|
|
|
+
|
|
|
|
|
+ function flushAll(err) {
|
|
|
|
|
+ var q = _pending401
|
|
|
|
|
+ _pending401 = []
|
|
|
|
|
+ _reloginLock = false
|
|
|
|
|
+ q.forEach(function (item) {
|
|
|
|
|
+ if (err) item.reject(err)
|
|
|
|
|
+ else item.reject(item.resData)
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
silentRelogin().then(function(loginData) {
|
|
silentRelogin().then(function(loginData) {
|
|
|
if (loginData && loginData.token) {
|
|
if (loginData && loginData.token) {
|
|
|
- console.warn('[API] 401 静默续期成功,重放原请求 ' + url)
|
|
|
|
|
|
|
+ console.warn('[API] 401 静默续期成功,重放 ' + queue.length + ' 个挂起请求')
|
|
|
_applyLoginData(loginData)
|
|
_applyLoginData(loginData)
|
|
|
- // 重放原请求;重试期间保持锁,防止重试仍 401 时再次触发续期
|
|
|
|
|
- // 401 重放请求跳过去重(原请求已 reject,缓存中的 promise 已过期)
|
|
|
|
|
- var _retryOpts = Object.assign({}, options, { _skipDedup: true })
|
|
|
|
|
- request(url, method, data, _retryOpts).then(function(r) {
|
|
|
|
|
- _reloginLock = false
|
|
|
|
|
- resolve(r)
|
|
|
|
|
- }, function(e) {
|
|
|
|
|
- _reloginLock = false
|
|
|
|
|
- reject(e)
|
|
|
|
|
|
|
+ // 重放:跳过去重(原 promise 已 reject,缓存已过期),
|
|
|
|
|
+ // 并带 _noRelogin —— 若重放仍 401 说明刚签发的 token 仍不可用,直接失败,
|
|
|
|
|
+ // 不能再触发新一轮续期,否则会 ping-pong 死循环。
|
|
|
|
|
+ var _retryOpts = { _skipDedup: true, _noRelogin: true }
|
|
|
|
|
+ var left = queue.length
|
|
|
|
|
+ queue.forEach(function (item) {
|
|
|
|
|
+ var opts = Object.assign({}, item.options, _retryOpts)
|
|
|
|
|
+ request(item.url, item.method, item.data, opts).then(function(r) {
|
|
|
|
|
+ item.resolve(r)
|
|
|
|
|
+ if (--left === 0) _reloginLock = false
|
|
|
|
|
+ }, function(e) {
|
|
|
|
|
+ item.reject(e)
|
|
|
|
|
+ if (--left === 0) _reloginLock = false
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
} else {
|
|
} else {
|
|
|
- _reloginLock = false
|
|
|
|
|
|
|
+ flushAll(resData)
|
|
|
_clearAuthAndRedirect()
|
|
_clearAuthAndRedirect()
|
|
|
- reject(resData)
|
|
|
|
|
}
|
|
}
|
|
|
}).catch(function() {
|
|
}).catch(function() {
|
|
|
- _reloginLock = false
|
|
|
|
|
|
|
+ flushAll(resData)
|
|
|
_clearAuthAndRedirect()
|
|
_clearAuthAndRedirect()
|
|
|
- reject(resData)
|
|
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -176,6 +203,7 @@ export function silentRelogin() {
|
|
|
// 无 openid 缓存 = 本设备从未成功登录过,属新用户,不做静默续期(直接引导手动登录)
|
|
// 无 openid 缓存 = 本设备从未成功登录过,属新用户,不做静默续期(直接引导手动登录)
|
|
|
var cachedOpenid = uni.getStorageSync('openid')
|
|
var cachedOpenid = uni.getStorageSync('openid')
|
|
|
if (!cachedOpenid) {
|
|
if (!cachedOpenid) {
|
|
|
|
|
+ console.warn('[Auth] 本地无 openid 缓存,跳过静默续期(需手动登录)')
|
|
|
resolve(null)
|
|
resolve(null)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|