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

196 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 上传状态管理代码审查报告
## 🔍 代码审查发现
### 问题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 块中添加状态清理逻辑。这是最可能导致当前问题的原因。