21 KiB
重构:发车任务复用游戏模块逻辑 v3.11.0
版本: v3.11.0
日期: 2025-10-08
类型: Major Refactoring (重大重构)
问题背景
用户反馈批量自动化的发车功能存在很大问题,虽然经过多次修复(v3.9.5 ~ v3.10.3),但仍然不稳定,包括:
- 超时问题(
car_getrolecar20000ms 仍然超时) - 错误 200400(账号未加入俱乐部)
- 错误 200020(车辆冷却期或状态未同步)
- 激活账号逻辑(
role_getroleinfo)不稳定 - 等待时间过长(3秒同步延迟)
而游戏功能模块(CarManagement.vue)中的发车功能一直工作正常,用户能够在单个 token 上快速、可靠地完成车辆查询、刷新、收获、发送操作。
用户建议:
"我觉得批量自动化的发车还是有很大问题,能不能直接复用游戏功能模块的查询车辆信息,然后先全部收获,再批量刷新(可选的),最后全部发车"
重构目标
- 代码复用: 直接移植游戏功能模块(
CarManagement.vue)中已验证可用的逻辑到批量自动化(batchTaskStore.js) - 简化流程: 移除不必要的"激活账号"步骤和过长的等待时间
- 统一超时: 使用游戏模块的超时配置(10000ms for query, 5000ms for refresh/claim/send)
- 保持一致: 确保批量自动化和游戏模块的行为完全一致
重构内容
1. 移除"激活账号"步骤
旧逻辑:
// 第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))
新逻辑:
// 直接查询车辆,无需"激活账号"步骤
理由:
- 游戏功能模块没有此步骤,直接查询车辆就能成功
- 此步骤增加了 6 秒的额外延迟(5秒超时 + 1秒等待),影响效率
- 实际测试表明,此步骤并非必需
2. 调整超时时间
旧配置:
car_getrolecar: 20000ms (20秒)car_refresh: 5000mscar_claim: 5000mscar_send: 5000ms
新配置(与游戏模块保持一致):
car_getrolecar: 10000ms (10秒)car_refresh: 5000mscar_claim: 5000mscar_send: 5000ms
理由:
- 游戏模块使用 10000ms 查询超时,实测非常快(< 1秒)
- 20000ms 的超时时间过于保守,反而掩盖了真实的服务器响应问题
- 如果 10000ms 仍然超时,说明是账号未加入俱乐部等服务器端问题
3. 简化批量收获逻辑
旧逻辑:
// 第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)
新逻辑(与游戏模块保持一致):
// 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)
}
关键改进:
- 提前筛选: 只遍历
state === 2的车辆,而不是所有车辆 - 减少等待: 收获后只等待 1 秒,而不是 3 秒
- 条件查询: 只有当收获成功时才重新查询,而不是无条件查询
- 移除手动状态修改: 不再手动修改
carInfo.claimAt和carInfo.sendAt,而是依赖服务器返回的最新状态
4. 简化批量发送逻辑
旧逻辑:
// 找到待发车的车辆
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))
}
新逻辑(与游戏模块保持一致):
// 筛选待发车的车辆(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))
}
}
}
关键改进:
- 增强边界检查: 明确区分"所有车辆都在运输中"和"没有可操作的车辆"
- 实时计数更新: 每次发送成功后立即更新
dailySendCount,而不是事后累加 - 更精确的日志: 显示当前已发次数,便于调试
5. 新增 queryClubCars 辅助函数
为了便于多次查询车辆,新增了一个独立的 queryClubCars 辅助函数:
// 辅助函数:查询车辆(与 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步:查询车辆
├─ 使用 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%
影响范围
修改的文件
src/stores/batchTaskStore.js- 批量任务核心逻辑(sendCar case 完全重写)
影响的功能
- 批量自动化 - sendCar 任务:
- 执行流程简化
- 超时时间调整
- 日志更详细
不受影响的功能
- 游戏功能模块 - CarManagement.vue: 无任何修改
- 其他批量自动化任务: 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,而是:
- 客户端维护
dailySendCount在localStorage中 - 每次发送成功后,立即增加
dailySendCount - 如果服务器返回
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% 时间
✅ 更稳定: 减少了超时和错误的可能性
✅ 更易维护: 逻辑清晰,日志详细,易于调试
如果后续仍有问题,建议:
- 先在游戏功能模块(单个 token)中测试,确认问题是否也存在
- 如果游戏模块正常,批量自动化异常,则是并发或状态管理问题
- 如果游戏模块也异常,则是服务器端或账号状态问题
版本标识: v3.11.0
后续优化方向:
- 在批量自动化执行前,预先检查账号的俱乐部状态,跳过未加入俱乐部的账号
- 根据实际测试情况,动态调整并发数和超时时间
- 考虑在游戏模块中添加"批量自动化模式",使用更保守的配置(更长的超时,更多的等待)