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

