From b3ce0fe148c664b7e560c28fc887aeebec5c9387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E6=A2=93=E5=BD=AD?= Date: Tue, 7 Apr 2026 23:50:41 +0800 Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=88=B7=E6=96=B0Token?= =?UTF-8?q?=E5=92=8C=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/apply/apply.js | 168 +++++++++++++++++++++++++++++++++++++- utils/http.js | 188 ++++++++++++++++++++++++++++++------------- 2 files changed, 300 insertions(+), 56 deletions(-) diff --git a/pages/apply/apply.js b/pages/apply/apply.js index b86db8a..9893981 100644 --- a/pages/apply/apply.js +++ b/pages/apply/apply.js @@ -607,10 +607,174 @@ Page({ }, /** 线上支付 */ - onPayOnline() { + async onPayOnline() { const payload = this._validateAndGetPayload(2); if (!payload) return; - this._submitApply(payload); + + try { + // 1. 获取 openid + const openid = await this._getOpenId(); + if (!openid) { + wx.showToast({ title: '获取 openid 失败', icon: 'none' }); + return; + } + + // 2. 提交报名 + this.setData({ submitting: true }); + const registrationId = await this._submitApplyForPayment(payload); + + // 3. 选择支付方式并获取支付参数 + await this._selectPaymentMethod(registrationId, openid, 2); + + // 4. 获取支付信息 + const paymentInfo = await this._getPaymentInfo(registrationId, openid); + + // 5. 调起微信支付 + await this._invokeWeChatPay(paymentInfo.wechatPayParams, registrationId); + + } catch (error) { + console.error('支付流程异常:', error); + wx.showToast({ + title: error && error.message ? error.message : '支付失败,请稍后重试', + icon: 'none' + }); + } finally { + this.setData({ submitting: false }); + } + }, + + /** 获取 openid */ + async _getOpenId() { + const app = getApp(); + const cached = + (app && app.globalData && app.globalData.openid) || wx.getStorageSync('openid') || ''; + if (cached) { + return cached; + } + + return new Promise((resolve, reject) => { + wx.login({ + success: (loginRes) => { + const code = loginRes.code; + if (!code) { + reject(new Error('未获取到 code')); + return; + } + + request({ + path: API.wechatOpenidByJsCode, + method: 'POST', + needAuth: false, + data: { code }, + contentType: 'application/json' + }) + .then((data) => { + const oid = + data && typeof data === 'object' + ? data.openId || data.OpenId || data.openid || '' + : ''; + const openid = typeof oid === 'string' ? oid : ''; + if (openid && app && typeof app.setAuth === 'function') { + app.setAuth({ openid }); + } + if (openid) { + resolve(openid); + } else { + reject(new Error('接口未返回 openid')); + } + }) + .catch((err) => { + reject(err); + }); + }, + fail: () => { + reject(new Error('wx.login 失败')); + } + }); + }); + }, + + /** 提交报名(用于支付流程) */ + _submitApplyForPayment(payload) { + return request({ + path: API.apply, + method: 'POST', + needAuth: true, + data: payload, + contentType: 'application/json' + }); + }, + + /** 选择支付方式 */ + _selectPaymentMethod(registrationId, openId, paymentMethod) { + return request({ + path: '/api/eventclient/select-payment-method', + method: 'POST', + needAuth: true, + data: { registrationId, openId, paymentMethod }, + contentType: 'application/json' + }); + }, + + /** 获取支付信息 */ + _getPaymentInfo(registrationId, openId) { + return request({ + path: '/api/eventclient/payment-info', + method: 'POST', + needAuth: true, + data: { registrationId, openId }, + contentType: 'application/json' + }); + }, + + /** 调起微信支付 */ + _invokeWeChatPay(wechatPayParams, registrationId) { + return new Promise((resolve, reject) => { + wx.requestPayment({ + ...wechatPayParams, + success: async () => { + try { + // 支付成功,同步支付状态 + await request({ + path: `/api/eventclient/sync-payment/${registrationId}`, + method: 'POST', + needAuth: true, + data: {}, + contentType: 'application/json' + }); + + wx.showToast({ title: '支付成功', icon: 'success' }); + setTimeout(() => { + wx.switchTab({ url: '/pages/mine/mine' }); + }, 1500); + resolve(); + } catch (err) { + console.error('同步支付状态失败:', err); + wx.showModal({ + title: '提示', + content: '支付成功,但同步状态失败,请稍后在订单列表中查看', + showCancel: false, + success: () => { + wx.switchTab({ url: '/pages/mine/mine' }); + } + }); + resolve(); + } + }, + fail: (err) => { + console.error('微信支付失败:', err); + if (err.errMsg && err.errMsg.includes('cancel')) { + wx.showToast({ title: '支付已取消', icon: 'none' }); + } else { + wx.showToast({ + title: err.errMsg || '支付失败,请稍后重试', + icon: 'none' + }); + } + reject(err); + } + }); + }); }, _submitApply(payload) { diff --git a/utils/http.js b/utils/http.js index ecbaffd..413f3f2 100644 --- a/utils/http.js +++ b/utils/http.js @@ -1,15 +1,27 @@ const { getBaseUrl } = require('./constants'); -function withAuthHeader(needAuth, header) { +// Token刷新状态和请求队列 +let isRefreshing = false; +let requestQueue = []; + +function withAuthHeader(needAuth, header, useRefreshTokenOnly = false) { const app = getApp(); if (!needAuth) return header; - const token = app && app.globalData ? app.globalData.accessToken : ''; + const accessToken = app && app.globalData ? app.globalData.accessToken : ''; const refreshToken = app && app.globalData ? app.globalData.refreshToken : ''; - if (typeof token === 'string' && token.length > 0) { - header = header || {}; - header.Authorization = `Bearer ${token}`; - header['x-access-token'] = `Bearer ${token}`; - header['x-refresh-token'] = `Bearer ${refreshToken}`; + + header = header || {}; + + if (useRefreshTokenOnly && refreshToken) { + // 只使用 refresh token 模式(用于刷新) + header['X-Authorization'] = `Bearer ${refreshToken}`; + } else if (accessToken) { + // 正常模式:使用 access token + header.Authorization = `Bearer ${accessToken}`; + // 同时带上 refresh token,供后端在需要时自动刷新 + if (refreshToken) { + header['X-Authorization'] = `Bearer ${refreshToken}`; + } } return header; } @@ -37,18 +49,68 @@ function getHeaderValue(headers, headerName) { function tryUpdateAccessTokenFromResponseHeaders(res) { // wx.request 的返回头在 res.header(部分环境也可能是 res.headers) - console.log( 'res', res); const headers = (res && res.header) || (res && res.headers) || null; - const newAccessToken = getHeaderValue(headers, 'x-access-token'); - if (!newAccessToken) return; + + // 从响应头获取新的 token + const newAccessToken = getHeaderValue(headers, 'access-token') || getHeaderValue(headers, 'x-access-token'); + const newRefreshToken = getHeaderValue(headers, 'x-access-token') || getHeaderValue(headers, 'x-refresh-token'); + + if (!newAccessToken && !newRefreshToken) return false; const app = getApp(); - if (app && typeof app.setAuth === 'function') { - app.setAuth({ accessToken: newAccessToken }); - } else { - wx.setStorageSync('accessToken', newAccessToken); - if (app && app.globalData) app.globalData.accessToken = newAccessToken; + const authData = {}; + let hasUpdate = false; + + if (newAccessToken && newAccessToken !== 'invalid_token') { + authData.accessToken = newAccessToken; + hasUpdate = true; } + if (newRefreshToken && newRefreshToken !== 'invalid_token') { + authData.refreshToken = newRefreshToken; + hasUpdate = true; + } + + if (hasUpdate) { + if (app && typeof app.setAuth === 'function') { + app.setAuth(authData); + } else { + if (authData.accessToken) { + wx.setStorageSync('accessToken', authData.accessToken); + if (app && app.globalData) app.globalData.accessToken = authData.accessToken; + } + if (authData.refreshToken) { + wx.setStorageSync('refreshToken', authData.refreshToken); + if (app && app.globalData) app.globalData.refreshToken = authData.refreshToken; + } + } + } + + // 如果正在刷新Token且获取到了新Token,执行队列中的请求 + if (isRefreshing && newAccessToken && newAccessToken !== 'invalid_token') { + isRefreshing = false; + requestQueue.forEach((callback) => callback(newAccessToken)); + requestQueue = []; + return true; + } + + return hasUpdate; +} + +function clearAllTokensAndRedirect() { + wx.clearStorageSync(); + const app = getApp(); + if (app && app.globalData) { + app.globalData.accessToken = ''; + app.globalData.refreshToken = ''; + } + wx.showModal({ + title: '登录失效', + content: '您的登录已失效,请重新登录。', + showCancel: false, + complete() { + wx.navigateTo({ url: '/subpackages/mine/login/login' }); + } + }); } function request({ @@ -58,14 +120,18 @@ function request({ needAuth = false, contentType = 'application/json', // Internal flag: prevent infinite retry when refresh token也失败 - __retry = false + __retry = false, + // Internal flag: 标记这是刷新token的重试请求 + __refreshRetry = false }) { const baseUrl = getBaseUrl(); const url = `${baseUrl}${path}`; + // 正常请求:同时带上 access token 和 refresh token + // 刷新重试请求:只使用 refresh token const header = withAuthHeader(needAuth, { 'content-type': contentType - }); + }, __refreshRetry); return new Promise((resolve, reject) => { wx.request({ @@ -75,31 +141,65 @@ function request({ header: header, success: async (res) => { // 如果后端在 Response Headers 下发新的 token,就替换本地 token - tryUpdateAccessTokenFromResponseHeaders(res); + const tokenUpdated = tryUpdateAccessTokenFromResponseHeaders(res); const statusCode = res && res.data && res.data.statusCode ? res.data.statusCode : 0; const payload = res && res.data !== undefined ? res.data : null; - // ── 401 未授权 ────────────────────────────────────────────── - if (statusCode === 401) { - wx.clearStorageSync(); - const app = getApp(); - if (app && app.globalData) { - app.globalData.accessToken = ''; - app.globalData.refreshToken = ''; + // ── 401 未授权:尝试刷新Token ── + if (statusCode === 401 && needAuth && !__retry) { + // 如果已经在刷新Token,将请求加入队列 + if (isRefreshing) { + requestQueue.push((newToken) => { + resolve(request({ path, method, data, needAuth, contentType, __retry: true })); + }); + return; } - wx.showModal({ - title: '登录失效', - content: '您的登录已失效,请重新登录。', - showCancel: false, - complete() { - wx.navigateTo({ url: '/subpackages/mine/login/login' }); + + // 开始刷新Token + // 重发原请求 + isRefreshing = true; + + try { + // 检查是否有 refresh token + const app = getApp(); + const refreshToken = app && app.globalData ? app.globalData.refreshToken : ''; + if (!refreshToken) { + throw new Error('No refresh token available'); } - }); + + // 重发请求 + await request({ + path, + method, + data, + needAuth, + contentType, + __retry: false, + __refreshRetry: true + }); + + // 刷新成功,现在重试原请求 + isRefreshing = false; + resolve(request({ path, method, data, needAuth, contentType, __retry: true })); + return; + } catch (refreshErr) { + // 刷新失败,清除Token并跳转登录 + isRefreshing = false; + requestQueue = []; + clearAllTokensAndRedirect(); + reject(new Error('Unauthorized')); + return; + } + } + + // ── 401 未授权(重试后仍然失败) ── + if (statusCode === 401) { + clearAllTokensAndRedirect(); reject(new Error('Unauthorized')); return; } - // ── 403 禁止访问 ───────────────────────────────────────────── + // ── 403 禁止访问 ── if (statusCode === 403) { wx.showToast({ title: '无访问权限', icon: 'none' }); reject(new Error('Forbidden')); @@ -141,27 +241,7 @@ function request({ resolve(payload); }, fail(err) { - if (err && err.statusCode === 401 && needAuth && !__retry) { - // 401: try refresh token, then retry the original request once. - const originalErr = err; - try { - const auth = require('./auth'); - if (auth && typeof auth.refreshAccessToken === 'function') { - auth - .refreshAccessToken() - .then(() => { - return request({ path, method, data, needAuth, contentType, __retry: true }); - }) - .then(resolve) - .catch(() => { - reject(originalErr); - }); - return; - } - } catch (e) { - // ignore and fall through to rejecting original error - } - } + // 网络请求失败处理 reject(err); } });