更新项目代码:新增彩种模块、组件优化、管理后台功能完善
This commit is contained in:
351
Store登录状态管理增强说明.md
Normal file
351
Store登录状态管理增强说明.md
Normal file
@@ -0,0 +1,351 @@
|
||||
# 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秒缓存)
|
||||
|
||||
实现了**被动响应 + 主动检查**的双重保障机制,确保多设备登录场景下的安全性和用户体验!
|
||||
Reference in New Issue
Block a user