93 lines
4.4 KiB
Markdown
93 lines
4.4 KiB
Markdown
# 上传垃圾资源清理 - 收尾计划
|
||
|
||
## 概述
|
||
|
||
本计划承接前一会话的工作。前一会话已实现上传垃圾清理的完整三层防御机制(前端即时清理 + 后端保存时清理 + 后端 GC 兜底),并完成了 8 个页面的接入。经探查验证,发现**一个遗漏场景**:后台顶栏的个人资料弹窗(AdminLayout.vue)使用了 IconUpload 上传头像,但未接入上传会话清理机制。本计划补齐该场景并完成整体验证。
|
||
|
||
## 当前状态分析
|
||
|
||
### 已完成(无需改动)
|
||
|
||
- **前端基础设施**:`api/index.ts` 的 `deleteUpload` 方法、`useUploadSession.ts` composable
|
||
- **上传组件**:`IconUpload.vue`、`VideoUpload.vue` 已支持 `uploadSession` prop + `currentKey` 跟踪
|
||
- **8 个页面接入**:Categories / Platforms / Banners / Partners / Employees / Customers / Settings / ProjectEdit
|
||
- **后端基础设施**:SystemController Upload/DELETE 端点、UploadTrackingService、UploadCleanupJob、各业务 Service 的 ConsumeAsync 调用
|
||
|
||
### 遗漏场景(本次修复)
|
||
|
||
- **AdminLayout.vue 个人资料弹窗**:使用 IconUpload 上传头像,但未接入上传会话。当用户上传新头像后点击"取消"或关闭弹窗,文件成为垃圾资源(仅靠 24h GC 兜底,不符合即时清理目标)。
|
||
|
||
### 范围外说明(不在本次修复)
|
||
|
||
- **ProjectPackage 软删除孤儿**:`DeletePackageAsync` 仅设 `IsDeleted = true`,不删除存储文件。这是实体级清理问题,不属于"上传垃圾清理"范畴(上传时立即消费,无 pending 状态),单独跟踪。
|
||
- **WebGL 解压失败孤儿**:ZIP 上传成功但解压失败时,文件已落库存储。属于异常流程清理,单独跟踪。
|
||
|
||
## 计划改动
|
||
|
||
### 改动 1:AdminLayout.vue 接入上传会话清理
|
||
|
||
**文件**:`d:\TraeProject\MilliSim4\frontend\src\layouts\AdminLayout.vue`
|
||
|
||
**改动内容**(参照 Employees.vue 模式):
|
||
|
||
1. **导入 composable**(script 顶部):
|
||
```typescript
|
||
import { useUploadSession } from '@/composables/useUploadSession'
|
||
```
|
||
|
||
2. **创建实例**(setup 区域):
|
||
```typescript
|
||
const uploadSession = useUploadSession()
|
||
```
|
||
|
||
3. **个人资料 el-dialog 增加 @closed 事件**(约 L142):
|
||
```vue
|
||
<el-dialog v-model="profileVisible" ... @closed="uploadSession.cleanupPending()">
|
||
```
|
||
|
||
4. **IconUpload 传入 uploadSession prop**(约 L157):
|
||
```vue
|
||
<IconUpload
|
||
v-model="profileForm.avatar"
|
||
folder="avatars"
|
||
alt="头像"
|
||
:upload-session="uploadSession"
|
||
/>
|
||
```
|
||
|
||
5. **saveProfile 成功后重置会话**(约 L259-276 内的成功分支):
|
||
```typescript
|
||
// 保存成功后清空 pending 列表:头像已被后端 ConsumeAsync 消费
|
||
uploadSession.reset()
|
||
```
|
||
|
||
**注意**:
|
||
- AdminLayout 是常驻布局组件,不会频繁卸载,`onBeforeUnmount` 自动清理在此场景下主要起兜底作用;真正的清理依赖 `@closed` 事件。
|
||
- 不需要改"取消"按钮逻辑,因为 `@closed` 事件在点击取消(关闭弹窗)时也会触发。
|
||
|
||
### 改动 2:验证步骤
|
||
|
||
**前端验证**:
|
||
1. 后端 dev server 已运行(前一会话已启动,确认端口 5041 监听)
|
||
2. 前端 Vite dev server 已运行(确认无 HMR 编译错误)
|
||
3. 如有编译错误则修复
|
||
|
||
**功能场景测试**(建议用户在浏览器手动验证至少 2 个场景):
|
||
- 场景 A:新增分类 → 上传图标 → 点击取消 → 确认图标被清理(检查后端日志 DELETE 请求)
|
||
- 场景 B:编辑员工 → 上传新头像 → 点击取消 → 确认头像被清理
|
||
- 场景 C:后台顶栏 → 点击头像 → 上传新头像 → 点击取消 → 确认头像被清理(本次新增场景)
|
||
|
||
## 假设与决策
|
||
|
||
1. **不动后端代码**:前一会话后端已完整实现,本次仅前端补漏。
|
||
2. **不动 ProjectPackage 软删除逻辑**:属于实体生命周期管理,不在上传垃圾清理范畴。
|
||
3. **保留 onBeforeUnmount 兜底**:AdminLayout 虽不常卸载,但 composable 的 onBeforeUnmount 仍是安全网,不删除。
|
||
4. **使用 `@closed` 而非 `@close`**:`@closed` 在弹窗动画结束后触发,避免与销毁逻辑冲突(与已接入页面保持一致)。
|
||
|
||
## 验证步骤
|
||
|
||
1. 修改 AdminLayout.vue 后,查看 Vite HMR 日志确认无编译错误
|
||
2. 检查后端进程仍在运行(端口 5041)
|
||
3. 向用户报告:本次补漏 + 整体验证结果
|
||
4. 建议用户手动测试场景 C(个人资料头像取消清理)
|