This commit is contained in:
2026-09-15 23:04:13 +08:00
parent ade2453083
commit 8c2ea84939
17 changed files with 749 additions and 161 deletions
+90 -23
View File
@@ -72,7 +72,7 @@
3. **衣物上传是异步的**,返回只代表"已排队",必须轮询状态接口。
4. **更新衣物的主颜色请求字段是 `color`**,响应里才是 `color_main`。
5. **金额单位都是分、时间都是秒**,展示前要换算。
6. **AI 推荐生成后不保存不退额度**;退款是人工审核,不是实时到账。
6. **AI 推荐是异步的**:`POST /ai-recommend` 只返回 `task_id`,必须轮询 `GET /ai-recommend/status` 才能拿到结果;生成后不保存不退额度。退款是人工审核,不是实时到账。
---
@@ -211,10 +211,11 @@ Authorization: Bearer <access_token>
- 穿搭公开 `PUT /outfit/:uuid/public`(重复公开**不会**重复扣额度;但**取消公开后再次公开会再扣 1 次**,见 [6.6](#66-额度规则全景))
- 标记已读 `POST /notification/:uuid/read`
- 保存 AI 推荐 `POST /ai-recommend/save`
- 提交 AI 推荐 `POST /ai-recommend`(任务生成中重复提交会返回**同一个 `task_id`**,不会重复扣额度)
- 取消订单 `POST /order/:orderNo/cancel`
- 取消分享 `POST /closet-share/:uuid/cancel`
**不建议重试**:`POST /clothes/upload`(每次都会扣 1 个上传额度)、`POST /ai-recommend`(每次扣 1 个 AI 额度,且并发会触发 `10003`)、`POST /order`(会创建新订单)。
**不建议重试**:`POST /clothes/upload`(每次都会扣 1 个上传额度)、`POST /order`(会创建新订单)。
---
@@ -225,6 +226,7 @@ Authorization: Bearer <access_token>
| 场景 | 轮询接口 | 终态 | 建议间隔 |
| --- | --- | --- | --- |
| 衣物 AI 处理 | `GET /clothes/:uuid/status` | `4` 成功 / `5` 失败(`1/2/3` 继续轮询) | 1~2 秒,建议上限 60 次 |
| AI 推荐生成 | `GET /ai-recommend/status` | `2` 成功 / `3` 失败(`1` 继续轮询) | 2 秒,建议上限 90 次 |
| 支付结果 | `GET /order`(按 `status` 或 `order_no` 过滤) | `2` 已支付 / `3` 已取消 / `4` 已过期 | 支付后 1 秒起,最多 15 分钟 |
| 退款进度 | `GET /order` | `5` 已退款 | 30 秒(人工审核,可能很久) |
| 今日运势未生成 | `GET /horoscope/today` | 返回数据(首次可能 `90300`) | 3~5 秒,最多 3 次 |
@@ -240,7 +242,7 @@ Authorization: Bearer <access_token>
---
## 3. 接口总表(48 个)
## 3. 接口总表(49 个)
### 健康检查(1)
@@ -411,9 +413,12 @@ Authorization: Bearer <access_token>
| source | int | 来源:`1`=用户创建 `2`=AI 推荐 |
| is_public | int | 公开:`1`=不公开 `2`=公开 |
| audit_status | int | 审核:`1`=待审核 `2`=审核通过 `3`=审核不通过 |
| status | int | 状态:`1`=正常 `2`=封禁 `3`=**待用户保存**(AI 推荐生成后未保存) |
| items | array | 衣物列表,元素 `{"clothes_uuid": string}` |
> ⚠️ 列表接口**不返回** `status` 字段,但数据库存在 `status`(`1`=正常 `2`=封禁 `3`=待用户保存)。AI 生成的"待保存"穿搭(`status=3`)也会出现在 `GET /outfit` 列表中,**前端需自行区分展示**(建议:AI 推荐未保存的单独归到"AI 推荐"入口,不要混入"我的搭配")。
> **关于 `status=3`(AI 待保存)**:AI 推荐生成的穿搭会以 `status=3` 出现在 `GET /outfit` 列表中,前端应据此把它归到「AI 推荐」入口,不要混入「我的搭配」。
>
> 这类穿搭**超过 7 天未被用户保存,会由后端定时任务自动清理**(软删除,列表与详情立即不可见)。用户超期后再调 `POST /ai-recommend/save` 会返回 `40000` 穿搭不存在,属预期行为。
### 4.4 商品对象 `GoodsItem`
@@ -927,7 +932,9 @@ Authorization: Bearer <access_token>
**响应** `data.list[]` = [穿搭对象](#43-穿搭对象-outfititem)
> ⚠️ 该接口**不返回 `status` 字段,也不会过滤 `status=3`(AI 待保存)的穿搭**。前端拿到列表后,建议用 `source` 区分来源;AI 推荐未保存的穿搭应引导用户去"AI 推荐"入口处理,不要混入"我的搭配"。
> ⚠️ 该接口**不会过滤 `status=3`(AI 待保存)的穿搭**,但响应中已带上 `status` 字段。前端应据此把 AI 未保存的穿搭归到「AI 推荐」入口,不要混入「我的搭配」。
>
> 这类穿搭超过 7 天未保存会被后端自动清理(软删除),详见 [穿搭对象](#43-穿搭对象-outfititem)。
**错误码**:`10000` 参数错误 · `19001` 数据库错误
@@ -1077,17 +1084,24 @@ Authorization: Bearer <access_token>
### 5.6 AI 推荐模块
#### POST `/ai-recommend` — 生成 AI 穿搭推荐(鉴权)
> AI 推荐是**异步接口**。`POST /ai-recommend` 只提交任务并立即返回 `task_id`,真正的生成在后台执行;前端用 `GET /ai-recommend/status` 轮询结果。
>
> 这样设计的好处:接口不再长时间挂起,**前端退出、切页面、断网都不会中断生成** —— 结果仍会落库为"待保存"穿搭,用户重新进入也能看到。
#### POST `/ai-recommend` — 提交 AI 穿搭推荐任务(鉴权)
**业务逻辑**
1. **并发锁**:同一用户 180 秒内只能有一个生成任务,抢锁失败返回 `10003` 请求过于频繁(**HTTP 429**),前端应提示"正在生成中,请稍候"。
2. **生成前先扣 1 个 AI 额度**(`quota_type=2`);额度不足返回 **`90401` AI 推荐次数已达上限**(注意不是 `80000`)。
3. 输入:当前用户**处理成功(状态 4)**的全部衣物 + 生日推算的星座 + 今日运势(星座/运势缺失会降级,不影响生成)。
4. **衣橱中没有可用衣物** → `90400` + `details:["衣橱暂无可用衣物"]`,**额度已自动回退**。
5. 调用大模型生成;失败或无有效单品(`details:["未生成有效搭配"]`)→ `90400`,**额度自动回退**。
6. 成功:落库为穿搭,`source=2`、`is_public=1`、`audit_status=1`、`status=3`(**待用户保存**),并返回完整内容供预览。
7. ⚠️ **生成成功但未调用 save,不退还 AI 额度**(额度在生成时已消耗)。
1. **提交幂等**:同一用户已有任务在生成中时,直接返回该任务的 `task_id`(**不会重复扣额度**),前端继续轮询原任务即可。
2. **提交时先扣 1 个 AI 额度**(`quota_type=2`);额度不足返回 **`90401` AI 推荐次数已达上限**(注意不是 `80000`)。
3. 接口**秒级返回**(实测 ~30ms),不等大模型。
4. 生成在后台执行,输入为:当前用户**处理成功(状态 4)**的全部衣物 + 生日推算的星座 + 今日运势(星座/运势缺失会降级,不影响生成)。
5. 失败时**额度自动回退**,失败原因写入任务状态(`error_code` / `error_msg`),详见下方状态接口:
- `90400` + `衣橱暂无可用衣物`
- `90400` + `大模型调用失败`
- `90400` + `未生成有效搭配`
- `90400` + `生成任务异常终止`(超过 3 分钟未完成)
6. ⚠️ **生成成功但未调用 save,不退还 AI 额度**(额度在提交时已消耗)。
**请求**(json)
@@ -1098,6 +1112,46 @@ Authorization: Bearer <access_token>
**响应** `data`
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| task_id | string | 任务 ID,用于轮询 `GET /ai-recommend/status` |
| status | int | 任务状态,提交后恒为 `1`(生成中) |
**错误码**:`10000` 参数错误 · `90401` AI 推荐次数已达上限(AI 额度不足)· `10003` 请求过于频繁(429,活跃位刚过期的极端情况,重试即可)· `19002` 缓存错误
---
#### GET `/ai-recommend/status` — 查询 AI 推荐任务状态(鉴权)
**业务逻辑**
- 任务快照保存在 Redis,**有效期 15 分钟**;任务不存在、已过期或不属于当前用户,统一返回 `90402`(不暴露他人任务是否存在)。
- 任务状态 `status`:
| 值 | 含义 | 前端处理 |
| --- | --- | --- |
| 1 | 生成中 | 继续轮询,建议 2 秒一次、上限 90 次(3 分钟) |
| 2 | **成功** | 终态:读取 `result` 渲染推荐结果 |
| 3 | **失败** | 终态:读 `error_code` / `error_msg` 提示用户(**额度已自动回退**) |
**请求**(query)
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| task_id | string | ✅ | `POST /ai-recommend` 返回的 `task_id` |
**响应** `data`
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| task_id | string | 任务 ID |
| status | int | 任务状态:`1` 生成中 / `2` 成功 / `3` 失败 |
| result | object | **仅 `status=2` 时存在**,结构见下表 |
| error_code | int | **仅 `status=3` 时存在**,业务错误码(通常 `90400`) |
| error_msg | string | **仅 `status=3` 时存在**,失败原因文案 |
`result` 对象(`status=2`):
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| outfit_uuid | string | 生成结果 UUID(**保存时要用**) |
@@ -1105,10 +1159,10 @@ Authorization: Bearer <access_token>
| outfit_desc | string | 穿搭描述 |
| scene | string | 场景 |
| source | int | `2`=AI 推荐 |
| status | int | `3`=待用户保存 |
| status | int | ⚠️ 穿搭状态 `3`=待用户保存(**与顶层的任务状态 `status` 同名但含义不同**) |
| outfit_item | array | ⚠️ 注意字段名是 `outfit_item`(单数 item),元素 `{"clothes_uuid": string}` |
**错误码**:`10000` 参数错误 · `10003` 请求过于频繁(429,180 秒内重复请求)· `90400` AI 推荐失败(含 `衣橱暂无可用衣物`、`未生成有效搭配`)· `90401` AI 推荐次数已达上限(AI 额度不足)· `19002` 缓存错误 · `19003` 下游服务调用失败
**错误码**:`10000` 参数错误 · `90402` 任务不存在或已过期 · `19002` 缓存错误
---
@@ -1125,7 +1179,7 @@ Authorization: Bearer <access_token>
| 参数 | 类型 | 必填 | 约束 | 说明 |
| --- | --- | --- | --- | --- |
| outfit_uuid | string | ✅ | required | 由 `/ai-recommend` 返回的 `outfit_uuid` |
| outfit_uuid | string | ✅ | required | 由 `GET /ai-recommend/status` 返回的 `result.outfit_uuid` |
**响应** `data` = `{}`
@@ -2014,20 +2068,32 @@ sequenceDiagram
sequenceDiagram
participant F as 小程序前端
participant B as 后端服务
participant R as Redis
participant L as 大模型网关(Tokenhub)
F->>B: POST /ai-recommend {scene_type, scene?}
B->>B: Redis 锁 180s(防并发,失败返回 10003)
B->>R: 抢占用户活跃位(已有任务则返回原 task_id,幂等)
B->>B: 扣 1 个 AI 额度(不足 → 90401)
B->>L: 结合星座运势 + 衣橱(状态 4)衣物生成
L-->>B: 推荐结果
B->>B: 校验结果(过滤模型幻觉衣物 ID)
B->>B: 落库 source=2, status=3 待用户保存
B-->>F: {outfit_uuid, outfit_name, outfit_item, ...}
B->>R: 写入任务快照(status=1 生成中)
B-->>F: {task_id, status: 1} · 秒级返回,不等大模型
par 后台生成(独立 ctx,前端退出也不中断)
B->>L: 结合星座运势 + 衣橱(状态 4)衣物生成
L-->>B: 推荐结果
B->>B: 校验结果(过滤模型幻觉衣物 ID)
B->>B: 落库 source=2, status=3 待用户保存
B->>R: 任务快照 status=2 + result
and 前端轮询
loop 每 2 秒,上限 90 次
F->>B: GET /ai-recommend/status?task_id=...
B->>R: 读任务快照
B-->>F: {task_id, status, result?}
end
end
alt 生成失败
B->>B: 回退 AI 额度 + 写失败流水
B-->>F: code 90400
B->>R: 任务快照 status=3 + error_code/error_msg
end
F->>F: 用户预览、确认
@@ -2233,6 +2299,7 @@ sequenceDiagram
| 90300 | 星座运势不存在(生成中,稍后重试) | 200 |
| 90400 | AI 推荐失败 | 200 |
| **90401** | **AI 推荐次数已达上限**(AI 额度不足) | 200 |
| 90402 | AI 推荐任务不存在或已过期(轮询用) | 200 |
---