498 lines
12 KiB
Markdown
498 lines
12 KiB
Markdown
# 批量自动化发车流程分析 v3.9.6
|
||
|
||
## 🎯 **问题描述**
|
||
|
||
**现象**:
|
||
- ✅ **游戏功能模块**单独测试:成功且非常快
|
||
- ❌ **批量自动化**测试:超时(20秒)
|
||
|
||
**用户反馈**:
|
||
> "单独测试是成功的,而且查询的非常快"
|
||
|
||
这说明:
|
||
- ✅ 账号已加入俱乐部
|
||
- ✅ 服务器响应正常且快速
|
||
- ❌ **问题出在批量自动化的实现上**
|
||
|
||
---
|
||
|
||
## 📋 **批量自动化发车完整流程**
|
||
|
||
### 阶段1:初始化(`startBatchExecution`)
|
||
|
||
**代码位置**:`batchTaskStore.js` 第187-232行
|
||
|
||
```javascript
|
||
// 1. 重置状态
|
||
isExecuting.value = true
|
||
executionStats.value = { ... }
|
||
|
||
// 2. 初始化任务队列
|
||
const selectedTokenIds = tokenStore.selectedTokens.map(t => t.id)
|
||
const tasks = getTaskList() // ['sendCar']
|
||
```
|
||
|
||
---
|
||
|
||
### 阶段2:并发控制(`executeBatchWithConcurrency`)
|
||
|
||
**代码位置**:`batchTaskStore.js` 第240-323行
|
||
|
||
**关键逻辑**:
|
||
```javascript
|
||
// 1. 错峰连接(v3.9.6: 每3秒一个)
|
||
const delayMs = connectionIndex * 3000
|
||
|
||
// 2. 完全串行执行(v3.9.6: maxConcurrency = 1)
|
||
while (executing.length < maxConcurrency.value && queue.length > 0) {
|
||
const tokenId = queue.shift()
|
||
|
||
// 延迟后执行
|
||
await new Promise(resolve => setTimeout(resolve, delayMs))
|
||
await executeTokenTasks(tokenId, tasks)
|
||
}
|
||
```
|
||
|
||
**v3.9.6 配置**:
|
||
- 并发数:1个(完全串行)
|
||
- 账号间隔:3秒
|
||
- 第1个账号:立即执行
|
||
- 第2个账号:3秒后执行
|
||
|
||
---
|
||
|
||
### 阶段3:建立连接(`executeTokenTasks`)
|
||
|
||
**代码位置**:`batchTaskStore.js` 第340-348行
|
||
|
||
```javascript
|
||
// 1. 确保WebSocket连接(带重试机制)
|
||
const wsClient = await ensureConnection(tokenId, 5) // 重试5次
|
||
if (!wsClient) {
|
||
throw new Error('WebSocket连接失败')
|
||
}
|
||
|
||
// 2. 等待连接稳定(2秒)
|
||
console.log(`⏳ 等待连接稳定...`)
|
||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||
```
|
||
|
||
**`ensureConnection` 逻辑**:
|
||
```javascript
|
||
// 检查现有连接
|
||
const connection = tokenStore.wsConnections[tokenId]
|
||
if (connection && connection.status === 'connected') {
|
||
return connection.client // 复用现有连接
|
||
}
|
||
|
||
// 新建连接
|
||
const wsClient = await tokenStore.reconnectWebSocket(tokenId)
|
||
return wsClient
|
||
```
|
||
|
||
---
|
||
|
||
### 阶段4:执行发车任务(`executeTask` - sendCar)
|
||
|
||
**代码位置**:`batchTaskStore.js` 第1115-1359行
|
||
|
||
#### 第1步:查询车辆
|
||
```javascript
|
||
console.log(`🚗 [${tokenId}] 开始查询俱乐部车辆...`)
|
||
const queryResponse = await client.sendWithPromise('car_getrolecar', {}, 20000)
|
||
|
||
if (!queryResponse || !queryResponse.roleCar) {
|
||
throw new Error('查询车辆失败:未返回车辆数据')
|
||
}
|
||
|
||
const carDataMap = queryResponse.roleCar.carDataMap || {}
|
||
console.log(`✅ [${tokenId}] 查询到 ${carIds.length} 辆车`)
|
||
```
|
||
|
||
#### 第2步:批量刷新(可选)
|
||
```javascript
|
||
const refreshCount = carRefreshCount.value // 配置的刷新次数
|
||
|
||
if (refreshCount > 0) {
|
||
for (let round = 1; round <= refreshCount; round++) {
|
||
for (const carId of carIds) {
|
||
// 跳过有刷新票的车辆
|
||
if (carHasRefreshTicket(carInfo)) {
|
||
continue
|
||
}
|
||
|
||
// 刷新车辆
|
||
await client.sendWithPromise('car_refresh', { carId }, 5000)
|
||
await new Promise(resolve => setTimeout(resolve, 300)) // 间隔300ms
|
||
}
|
||
}
|
||
|
||
// 重新查询车辆状态
|
||
await new Promise(resolve => setTimeout(resolve, 500))
|
||
const reQueryResponse = await client.sendWithPromise('car_getrolecar', {}, 20000)
|
||
}
|
||
```
|
||
|
||
#### 第3步:检查每日发车次数
|
||
```javascript
|
||
const dailySendKey = getTodayKey(tokenId) // 'car_daily_send_count_2025-10-08_tokenId'
|
||
const dailySendCount = parseInt(localStorage.getItem(dailySendKey) || '0')
|
||
|
||
if (dailySendCount >= 4) {
|
||
return { success: true, message: '今日发车次数已达上限(4/4)' }
|
||
}
|
||
```
|
||
|
||
#### 第4步:批量收获
|
||
```javascript
|
||
for (const carId of carIds) {
|
||
const state = getCarState(carInfo) // 0=待发车, 1=运输中, 2=已到达
|
||
|
||
if (state === 2) { // 已到达
|
||
await client.sendWithPromise('car_claim', { carId }, 5000)
|
||
await new Promise(resolve => setTimeout(resolve, 300))
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 第5步:批量发送
|
||
```javascript
|
||
// 重新查询车辆状态
|
||
await new Promise(resolve => setTimeout(resolve, 500))
|
||
const finalQueryResponse = await client.sendWithPromise('car_getrolecar', {}, 20000)
|
||
|
||
// 找到待发车的车辆
|
||
const readyToSendCars = carIds.filter(carId => getCarState(carDataMap[carId]) === 0)
|
||
const remainingSendCount = 4 - dailySendCount
|
||
const carsToSend = readyToSendCars.slice(0, remainingSendCount)
|
||
|
||
// 发送车辆
|
||
for (const carId of carsToSend) {
|
||
await client.sendWithPromise('car_send', { carId, helperId: 0, text: "" }, 5000)
|
||
|
||
// 更新发车次数
|
||
const newCount = dailySendCount + sendSuccessCount
|
||
localStorage.setItem(dailySendKey, newCount.toString())
|
||
|
||
await new Promise(resolve => setTimeout(resolve, 300))
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🆚 **批量自动化 vs 游戏功能模块对比**
|
||
|
||
### 游戏功能模块(成功,快速)
|
||
|
||
**代码位置**:`CarManagement.vue` 第505行
|
||
|
||
```javascript
|
||
// 直接调用
|
||
const response = await tokenStore.sendMessageAsync(
|
||
tokenId,
|
||
'car_getrolecar',
|
||
{},
|
||
10000 // 10秒超时
|
||
)
|
||
```
|
||
|
||
**`sendMessageAsync` 内部**:
|
||
```javascript
|
||
// tokenStore.js 第1414-1430行
|
||
const sendMessageWithPromise = async (tokenId, cmd, params = {}, timeout = 1000) => {
|
||
// 1. 获取连接
|
||
const connection = wsConnections.value[tokenId]
|
||
if (!connection || connection.status !== 'connected') {
|
||
return Promise.reject(new Error(`WebSocket未连接 [${tokenId}]`))
|
||
}
|
||
|
||
// 2. 发送命令
|
||
const client = connection.client
|
||
return await client.sendWithPromise(cmd, params, timeout)
|
||
}
|
||
```
|
||
|
||
**特点**:
|
||
- ✅ 使用 `wsConnections.value[tokenId]`(响应式)
|
||
- ✅ 检查 `connection.status === 'connected'`
|
||
- ✅ 超时10秒
|
||
- ✅ 直接发送,无延迟
|
||
|
||
---
|
||
|
||
### 批量自动化(失败,超时)
|
||
|
||
**代码位置**:`batchTaskStore.js` 第1152行
|
||
|
||
```javascript
|
||
// 通过 ensureConnection 获取 client
|
||
const client = await ensureConnection(tokenId)
|
||
|
||
// 发送命令
|
||
const queryResponse = await client.sendWithPromise('car_getrolecar', {}, 20000)
|
||
```
|
||
|
||
**`ensureConnection` 内部**:
|
||
```javascript
|
||
// batchTaskStore.js 第1375-1380行
|
||
const connection = tokenStore.wsConnections[tokenId] // ⚠️ 非响应式
|
||
|
||
if (connection && connection.status === 'connected') {
|
||
return connection.client
|
||
}
|
||
|
||
// 新建连接
|
||
const wsClient = await tokenStore.reconnectWebSocket(tokenId)
|
||
return wsClient
|
||
```
|
||
|
||
**特点**:
|
||
- ⚠️ 使用 `tokenStore.wsConnections[tokenId]`(非响应式)
|
||
- ✅ 检查 `connection.status === 'connected'`
|
||
- ✅ 超时20秒
|
||
- ⚠️ 等待2秒才发送(可能导致连接状态变化)
|
||
|
||
---
|
||
|
||
## 🔍 **关键差异分析**
|
||
|
||
### 差异1:响应式 vs 非响应式
|
||
|
||
**游戏功能模块**:
|
||
```javascript
|
||
wsConnections.value[tokenId] // 响应式,实时状态
|
||
```
|
||
|
||
**批量自动化**:
|
||
```javascript
|
||
tokenStore.wsConnections[tokenId] // 非响应式,可能是旧状态
|
||
```
|
||
|
||
**影响**:
|
||
- 批量自动化可能获取到过期的连接对象
|
||
- 连接状态可能已经改变,但引用还是旧的
|
||
|
||
### 差异2:等待时间
|
||
|
||
**游戏功能模块**:
|
||
```javascript
|
||
// 立即发送
|
||
await tokenStore.sendMessageAsync(...)
|
||
```
|
||
|
||
**批量自动化**:
|
||
```javascript
|
||
// 等待2秒后发送
|
||
await ensureConnection(tokenId)
|
||
await new Promise(resolve => setTimeout(resolve, 2000)) // ⚠️ 等待
|
||
await executeTask(tokenId, taskName)
|
||
```
|
||
|
||
**影响**:
|
||
- 2秒内连接状态可能发生变化
|
||
- WebSocket client 可能被替换或失效
|
||
|
||
### 差异3:获取 client 的方式
|
||
|
||
**游戏功能模块**:
|
||
```javascript
|
||
// 每次发送时重新获取
|
||
const connection = wsConnections.value[tokenId]
|
||
const client = connection.client
|
||
```
|
||
|
||
**批量自动化**:
|
||
```javascript
|
||
// 一次性获取,后续复用
|
||
const client = await ensureConnection(tokenId)
|
||
// ... 2秒后
|
||
// ... 多次使用这个 client
|
||
await client.sendWithPromise('car_getrolecar', {}, 20000)
|
||
await client.sendWithPromise('car_refresh', { carId }, 5000)
|
||
await client.sendWithPromise('car_claim', { carId }, 5000)
|
||
```
|
||
|
||
**影响**:
|
||
- client 对象可能在获取后失效
|
||
- 特别是在等待期间,连接可能被重置
|
||
|
||
---
|
||
|
||
## 💡 **问题根源推测**
|
||
|
||
### 最可能原因:client 对象失效
|
||
|
||
**流程**:
|
||
1. `ensureConnection` 获取 `client` 对象(时间点 T0)
|
||
2. 等待连接稳定 2秒(T0 → T2)
|
||
3. 发送 `car_getrolecar` 命令(时间点 T2)
|
||
4. ❌ **但此时 client 对象可能已经失效**
|
||
|
||
**为什么 client 会失效?**
|
||
|
||
可能的情况:
|
||
1. **连接在等待期间被替换**
|
||
- `tokenStore.reconnectWebSocket` 可能创建了新的 client
|
||
- 旧的 client 引用失效
|
||
|
||
2. **Promise 管理器状态不一致**
|
||
- WebSocket client 内部有 Promise 管理器
|
||
- 如果连接重置,Promise 管理器也会重置
|
||
- 旧的 Promise 永远不会被 resolve
|
||
|
||
3. **事件监听器失效**
|
||
- WebSocket client 依赖 `onmessage` 事件
|
||
- 连接重置后,旧的事件监听器失效
|
||
- 新消息不会触发旧 client 的 Promise resolve
|
||
|
||
---
|
||
|
||
## 🛠️ **解决方案**
|
||
|
||
### 方案1:直接使用 `tokenStore.sendMessageAsync` ⭐ 推荐
|
||
|
||
**修改**:`batchTaskStore.js` 第1152行及后续所有 `client.sendWithPromise` 调用
|
||
|
||
```javascript
|
||
// 从
|
||
const client = await ensureConnection(tokenId)
|
||
const queryResponse = await client.sendWithPromise('car_getrolecar', {}, 20000)
|
||
|
||
// 改为
|
||
await ensureConnection(tokenId) // 只确保连接,不获取 client
|
||
const queryResponse = await tokenStore.sendMessageAsync(
|
||
tokenId,
|
||
'car_getrolecar',
|
||
{},
|
||
20000
|
||
)
|
||
```
|
||
|
||
**优点**:
|
||
- ✅ 每次发送时都获取最新的 client
|
||
- ✅ 使用响应式连接对象
|
||
- ✅ 与游戏功能模块保持一致
|
||
- ✅ 简单可靠
|
||
|
||
**缺点**:
|
||
- 需要修改所有 `client.sendWithPromise` 调用
|
||
|
||
---
|
||
|
||
### 方案2:每次获取最新的 client
|
||
|
||
**修改**:在每次发送命令前重新获取 client
|
||
|
||
```javascript
|
||
// 获取最新的 client
|
||
const getLatestClient = (tokenId) => {
|
||
const connection = tokenStore.wsConnections[tokenId]
|
||
if (!connection || connection.status !== 'connected') {
|
||
throw new Error('WebSocket未连接')
|
||
}
|
||
return connection.client
|
||
}
|
||
|
||
// 使用
|
||
const client = getLatestClient(tokenId)
|
||
const queryResponse = await client.sendWithPromise('car_getrolecar', {}, 20000)
|
||
```
|
||
|
||
**优点**:
|
||
- ✅ 总是使用最新的 client
|
||
- ✅ 修改量相对较小
|
||
|
||
**缺点**:
|
||
- ⚠️ 仍需修改多处
|
||
- ⚠️ 不如方案1简洁
|
||
|
||
---
|
||
|
||
### 方案3:移除等待稳定时间
|
||
|
||
**修改**:`batchTaskStore.js` 第346-348行
|
||
|
||
```javascript
|
||
// 删除或减少等待时间
|
||
// await new Promise(resolve => setTimeout(resolve, 2000))
|
||
```
|
||
|
||
**优点**:
|
||
- ✅ 减少 client 失效的时间窗口
|
||
- ✅ 提升执行速度
|
||
|
||
**缺点**:
|
||
- ❌ 可能导致连接不稳定
|
||
- ❌ 不能根本解决问题
|
||
|
||
---
|
||
|
||
## 📊 **推荐实施方案**
|
||
|
||
### ⭐ 采用方案1:统一使用 `tokenStore.sendMessageAsync`
|
||
|
||
**修改列表**:
|
||
1. `car_getrolecar` (第1152行) - 初次查询
|
||
2. `car_refresh` (第1200行) - 刷新车辆
|
||
3. `car_getrolecar` (第1227行) - 刷新后重新查询
|
||
4. `car_claim` (第1268行) - 收获车辆
|
||
5. `car_getrolecar` (第1298行) - 发送前最后查询
|
||
6. `car_send` (第1311行) - 发送车辆
|
||
|
||
**修改模板**:
|
||
```javascript
|
||
// 从
|
||
const client = await ensureConnection(tokenId)
|
||
await client.sendWithPromise('COMMAND', params, timeout)
|
||
|
||
// 改为
|
||
await ensureConnection(tokenId)
|
||
await tokenStore.sendMessageAsync(tokenId, 'COMMAND', params, timeout)
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 **预期效果**
|
||
|
||
修改后:
|
||
- ✅ 批量自动化与游戏功能模块使用相同的发送机制
|
||
- ✅ 每次发送都获取最新的 client 对象
|
||
- ✅ 使用响应式连接状态
|
||
- ✅ 避免 client 失效问题
|
||
- ✅ **批量自动化应该也能快速成功**
|
||
|
||
---
|
||
|
||
## 📝 **后续测试步骤**
|
||
|
||
1. 应用修改
|
||
2. 重启开发服务器
|
||
3. 批量测试2个账号(第2个账号是待发车状态)
|
||
4. 观察是否快速成功
|
||
|
||
**期望结果**:
|
||
```
|
||
✅ [token_xxx] 开始查询俱乐部车辆...
|
||
✅ [token_xxx] 查询到 4 辆车 ← 应该1-2秒内完成
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 **总结**
|
||
|
||
**问题本质**:
|
||
- 批量自动化一次性获取 client 对象后长时间复用
|
||
- 在等待和执行期间,client 对象可能失效
|
||
- 导致后续命令的 Promise 永远不会被 resolve
|
||
|
||
**解决思路**:
|
||
- 不复用 client 对象
|
||
- 每次发送命令时获取最新的 client
|
||
- 使用响应式连接状态
|
||
|
||
**关键改进**:
|
||
- 统一使用 `tokenStore.sendMessageAsync`
|
||
- 与游戏功能模块保持一致
|
||
- 确保每次都使用最新、有效的 client
|
||
|