# 上传状态管理代码审查报告 ## 🔍 代码审查发现 ### 问题1:错误情况下状态未清理 ⚠️ **位置**:`components/UploadModal.tsx` 第1292-1356行 **问题描述**: 如果 `uploadSingleFile` 或 `submitFormData` 抛出错误,catch 块中没有清理 IndexedDB 中的上传状态。 **代码片段**: ```typescript 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` 在重试过程中一直失败,最终抛出错误时,状态可能不会被清理。 **代码片段**: ```typescript 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行 **问题描述**: 如果在上传分片的过程中抛出错误(比如"上传已取消"),状态不会被清理。 **代码片段**: ```typescript 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:在错误处理中添加状态清理 ```typescript // 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 重试失败时清理状态 ```typescript // 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 确保清理(谨慎使用) ```typescript // 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 块中添加状态清理逻辑。这是最可能导致当前问题的原因。