// 待核对处理结果的衣物(本地持久化 + 结果核对)。 // // 衣物上传成功后写入;即使用户退出小程序,下次进入衣橱时也会逐条查询 // /clothes/:uuid/status,把「处理成功 / 处理失败」的最终结果补告给用户, // 避免"退出后不知道衣服到底处理成功没有"。 // // 处理本身在后端与连接解耦(独立 ctx + 定时恢复调度器),前端只承担 // 「进入时核对一次」的最小职责:pending 为空时零额外请求,不做常驻轮询。 import { STORAGE_KEYS, ProcessStatus } from './config' import { request } from './request' import { logger } from './logger' // 日志模块名 const LOG = 'pending-clothes' // 存储上限:防御性上限,在途任务正常只有 1-2 条 const MAX_ITEMS = 20 // 过期时长(秒):超时直接丢弃;后端恢复调度器最迟约 5 分钟接手,24h 足够冗余 const EXPIRE_SEC = 24 * 60 * 60 // 衣物不存在(已被用户删除):静默移出,避免残留 const CLOTHES_NOT_FOUND_CODE = 50000 // 无权操作该衣物(换账号后残留的 uuid):同样静默移出,避免每次进入都反复请求 const CLOTHES_NO_PERMISSION_CODE = 50005 export interface PendingClothes { uuid: string createdAt: number // 秒级时间戳 } export interface PendingResolveResult { finished: string[] // 本轮核对为处理成功 failed: Array<{ uuid: string; reason: string }> // 本轮核对为处理失败(含原因) processing: string[] // 仍在处理中,保留待下次核对 } function writeAll(list: PendingClothes[]): void { try { wx.setStorageSync(STORAGE_KEYS.PENDING_CLOTHES, list) } catch (e) { logger.warn(LOG, '待核对衣物写入失败(降级为仅本次会话生效)') } } // 读取并清洗存储:非法结构 / 过期记录一并剔除;读取即顺手落盘 export function getPendingClothes(): PendingClothes[] { let raw: any = null try { raw = wx.getStorageSync(STORAGE_KEYS.PENDING_CLOTHES) } catch (e) { raw = null } if (!Array.isArray(raw)) return [] const now = Math.floor(Date.now() / 1000) const valid = (raw as any[]) .filter((it) => it && typeof it.uuid === 'string' && !!it.uuid) .filter((it) => Number(it.createdAt) > 0 && now - Number(it.createdAt) <= EXPIRE_SEC) .slice(-MAX_ITEMS) as PendingClothes[] if (valid.length !== raw.length) writeAll(valid) return valid } // 登记待核对衣物(按 uuid 幂等去重) export function addPendingClothes(uuid: string): void { if (!uuid) return const list = getPendingClothes().filter((it) => it.uuid !== uuid) list.push({ uuid, createdAt: Math.floor(Date.now() / 1000) }) const next = list.slice(-MAX_ITEMS) writeAll(next) logger.debug(LOG, '待核对衣物已登记, uuid=', uuid, '总数=', next.length) } export function removePendingClothes(uuid: string): void { if (!uuid) return const list = getPendingClothes() const next = list.filter((it) => it.uuid !== uuid) if (next.length === list.length) return writeAll(next) logger.debug(LOG, '待核对衣物已移出, uuid=', uuid, '剩余=', next.length) } /** * 逐条核对处理状态: * - 成功 / 失败:移出存储并计入结果(失败带原因); * - 衣物已删除:静默移出; * - 仍在处理中 / 网络失败 / 未登录:保留待下次核对。 * 后台核对不弹错、不触发登录跳转(showError:false + silentAuth:true)。 */ export function resolvePendingClothes(): Promise { const list = getPendingClothes() const result: PendingResolveResult = { finished: [], failed: [], processing: [] } if (!list.length) return Promise.resolve(result) const tasks = list.map((item) => request<{ image_process_status: number; fail_reason?: string }>({ url: `/clothes/${item.uuid}/status`, showError: false, silentAuth: true, }) .then((res) => { const status = Number(res && res.image_process_status) if (status === ProcessStatus.SUCCESS) { removePendingClothes(item.uuid) result.finished.push(item.uuid) return } if (status === ProcessStatus.FAILED) { removePendingClothes(item.uuid) result.failed.push({ uuid: item.uuid, reason: (res && res.fail_reason) || '识别失败' }) return } result.processing.push(item.uuid) }) .catch((e: any) => { // 衣物已被删除 / 已不属于当前账号:静默移出,避免每次进入都反复请求 if (e && (e.code === CLOTHES_NOT_FOUND_CODE || e.code === CLOTHES_NO_PERMISSION_CODE)) { removePendingClothes(item.uuid) return } logger.warn(LOG, '核对衣物处理状态失败, uuid=', item.uuid, 'code=', e && e.code) result.processing.push(item.uuid) }), ) return Promise.all(tasks).then(() => { logger.info( LOG, '待核对衣物解析完成, 成功=', result.finished.length, '失败=', result.failed.length, '处理中=', result.processing.length, ) return result }) }