# 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秒缓存）

实现了**被动响应 + 主动检查**的双重保障机制，确保多设备登录场景下的安全性和用户体验！
