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