Files
zt-closet-wxmini-frontend/.codebuddy/plans/upload-process-resilience_7d735905.md

13 KiB
Raw Permalink Blame History

name, overview, todos
name overview todos
upload-process-resilience 修复"用户退出小程序后衣物处理结果无法感知、失败态误报"的前后端一致性问题:修正上传页 90s 超时误报与"额度已自动退回"文案,增加待处理衣物持久化与重进恢复查询,衣橱列表正确区分"处理中/处理失败"(后端列表与详情接口补充 image_process_status / fail_reason)。
id content status
status-utils-storage 新增 clothes-status 与 pending-clothes 工具,config 加 PENDING_CLOTHES,模型补可选状态字段 completed
id content status dependencies
upload-fix 改造上传页:轮询预算对齐后端、超时不误报失败、失败与额度文案仅真实失败触发、onShow 续查 completed
status-utils-storage
id content status dependencies
wardrobe-recover 衣橱接入待处理解析:失败弹窗引导详情、列表区分处理中与失败、终结后静默刷新 completed
status-utils-storage
id content status dependencies
detail-align 详情页用接口状态初始化并跳过终态轮询,修正处理中与失败态展示时机 completed
status-utils-storage
id content status
backend-fields 后端列表与详情补处理状态字段、非属主隐藏失败原因、上传回退改用独立 context、更新接口文档 completed
id content status dependencies
verify-audit Use [subagent:code-explorer] 审计字段消费方无回归,运行前端 tsc 与后端 go build 验证 completed
upload-fix
wardrobe-recover
detail-align
backend-fields

用户需求

用户报告疑虑:"用户退出小程序之后,会导致上传的衣服处理失败?"。经排查确认:后端处理任务与客户端连接解耦,已成功上传的衣物不会因退出而处理失败;但由此暴露出多处真实的前端体验缺陷与前后端契约缺口,用户要求全面修复,包括 90s 超时误报与"额度已退回"文案不实的问题,并允许同步调整后端接口。

产品概述

修复「上传衣物后退出小程序」场景下的状态可感知性:处理结果在重新进入小程序后自动恢复与告知;处理中、处理失败状态如实展示;超时不再被误判为失败;额度退回文案只在真实失败时出现。

核心功能

  • 退出后恢复处理结果:衣物上传成功后,即使用户退出小程序或离开页面,重新进入衣橱时自动核对该衣物的最终处理结果;成功无感展示,失败给出明确提示并可直达衣物详情。
  • 处理状态如实展示:衣橱列表正确区分「处理中(排队中/抠图中/分类中)」与「处理失败」,失败项显示醒目标识,不再将失败项永久显示为"AI 处理中"。
  • 超时不再误报失败:上传页轮询时长与后端处理现实对齐;轮询结束但未收到最终结果时,展示中性提示(仍在处理中,可先返回衣橱稍后查看),不进入"识别失败"界面。
  • 额度文案准确:仅当后端确认处理失败(此时额度已自动退回)才展示"识别失败 + 额度已自动退回";处理超时、网络中断等场景不出现该文案。
  • 失败原因可达:衣物详情页进入时即可正确渲染处理中/失败状态(不再依赖轮询首次返回),失败原因与后续操作入口清晰可见。

技术栈

  • 前端:微信小程序原生框架 + TypeScript(沿用现有工程与组件体系,无新增依赖)
  • 后端:Go(Gin + GORM,仓库 /root/zt-closet-backend,仅做加法式接口扩展与回退加固)
  • 复用既有设施:utils/request.ts(request/uploadFile、silentAuth/showError)、utils/logger.ts、utils/outfit-status.ts(状态语义收敛范式)、STORAGE_KEYS + Ax_TASK 式续接范式(ai-recommend 的 onShow 续轮询/onHide 停轮询/结果落库即清理)
  • 组件:复用 v2-overlay(modal 形态)与 clothes-card(statusText/statusType,已有 .tile-flag.is-ink 醒目样式)

实现方案

