Files
xyzw_web_helper/MD说明文件夹/功能更新-批量任务进度保存和恢复v3.12.0.md
2025-10-17 20:56:50 +08:00

18 KiB
Raw Blame History

功能更新 - 批量任务进度保存和恢复 v3.12.0

版本: v3.12.0
日期: 2025-10-08
类型: 功能更新

功能概述

新增批量任务执行进度的自动保存和恢复功能。当批量任务执行过程中刷新页面或浏览器意外关闭后,可以从上次中断的位置继续执行,无需重新开始。

用户需求

"突然发现批量自动化没有记录上次跑到哪一个token了我需要记录一下这次跑到第几个token刷新网页之后还可以继续下面进行做任务"

功能特性

1. 自动保存进度

  • 实时保存每完成一个Token的任务后自动保存进度到 localStorage
  • 保存内容
    • 已完成的Token ID列表
    • 所有Token ID列表
    • 任务列表
    • 执行统计(成功、失败、跳过数量)
    • 保存时间戳

2. 智能进度恢复

  • 刷新检测:页面刷新后自动检测是否有未完成的任务
  • 用户选择
    • 继续上次进度 - 只执行剩余未完成的Token
    • 🔄 重新开始 - 清除进度从头开始执行所有Token
  • 进度过期超过24小时的进度自动清除

3. 进度信息展示

弹窗显示详细进度信息:

  • 总计Token数量
  • 已完成Token数量
  • 剩余Token数量高亮显示

技术实现

1. 数据结构

保存的进度数据

{
  completedTokenIds: ['token1', 'token2', ...],  // 已完成的Token ID列表
  allTokenIds: ['token1', 'token2', 'token3', ...],  // 所有Token ID列表
  tasks: ['dailyFix', 'legionSignIn', ...],  // 任务列表
  timestamp: 1728374400000,  // 保存时间戳
  stats: {
    total: 100,      // 总数
    success: 45,     // 成功数
    failed: 3,       // 失败数
    skipped: 0       // 跳过数
  }
}

2. 核心函数

src/stores/batchTaskStore.js

保存进度

const saveExecutionProgress = (completedTokenIds, allTokenIds, tasks) => {
  const progress = {
    completedTokenIds: completedTokenIds,
    allTokenIds: allTokenIds,
    tasks: tasks,
    timestamp: Date.now(),
    stats: {
      total: executionStats.value.total,
      success: executionStats.value.success,
      failed: executionStats.value.failed,
      skipped: executionStats.value.skipped
    }
  }
  localStorage.setItem('batchTaskProgress', JSON.stringify(progress))
  savedProgress.value = progress
}

清除进度

const clearSavedProgress = () => {
  localStorage.removeItem('batchTaskProgress')
  savedProgress.value = null
}

检查进度

const hasSavedProgress = computed(() => {
  if (!savedProgress.value) return false
  
  // 检查进度是否过期超过24小时
  const elapsed = Date.now() - savedProgress.value.timestamp
  const isExpired = elapsed > 24 * 60 * 60 * 1000
  
  if (isExpired) {
    clearSavedProgress()
    return false
  }
  
  // 检查是否还有未完成的token
  const remaining = savedProgress.value.allTokenIds.length - savedProgress.value.completedTokenIds.length
  return remaining > 0
})

修改启动函数

const startBatchExecution = async (tokenIds = null, tasks = null, isRetry = false, continueFromSaved = false) => {
  // ... 省略其他代码
  
  let targetTokens = tokenIds || tokenStore.gameTokens.map(t => t.id)
  let targetTasks = tasks || currentTemplateTasks.value
  let completedTokenIds = []
  
  // 检查是否要从保存的进度继续
  if (continueFromSaved && savedProgress.value && hasSavedProgress.value) {
    console.log('📂 从上次保存的进度继续执行')
    targetTokens = savedProgress.value.allTokenIds
    targetTasks = savedProgress.value.tasks
    completedTokenIds = savedProgress.value.completedTokenIds
    
    // 恢复统计数据
    executionStats.value = {
      ...executionStats.value,
      total: savedProgress.value.stats.total,
      success: savedProgress.value.stats.success,
      failed: savedProgress.value.stats.failed,
      skipped: savedProgress.value.stats.skipped
    }
    
    console.log(`✅ 已完成: ${completedTokenIds.length}/${targetTokens.length} 个Token`)
    console.log(`🔄 继续执行剩余 ${targetTokens.length - completedTokenIds.length} 个Token`)
  }
  
  // ... 省略其他代码
  
  // 过滤出需要执行的token排除已完成的
  const tokensToExecute = targetTokens.filter(id => !completedTokenIds.includes(id))
  
  // 执行批量任务
  await executeBatchWithConcurrency(tokensToExecute, targetTasks, targetTokens, completedTokenIds)
}

