# 重构：发车任务复用游戏模块逻辑 v3.11.0

**版本**: v3.11.0  
**日期**: 2025-10-08  
**类型**: Major Refactoring (重大重构)

---

## 问题背景

用户反馈批量自动化的发车功能存在很大问题，虽然经过多次修复（v3.9.5 ~ v3.10.3），但仍然不稳定，包括：
- 超时问题（`car_getrolecar` 20000ms 仍然超时）
- 错误 200400（账号未加入俱乐部）
- 错误 200020（车辆冷却期或状态未同步）
- 激活账号逻辑（`role_getroleinfo`）不稳定
- 等待时间过长（3秒同步延迟）

而游戏功能模块（`CarManagement.vue`）中的发车功能一直工作正常，用户能够在单个 token 上快速、可靠地完成车辆查询、刷新、收获、发送操作。

**用户建议**:
> "我觉得批量自动化的发车还是有很大问题，能不能直接复用游戏功能模块的查询车辆信息，然后先全部收获，再批量刷新（可选的），最后全部发车"

---

## 重构目标

1. **代码复用**: 直接移植游戏功能模块（`CarManagement.vue`）中已验证可用的逻辑到批量自动化（`batchTaskStore.js`）
2. **简化流程**: 移除不必要的"激活账号"步骤和过长的等待时间
3. **统一超时**: 使用游戏模块的超时配置（10000ms for query, 5000ms for refresh/claim/send）
4. **保持一致**: 确保批量自动化和游戏模块的行为完全一致

---

## 重构内容

### 1. 移除"激活账号"步骤

**旧逻辑**:
```javascript
// 第0步：先获取角色信息（激活账号）
console.log(`👤 [${tokenId}] 获取角色信息（激活账号）...`)
try {
  await tokenStore.sendMessageAsync(tokenId, 'role_getroleinfo', {}, 5000)
  console.log(`✅ [${tokenId}] 角色信息获取成功`)
} catch (error) {
  console.warn(`⚠️ [${tokenId}] 角色信息获取失败: ${error.message}，继续尝试查询车辆`)
}

// 等待一下，让服务器处理
await new Promise(resolve => setTimeout(resolve, 1000))
```

**新逻辑**:
```javascript
// 直接查询车辆，无需"激活账号"步骤
```

**理由**:
- 游戏功能模块没有此步骤，直接查询车辆就能成功
- 此步骤增加了 6 秒的额外延迟（5秒超时 + 1秒等待），影响效率
- 实际测试表明，此步骤并非必需

---

### 2. 调整超时时间

**旧配置**:
- `car_getrolecar`: 20000ms (20秒)
- `car_refresh`: 5000ms
- `car_claim`: 5000ms
- `car_send`: 5000ms

**新配置**（与游戏模块保持一致）:
- `car_getrolecar`: 10000ms (10秒)
- `car_refresh`: 5000ms
- `car_claim`: 5000ms
- `car_send`: 5000ms

**理由**:
- 游戏模块使用 10000ms 查询超时，实测非常快（< 1秒）
- 20000ms 的超时时间过于保守，反而掩盖了真实的服务器响应问题
- 如果 10000ms 仍然超时，说明是账号未加入俱乐部等服务器端问题

---

### 3. 简化批量收获逻辑

**旧逻辑**:
```javascript
// 第4步：批量收获
console.log(`🎁 [${tokenId}] 开始批量收获...`)
let claimSuccessCount = 0
let claimSkipCount = 0

for (const carId of carIds) {
  const carInfo = carDataMap[carId]
  const state = getCarState(carInfo)
  
  if (state === 2) { // 已到达
    try {
      await tokenStore.sendMessageAsync(tokenId, 'car_claim', { carId: carId }, 5000)
      claimSuccessCount++
      console.log(`✅ [${tokenId}] 收获车辆成功: ${carId}`)
      carInfo.claimAt = 1 // 标记已收获
      carInfo.sendAt = 0
    } catch (error) {
      console.log(`❌ [${tokenId}] 收获车辆失败: ${carId} - ${error.message}`)
    }
  } else {
    claimSkipCount++
  }
  
  await new Promise(resolve => setTimeout(resolve, 300))
}

// 等待服务器状态同步（收获→待发车需要时间）
console.log(`⏳ [${tokenId}] 等待服务器状态同步（3秒）...`)
await new Promise(resolve => setTimeout(resolve, 3000))

// 重新查询车辆状态
console.log(`🔍 [${tokenId}] 重新查询车辆状态...`)
const finalQueryResponse = await tokenStore.sendMessageAsync(tokenId, 'car_getrolecar', {}, 20000)
```

