Files
xyzw_web_helper/MD说明文件夹/批量自动化发车流程分析v3.9.6.md
2025-10-17 20:56:50 +08:00

498 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 批量自动化发车流程分析 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