const api = require('./api'); let loginPromise = null; function getToken() { return wx.getStorageSync('token') || ''; } function setToken(token) { wx.setStorageSync('token', token); } function clearToken() { wx.removeStorageSync('token'); // 顺带清掉签名 URL 缓存:换账号后不该复用上一个人的图片地址 api.clearSignedCache(); } function wxLogin(retries) { const remain = typeof retries === 'number' ? retries : 3; return new Promise((resolve, reject) => { wx.login({ success(res) { if (res && res.code) { resolve(res.code); } else { reject(new Error((res && res.errMsg) || '登录失败')); } }, fail: reject }); }).catch((err) => { // 冷启动时微信登录服务可能尚未就绪,指数退避重试 if (remain > 0) { const delay = 400 + (4 - remain) * 200; // 首次 600ms,依次 800ms、1000ms return new Promise((resolve) => setTimeout(resolve, delay)) .then(() => wxLogin(remain - 1)); } throw err; }); } // 整个微信登录流程失败时重新取 code 再试一次,覆盖冷启动首个网络请求偶发失败 function wechatLogin(retries) { const remain = typeof retries === 'number' ? retries : 1; return wxLogin() .then((code) => api.request({ url: '/api/v1/auth/wechat-login', method: 'POST', data: { code }, auth: false })) .catch((err) => { if (remain > 0) { return new Promise((resolve) => setTimeout(resolve, 500)).then(() => wechatLogin(remain - 1)); } throw err; }); } // 单例登录:App.onLaunch 与首屏页面 onShow 可能几乎同时调用, // 共享同一个进行中的登录 Promise,避免重复 wx.login / wechat-login 造成竞态。 // 关键:登录失败时必须清空单例,让下一次调用能重新发起, // 否则冷启动 wx.login 偶发失败后,后续页面永远拿不到 token。 function ensureLogin(refresh) { const existing = getToken(); if (existing && !refresh) { return Promise.resolve(existing); } if (loginPromise) { return loginPromise; } const p = wechatLogin() .then((res) => { if (res && res.success) { setToken(res.token); return res.token; } // need_phone:未绑定手机号时 resolve(null),由页面引导去「我的」绑定 return null; }) .finally(() => { // 无论成功失败,完成后都清掉单例,让下一次调用可以重新发起 if (loginPromise === p) { loginPromise = null; } }); loginPromise = p; return p; } function loginWithPhone(phoneCode) { return wxLogin() .then((code) => api.request({ url: '/api/v1/auth/bind-phone', method: 'POST', data: { code, phone_code: phoneCode }, auth: false })) .then((res) => { if (res && res.success) { setToken(res.token); return res; } const error = new Error((res && res.error) || '手机号登录失败'); error.error = (res && res.error) || '手机号登录失败'; throw error; }); } module.exports = { getToken, setToken, clearToken, wxLogin, ensureLogin, loginWithPhone };