This commit is contained in:
2026-04-16 12:20:40 +08:00
parent e23c6158da
commit 85465314af
2 changed files with 0 additions and 377 deletions

View File

@@ -1,256 +0,0 @@
# 微信 H5 授权流程重构 - 设计文档
## 📋 核心业务需求
在微信 H5 环境中,**整个应用在没有有效 token 时,都需要进行微信授权登录**。授权完成后,用户应该被重定向回他们尝试访问的原始页面。
---
## 🔄 流程设计
### **完整的授权流程图**
```
┌─────────────────────────────────────────────────────────────┐
│ 用户在微信中访问任意页面(无 token
└──────────────────┬──────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 路由守卫检测:微信 + 无 token │
│ → 保存目标路由到 authStore.pendingRoute │
│ → 生成微信授权 URL │
│ → window.location.href = 授权 URL (不调用 next()
└──────────────────┬──────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 跳转到微信授权页面 │
│ https://open.weixin.qq.com/connect/oauth2/authorize?... │
└──────────────────┬──────────────────────────────────────────┘
┌─────────┴─────────┐
│ 用户点击"同意" │
└────────┬──────────┘
┌─────────────────────────────────────────────────────────────┐
│ 微信回调redirectUri?code=xxx&state=yyy │
│ 浏览器重新加载应用URL 中包含 code/state 参数 │
└──────────────────┬──────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ App.vue onMounted 检测到 code + state 参数 │
│ → 调用 handleWeixinAuthCallback(code) │
└──────────────────┬──────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 1. 调用后端接口 /user/wxh5Auth 交换 token │
│ 2. 保存 token 到 localStorage │
│ 3. 清理 URL 中的 code/state 参数 │
│ 4. 获取用户信息 │
│ 5. 标记授权完成 authStore.completeWeixinAuth() │
└──────────────────┬──────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 获取 pendingRoute重定向回原始页面 │
│ router.replace(pendingRoute) │
│ 如果没有 pendingRoute重定向到首页 "/" │
└──────────────────┬──────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ✅ 授权完成,用户看到原始页面 │
└─────────────────────────────────────────────────────────────┘
```
---
## 📁 关键文件修改
### **1. `src/router/index.js` - 路由守卫**
**关键变化:**
- 检测到 **微信 + 无 token** 的情况
- **直接发起授权**(调用 `window.location.href`
- **不调用 `next()`**,完全停止导航
- 授权 URL 中的 redirectUri 指向当前页面(清理旧的 code/state 参数)
**代码片段:**
```javascript
if (isWeChat.value && !isAuthenticated && !isTokenExpired) {
// 保存目标路由
authStore.startWeixinAuth(to);
// 生成授权 URL
const appId = import.meta.env.VITE_WECHAT_APP_ID;
const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
params.delete("code");
params.delete("state");
const cleanUrl = `${url.origin}${url.pathname}${params.toString() ? "?" + params.toString() : ""}`;
const redirectUri = encodeURIComponent(cleanUrl);
const weixinAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_base&state=snsapi_base#wechat_redirect`;
// 直接跳转,不调用 next()
window.location.href = weixinAuthUrl;
return;
}
```
### **2. `src/App.vue` - 授权回调处理**
**关键职责:**
1. **检测回调**:读取 URL 中的 code/state 参数
2. **处理回调**:调用 `/user/wxh5Auth` 交换 token
3. **恢复导航**:使用保存的 pendingRoute 重定向用户
**核心函数:`handleWeixinAuthCallback(code)`**
```javascript
const handleWeixinAuthCallback = async (code) => {
// 1. 交换 token
const { data, error } = await useApiFetch("/user/wxh5Auth").post({ code }).json();
// 2. 保存 token
localStorage.setItem("token", data.value.data.accessToken);
localStorage.setItem("refreshAfter", data.value.data.refreshAfter);
localStorage.setItem("accessExpire", data.value.data.accessExpire);
// 3. 清理 URL
const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
params.delete("code");
params.delete("state");
window.history.replaceState({}, "", newUrl);
// 4. 获取用户信息
await userStore.fetchUserInfo();
// 5. 标记授权完成
authStore.completeWeixinAuth();
// 6. 重定向回 pendingRoute
const pendingRoute = authStore.pendingRoute;
await router.replace(pendingRoute || "/");
};
```
### **3. `src/stores/authStore.js` - 授权状态管理**
**关键方法:**
| 方法 | 作用 |
|------|------|
| `startWeixinAuth(targetRoute)` | 开始授权,保存目标路由 |
| `completeWeixinAuth()` | 标记授权完成 |
| `clearPendingRoute()` | 清除待处理路由 |
| `resetAuthState()` | 重置所有授权状态 |
| `restoreFromStorage()` | 页面刷新后恢复状态 |
---
## 🎯 核心特性
### **1. 同步流程控制**
-**不使用** `watch` 监听 state 的变化
-**不使用** 异步的 route guard + next() 来触发授权
-**直接** 在 route guard 中调用 `window.location.href` 发起授权
**为什么?**
- Watch 是异步的,可能被路由完成打断
- next() 后可能路由已经切换,导致授权流程混乱
- 直接跳转 URL 更加可靠和同步
### **2. 状态持久化**
- 授权开始时保存到 localStorage
- 页面刷新时自动恢复状态
- 防止超时30秒导致的无限授权循环
### **3. URL 参数清理**
- 每次发起授权前,都清理 URL 中旧的 code/state 参数
- 防止参数被重复编码进 redirectUri
- 微信回调时才注入新的 code/state
### **4. 目标页面恢复**
- 路由守卫保存用户尝试访问的页面
- 授权完成后自动重定向到该页面
- 用户无感知的完整授权体验
---
## 🔐 场景处理
### **场景 1无 token + 访问开放页面**
```
用户访问 "/" → 微信检测无 token → 发起授权 → 授权完成后回到首页
```
### **场景 2无 token + 访问推广链接**
```
用户访问 "/agent/promotionInquire/abc123"
→ 微信检测无 token
→ 保存 pendingRoute = "/agent/promotionInquire/abc123"
→ 发起授权
→ 授权完成后回到 "/agent/promotionInquire/abc123"
```
### **场景 3有效 token + 访问任意页面**
```
用户有 token 且未过期 → 路由守卫放行 → 直接加载页面,无授权
```
### **场景 4Token 过期 + 访问需登录页面**
```
用户在非微信环境 → 检测到 token 过期 → 跳转登录页面
```
### **场景 5授权超时**
```
用户授权流程中30 秒内未完成 → 自动重置状态 → 页面刷新时重新授权
```
---
## 🐛 常见问题排查
### **Q1为什么授权后没有跳到原页面**
**A** 检查 authStore 中是否有 pendingRoute
```javascript
console.log('pendingRoute:', authStore.pendingRoute);
```
### **Q2为什么一直在授权页面循环**
**A** 可能是授权回调处理失败。检查:
1. 后端 `/user/wxh5Auth` 接口是否正确返回 token
2. Token 是否正确保存到 localStorage
3. URL 中的 code/state 是否正确清理
### **Q3为什么刷新页面后授权状态丢失**
**A** authStore.restoreFromStorage() 应该在 onMounted 中被调用。检查:
```javascript
authStore.restoreFromStorage(); // 这行很重要!
```
### **Q4在 PC 上测试,为什么无法触发授权?**
**A** 授权只在微信环境中触发isWeChat.value === true。在 PC 上:
- 访问需登录的页面 → 跳转登录页
- 访问开放页面 → 正常加载
---
## 📝 总结
这个重构的核心思想是:
1. **路由守卫 = 决策者**:检测到需要授权就立即发起
2. **App.vue = 处理器**:处理授权回调和状态恢复
3. **AuthStore = 状态管理**:保存和恢复授权相关的状态
4. **同步流程 = 高可靠性**:避免异步竞态条件
通过这个设计,微信授权流程变得:
- ✅ 清晰易懂
- ✅ 可靠稳定
- ✅ 易于测试和调试
- ✅ 完整的用户体验

View File

@@ -1,121 +0,0 @@
# 微信授权重定向问题分析与修复
## 问题描述
微信授权回调成功后,虽然 `pendingRoute` 有正确的值,回调成功也能跳转到应该到达的页面(如 `promotionInquire`),但在加载该页面后,又会自动跳转到首页。
## 根本原因分析
### 问题 1: pendingRoute 清除时序错误 ❌
**位置**: `src/App.vue` 第 69-72 行
**原始代码**:
```javascript
if (pendingRoute) {
authStore.clearPendingRoute(); // ❌ 先清除
await router.replace(pendingRoute); // 用已清空的 null 值跳转
}
```
**问题**
- `clearPendingRoute()``authStore.pendingRoute` 设为 `null`
- 之后 `router.replace(pendingRoute)` 使用的是已清空的 `null`
- 导致实际跳转到 `undefined` 而非目标路由
**修复**: 先跳转再清除 ✅
### 问题 2: 路由守卫重复授权防护缺失 ❌
**位置**: `src/router/index.js` 第 428-444 行
**问题**
- 当用户在授权完成后,路由守卫再次执行时
- 如果 `isWeChat.value && !isAuthenticated` 条件满足
- 会重新触发授权流程,导致页面跳转
**原因**:
- `authStore.isWeixinAuthing``authStore.weixinAuthComplete` 状态未被路由守卫检查
- 虽然 token 已保存,但守卫仍可能因某些时序问题再次触发授权
**修复**: 在路由守卫中增加授权状态检查 ✅
### 问题 3: localStorage 同步问题
**位置**: `src/stores/authStore.js`
**问题**
- 清除 `pendingRoute`可能只清除内存状态localStorage 可能残留数据
- 页面刷新时恢复的数据可能不完整
**修复**: 确保内存和 localStorage 同步清除 ✅
## 修复清单
### ✅ 修复 1: App.vue 中的时序问题
```javascript
if (pendingRoute) {
// ✅ 先跳转
await router.replace(pendingRoute);
// ✅ 再清除
authStore.clearPendingRoute();
}
```
### ✅ 修复 2: 路由守卫的防护检查
在路由守卫中增加:
```javascript
if (isWeChat.value && !isAuthenticated && !isTokenExpired) {
// ✨ 新增:检查是否正在授权或已完成授权
if (authStore.isWeixinAuthing || authStore.weixinAuthComplete) {
console.warn("⚠️ WeChat auth already in progress or completed");
NProgress.done();
next();
return;
}
// ... 继续原有逻辑
}
```
### ✅ 修复 3: authStore 中的日志和同步
增强 `clearPendingRoute()``restoreFromStorage()` 方法的日志记录和错误处理。
### ✅ 修复 4: PromotionInquire.vue 中的延迟处理
添加延迟以确保页面完全加载后再进行任何路由跳转:
```javascript
function isFinishPayment() {
const query = new URLSearchParams(window.location.search);
let orderNo = query.get("out_trade_no");
if (orderNo) {
// ✨ 延迟 100ms 确保页面加载完成
setTimeout(() => {
router.push({ path: "/report", query: { orderNo } });
}, 100);
}
}
```
## 调试建议
### 观察日志的关键点:
1. **微信授权开始**: 看是否出现 "Triggering WeChat auth from route guard"
2. **Token 保存**: 看是否出现 "✅ Token saved successfully"
3. **用户信息加载**: 看 "✅ User info fetched" 是否成功
4. **pendingRoute 获取**: 看 "🎯 pendingRoute:" 后面的值
5. **导航执行**: 看 "🚀 Navigating to pendingRoute:" 和 "✅ Navigated to pendingRoute"
6. **是否重复授权**: 看是否出现 "⚠️ WeChat auth already in progress"
### 测试步骤:
1. 在微信中打开推广链接: `https://xxx.com/agent/promotionInquire/abc123`
2. 观察控制台日志,确保看到上述所有成功日志
3. 验证最终页面是 `promotionInquire` 而非首页
## 修复文件列表
-`src/App.vue` - 修复 pendingRoute 时序问题
-`src/router/index.js` - 增强路由守卫防护
-`src/stores/authStore.js` - 改进日志和同步
-`src/views/PromotionInquire.vue` - 添加延迟处理
## 总结
这个问题是由多个时序和状态管理问题联合造成的:
1. pendingRoute 被过早清除导致跳转失败
2. 路由守卫缺少防护措施可能重复授权
3. 状态同步不完整可能导致恢复失败
通过修复这些问题,应该能够确保微信授权后正确重定向到目标页面。