- 创建统一的下载错误处理模块 (lib/download-errors.ts) - 实现13种错误类型分类和中文错误消息 - 后端统一使用 createDownloadError 创建标准化错误响应 - 前端统一使用 parseDownloadError 解析错误并显示 - 修复正式系统数据库Schema,添加产品全案相关字段和表 - 执行数据库迁移:添加 task_no, task_status, task_fetched_at 字段 - 创建 material_assets, api_keys, api_call_logs 表
196 lines
6.0 KiB
Markdown
196 lines
6.0 KiB
Markdown
# 上传状态管理代码审查报告
|
||
|
||
## 🔍 代码审查发现
|
||
|
||
### 问题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 块中添加状态清理逻辑。这是最可能导致当前问题的原因。
|
||
|