**新逻辑**（与游戏模块保持一致）:
```javascript
// Step 3.1: 批量收获已到达的车辆（state: 2）
console.log(`🎁 [${tokenId}] 第1步：批量收获已到达的车辆`)
const arrivedCars = carIds.filter(carId => getCarState(carDataMap[carId]) === 2)

if (arrivedCars.length === 0) {
  console.log(`⚠️ [${tokenId}] 没有已到达的车辆可以收获`)
} else {
  console.log(`🎁 [${tokenId}] 找到 ${arrivedCars.length} 辆已到达的车辆`)
  
  for (let i = 0; i < arrivedCars.length; i++) {
    const carId = arrivedCars[i]
    console.log(`🎁 [${tokenId}] 收获进度: ${i + 1}/${arrivedCars.length}`)
    
    try {
      await tokenStore.sendMessageAsync(tokenId, 'car_claim', { carId: carId }, 5000)
      claimSuccessCount++
      console.log(`✅ [${tokenId}] 收获车辆成功: ${carId}`)
    } catch (error) {
      console.error(`❌ [${tokenId}] 收获车辆失败: ${carId} - ${error.message}`)
    }
    
    // 添加间隔（与 CarManagement.vue 保持一致）
    if (i < arrivedCars.length - 1) {
      await new Promise(resolve => setTimeout(resolve, 300))
    }
  }
  
  console.log(`🎁 [${tokenId}] 收获完成：成功 ${claimSuccessCount}, 失败 ${arrivedCars.length - claimSuccessCount}`)
}

// 如果有收获成功的车辆，重新查询车辆列表（与 CarManagement.vue 保持一致）
if (claimSuccessCount > 0) {
  console.log(`🔄 [${tokenId}] 收获成功，等待1秒后重新查询车辆状态...`)
  await new Promise(resolve => setTimeout(resolve, 1000))
  const { carDataMap: newCarDataMap } = await queryClubCars()
  Object.assign(carDataMap, newCarDataMap)
}
```

**关键改进**:
1. **提前筛选**: 只遍历 `state === 2` 的车辆，而不是所有车辆
2. **减少等待**: 收获后只等待 1 秒，而不是 3 秒
3. **条件查询**: 只有当收获成功时才重新查询，而不是无条件查询
4. **移除手动状态修改**: 不再手动修改 `carInfo.claimAt` 和 `carInfo.sendAt`，而是依赖服务器返回的最新状态

---

### 4. 简化批量发送逻辑

**旧逻辑**:
```javascript
// 找到待发车的车辆
const readyToSendCars = carIds.filter(carId => getCarState(carDataMap[carId]) === 0)
const carsToSend = readyToSendCars.slice(0, currentRemainingSendCount)

console.log(`🚀 [${tokenId}] 待发车: ${readyToSendCars.length}辆，剩余额度: ${currentRemainingSendCount}个，将发送: ${carsToSend.length}辆`)

for (const carId of carsToSend) {
  try {
    await tokenStore.sendMessageAsync(tokenId, 'car_send', {
      carId: carId,
      helperId: 0,
      text: ""
    }, 5000)
    sendSuccessCount++
    console.log(`✅ [${tokenId}] 发送车辆成功: ${carId}`)
    
    // 更新发车次数
    const newCount = dailySendCount + sendSuccessCount
    localStorage.setItem(dailySendKey, newCount.toString())
    
    if (newCount >= 4) {
      console.log(`✅ [${tokenId}] 今日发车次数已达上限，停止发送`)
      break
    }
  } catch (error) {
    // 错误处理...
  }
  
  await new Promise(resolve => setTimeout(resolve, 300))
}
```