整体策略:前后端契约补齐 + 前端状态语义收敛 + 轻量持久化续接,分四层落地。

  1. 状态语义单一来源(前端):新增 utils/clothes-status.ts,对齐 utils/outfit-status.ts 范式,统一输出「处理中 / 处理失败 / 无标识」的文案与样式判定;对旧后端响应(无状态字段)保留"字段为空即处理中"的降级推断,保证向后兼容。
  2. 续接机制(前端):新增 utils/pending-clothes.ts,上传成功即把 {uuid, createdAt} 写入本地存储(新增 STORAGE_KEYS.PENDING_CLOTHES);衣橱 onShow 时仅当有待处理记录才逐条查询 /clothes/:uuid/status(showError:false + silentAuth:true),终结态移出存储:成功静默、失败用 v2-overlay 提示并可跳详情;未完成保留待下次核对。存储上限 20 条、超 24 小时自动清理,防止膨胀。上传页自身 onHide 停轮询(对齐 ai-recommend)、onShow 恢复续查。
  3. 超时策略修正(前端):上传页轮询预算由 60×1.5s≈90s 调整为 2s×90≈180s(与后端单任务 3 分钟下限对齐);预算耗尽只停止轮询并展示中性提示,绝不置 failed;failed 仅在收到后端 image_process_status=5 时进入,此时"额度已自动退回"文案才成立(后端失败路径确实回退)。
  4. 后端字段补齐:ClothesGetByUUIDResponse 增加 image_process_status、fail_reason(buildClothesResponse 填充),列表与详情共用;衣物详情页据此在 onLoad 即正确渲染状态并跳过终态轮询,消除"先显示正常详情/空白再回退处理中"的闪烁。非属主读取(穿搭分享 share_uuid、衣柜分享)屏蔽 fail_reason(可能含内部错误信息),保留状态字段。另加固:上传同步阶段失败回退额度改用不受请求取消影响的独立 context(沿用 processImageAsync 的 trace+Background 派生手法),消除"客户端中途断开导致额度已扣无法回退"窗口。

关键取舍:

  • 不做全局常驻轮询/不做后台任务队列:处理本身在服务端持续进行且有恢复调度器兜底,前端只承担"进入时核对一次"的最小职责,避免耗电与请求浪费。
  • 不新增接口:复用已有 status 接口(幂等只读),列表/详情字段为向后兼容的加法;旧后端可正常降级使用。
  • 明确不在范围:上传请求尚未返回时退出导致该次上传中断(属"上传失败",前端无法挽回)。

性能与可靠性:pending 为空时零额外请求;解析为并发 N 条 status 查询(N 受上限 20 约束且通常为 1-2);列表刷新与解析并行、由已有 reqSeq 序号防竞态;解析完成后若存在终结态记录再做一次静默刷新,保证列表状态与服务端一致。

架构设计

数据流(新增/变更部分):

flowchart TB
  subgraph 前端
    U[上传页 upload] -->|uploadFile 成功写入| S[(本地存储 pending 衣物)]
    U -->|180s 有界轮询| ST[/clothes/:uuid/status/]
    U -->|真实失败: 弹失败态| U
    W[衣橱 onShow] -->|读取 pending 逐条核对| ST
    W -->|失败提示弹层| M[衣物详情]
    W -->|刷新| L[列表: 处理中/处理失败]
    D[衣物详情页] -->|onLoad 用详情状态初始化, 仅处理中才轮询| ST
  end
  subgraph 后端
    ST --> P[异步处理流水线: 与连接解耦 + 定时恢复]
    P --> DB[(clothes 表: 状态/失败原因)]
    DB -->|列表/详情新增字段| L
  end

沿用现有分层:页面层(upload/wardrobe/clothes-detail)→ 工具层(clothes-status、pending-clothes、request、logger)→ 存储层(wx storage);后端 controller(router) → service → dao 分层不变,仅在 DTO 与 service 映射层做加法。

实现说明(执行要点)

  • 存储写入需按 uuid 幂等去重;读取时先做过期清理(>24h)。
  • status 查询统一 showError:false、silentAuth:true:后台核对不弹错、不触发登录跳转;未登录时 request 内置直接 reject,pending 保留待下次。
  • 衣物不存在(50000,用户已删除)时静默移出 pending,避免残留。
  • 日志沿用 logger 模块名(upload/wardrobe/pending-clothes/clothes-detail),仅记录 uuid 与状态码,不打印敏感信息。
  • 列表渲染保持 _statusText/_statusType 字段形状不变,仅替换计算逻辑,wxml 主结构不动,降低改动面。
  • 后端字段为可选扩展:前端所有读取均做缺省降级,灰度期间新旧接口均可工作。
  • 失败弹窗文案聚合("有 N 件衣物 AI 识别失败"),主按钮跳转最近一条失败衣物详情。
  • 不改动 ai-recommend、outfit 等无关页面逻辑;clothes-card 组件无需改动。

目录结构