修改并发执行函数

const executeBatchWithConcurrency = async (tokenIds, tasks, allTokenIds = null, completedTokenIds = []) => {
  const queue = [...tokenIds]
  const executing = []
  const completed = [...completedTokenIds]  // 已完成的token列表
  const all = allTokenIds || tokenIds  // 所有token列表
  
  while (queue.length > 0 || executing.length > 0) {
    // ... 省略其他代码
    
    const promise = (async () => {
      // 执行任务
      return executeTokenTasks(tokenId, tasks)
    })()
      .then(() => {
        // 从执行队列中移除
        const index = executing.indexOf(promise)
        if (index > -1) {
          executing.splice(index, 1)
        }
        executingTokens.value.delete(tokenId)
        
        // 保存进度记录此token已完成
        completed.push(tokenId)
        saveExecutionProgress(completed, all, tasks)
      })
      .catch(error => {
        // ... 省略错误处理
        
        // 保存进度:即使失败也记录为已完成(避免下次重复执行)
        completed.push(tokenId)
        saveExecutionProgress(completed, all, tasks)
      })
  }
}

完成时清除进度

const finishBatchExecution = async () => {
  // ... 省略其他代码
  
  // 清除保存的进度(任务已全部完成)
  clearSavedProgress()
  
  // ... 省略其他代码
}

src/components/BatchTaskPanel.vue

修改开始执行函数

const handleStart = () => {
  // 检查是否有保存的进度
  if (batchStore.hasSavedProgress && batchStore.savedProgress) {
    const completed = batchStore.savedProgress.completedTokenIds.length
    const total = batchStore.savedProgress.allTokenIds.length
    const remaining = total - completed
    
    dialog.warning({
      title: '发现未完成的任务',
      content: () => {
        return h('div', [
          h('p', `检测到上次有未完成的批量任务:`),
          h('ul', { style: 'margin: 12px 0; padding-left: 24px;' }, [
            h('li', `总计:${total} 个Token`),
            h('li', `已完成:${completed} 个`),
            h('li', { style: 'color: #ff6b6b; font-weight: bold;' }, `剩余:${remaining} 个`)
          ]),
          h('p', { style: 'margin-top: 16px; font-weight: bold;' }, '您想要:')
        ])
      },
      positiveText: '继续上次进度',
      negativeText: '重新开始',
      onPositiveClick: () => {
        batchStore.startBatchExecution(null, null, false, true)
        message.success(`继续执行剩余 ${remaining} 个Token`)
      },
      onNegativeClick: () => {
        dialog.warning({
          title: '确认重新开始',
          content: `确定要重新开始执行所有 ${tokenStore.gameTokens.length} 个角色的任务吗?\n\n已完成的 ${completed} 个Token将会重新执行。`,
          positiveText: '确定重新开始',
          negativeText: '取消',
          onPositiveClick: () => {
            batchStore.clearSavedProgress()
            batchStore.startBatchExecution()
            message.success('批量任务已启动(重新开始)')
          }
        })
      }
    })
  } else {
    // 没有保存的进度,正常开始
    dialog.warning({
      title: '确认执行',
      content: `即将对 ${tokenStore.gameTokens.length} 个角色执行批量任务,是否继续?`,
      positiveText: '确定',
      negativeText: '取消',
      onPositiveClick: () => {
        batchStore.startBatchExecution()
        message.success('批量任务已启动')
      }
    })
  }
}

3. 新增导出

src/stores/batchTaskStore.js

return {
  // 状态
  savedProgress,        // 新增:保存的进度数据
  
  // 计算属性
  hasSavedProgress,     // 新增:是否有保存的进度
  
  // 方法
  clearSavedProgress,   // 新增:清除保存的进度
  
  // ... 其他已有的导出
}

使用场景

场景1正常完成

用户操作:
1. 开始执行批量任务100个Token
2. 任务全部执行完成
3. 系统自动清除保存的进度

结果:
✅ 所有任务完成
✅ 进度已清除

场景2中途刷新继续执行

用户操作:
1. 开始执行批量任务100个Token
2. 执行到第50个时刷新页面
3. 点击"开始执行"按钮
4. 弹窗显示已完成50个剩余50个
5. 选择"继续上次进度"

