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

12 KiB
Raw Permalink Blame History

批量自动化发车流程分析 v3.9.6

🎯 问题描述

现象

  • 游戏功能模块单独测试:成功且非常快
  • 批量自动化测试超时20秒

用户反馈

"单独测试是成功的,而且查询的非常快"

这说明:

  • 账号已加入俱乐部
  • 服务器响应正常且快速
  • 问题出在批量自动化的实现上

📋 批量自动化发车完整流程

阶段1初始化startBatchExecution

代码位置batchTaskStore.js 第187-232行

// 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行

关键逻辑

// 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行

// 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 逻辑

// 检查现有连接
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步查询车辆

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步批量刷新可选

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步检查每日发车次数

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步批量收获

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步批量发送

// 重新查询车辆状态
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行

// 直接调用
const response = await tokenStore.sendMessageAsync(
  tokenId,
  'car_getrolecar',
  {},
  10000  // 10秒超时
)

sendMessageAsync 内部

// 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行

// 通过 ensureConnection 获取 client
const client = await ensureConnection(tokenId)

// 发送命令
const queryResponse = await client.sendWithPromise('car_getrolecar', {}, 20000)

ensureConnection 内部

// 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 非响应式

游戏功能模块

wsConnections.value[tokenId]  // 响应式,实时状态

批量自动化

tokenStore.wsConnections[tokenId]  // 非响应式,可能是旧状态

影响

  • 批量自动化可能获取到过期的连接对象
  • 连接状态可能已经改变,但引用还是旧的

差异2等待时间

游戏功能模块

// 立即发送
await tokenStore.sendMessageAsync(...)

批量自动化

// 等待2秒后发送
await ensureConnection(tokenId)
await new Promise(resolve => setTimeout(resolve, 2000))  // ⚠️ 等待
await executeTask(tokenId, taskName)

影响

  • 2秒内连接状态可能发生变化
  • WebSocket client 可能被替换或失效

差异3获取 client 的方式

游戏功能模块

// 每次发送时重新获取
const connection = wsConnections.value[tokenId]
const client = connection.client

批量自动化

// 一次性获取,后续复用
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 调用

// 从
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

// 获取最新的 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行

// 删除或减少等待时间
// 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行) - 发送车辆

修改模板

// 从
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