**新逻辑**（与游戏模块保持一致）:
```javascript
// 筛选待发车的车辆（state: 0）
const readyToSendCars = carIds.filter(carId => getCarState(carDataMap[carId]) === 0)
const currentShippingCars = carIds.filter(carId => getCarState(carDataMap[carId]) === 1)

// 计算剩余可发车次数
const remainingSendCount = 4 - dailySendCount
console.log(`📊 [${tokenId}] 今日已发${dailySendCount}辆，剩余可发${remainingSendCount}辆`)

if (readyToSendCars.length === 0) {
  if (claimSuccessCount === 0 && currentShippingCars.length > 0) {
    console.log(`⚠️ [${tokenId}] 所有车辆都在运输中`)
  } else if (claimSuccessCount === 0) {
    console.log(`⚠️ [${tokenId}] 没有可操作的车辆`)
  } else {
    console.log(`⚠️ [${tokenId}] 没有待发车的车辆`)
  }
} else {
  console.log(`🚀 [${tokenId}] 找到 ${readyToSendCars.length} 辆待发车的车辆`)
  
  // 如果待发车数量大于剩余可发数量，显示提示
  if (readyToSendCars.length > remainingSendCount) {
    console.log(`⚠️ [${tokenId}] 待发车${readyToSendCars.length}辆，但今日仅剩${remainingSendCount}个发车名额`)
  }
  
  // 限制发送数量不超过剩余可发次数
  const carsToSend = readyToSendCars.slice(0, remainingSendCount)
  
  for (let i = 0; i < carsToSend.length; i++) {
    const carId = carsToSend[i]
    console.log(`🚀 [${tokenId}] 发送进度: ${i + 1}/${carsToSend.length}（今日已发${dailySendCount}/4）`)
    
    try {
      await tokenStore.sendMessageAsync(tokenId, 'car_send', {
        carId: carId,
        helperId: 0,
        text: ""
      }, 5000)
      sendSuccessCount++
      console.log(`✅ [${tokenId}] 发送车辆成功: ${carId}`)
      
      // 发送成功后，增加今日发车次数（与 CarManagement.vue 保持一致）
      dailySendCount++
      localStorage.setItem(dailySendKey, dailySendCount.toString())
      
      // 发送成功后检查是否达到上限
      if (dailySendCount >= 4) {
        console.log(`✅ [${tokenId}] 今日发车次数已达上限，停止继续发送`)
        break
      }
    } catch (error) {
      const errorMsg = error.message || String(error)
      
      // 区分不同的错误类型
      if (errorMsg.includes('12000050')) {
        console.log(`⚠️ [${tokenId}] 车辆 ${carId} 发送失败: 今日发车次数已达上限（服务器端限制）`)
        // 服务器返回已达上限，更新客户端记录
        localStorage.setItem(dailySendKey, '4')
        dailySendCount = 4
        break
      } else if (errorMsg.includes('200020')) {
        console.log(`⚠️ [${tokenId}] 车辆 ${carId} 处于发送冷却期`)
      } else {
        console.error(`❌ [${tokenId}] 发送车辆失败: ${carId} - ${errorMsg}`)
      }
    }
    
    // 添加间隔（与 CarManagement.vue 保持一致）
    if (i < carsToSend.length - 1) {
      await new Promise(resolve => setTimeout(resolve, 300))
    }
  }
}
```

**关键改进**:
1. **增强边界检查**: 明确区分"所有车辆都在运输中"和"没有可操作的车辆"
2. **实时计数更新**: 每次发送成功后立即更新 `dailySendCount`，而不是事后累加
3. **更精确的日志**: 显示当前已发次数，便于调试

---

### 5. 新增 `queryClubCars` 辅助函数

为了便于多次查询车辆，新增了一个独立的 `queryClubCars` 辅助函数：

