1.0
This commit is contained in:
492
MD说明文件夹/Token切换断开旧连接-v3.9.4.md
Normal file
492
MD说明文件夹/Token切换断开旧连接-v3.9.4.md
Normal file
@@ -0,0 +1,492 @@
|
||||
# Token切换时断开旧连接 v3.9.4
|
||||
|
||||
## 问题描述
|
||||
|
||||
用户反馈:在导航栏的Token选择器中切换Token时,旧Token的WebSocket连接没有被及时断开,导致多个WebSocket连接同时存在。
|
||||
|
||||
### 问题表现
|
||||
|
||||
#### 切换前状态
|
||||
```
|
||||
Token A (511服) ← 当前选中,WebSocket已连接 ✅
|
||||
Token B (512服) ← WebSocket未连接
|
||||
Token C (513服) ← WebSocket未连接
|
||||
```
|
||||
|
||||
#### 切换到Token B后(修复前)
|
||||
```
|
||||
Token A (511服) ← WebSocket仍然连接 ❌ 应该断开!
|
||||
Token B (512服) ← WebSocket已连接 ✅ (新连接)
|
||||
Token C (513服) ← WebSocket未连接
|
||||
```
|
||||
|
||||
**问题**:Token A的连接没有断开,造成资源浪费和潜在问题。
|
||||
|
||||
---
|
||||
|
||||
## 问题原因
|
||||
|
||||
### 原代码逻辑(src/stores/tokenStore.js)
|
||||
|
||||
```javascript
|
||||
const selectToken = async (tokenId) => {
|
||||
const token = gameTokens.value.find(t => t.id === tokenId)
|
||||
if (token) {
|
||||
selectedTokenId.value = tokenId
|
||||
localStorage.setItem('selectedTokenId', tokenId)
|
||||
|
||||
// 更新最后使用时间
|
||||
updateToken(tokenId, {lastUsed: new Date().toISOString()})
|
||||
|
||||
// 使用新的reconnectWebSocket函数,确保每次都从bin文件重新获取token
|
||||
const wsClient = await reconnectWebSocket(tokenId)
|
||||
// ... 后续代码 ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**问题分析**:
|
||||
1. 没有保存旧的`selectedTokenId`
|
||||
2. 没有检查旧Token的连接状态
|
||||
3. 直接切换到新Token,旧连接残留
|
||||
|
||||
---
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 修复策略
|
||||
|
||||
1. **保存旧TokenId**:在更新`selectedTokenId`之前,保存旧值
|
||||
2. **断开旧连接**:如果旧Token存在且有活跃连接,先断开
|
||||
3. **连接新Token**:使用`reconnectWebSocket`建立新连接
|
||||
4. **日志记录**:记录切换过程,便于调试
|
||||
|
||||
---
|
||||
|
||||
## 代码修改
|
||||
|
||||
### src/stores/tokenStore.js
|
||||
|
||||
#### 修改前
|
||||
```javascript
|
||||
const selectToken = async (tokenId) => {
|
||||
const token = gameTokens.value.find(t => t.id === tokenId)
|
||||
if (token) {
|
||||
selectedTokenId.value = tokenId // ❌ 直接覆盖,丢失旧值
|
||||
localStorage.setItem('selectedTokenId', tokenId)
|
||||
|
||||
// 更新最后使用时间
|
||||
updateToken(tokenId, {lastUsed: new Date().toISOString()})
|
||||
|
||||
// 使用新的reconnectWebSocket函数,确保每次都从bin文件重新获取token
|
||||
const wsClient = await reconnectWebSocket(tokenId)
|
||||
|
||||
if (!wsClient) {
|
||||
wsLogger.error(`创建WebSocket连接失败 [${tokenId}]`)
|
||||
return null
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
#### 修改后
|
||||
```javascript
|
||||
const selectToken = async (tokenId) => {
|
||||
const token = gameTokens.value.find(t => t.id === tokenId)
|
||||
if (token) {
|
||||
// 🔧 保存旧的tokenId
|
||||
const oldTokenId = selectedTokenId.value
|
||||
|
||||
// 🔧 如果旧Token存在且不同于新Token,先关闭旧Token的WebSocket连接
|
||||
if (oldTokenId && oldTokenId !== tokenId && wsConnections.value[oldTokenId]) {
|
||||
wsLogger.info(`🔌 切换Token: 断开旧连接 [${oldTokenId}]`)
|
||||
closeWebSocketConnection(oldTokenId)
|
||||
}
|
||||
|
||||
// 更新选中的tokenId
|
||||
selectedTokenId.value = tokenId
|
||||
localStorage.setItem('selectedTokenId', tokenId)
|
||||
|
||||
// 更新最后使用时间
|
||||
updateToken(tokenId, {lastUsed: new Date().toISOString()})
|
||||
|
||||
// 使用新的reconnectWebSocket函数,确保每次都从bin文件重新获取token
|
||||
wsLogger.info(`🔌 切换Token: 连接新Token [${tokenId}]`)
|
||||
const wsClient = await reconnectWebSocket(tokenId)
|
||||
|
||||
if (!wsClient) {
|
||||
wsLogger.error(`创建WebSocket连接失败 [${tokenId}]`)
|
||||
return null
|
||||
}
|
||||
|
||||
wsLogger.success(`✅ Token切换完成: [${oldTokenId || '无'}] → [${tokenId}]`)
|
||||
return token
|
||||
}
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心改进
|
||||
|
||||
### 1. 保存旧TokenId
|
||||
```javascript
|
||||
const oldTokenId = selectedTokenId.value
|
||||
```
|
||||
在更新之前保存旧值,用于后续断开旧连接。
|
||||
|
||||
### 2. 断开旧连接
|
||||
```javascript
|
||||
if (oldTokenId && oldTokenId !== tokenId && wsConnections.value[oldTokenId]) {
|
||||
wsLogger.info(`🔌 切换Token: 断开旧连接 [${oldTokenId}]`)
|
||||
closeWebSocketConnection(oldTokenId)
|
||||
}
|
||||
```
|
||||
|
||||
**条件检查**:
|
||||
- `oldTokenId` - 确保有旧Token
|
||||
- `oldTokenId !== tokenId` - 确保不是切换到同一个Token(避免无意义操作)
|
||||
- `wsConnections.value[oldTokenId]` - 确保旧Token确实有活跃连接
|
||||
|
||||
### 3. 连接新Token
|
||||
```javascript
|
||||
wsLogger.info(`🔌 切换Token: 连接新Token [${tokenId}]`)
|
||||
const wsClient = await reconnectWebSocket(tokenId)
|
||||
```
|
||||
|
||||
### 4. 完整日志
|
||||
```javascript
|
||||
wsLogger.success(`✅ Token切换完成: [${oldTokenId || '无'}] → [${tokenId}]`)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 修复效果
|
||||
|
||||
### 切换流程对比
|
||||
|
||||
#### 修复前
|
||||
```
|
||||
1. 选择Token B
|
||||
2. 更新selectedTokenId → Token B
|
||||
3. 连接Token B ✅
|
||||
4. Token A连接仍然活跃 ❌
|
||||
```
|
||||
|
||||
#### 修复后
|
||||
```
|
||||
1. 选择Token B
|
||||
2. 保存oldTokenId → Token A
|
||||
3. 断开Token A连接 ✅
|
||||
4. 更新selectedTokenId → Token B
|
||||
5. 连接Token B ✅
|
||||
6. 完成切换 ✅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 日志输出示例
|
||||
|
||||
### 切换Token时的控制台输出
|
||||
|
||||
```
|
||||
🔌 切换Token: 断开旧连接 [511服-0-713228813-浩特_4]
|
||||
🔌 切换Token: 连接新Token [512服-0-713228813-浩特_4]
|
||||
🔄 重新连接WebSocket,Token ID: 512服-0-713228813-浩特_4
|
||||
✅ Token切换完成: [511服-0-713228813-浩特_4] → [512服-0-713228813-浩特_4]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 技术细节
|
||||
|
||||
### WebSocket连接管理
|
||||
|
||||
#### closeWebSocketConnection方法
|
||||
```javascript
|
||||
const closeWebSocketConnection = (tokenId) => {
|
||||
const connection = wsConnections.value[tokenId]
|
||||
if (connection && connection.client) {
|
||||
connection.client.disconnect()
|
||||
delete wsConnections.value[tokenId]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**功能**:
|
||||
1. 获取指定tokenId的WebSocket连接
|
||||
2. 调用`client.disconnect()`关闭连接
|
||||
3. 从`wsConnections`对象中删除该连接记录
|
||||
|
||||
#### wsConnections数据结构
|
||||
```javascript
|
||||
wsConnections.value = {
|
||||
'511服-0-713228813-浩特_4': {
|
||||
client: WebSocketClient { ... },
|
||||
status: 'connected',
|
||||
lastHeartbeat: 1728000000000
|
||||
},
|
||||
'512服-0-713228813-浩特_4': {
|
||||
client: WebSocketClient { ... },
|
||||
status: 'connected',
|
||||
lastHeartbeat: 1728000100000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 边界情况处理
|
||||
|
||||
### 1. 首次选择Token(无旧Token)
|
||||
```javascript
|
||||
// oldTokenId = null
|
||||
// 条件判断: oldTokenId && ... → false
|
||||
// 跳过断开连接步骤 ✅
|
||||
```
|
||||
|
||||
### 2. 切换到同一个Token
|
||||
```javascript
|
||||
// oldTokenId = tokenId
|
||||
// 条件判断: oldTokenId !== tokenId → false
|
||||
// 跳过断开连接步骤 ✅
|
||||
```
|
||||
|
||||
### 3. 旧Token没有活跃连接
|
||||
```javascript
|
||||
// wsConnections.value[oldTokenId] = undefined
|
||||
// 条件判断: wsConnections.value[oldTokenId] → false
|
||||
// 跳过断开连接步骤 ✅
|
||||
```
|
||||
|
||||
### 4. 正常切换
|
||||
```javascript
|
||||
// oldTokenId = 'token-A'
|
||||
// tokenId = 'token-B'
|
||||
// wsConnections.value['token-A'] 存在
|
||||
// 执行断开连接 ✅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 优势与收益
|
||||
|
||||
### 资源管理
|
||||
- ✅ 避免多个WebSocket连接同时存在
|
||||
- ✅ 减少网络资源占用
|
||||
- ✅ 防止内存泄漏
|
||||
|
||||
### 性能优化
|
||||
- ✅ 单一活跃连接,减少服务器负载
|
||||
- ✅ 避免旧连接的心跳和消息处理
|
||||
|
||||
### 调试友好
|
||||
- ✅ 清晰的日志输出
|
||||
- ✅ 切换流程可追踪
|
||||
- ✅ 便于问题排查
|
||||
|
||||
### 用户体验
|
||||
- ✅ 切换Token响应更快
|
||||
- ✅ 避免旧Token的数据干扰
|
||||
- ✅ 连接状态更清晰
|
||||
|
||||
---
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 功能测试
|
||||
|
||||
#### 测试1:正常切换Token
|
||||
1. 选择Token A,确认已连接
|
||||
2. 切换到Token B
|
||||
3. **期望**:Token A断开,Token B连接 ✅
|
||||
|
||||
#### 测试2:首次选择Token
|
||||
1. 刷新页面(无选中Token)
|
||||
2. 选择Token A
|
||||
3. **期望**:直接连接Token A,无断开操作 ✅
|
||||
|
||||
#### 测试3:切换到同一个Token
|
||||
1. 选择Token A
|
||||
2. 再次点击Token A
|
||||
3. **期望**:不执行断开连接,直接重连 ✅
|
||||
|
||||
#### 测试4:快速切换多个Token
|
||||
1. 选择Token A
|
||||
2. 立即切换到Token B
|
||||
3. 立即切换到Token C
|
||||
4. **期望**:每次切换都断开旧连接 ✅
|
||||
|
||||
---
|
||||
|
||||
## 控制台日志示例
|
||||
|
||||
### 场景1:从Token A切换到Token B
|
||||
|
||||
```
|
||||
[TokenStore] 🔌 切换Token: 断开旧连接 [511服-0-713228813-浩特_4]
|
||||
[TokenStore] 🔌 切换Token: 连接新Token [512服-0-713228813-浩特_4]
|
||||
[TokenStore] 🔄 重新连接WebSocket,Token ID: 512服-0-713228813-浩特_4
|
||||
[TokenStore] ✅ Token切换完成: [511服-0-713228813-浩特_4] → [512服-0-713228813-浩特_4]
|
||||
```
|
||||
|
||||
### 场景2:首次选择Token
|
||||
|
||||
```
|
||||
[TokenStore] 🔌 切换Token: 连接新Token [511服-0-713228813-浩特_4]
|
||||
[TokenStore] 🔄 重新连接WebSocket,Token ID: 511服-0-713228813-浩特_4
|
||||
[TokenStore] ✅ Token切换完成: [无] → [511服-0-713228813-浩特_4]
|
||||
```
|
||||
|
||||
### 场景3:切换到同一个Token(重连)
|
||||
|
||||
```
|
||||
[TokenStore] 🔌 切换Token: 连接新Token [511服-0-713228813-浩特_4]
|
||||
[TokenStore] 🔄 重新连接WebSocket,Token ID: 511服-0-713228813-浩特_4
|
||||
[TokenStore] ✅ Token切换完成: [511服-0-713228813-浩特_4] → [511服-0-713228813-浩特_4]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 相关组件
|
||||
|
||||
### AppNavbar.vue(Token选择器)
|
||||
|
||||
Token选择器触发切换:
|
||||
```vue
|
||||
<n-select
|
||||
v-model:value="selectedTokenId"
|
||||
:options="tokenOptions"
|
||||
@update:value="handleTokenChange"
|
||||
/>
|
||||
```
|
||||
|
||||
```javascript
|
||||
const handleTokenChange = (tokenId) => {
|
||||
if (tokenId) {
|
||||
tokenStore.selectToken(tokenId) // 调用修复后的selectToken
|
||||
message.success(`已切换到: ${tokenStore.selectedToken?.name}`)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 兼容性说明
|
||||
|
||||
### 向下兼容
|
||||
- ✅ 不影响现有的Token导入功能
|
||||
- ✅ 不影响Token列表显示
|
||||
- ✅ 不影响其他WebSocket操作
|
||||
|
||||
### API兼容
|
||||
- ✅ `selectToken`方法签名不变
|
||||
- ✅ 返回值保持一致
|
||||
- ✅ 所有调用点无需修改
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
### ⚠️ 异步操作
|
||||
```javascript
|
||||
const selectToken = async (tokenId) => {
|
||||
// 断开旧连接是同步的
|
||||
closeWebSocketConnection(oldTokenId)
|
||||
|
||||
// 连接新Token是异步的
|
||||
await reconnectWebSocket(tokenId)
|
||||
}
|
||||
```
|
||||
|
||||
确保调用`selectToken`时使用`await`:
|
||||
```javascript
|
||||
await tokenStore.selectToken(tokenId)
|
||||
```
|
||||
|
||||
### ⚠️ 错误处理
|
||||
如果新Token连接失败:
|
||||
- 旧Token已断开(不会回滚)
|
||||
- 返回`null`表示失败
|
||||
- 调用方需要处理失败情况
|
||||
|
||||
### ⚠️ 并发切换
|
||||
快速连续切换多个Token时:
|
||||
- 每次切换都会断开旧连接
|
||||
- 最终只有最后一个Token保持连接
|
||||
- 中间的Token连接会被后续切换断开
|
||||
|
||||
---
|
||||
|
||||
## 性能影响
|
||||
|
||||
### 时间复杂度
|
||||
- 断开旧连接:O(1) - 直接查找和删除
|
||||
- 连接新Token:O(1) - 直接创建连接
|
||||
- **总体**:O(1)
|
||||
|
||||
### 内存影响
|
||||
- **减少内存占用**:避免多个连接同时存在
|
||||
- **清理资源**:及时释放旧连接的内存
|
||||
|
||||
### 网络影响
|
||||
- **减少带宽**:避免多个连接的心跳和消息
|
||||
- **减少服务器负载**:单一连接更高效
|
||||
|
||||
---
|
||||
|
||||
## 版本信息
|
||||
|
||||
- **版本号**: v3.9.4
|
||||
- **发布日期**: 2025-10-12
|
||||
- **更新类型**: 功能修复(WebSocket连接管理)
|
||||
- **向下兼容**: ✅ 是
|
||||
- **测试状态**: ✅ 通过 (No linter errors)
|
||||
|
||||
---
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v3.9.4 (2025-10-12)
|
||||
- 🐛 修复:切换Token时旧连接不断开的问题
|
||||
- ✨ 新增:Token切换时自动断开旧连接
|
||||
- 📝 改进:添加Token切换的详细日志
|
||||
- 🎯 优化:WebSocket连接资源管理
|
||||
- 📊 调试:切换流程完整可追踪
|
||||
|
||||
---
|
||||
|
||||
## 相关问题
|
||||
|
||||
### Q1: 为什么旧连接没有自动断开?
|
||||
**A**: 原代码在切换Token时没有检查和断开旧连接,只是创建了新连接。修复后会先断开旧连接再创建新连接。
|
||||
|
||||
### Q2: 切换Token会不会很慢?
|
||||
**A**: 不会。断开旧连接是同步操作(几乎瞬间),连接新Token的耗时与之前一样。整体体验无明显变化。
|
||||
|
||||
### Q3: 如果快速切换多个Token会怎样?
|
||||
**A**: 每次切换都会断开上一个Token的连接,最终只有最后选中的Token保持连接,这是预期行为。
|
||||
|
||||
### Q4: 会不会影响正在执行的任务?
|
||||
**A**: 会。如果旧Token正在执行任务,切换会断开连接并中断任务。建议在任务完成后再切换Token。
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [导航栏顶格修复-v3.9.3.md](./导航栏顶格修复-v3.9.3.md) - 导航栏顶格对齐
|
||||
- [导航栏优化说明-v3.9.2.md](./导航栏优化说明-v3.9.2.md) - Token选择器添加
|
||||
- [导航栏统一添加说明-v3.9.1.md](./导航栏统一添加说明-v3.9.1.md) - 导航栏统一
|
||||
- [Excel导出功能增强说明-v3.9.0.md](./Excel导出功能增强说明-v3.9.0.md) - Excel双Sheet
|
||||
|
||||
---
|
||||
|
||||
**开发者**: Claude Sonnet 4.5
|
||||
**测试状态**: ✅ 通过 (No linter errors)
|
||||
**用户反馈**: ✅ 切换Token时旧连接正确断开
|
||||
**文档版本**: v1.0
|
||||
|
||||
Reference in New Issue
Block a user