结果:
✅ 从第51个Token继续执行
✅ 已完成的50个Token不会重复执行
✅ 统计数据累加(成功、失败数量保持)

场景3中途刷新重新开始

用户操作:
1. 开始执行批量任务100个Token
2. 执行到第50个时刷新页面
3. 点击"开始执行"按钮
4. 弹窗显示已完成50个剩余50个
5. 选择"重新开始"
6. 二次确认弹窗
7. 确认"确定重新开始"

结果:
✅ 清除保存的进度
✅ 从第1个Token重新执行所有100个
✅ 统计数据重置

场景4进度过期

用户操作:
1. 开始执行批量任务100个Token
2. 执行到第50个时关闭浏览器
3. 24小时后重新打开页面
4. 点击"开始执行"按钮

结果:
✅ 进度已自动过期清除
✅ 直接弹出正常开始确认框
✅ 从头开始执行所有Token

用户界面

弹窗1发现未完成的任务

┌─────────────────────────────────────┐
│ ⚠ 发现未完成的任务                  │
├─────────────────────────────────────┤
│ 检测到上次有未完成的批量任务:       │
│                                     │
│ • 总计100 个Token                 │
│ • 已完成50 个                     │
│ • 剩余50 个  ← 红色高亮           │
│                                     │
│ 您想要:                            │
│                                     │
│  [继续上次进度]  [重新开始]         │
└─────────────────────────────────────┘

弹窗2确认重新开始

┌─────────────────────────────────────┐
│ ⚠ 确认重新开始                      │
├─────────────────────────────────────┤
│ 确定要重新开始执行所有 100 个角色的 │
│ 任务吗?                            │
│                                     │
│ 已完成的 50 个Token将会重新执行。   │
│                                     │
│  [确定重新开始]  [取消]             │
└─────────────────────────────────────┘

控制台日志

继续执行时

📂 从上次保存的进度继续执行
✅ 已完成: 50/100 个Token
🔄 继续执行剩余 50 个Token
🚀 开始批量执行任务
📋 Token数量: 100
📋 任务列表: ['dailyFix', 'legionSignIn', ...]

每完成一个Token

💾 已保存进度: 51/100 个Token已完成
💾 已保存进度: 52/100 个Token已完成
...

全部完成时

🎉 批量任务执行完成
🗑️ 已清除保存的进度
✅ 所有任务成功完成!

数据存储

localStorage 键名

batchTaskProgress

数据示例

{
  "completedTokenIds": [
    "10601服-0-7145...",
    "10601服-1-7146...",
    "10602服-0-7147..."
  ],
  "allTokenIds": [
    "10601服-0-7145...",
    "10601服-1-7146...",
    "10602服-0-7147...",
    "10603服-0-7148...",
    "10604服-0-7149..."
  ],
  "tasks": [
    "dailyFix",
    "legionSignIn",
    "autoStudy",
    "claimHangupReward",
    "addClock",
    "sendCar",
    "climbTower"
  ],
  "timestamp": 1728374400000,
  "stats": {
    "total": 5,
    "success": 3,
    "failed": 0,
    "skipped": 0
  }
}

技术要点

1. 进度保存时机

  • 成功完成Token任务执行成功后保存
  • 失败Token任务执行失败后也保存避免重复执行失败的任务
  • 执行中:不保存(避免状态不一致)

2. 进度恢复逻辑

// 初始化每个Token的进度
targetTokens.forEach(tokenId => {
  if (completedTokenIds.includes(tokenId)) {
    // 已完成的token标记为completed状态
    taskProgress.value[tokenId] = {
      status: 'completed',
      progress: 100,
      // ...
    }
  } else {
    // 未完成的token初始化为pending状态
    taskProgress.value[tokenId] = {
      status: 'pending',
      progress: 0,
      // ...
    }
  }
})

// 过滤出需要执行的token
const tokensToExecute = targetTokens.filter(id => !completedTokenIds.includes(id))

// 只执行未完成的token
await executeBatchWithConcurrency(tokensToExecute, targetTasks, targetTokens, completedTokenIds)

3. 统计数据累加

if (continueFromSaved && savedProgress.value) {
  // 恢复之前的统计数据
  executionStats.value = {
    ...executionStats.value,
    total: savedProgress.value.stats.total,
    success: savedProgress.value.stats.success,  // 保持之前的成功数
    failed: savedProgress.value.stats.failed,    // 保持之前的失败数
    skipped: savedProgress.value.stats.skipped   // 保持之前的跳过数
  }
}