```javascript
// 辅助函数：查询车辆（与 CarManagement.vue 保持一致）
const queryClubCars = async () => {
  console.log(`🚗 [${tokenId}] 开始查询俱乐部车辆...`)
  try {
    const response = await tokenStore.sendMessageAsync(tokenId, 'car_getrolecar', {}, 10000)
    
    if (!response || !response.roleCar) {
      throw new Error('查询车辆失败：未返回车辆数据')
    }
    
    const carDataMap = response.roleCar.carDataMap || {}
    const carIds = Object.keys(carDataMap).sort() // 按ID排序
    
    return { carDataMap, carIds }
  } catch (error) {
    // 检查是否是 200400 错误（账号未加入俱乐部）
    if (error.message && error.message.includes('200400')) {
      throw new Error('该账号未加入俱乐部或没有赛车权限')
    }
    throw error
  }
}
```

**使用场景**:
1. 初始查询车辆
2. 刷新后重新查询车辆
3. 收获后重新查询车辆

---

## 新的执行流程

### 整体流程（与游戏模块保持一致）

```
第1步：查询车辆
  ├─ 使用 tokenStore.sendMessageAsync(tokenId, 'car_getrolecar', {}, 10000)
  ├─ 获取 carDataMap 和 carIds
  └─ 读取客户端的 dailySendCount

第2步：批量刷新（可选，根据 carRefreshCount 配置）
  ├─ 遍历所有车辆
  ├─ 跳过有刷新票的车辆
  ├─ 调用 car_refresh (5000ms 超时)
  ├─ 每次刷新间隔 300ms
  └─ 刷新后等待 500ms，重新查询车辆

第3步：一键发车
  │
  ├─ 3.1: 批量收获已到达的车辆（state: 2）
  │   ├─ 筛选 arrivedCars (state === 2)
  │   ├─ 遍历 arrivedCars，调用 car_claim (5000ms 超时)
  │   ├─ 每次收获间隔 300ms
  │   └─ 如果有收获成功，等待 1 秒，重新查询车辆
  │
  └─ 3.2: 批量发送待发车的车辆（state: 0）
      ├─ 重新读取客户端的 dailySendCount
      ├─ 检查每日发车次数限制（dailySendCount >= 4）
      ├─ 筛选 readyToSendCars (state === 0)
      ├─ 限制发送数量 = min(readyToSendCars.length, 4 - dailySendCount)
      ├─ 遍历 carsToSend，调用 car_send (5000ms 超时)
      ├─ 每次发送成功后，立即更新 dailySendCount
      ├─ 每次发送间隔 300ms
      └─ 达到上限（dailySendCount >= 4）后立即停止
```

---

## 性能对比

### 旧流程（v3.10.3）

```
1. 获取角色信息（激活账号）: 5秒（超时） + 1秒（等待） = 6秒
2. 查询车辆: 20秒（超时）
3. 批量刷新（假设1轮，4辆车）: 4 × 5秒（超时） + 3 × 0.3秒（间隔） = 20.9秒
4. 刷新后查询: 20秒（超时） + 0.5秒（等待） = 20.5秒
5. 批量收获（假设2辆已到达）: 2 × 5秒（超时） + 1 × 0.3秒（间隔） = 10.3秒
6. 等待同步: 3秒
7. 收获后查询: 20秒（超时）
8. 批量发送（假设4辆待发车）: 4 × 5秒（超时） + 3 × 0.3秒（间隔） = 20.9秒

总计（最坏情况，全部超时）: 6 + 20 + 20.9 + 20.5 + 10.3 + 3 + 20 + 20.9 = 121.6秒
总计（正常情况，无超时）: 6 + 1 + 20.9 + 1 + 1 + 3 + 1 + 1 = 34.9秒
```

### 新流程（v3.11.0）

```
1. 查询车辆: 10秒（超时）
2. 批量刷新（假设1轮，4辆车）: 4 × 5秒（超时） + 3 × 0.3秒（间隔） = 20.9秒
3. 刷新后查询: 10秒（超时） + 0.5秒（等待） = 10.5秒
4. 批量收获（假设2辆已到达）: 2 × 5秒（超时） + 1 × 0.3秒（间隔） = 10.3秒
5. 收获后查询: 10秒（超时） + 1秒（等待） = 11秒
6. 批量发送（假设4辆待发车）: 4 × 5秒（超时） + 3 × 0.3秒（间隔） = 20.9秒

总计（最坏情况，全部超时）: 10 + 20.9 + 10.5 + 10.3 + 11 + 20.9 = 83.6秒
总计（正常情况，无超时）: 1 + 20.9 + 1 + 1 + 2 + 1 = 27.9秒
```

