- 创建统一的下载错误处理模块 (lib/download-errors.ts) - 实现13种错误类型分类和中文错误消息 - 后端统一使用 createDownloadError 创建标准化错误响应 - 前端统一使用 parseDownloadError 解析错误并显示 - 修复正式系统数据库Schema,添加产品全案相关字段和表 - 执行数据库迁移:添加 task_no, task_status, task_fetched_at 字段 - 创建 material_assets, api_keys, api_call_logs 表
6.0 KiB
6.0 KiB
上传状态管理代码审查报告
🔍 代码审查发现
问题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 - 错误情况下状态未清理
- 这是最可能导致残留状态的问题
- 修复后可以解决大部分问题
-
中优先级:问题2 - completeUpload 重试失败后的状态
- 虽然默认不会触发,但边界情况下可能发生
-
低优先级:问题3 和 问题4
- 问题3是设计选择(支持断点续传)
- 问题4状态已清理,主要是服务器端文件清理问题
✅ 推荐修复
建议优先修复问题1,在 handleSubmit 的 catch 块中添加状态清理逻辑。这是最可能导致当前问题的原因。