gongxiangp2/docs/code-review-upload-state-issues.md
chenpei ea9076df45 feat: 实现统一的下载错误处理系统并修复产品全案数据库Schema
- 创建统一的下载错误处理模块 (lib/download-errors.ts)
- 实现13种错误类型分类和中文错误消息
- 后端统一使用 createDownloadError 创建标准化错误响应
- 前端统一使用 parseDownloadError 解析错误并显示
- 修复正式系统数据库Schema,添加产品全案相关字段和表
- 执行数据库迁移:添加 task_no, task_status, task_fetched_at 字段
- 创建 material_assets, api_keys, api_call_logs 表
2026-01-05 17:52:42 +08:00

6.0 KiB
Raw Blame History

上传状态管理代码审查报告

🔍 代码审查发现

问题1:错误情况下状态未清理 ⚠️

位置:components/UploadModal.tsx 第1292-1356行

问题描述: 如果 uploadSingleFile 或 submitFormData 抛出错误,catch 块中没有清理 IndexedDB 中的上传状态。

代码片段:

try {
  const chunkResult = await uploadSingleFile({...})
  // ...
  await submitFormData(formData, {...})
} catch (error) {
  console.error('Upload error:', error)
  setError((error as Error)?.message || '上传失败,请稍后再试')
  setUploading(false)
  // ⚠️ 缺少:清理 IndexedDB 状态
}

影响:

  • 如果 uploadSingleFile 成功(分片上传完成,状态已保存到 IndexedDB)
  • 但 submitFormData 失败
  • IndexedDB 中的状态不会被清理,导致残留状态

问题2:completeUpload 重试失败后的状态 ⚠️

位置:lib/chunk-upload.ts 第428-458行

问题描述: 如果 completeUpload 在重试过程中一直失败,最终抛出错误时,状态可能不会被清理。

代码片段:

while (!result && retries < maxRetries) {
  try {
    result = await completeUpload(state.uploadId!, metadata)
    await deleteUploadState(fileId)  // ✅ 成功时清理
    return result
  } catch (error) {
    if (error.message === 'UPLOAD_SESSION_EXPIRED') {
      await deleteUploadState(fileId)  // ✅ 会话过期时清理
      throw new Error('上传会话已过期,请重新上传文件')
    }
    retries++
    if (retries < maxRetries) {
      await new Promise(resolve => setTimeout(resolve, retryDelay * retries))
    } else {
      throw new Error('完成上传失败,已重试多次')
      // ⚠️ 达到最大重试次数时,没有清理状态
    }
  }
}

影响:

  • 虽然默认 maxRetries = Infinity,理论上不会达到上限
  • 但如果在某些边界情况下(比如用户关闭页面),状态会残留

问题3:分片上传过程中异常的状态 ⚠️

位置:lib/chunk-upload.ts 第361-411行

问题描述: 如果在上传分片的过程中抛出错误(比如"上传已取消"),状态不会被清理。

代码片段:

for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
  // ...
  try {
    await uploadChunk(...)
    state.uploadedChunks.add(chunkIndex)
    await saveUploadState(state)
  } catch (error) {
    if ((error as Error)?.message === '上传已取消') {
      onRequest?.(null)
      throw error  // ⚠️ 直接抛出,没有清理状态
    }
    // ...
  }
}

影响:

  • 用户取消上传时,状态会残留
  • 虽然这是"预期的"行为(支持断点续传),但如果用户明确取消,应该清理状态

问题4:completeUpload 成功但后续 submitFormData 失败 ⚠️

位置:components/UploadModal.tsx 第1292-1356行

问题描述: 如果 chunkUpload(包含 completeUpload)成功,状态已清理,但后续 submitFormData 失败,理论上状态已经清理了,但可能还有其他边界情况。

分析:

  • chunkUpload 成功 → deleteUploadState 已执行 → 状态已清理 ✅
  • 如果 submitFormData 失败,虽然状态已清理,但服务器端可能已经合并了文件,需要手动清理

💡 建议的修复方案

方案1:在错误处理中添加状态清理

// components/UploadModal.tsx handleSubmit
try {
  const chunkResult = await uploadSingleFile({...})
  // ...
  await submitFormData(formData, {...})
} catch (error) {
  console.error('Upload error:', error)
  
  // 如果使用了分片上传,清理状态
  if (zipFile && zipFile.size >= CHUNK_THRESHOLD) {
    try {
      const { deleteUploadState, generateFileId } = await import('@/lib/chunk-upload')
      const fileId = generateFileId(zipFile)
      await deleteUploadState(fileId)
    } catch (cleanupError) {
      console.warn('Failed to cleanup upload state:', cleanupError)
    }
  }
  
  setError((error as Error)?.message || '上传失败,请稍后再试')
  setUploading(false)
}

方案2:在 completeUpload 重试失败时清理状态

// lib/chunk-upload.ts chunkUpload
while (!result && retries < maxRetries) {
  try {
    result = await completeUpload(state.uploadId!, metadata)
    await deleteUploadState(fileId)
    return result
  } catch (error) {
    if (error instanceof Error && error.message === 'UPLOAD_SESSION_EXPIRED') {
      await deleteUploadState(fileId)
      throw new Error('上传会话已过期,请重新上传文件')
    }
    
    retries++
    if (retries < maxRetries) {
      await new Promise(resolve => setTimeout(resolve, retryDelay * retries))
    } else {
      // 达到最大重试次数,清理状态
      await deleteUploadState(fileId).catch(() => {})
      throw new Error('完成上传失败,已重试多次')
    }
  }
}

方案3:使用 finally 确保清理(谨慎使用)

// lib/chunk-upload.ts chunkUpload
let shouldCleanup = true
try {
  // ... 上传逻辑 ...
  result = await completeUpload(state.uploadId!, metadata)
  await deleteUploadState(fileId)
  shouldCleanup = false  // 成功清理后,标记为不需要清理
  return result
} finally {
  // ⚠️ 注意:这可能会在成功时也执行,需要标记
  // 实际上,如果 completeUpload 成功,状态已经清理了
  // 这里主要用于边界情况
}

📊 优先级评估

  1. 高优先级:问题1 - 错误情况下状态未清理

    • 这是最可能导致残留状态的问题
    • 修复后可以解决大部分问题
  2. 中优先级:问题2 - completeUpload 重试失败后的状态

    • 虽然默认不会触发,但边界情况下可能发生
  3. 低优先级:问题3 和 问题4

    • 问题3是设计选择(支持断点续传)
    • 问题4状态已清理,主要是服务器端文件清理问题

✅ 推荐修复

建议优先修复问题1,在 handleSubmit 的 catch 块中添加状态清理逻辑。这是最可能导致当前问题的原因。