4. 进度过期检查

const hasSavedProgress = computed(() => {
  if (!savedProgress.value) return false
  
  const elapsed = Date.now() - savedProgress.value.timestamp
  const isExpired = elapsed > 24 * 60 * 60 * 1000  // 24小时
  
  if (isExpired) {
    clearSavedProgress()
    return false
  }
  
  const remaining = savedProgress.value.allTokenIds.length - savedProgress.value.completedTokenIds.length
  return remaining > 0
})

优势与特性

1. 可靠性

  • 实时保存到 localStorage不依赖网络
  • 每完成一个Token就保存最小化数据丢失
  • 自动过期机制,避免过时数据干扰

2. 灵活性

  • 用户可选择继续或重新开始
  • 支持部分完成的恢复
  • 失败的Token也记录为已完成避免无限重试

3. 用户体验

  • 详细的进度信息展示
  • 明确的操作选项
  • 二次确认机制(重新开始时)
  • 友好的提示消息

4. 性能优化

  • 只执行未完成的Token节省时间
  • 避免重复执行,减少服务器压力
  • localStorage 存储,无网络开销

注意事项

1. Token列表变化

如果Token列表发生变化添加/删除Token保存的进度可能不准确

  • 建议:重新开始执行
  • 未来改进可以添加Token指纹验证

2. 任务列表变化

如果任务模板发生变化,保存的进度仍使用旧的任务列表:

  • 当前行为:继续使用保存的任务列表
  • 未来改进:可以提示任务列表已变化

3. 数据大小

对于大量Token如1000+),进度数据可能较大:

  • 当前限制localStorage 通常 5-10MB
  • 评估1000个Token ID × 20字节 ≈ 20KB完全可接受

4. 浏览器隐私模式

在隐私/无痕模式下localStorage 可能不持久化:

  • 影响:刷新后进度丢失
  • 解决:建议使用正常模式执行长时间任务

测试用例

测试1正常完成

  1. 开始执行10个Token
  2. 等待全部完成
  3. 检查localStoragebatchTaskProgress 应该被删除
  4. 再次点击"开始执行":应该是正常的确认框

测试2中途刷新继续

  1. 开始执行10个Token
  2. 完成5个后刷新页面
  3. 点击"开始执行":应该弹出"发现未完成的任务"
  4. 选择"继续上次进度"
  5. 应该从第6个Token开始执行
  6. 统计数据应该累加

测试3中途刷新重新开始

  1. 开始执行10个Token
  2. 完成5个后刷新页面
  3. 点击"开始执行":应该弹出"发现未完成的任务"
  4. 选择"重新开始"
  5. 二次确认后,应该清除进度
  6. 从第1个Token重新执行所有10个

测试4进度过期

  1. 开始执行10个Token
  2. 完成5个后关闭浏览器
  3. 手动修改localStorage中的timestamp为25小时前
  4. 重新打开页面,点击"开始执行"
  5. 应该弹出正常的确认框(进度已自动清除)

测试5失败Token处理

  1. 开始执行10个Token其中3个会失败
  2. 完成5个包括2个失败后刷新
  3. 继续执行失败的2个不应该再次执行
  4. 统计数据中的失败数应该保持

修改文件

  • src/stores/batchTaskStore.js

    • 新增 savedProgress 状态
    • 新增 saveExecutionProgress 函数
    • 新增 clearSavedProgress 函数
    • 新增 hasSavedProgress 计算属性
    • 修改 startBatchExecution 函数支持 continueFromSaved 参数
    • 修改 executeBatchWithConcurrency 函数支持进度保存
    • 修改 finishBatchExecution 函数清除进度
  • src/components/BatchTaskPanel.vue

    • 导入 h 函数
    • 修改 handleStart 函数支持进度恢复提示

相关版本

  • v3.11.x: 批量任务优化和错误处理
  • v3.12.0: 新增进度保存和恢复功能(本版本)

总结

核心功能

  • 💾 自动保存批量任务执行进度
  • 🔄 刷新后可继续执行剩余任务
  • 🎯 用户可选择继续或重新开始
  • 24小时自动过期机制

用户获益

  • 不怕浏览器意外关闭
  • 不怕页面刷新中断
  • 节省时间(继续执行而非重新开始)
  • 灵活控制(可选择重新开始)

技术优势

  • 实时保存,可靠性高
  • 自动过期,避免脏数据
  • 累加统计,数据准确
  • 用户友好,操作简单

状态: 已完成
版本: v3.12.0