Files
cpzs-frontend/Store登录状态管理增强说明.md

352 lines
8.3 KiB
Markdown
Raw Normal View History

# Store 登录状态管理增强说明
## 📋 问题背景
在多设备登录踢出场景中,仅依赖 API 拦截器**被动响应**是不够的Store 层面也需要:
1. **主动检查**登录状态是否有效
2. **标记状态**区分正常登出和被踢出
3. **提供方法**供其他组件调用检查
---
## ✅ 已实现的增强功能
### 1. **新增状态字段**
```javascript
export const userStore = reactive({
user: null,
isLoggedIn: false,
isKickedOut: false, // 🆕 标记是否被其他设备踢出
lastCheckTime: null, // 🆕 最后一次检查登录状态的时间
// ...
})
```
### 2. **增强 `logout()` 方法**
支持标记被踢出状态:
```javascript
// 正常登出
userStore.logout()
// 被踢出时登出
userStore.logout(true) // 会标记 isKickedOut = true
```
**实现代码:**
```javascript
logout(isKickedOut = false) {
// 如果是被踢出,标记状态
if (isKickedOut) {
this.isKickedOut = true
console.log('[安全] 账号在其他设备登录,当前会话已被踢出')
}
this.user = null
this.isLoggedIn = false
this.lastCheckTime = null
// 清除本地存储...
}
```
### 3. **增强 `adminLogout()` 方法**
同样支持标记被踢出:
```javascript
adminLogout(isKickedOut = false) {
if (isKickedOut) {
this.isKickedOut = true
console.log('[安全] 管理员账号在其他设备登录,当前会话已被踢出')
}
// 清除状态和存储...
}
```
### 4. **新增 `checkLoginStatus()` 方法** 🔥
主动检查登录状态是否仍然有效:
```javascript
async checkLoginStatus() {
// 1⃣ 如果没有登录,直接返回
if (!this.isLoggedIn) {
return { valid: false, reason: 'not_logged_in' }
}
// 2⃣ 避免频繁检查10秒内只检查一次
const now = Date.now()
if (this.lastCheckTime && (now - this.lastCheckTime) < 10000) {
return { valid: true, reason: 'recently_checked' }
}
try {
// 3⃣ 调用后端接口验证登录状态
const response = await lotteryApi.getLoginUser()
if (response.success === true) {
// ✅ 登录状态有效
this.lastCheckTime = now
this.isKickedOut = false
return { valid: true, reason: 'verified' }
} else {
// ❌ 登录状态无效
const message = response.message || ''
// 检查是否是被踢出
if (message.includes('其他设备登录') || message.includes('当前会话已失效')) {
this.logout(true) // 标记为被踢出
return { valid: false, reason: 'kicked_out', message }
} else {
this.logout(false)
return { valid: false, reason: 'session_expired', message }
}
}
} catch (error) {
// ⚠️ 网络错误等情况,不清除登录状态,让用户继续使用
console.error('[Store] 检查登录状态失败:', error)
return { valid: true, reason: 'check_failed', error }
}
}
```
### 5. **新增 `resetKickedOutStatus()` 方法**
用于重新登录后重置被踢出状态:
```javascript
resetKickedOutStatus() {
this.isKickedOut = false
}
```
### 6. **增强 `login()` 方法**
登录时自动重置被踢出状态:
```javascript
login(userInfo) {
// ... 设置用户信息
this.isLoggedIn = true
this.isKickedOut = false // 🆕 重置被踢出状态
this.lastCheckTime = Date.now() // 🆕 记录登录时间
// 保存到本地存储...
}
```
---
## 🎯 使用场景
### 场景 1组件中主动检查登录状态
```javascript
// 在关键操作前检查登录状态
async function performCriticalAction() {
const status = await userStore.checkLoginStatus()
if (!status.valid) {
if (status.reason === 'kicked_out') {
ElMessage.warning('您的账号已在其他设备登录')
// 跳转到登录页
router.push('/login')
} else {
ElMessage.error('登录已过期,请重新登录')
router.push('/login')
}
return
}
// 继续执行操作
// ...
}
```
### 场景 2路由守卫中使用
```javascript
// router/index.js
router.beforeEach(async (to, from, next) => {
if (to.meta.requiresAuth) {
const status = await userStore.checkLoginStatus()
if (!status.valid) {
next('/login')
return
}
}
next()
})
```
### 场景 3定期心跳检测可选
```javascript
// 在 App.vue 中设置定期检查
import { onMounted, onUnmounted } from 'vue'
import { userStore } from '@/store/user'
let heartbeatTimer = null
onMounted(() => {
// 每 5 分钟检查一次登录状态
heartbeatTimer = setInterval(async () => {
if (userStore.isLoggedIn) {
await userStore.checkLoginStatus()
}
}, 5 * 60 * 1000) // 5分钟
})
onUnmounted(() => {
if (heartbeatTimer) {
clearInterval(heartbeatTimer)
}
})
```
### 场景 4判断被踢出状态
```javascript
// 在登录页面显示提示
if (userStore.isKickedOut) {
ElMessage.warning({
message: '您的账号已在其他设备登录,请重新登录',
duration: 5000
})
userStore.resetKickedOutStatus() // 重置状态
}
```
---
## 🔄 完整流程
### 被动检测API 拦截器)
```
用户操作 → API 请求 → 后端检测 token 不一致 → 返回错误
API 拦截器捕获 → 调用 userStore.logout(true)
标记 isKickedOut = true → 显示提示 → 跳转登录页
```
### 主动检测Store 方法)
```
组件调用 checkLoginStatus()
10秒内检查过
├─ 是 → 返回缓存结果
└─ 否 → 调用后端 API
token 有效?
├─ 是 → 更新检查时间,返回 valid: true
└─ 否 → 检查错误类型
├─ 被踢出 → logout(true), 返回 kicked_out
└─ 其他 → logout(false), 返回 session_expired
```
---
## 🛡️ 防护机制
### 1. **防止频繁检查**
- 10 秒内只检查一次
- 避免给后端造成压力
### 2. **容错处理**
- 网络错误时不清除登录状态
- 让用户继续使用,避免误判
### 3. **状态标记**
- 明确区分正常登出和被踢出
- 便于显示不同的提示信息
### 4. **自动恢复**
- 登录时自动重置被踢出状态
- 提供手动重置方法
---
## 📊 返回值说明
`checkLoginStatus()` 方法返回对象:
```typescript
interface CheckResult {
valid: boolean // 登录状态是否有效
reason: string // 原因
message?: string // 错误消息(可选)
error?: Error // 错误对象(可选)
}
```
### Reason 枚举值:
| Reason | 说明 | valid |
|--------|------|-------|
| `not_logged_in` | 未登录 | false |
| `recently_checked` | 10秒内已检查过 | true |
| `verified` | 后端验证通过 | true |
| `kicked_out` | 被其他设备踢出 | false |
| `session_expired` | 会话过期 | false |
| `check_failed` | 检查失败(网络错误等) | true |
---
## ✅ 配合 API 拦截器
API 拦截器已更新,会在检测到被踢出时调用:
```javascript
// src/api/index.js
userStore.logout(true) // 前台用户
userStore.adminLogout(true) // 后台管理员
```
这样确保了**被动响应**和**主动检查**两种机制的完美配合。
---
## 🎨 最佳实践
### ✅ 推荐
1. **关键操作前检查**:支付、提交表单等操作前主动检查
2. **路由守卫检查**:切换到需要登录的页面时检查
3. **显示友好提示**:根据 `isKickedOut` 显示不同的提示信息
4. **定期心跳检测**:长时间停留在页面时定期检查(可选)
### ❌ 避免
1. **过度频繁检查**:利用 10 秒缓存机制,避免每次操作都检查
2. **忽略返回值**:检查失败时要及时处理,引导用户登录
3. **混淆状态**:不区分正常登出和被踢出,提示信息不明确
---
## 🔧 相关文件
-`src/store/user.js` - Store 状态管理(已增强)
-`src/api/index.js` - API 拦截器(已更新)
- 📄 `前端多设备登录处理方案.md` - 总体方案说明
---
## 🎉 总结
通过在 Store 层面添加:
- ✅ 被踢出状态标记 `isKickedOut`
- ✅ 主动检查方法 `checkLoginStatus()`
- ✅ 增强的登出方法 `logout(isKickedOut)`
- ✅ 防频繁检查机制10秒缓存
实现了**被动响应 + 主动检查**的双重保障机制,确保多设备登录场景下的安全性和用户体验!