Files
xyzw_web_helper/MD说明文件夹/批量自动化发车流程分析v3.9.6.md

498 lines
12 KiB
Markdown
Raw Normal View History

2025-10-17 20:56:50 +08:00
# 批量自动化发车流程分析 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