Files
zt-closet-backend/.codebuddy/plans/删除衣物时清理COS图片_39034235.md
2026-09-11 01:41:52 +08:00

4.7 KiB
Raw Permalink Blame History

name, overview, todos
name overview todos
删除衣物时清理COS图片 在衣物删除流程中,同步删除 COS 上对应的处理后图片对象,并对上传处理中的并发竞态做防护,避免删除衣物后 COS 图片残留。
id content status
add-cos-delete-util 在 pkg/upload/upload.go 新增 FileDeleteCos 删除工具函数 completed
id content status dependencies
delete-cos-on-clothes-delete 修改 DeleteByUUID,事务成功后删除 processed_image 对应 COS 对象 completed
add-cos-delete-util
id content status dependencies
guard-async-upload-race 修改 processImageAsync,上传后检测衣物已删除则清理 COS 对象 completed
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