zt-closet-wxmini-frontend/
├── miniprogram/
│   ├── utils/
│   │   ├── config.ts                  # [MODIFY] STORAGE_KEYS 新增 PENDING_CLOTHES(待核对处理结果的衣物 uuid 列表)
│   │   ├── clothes-status.ts          # [NEW] 衣物处理状态语义单一来源。导出 isProcessing/isTerminal/clothesStatusPill 等:1/2/3 归"AI 处理中"(plain),5 归"处理失败"(ink),4 为空;字段缺失时降级为原"category_main 与 processed_image 均为空则处理中"推断,保证旧后端兼容
│   │   └── pending-clothes.ts         # [NEW] 待处理衣物持久化与解析器。add/remove/get(幂等、上限 20、24h 过期清理);resolvePendingClothes() 并发查询各 uuid 的 /clothes/:uuid/status,返回 {finished, failed:[{uuid,reason}], processing},终结态移出存储;50000 静默移除;network 失败保留
│   ├── types/
│   │   └── models.ts                  # [MODIFY] ClothesItem 增加可选 image_process_status?: number、fail_reason?: string(对齐后端新字段)
│   └── pages/
│       ├── upload/
│       │   ├── upload.ts              # [MODIFY] 上传成功即 addPendingClothes;轮询改 2s×90≈180s;预算耗尽只停轮询并置 slowHint 中性提示,不再置 failed;仅后端返回 FAILED 时进入失败态(同时 removePending);onHide 停轮询、onShow 续查恢复;SUCCESS 移除 pending
│       │   └── upload.wxml            # [MODIFY] 处理中区块增加 slowHint 中性文案(可先返回衣橱,结果自动更新);失败区块的"额度已自动退回"改为受数据开关控制,仅真实失败展示
│       ├── wardrobe/
│       │   ├── wardrobe.ts            # [MODIFY] onShow 在有待处理记录时调用 resolvePendingClothes:失败弹窗(v2-overlay)可跳详情、存在终结态则静默重刷列表;fetchList 映射改用 clothesStatusPill
│       │   ├── wardrobe.wxml          # [MODIFY] 新增 v2-overlay 节点承接失败提示
│       │   └── wardrobe.json          # [MODIFY] 注册 v2-overlay 组件
│       └── clothes-detail/
│           ├── clothes-detail.ts      # [MODIFY] fetchDetail 用响应状态初始化 imageStatus/step/failReason;终态(4/5)不启动轮询,仅处理中才轮询;轮询预算耗尽保持处理中视图不误报
│           └── clothes-detail.wxml    # [MODIFY] 处理中提示补"排队时可能更久"中性说明(可选微调)

zt-closet-backend/
├── internal/
│   ├── dto/
│   │   └── clothes.go                 # [MODIFY] ClothesGetByUUIDResponse 增加 image_process_status、fail_reason(列表/详情共用)
│   └── service/
│       ├── clothes.go                 # [MODIFY] buildClothesResponse 填充新字段;GetByUUID 非属主(share_uuid)清空 fail_reason;上传失败回退 refundCosQuota 改用独立 context(trace+Background 派生,断开连接也能回退)
│       └── closet.go                  # [MODIFY] 衣柜分享响应路径同样屏蔽非属主 fail_reason,保留状态字段
└── 接口文档.md                         # [MODIFY] 列表/详情响应补充新字段说明,并注明 fail_reason 仅属主可见

关键代码结构

// utils/pending-clothes.ts —— 跨模块契约
export interface PendingClothes { uuid: string; createdAt: number } // createdAt: 秒级时间戳

export function addPendingClothes(uuid: string): void
export function removePendingClothes(uuid: string): void
export function getPendingClothes(): PendingClothes[] // 读取时顺带清理过期项

export function resolvePendingClothes(): Promise<{
  finished: string[]                          // 本轮确认成功
  failed: Array<{ uuid: string; reason: string }> // 本轮确认失败(含原因)
  processing: string[]                        // 仍在处理中,保留待下次
}>

Agent Extensions

SubAgent

  • code-explorer
  • Purpose: 在改动完成后跨仓库审计 ClothesGetByUUIDResponse(后端 closet.go、clothes.go 等消费方)与前端 ClothesItem 全部使用页面(wardrobe、ai-recommend、outfit-edit、closet-share-create、outfit-detail 等)的受影响调用点,确认新增字段与非属主屏蔽逻辑无遗漏、无破坏。
  • Expected outcome: 输出受影响调用点清单与回归结论(是否存在遗漏消费方或破坏性变更),作为验证环节的依据。