**性能提升**:
- 最坏情况（全部超时）: 从 121.6秒 降低到 83.6秒，**节省 31.3%**
- 正常情况（无超时）: 从 34.9秒 降低到 27.9秒，**节省 20.1%**

---

## 影响范围

### 修改的文件
1. `src/stores/batchTaskStore.js` - 批量任务核心逻辑（sendCar case 完全重写）

### 影响的功能
1. **批量自动化 - sendCar 任务**: 
   - 执行流程简化
   - 超时时间调整
   - 日志更详细

### 不受影响的功能
1. **游戏功能模块 - CarManagement.vue**: 无任何修改
2. **其他批量自动化任务**: dailyFix, legionSignIn, autoStudy, claimHangupReward, addClock, climbTower - 均不受影响

---

## 测试建议

### 测试场景 1：所有车辆都在运输中

**预期行为**:
- 查询车辆成功
- 收获阶段：0 辆已到达，跳过收获
- 发送阶段：0 辆待发车，提示"所有车辆都在运输中"

**预期日志**:
```
🚗 [token_xxx] 开始查询俱乐部车辆...
✅ [token_xxx] 查询到 4 辆车（今日已发车: 0/4）
🎯 [token_xxx] 开始一键发车（收获 + 发送）...
🎁 [token_xxx] 第1步：批量收获已到达的车辆
⚠️ [token_xxx] 没有已到达的车辆可以收获
⚠️ [token_xxx] 4辆车正在运输中
🚀 [token_xxx] 第2步：批量发送待发车的车辆
⚠️ [token_xxx] 所有车辆都在运输中
```

---

### 测试场景 2：有2辆已到达，2辆待发车

**预期行为**:
- 查询车辆成功
- 收获阶段：成功收获 2 辆
- 等待 1 秒，重新查询车辆
- 发送阶段：找到 4 辆待发车，成功发送 4 辆

**预期日志**:
```
🚗 [token_xxx] 开始查询俱乐部车辆...
✅ [token_xxx] 查询到 4 辆车（今日已发车: 0/4）
🎯 [token_xxx] 开始一键发车（收获 + 发送）...
🎁 [token_xxx] 第1步：批量收获已到达的车辆
🎁 [token_xxx] 找到 2 辆已到达的车辆
🎁 [token_xxx] 收获进度: 1/2
✅ [token_xxx] 收获车辆成功: car_id_1
🎁 [token_xxx] 收获进度: 2/2
✅ [token_xxx] 收获车辆成功: car_id_2
🎁 [token_xxx] 收获完成：成功 2, 失败 0
🔄 [token_xxx] 收获成功，等待1秒后重新查询车辆状态...
🚗 [token_xxx] 开始查询俱乐部车辆...
🚀 [token_xxx] 第2步：批量发送待发车的车辆
📊 [token_xxx] 今日已发0辆，剩余可发4辆
🚀 [token_xxx] 找到 4 辆待发车的车辆
🚀 [token_xxx] 发送进度: 1/4（今日已发0/4）
✅ [token_xxx] 发送车辆成功: car_id_1
🚀 [token_xxx] 发送进度: 2/4（今日已发1/4）
✅ [token_xxx] 发送车辆成功: car_id_2
🚀 [token_xxx] 发送进度: 3/4（今日已发2/4）
✅ [token_xxx] 发送车辆成功: car_id_3
🚀 [token_xxx] 发送进度: 4/4（今日已发3/4）
✅ [token_xxx] 发送车辆成功: car_id_4
✅ [token_xxx] 今日发车次数已达上限，停止继续发送
🚀 [token_xxx] 发送完成：成功4次，跳过0次
```

---

### 测试场景 3：账号未加入俱乐部（错误 200400）

