--- name: 删除衣物时清理COS图片 overview: 在衣物删除流程中,同步删除 COS 上对应的处理后图片对象,并对上传处理中的并发竞态做防护,避免删除衣物后 COS 图片残留。 todos: - id: add-cos-delete-util content: 在 pkg/upload/upload.go 新增 FileDeleteCos 删除工具函数 status: completed - id: delete-cos-on-clothes-delete content: 修改 DeleteByUUID,事务成功后删除 processed_image 对应 COS 对象 status: completed dependencies: - add-cos-delete-util - id: guard-async-upload-race content: 修改 processImageAsync,上传后检测衣物已删除则清理 COS 对象 status: completed dependencies: - add-cos-delete-util --- ## 产品概述 修复用户删除衣物后,腾讯云 COS 中对应抠图图片对象未被删除的问题。当前删除衣物只软删除了数据库记录,衣物抠图后上传到 COS 的图片仍残留在云端,产生无效存储占用。 ## 核心功能 - 删除衣物时,若该衣物已有处理后的图片(`processed_image` 非空),同步删除 COS 上对应的图片对象。 - COS 删除失败不影响衣物删除结果:用户删除请求始终成功返回,删除失败仅记录日志,便于后续排查与补偿。 - 处理「上传处理中删除衣物」的竞态:用户在图片异步抠图/上传期间删除衣物时,异步流程在检测到衣物已被删除后,主动清理刚上传的 COS 对象,避免残留。 ## 技术栈 - 语言与框架:Go + Gin + GORM(沿用现有项目技术栈) - 对象存储:腾讯云 COS SDK `github.com/tencentyun/cos-go-sdk-v5 v0.7.75`(已存在) - 复用现有:`global.CosClient`、`pkg/upload` 工具层、`slog` 日志、OpenTelemetry trace span ## 实现方案 ### 总体思路 在现有文件上传工具层新增一个 COS 删除工具函数,供删除流程与异步处理流程复用;在衣物删除事务提交成功后调用删除,删除失败仅记录日志不影响主流程;同时在异步图片处理成功回填前增加「衣物是否已被删除」的存在性检查,处理上传/删除并发竞态。 ### 关键设计决策 - **删除失败不影响主流程**:COS 删除是尽力而为的清理操作。数据库软删除与额度回退仍以现有事务为准,COS 删除放在事务提交之后、并忽略错误返回,保证用户删除体验不因外部存储异常而失败。 - **复用对象键语义**:`clothes.ProcessedImage` 字段存储的值即 COS 对象键(如 `{用户UUID}/{用户ID}/{随机名}.png`),上传时 `FileUploadCos(ctx, cosKey)` 正是以该值作为对象名上传,因此删除时直接用 `c.ProcessedImage` 作为 key 调用 `Object.Delete`。 - **短超时防阻塞**:删除工具函数内部使用 10 秒超时 context,避免 COS 网络异常长时间阻塞删除接口。 - **竞态防护位置**:在 `processImageAsync` 上传 COS 成功后、回填 `processed_image` 之前,通过 `ClothesGetByUUID` 检查衣物是否仍存在(软删除后普通查询会过滤已删行,`isNotFound` 为 true),若已删除则删除刚上传的对象并结束,避免回填丢失造成残留。 ### 性能与可靠性 - COS Delete 为单次网络调用,时间复杂度 O(1);删除接口新增一次短超时调用,影响可控。 - 上传/删除竞态窗口:检测存在性与删除 COS 之间仍可能存在极小窗口,但该窗口下后果仅为偶发残留单个对象,风险可控;核心场景(已处理完成再删除)被完整覆盖。 - 不改动现有额度回退逻辑、错误码与软删除行为,保持向后兼容。 ### 实现要点 - `pkg/upload/upload.go` 新增 `FileDeleteCos`,风格与 `FileUploadCos` 一致:nil client 保护、空 key 直接返回、trace span、10s 超时。 - `internal/service/clothes.go` 的 `DeleteByUUID`:在事务闭包外声明变量承接 `c.ProcessedImage`,事务成功后非空则调用删除,失败用 `slog.WarnContext` 记录 `clothes_uuid` 与 `cos_key`,不记录密钥。 - `internal/service/clothes.go` 的 `processImageAsync`:上传 COS 成功后、分类与回填前检查衣物是否存在;若已删除,删除刚上传对象并 `return`(额度已由删除事务回退,无需再处理)。 ## 目录结构 ``` project-root/ ├── pkg/upload/upload.go # [MODIFY] 新增 FileDeleteCos 删除 COS 对象工具函数 └── internal/service/clothes.go # [MODIFY] DeleteByUUID 删除后清理 COS;processImageAsync 增加删除竞态防护 ``` ## 关键代码结构 ``` // FileDeleteCos 删除 COS 对象。key 为对象键(即 processed_image 存储值)。 // client 未初始化返回 error;key 为空返回 nil。 func FileDeleteCos(ctx context.Context, key string) error ```