--- name: model-call-retry overview: 为抠图(Matting)与分类(Classification)两个模型调用增加可配置的自动重试机制,默认最大重试 3 次;配置项分别加到 MattingModel 与 ClassificationModel 配置段下,并在衣物异步处理流程中接入重试逻辑。 todos: - id: add-retry-config content: 在 section.go 结构体及 config.yaml、config.example.yaml 中为 MattingModel/ClassificationModel 增加 MaxRetry 配置 status: completed - id: default-retry-value content: 在 main.go 读取配置后对两个模型 MaxRetry status: completed dependencies: - add-retry-config --- ## 产品概述 为衣物图片处理流水线中的抠图(Matting)与分类(Classification)两个模型调用增加自动重试能力,降低下游模型服务偶发失败导致的衣物处理失败率。重试次数通过配置文件分别控制,默认最大重试 3 次。 ## 核心功能 - 抠图调用失败时自动重试,默认最大重试 3 次(总尝试 4 次) - 分类调用失败时自动重试,默认最大重试 3 次(总尝试 4 次) - 重试次数分别配置在 `MattingModel` 与 `ClassificationModel` 两个配置段下,配置缺失或小于等于 0 时兜底为 3 - 全部重试失败后维持现有失败处理:抠图失败回退上传额度并标记失败;分类失败保留已抠图结果(processed_image)并标记失败,便于用户手动补全 - 删除处理中衣物不回退额度的既有防滥用设计保持不变(不做修改) ## 技术栈 - Go 1.27(使用泛型、`log/slog`、`context` + `time.After`) - 配置:viper(复用现有 `setting.ReadSection` 读取模式) - 日志:`log/slog`(复用现有 `slog.WarnContext` / `slog.ErrorContext`) ## 实现方案 ### 总体策略 复用现有配置读取与兜底模式,在配置结构体、配置文件中增加 `MaxRetry` 字段;在异步处理函数 `processImageAsync` 中新增一个泛型重试辅助函数 `retryCall`,包裹抠图与分类两处调用,实现失败自动重试,失败分支逻辑保持不变。 ### 关键设计 1. **配置层(最小改动)** - `pkg/setting/section.go` 的 `MattingModelSettingS`、`ClassificationModelSettingS` 各新增 `MaxRetry int` 字段。 - `configs/config.yaml`(实际配置)与 `configs/config.example.yaml`(示例配置)的 `MattingModel`、`ClassificationModel` 段各新增 `MaxRetry: 3`。 - `main.go` 在读取 `MattingModel`、`ClassificationModel` 后,对 `MaxRetry <= 0` 兜底为 3,与现有 `MaxSizeMB <= 0` 兜底 20、`Model == ""` 兜底 `hy4` 的模式保持一致。 2. **重试层(泛型辅助函数)** - 在 `internal/service/clothes.go` 新增 `retryCall[T any]` 泛型函数:循环尝试 `maxRetry+1` 次;每次失败记录 `slog.WarnContext`;重试间隔使用 `select` + `time.After(1s)` + `ctx.Done()`,保证 ctx 到期时提前终止。 - 抠图与分类调用分别用 `retryCall` 包裹,`maxRetry` 分别取 `global.MattingModelSetting.MaxRetry` 与 `global.ClassificationModelSetting.MaxRetry`。 - `fn` 闭包捕获外部 `ctx`,每次调用内部会重建 multipart body,无需额外重置状态。 3. **失败分支保持不变** - 抠图全部重试失败后仍调用 `markProcessFailed`(标记失败 + 回退上传额度)。 - 分类全部重试失败后仍保留 `processed_image` 并标记 `ImageProcessFailed`(不回退额度,允许用户手动补全)。 ### 关键代码结构 ``` // retryCall 对 fn 进行最多 maxRetry 次额外重试(总尝试 maxRetry+1 次)。 // 重试间隔 1s;ctx 到期则提前终止并返回 ctx.Err()。 func retryCall[T any](ctx context.Context, maxRetry int, name string, fn func() (T, error)) (T, error) // 调用示例 processedData, err := retryCall(ctx, global.MattingModelSetting.MaxRetry, "matting", func() ([]byte, error) { return mattingClient.Matting(ctx, imageData) }) ``` ## 实现注意 - **MaxRetry 语义**:失败后的额外重试次数,总尝试次数 = MaxRetry + 1;默认 3 即总尝试 4 次。 - **超时预算**:异步 ctx 总超时为 3 分钟,单次 HTTP 客户端超时为 120s。`retryCall` 通过 `ctx.Done()` 检查避免超时后继续重试;瞬时失败(连接拒绝、偶发非 200)通常快速返回,3 分钟预算足够。 - **并发限流不受影响**:`imageProcessSem` 信号量在 `processImageAsync` 入口已获取,重试发生在同一 goroutine 内,不会放大并发。 - **blast radius 控制**:仅新增配置字段与重试辅助函数,不修改 `DeleteByUUID` 额度回退逻辑;`config.yaml` 中重复的 `UserDefaultQuota` 段为既有问题,本次不处理。 ## 目录结构 ``` zt-closet-backend/ ├── pkg/setting/section.go # [MODIFY] MattingModelSettingS、ClassificationModelSettingS 各新增 MaxRetry int 字段 ├── configs/config.yaml # [MODIFY] MattingModel、ClassificationModel 段各新增 MaxRetry: 3 ├── configs/config.example.yaml # [MODIFY] MattingModel、ClassificationModel 段各新增 MaxRetry: 3 ├── main.go # [MODIFY] 读取 MattingModel、ClassificationModel 后对 MaxRetry<=0 兜底为 3 └── internal/service/clothes.go # [MODIFY] 新增 retryCall 泛型辅助函数,抠图/分类调用接入重试 ```