**预期行为**:
- 查询车辆失败，抛出友好错误："该账号未加入俱乐部或没有赛车权限"
- 任务标记为失败

**预期日志**:
```
🚗 [token_xxx] 开始查询俱乐部车辆...
❌ [token_xxx] 发车任务失败: Error: 该账号未加入俱乐部或没有赛车权限
```

---

### 测试场景 4：今日已发车4次

**预期行为**:
- 查询车辆成功
- 收获阶段：成功收获 N 辆
- 发送阶段：检测到 `dailySendCount >= 4`，跳过发送

**预期日志**:
```
🚗 [token_xxx] 开始查询俱乐部车辆...
✅ [token_xxx] 查询到 4 辆车（今日已发车: 4/4）
🎯 [token_xxx] 开始一键发车（收获 + 发送）...
🎁 [token_xxx] 第1步：批量收获已到达的车辆
...（收获流程）...
🚀 [token_xxx] 第2步：批量发送待发车的车辆
⚠️ [token_xxx] 今日发车次数已达上限: 4/4，跳过发送步骤
```

---

## 注意事项

### 1. 游戏模块逻辑的准确移植

本次重构的核心原则是**完全复用游戏模块的逻辑**，包括：
- 超时时间（10000ms for query, 5000ms for refresh/claim/send）
- 操作间隔（300ms）
- 等待时间（刷新后 500ms，收获后 1000ms）
- 状态判断（getCarState 函数）
- 错误处理（200400, 200020, 12000050）

任何后续修改都应**优先在游戏模块中测试**，验证成功后再同步到批量自动化。

### 2. 服务器 `sendCount` 字段不可靠

从 v3.10.1 开始，我们已经知道服务器的 `roleCar.sendCount` 字段不可靠（总是返回 0）。因此，批量自动化和游戏模块都**不再同步服务器的 sendCount**，而是：
1. 客户端维护 `dailySendCount` 在 `localStorage` 中
2. 每次发送成功后，立即增加 `dailySendCount`
3. 如果服务器返回 `12000050` 错误（今日发车次数已达上限），则将 `dailySendCount` 设置为 4

### 3. 并发控制

当前批量自动化的默认并发数为 1（`maxConcurrency = 1`），这是为了：
- 避免服务器反批量检测
- 减少超时和错误发生的概率

如果用户增加并发数（如 3-6），可能会遇到：
- `car_getrolecar` 超时（即使是 10000ms）
- 错误 200400（账号未加入俱乐部）

这些错误**不是客户端代码的问题**，而是服务器端的限制或账号状态问题。

### 4. 日志的重要性

本次重构增强了日志输出，包括：
- 每一步的进度（`收获进度: 1/2`，`发送进度: 3/4`）
- 每辆车的 ID 和操作结果
- 今日已发车次数的实时更新

这些日志对于调试和用户反馈非常重要，**请不要删除或简化这些日志**。

---

## 总结

本次重构（v3.11.0）彻底简化了批量自动化的发车逻辑，通过**直接复用游戏功能模块的已验证逻辑**，实现了：

✅ **代码复用**: 批量自动化和游戏模块的行为完全一致  
✅ **流程简化**: 移除不必要的"激活账号"步骤和过长的等待时间  
✅ **性能提升**: 正常情况下节省 20.1% 时间，最坏情况节省 31.3% 时间  
✅ **更稳定**: 减少了超时和错误的可能性  
✅ **更易维护**: 逻辑清晰，日志详细，易于调试  

如果后续仍有问题，建议：
1. 先在游戏功能模块（单个 token）中测试，确认问题是否也存在
2. 如果游戏模块正常，批量自动化异常，则是并发或状态管理问题
3. 如果游戏模块也异常，则是服务器端或账号状态问题

---

**版本标识**: v3.11.0  
**后续优化方向**:
1. 在批量自动化执行前，预先检查账号的俱乐部状态，跳过未加入俱乐部的账号
2. 根据实际测试情况，动态调整并发数和超时时间
3. 考虑在游戏模块中添加"批量自动化模式"，使用更保守的配置（更长的超时，更多的等待）

