109 KiB
智能衣橱(zt-closet)后端接口文档 · 前端版
面向前端/小程序工程师的联调文档。全文基于后端源码(
internal/routers、internal/dto、internal/service、pkg/errcode)逐字段核对生成,包含接口入参出参、业务逻辑与状态机、调用时序、错误码全表。
- 服务:Go + Gin,微信小程序后端
- 统一前缀:
/api/v1(下文中路径如/user/wxlogin均表示/api/v1/user/wxlogin)- 健康检查:
GET /ping(无/api/v1前缀)- 文档版本:v1.1 · 生成日期:2026-09-09
目录
- 1. 五分钟上手
- 2. 通用调用约定
- 3. 接口总表(48 个)
- 4. 公共数据结构
- 5. 接口详解
- 6. 业务逻辑与状态机
- 7. 核心调用时序图
- 8. 错误码全表
- 9. 前端联调 FAQ 与检查清单
- 10. 接口调用示例(curl)
1. 五分钟上手
标准联调路径:
1. wx.login() 拿到 code
└─> POST /user/wxlogin -> access_token / refresh_token / user_info
2. 所有需登录接口带 Header:
Authorization: Bearer <access_token>
3. 上传衣物
POST /clothes/upload (multipart, 字段名 file)
└─> { clothes_uuid, image_process_status: 3 }
└─> 轮询 GET /clothes/:uuid/status 直到 4(成功)或 5(失败)
4. 做穿搭
POST /outfit 创建 -> PUT /outfit/:uuid/public 公开(扣额度,进入社区)
或 POST /ai-recommend AI 推荐 -> POST /ai-recommend/save 保存
5. 额度不够
GET /goods 商品列表 -> POST /order 下单 -> wx.requestPayment(pay_params)
-> 轮询 GET /order 直到 order_status = 2(已支付)
最容易踩的 6 个坑(详见 第 9 节):
- 判断成败只看 body 里的
code,绝大多数业务错误返回 HTTP 200(包括21001 账号已被禁用)。 - 刷新 Token 必须同时传
refresh_token和access_token,否则报10000;成功后旧的两个 token 立即失效,必须整体替换。 - 衣物上传是异步的,返回只代表"已排队",必须轮询状态接口。
- 更新衣物的主颜色请求字段是
color,响应里才是color_main。 - 金额单位都是分、时间都是秒,展示前要换算。
- AI 推荐是异步的:
POST /ai-recommend只返回task_id,必须轮询GET /ai-recommend/status才能拿到结果;生成后不保存不退额度。退款是人工审核,不是实时到账。
2. 通用调用约定
2.1 鉴权与 Token
请求头格式
Authorization: Bearer <access_token>
- 必须恰好是
Bearer+ 空格 + token(大小写敏感),其它写法一律11001。 - access_token 默认有效期 7200 秒(配置
JWT.AccessTokenExpire),refresh_token 默认 86400 秒。 - 登录响应里的
expire_time是 access_token 的过期时间戳(秒),前端可据此提前刷新。
服务端校验顺序(失败即中断)
| 顺序 | 校验 | 返回 | HTTP |
|---|---|---|---|
| 1 | 无 Authorization 头 |
11004 Token 缺失 |
401 |
| 2 | 格式不是 Bearer xxx |
11001 + details:["invalid Authorization format"] |
401 |
| 3 | access_token 已过期 | 11002 Token 已过期 |
401 |
| 4 | 拿 refresh_token 当 access 用 | 11001 + details:["token type mismatch"] |
401 |
| 5 | Redis 会话不存在(已登出/被顶替) | 11001 + details:["token session invalid"] |
401 |
| 6 | 会话 userID 与 token 不一致 | 11001 + details:["token user mismatch"] |
401 |
| 7 | 用户不存在 | 11001 + details:["用户不存在"] |
401 |
| 8 | 用户 status != 1(禁用/注销) |
21001 账号已被禁用 |
200 |
⚠️ 第 8 步是 HTTP 200 + code 21001,不是 401。拦截器里必须单独判断并跳转封禁提示页。
刷新流程(前端必须实现)
任意接口返回 code = 11002 / 11001(HTTP 401)
└─> POST /user/refresh-token { refresh_token, access_token } // 两个都要传
├─ 成功 -> 用返回的两个新 token **整体替换**本地存储,再重试原请求
└─ 失败(11001/11002)-> 清空本地登录态,重新走 wx.login
- 刷新成功后,服务端会立即吊销旧的 access_token 与 refresh_token,前端不能沿用任何旧值。
- refresh_token 也是一次性旋转的:每次刷新都会返回新的 refresh_token。
公开接口(无需 Token):/ping、/user/wxlogin、/user/refresh-token、/goods、/goods/:code、/order/pay/notify、/order/refund/notify。
2.2 统一响应结构
成功(HTTP 200)
{
"code": 0,
"msg": "成功",
"data": { }
}
code恒为0,msg恒为"成功"。data永不为null:无业务数据(删除、取消、标记已读等)时是空对象{}。- 成功响应没有
details字段。
失败
{
"code": 80000,
"msg": "额度不足",
"details": ["额度不足,请先购买"]
}
- 失败响应没有
data字段,前端不要读取data。 details是可选的字符串数组,仅在有补充信息时出现(参数校验错误、业务提示等)。典型内容:"请先完善生日信息"、"过期时间必须晚于当前时间"、"部分分享需选择衣物"、"衣橱暂无可用衣物"、"未生成有效搭配"、"缺少旧 access token"、"缺少微信 openid"等。details可直接用于 toast 提示用户,但建议仅对10000 参数错误与业务提示类展示,系统错误统一展示兜底文案。
2.3 HTTP 状态码与业务码
绝大多数业务错误返回 HTTP 200,只有下表这些会返回非 200:
| HTTP | 业务 code | 场景 |
|---|---|---|
| 200 | 0 成功,以及除下表外的所有业务码 |
成功与绝大多数业务错误 |
| 401 | 11000 未授权、11001 Token 鉴权失败、11002 Token 已过期、11004 Token 缺失 |
鉴权失败 → 走刷新或重新登录 |
| 403 | 12000 无权限访问 |
越权操作他人资源 |
| 429 | 10003 请求过于频繁 |
限流(如 AI 推荐并发锁),稍后重试 |
| 500 | 19000 服务内部错误、19001 数据库错误、19002 缓存错误、19003 下游服务调用失败、19999 未知错误、39001 抠图失败、39002 分类失败 |
服务端异常 → 提示"服务繁忙" |
前端拦截器建议:先取 body 的
code判断业务结果;code缺失(网络层失败)时才回落到 HTTP 状态码。
2.4 分页约定
所有列表接口统一使用 GET query 参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page | int | 否 | 页码,≥1,默认 1 |
| page_size | int | 否 | 每页条数,1~50,默认 10;超过 50 服务端自动收敛为 50 |
列表响应统一结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| total | int64 | 总条数 |
| page | int | 当前页码 |
| page_size | int | 每页条数(服务端实际生效值) |
| list | array | 数据数组;无数据时为空数组 [] |
2.5 单位约定
| 类别 | 单位 | 涉及字段 |
|---|---|---|
| 金额 | 分(int64) | price、discount_price、unit_price、pay_amount、refund_amount |
| 时间 | 秒级 Unix 时间戳(int64) | expire_time、pay_time、refund_time、created_at、birth_day、vip_expire、ban_start、ban_expire、apply_time |
| 日期 | YYYYMMDD 整数 |
运势 stat_date |
展示金额需
÷ 100;展示时间需× 1000再交给 JSDate(因为 JS 用毫秒)。
2.6 幂等与重试
以下接口天然幂等,可安全重试(重复提交不会产生重复数据或重复扣费):
- 点赞 / 取消点赞
POST /like - 收藏 / 取消收藏
POST /collect - 穿搭公开
PUT /outfit/:uuid/public(重复公开不会重复扣额度;但取消公开后再次公开会再扣 1 次,见 6.6) - 标记已读
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 /order(会创建新订单)。
2.7 需要轮询的场景
后端没有 WebSocket / 消息推送,以下场景前端需主动轮询:
| 场景 | 轮询接口 | 终态 | 建议间隔 |
|---|---|---|---|
| 衣物 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 次 |
| 未读通知数 | GET /notification/unread |
无终态,按需刷新 | 进入页面时 / 30~60 秒 |
2.8 CORS 与链路追踪
- 允许的 Method:
GET, POST, PUT, PATCH, DELETE, OPTIONS;允许的请求头:Origin, Content-Type, Authorization。 - 响应头
X-Request-ID可用于排障,但未加入 CORSExpose-Headers,跨域场景下 JS 读不到;需要排障时请在请求侧自带X-Request-ID头,服务端会透传。 - 后端已开启 OpenTelemetry 全链路追踪,反馈问题时提供
X-Request-ID可快速定位。
3. 接口总表(49 个)
健康检查(1)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /ping |
存活探针,返回 {"message":"pong"},不在 /api/v1 下 |
公开路由(6,无需登录)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /user/wxlogin |
微信登录(code 换 openid,首次自动注册) |
| POST | /user/refresh-token |
刷新 access_token |
| GET | /goods |
商品列表(仅上架) |
| GET | /goods/:code |
按商品编码查询 |
| POST | /order/pay/notify |
🚫 微信支付回调,服务端专用,前端禁止调用 |
| POST | /order/refund/notify |
🚫 微信退款回调,服务端专用,前端禁止调用 |
| GET/POST | /xpay/notify |
🚫 虚拟支付消息推送(GET 为 URL 验证 / POST 为发货·退款·iOS 退款问询回调),服务端专用,前端禁止调用 |
鉴权路由(36,需 Authorization: Bearer)
用户 / 额度 / 运势(5)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /user/info |
当前用户信息 |
| PUT | /user/profile |
更新个人资料 |
| GET | /quota/info |
额度概览(4 类额度余量) |
| GET | /quota/flow |
额度流水 |
| GET | /horoscope/today |
今日星座运势(按生日推算星座) |
衣物(6)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /clothes/upload |
上传衣物图片(扣上传额度,触发异步 AI 流水线) |
| GET | /clothes |
我的衣物列表 |
| GET | /clothes/:uuid |
衣物详情 |
| GET | /clothes/:uuid/status |
衣物 AI 处理状态(轮询用) |
| PUT | /clothes/:uuid |
更新衣物属性 |
| DELETE | /clothes/:uuid |
删除衣物(满足条件时回退上传额度) |
穿搭(7)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /outfit |
创建穿搭 |
| GET | /outfit |
我的穿搭列表 |
| GET | /outfit/community |
社区穿搭列表(其他用户公开的穿搭) |
| GET | /outfit/:uuid |
穿搭详情 |
| PUT | /outfit/:uuid |
更新穿搭 |
| DELETE | /outfit/:uuid |
删除穿搭 |
| PUT | /outfit/:uuid/public |
公开 / 取消公开(公开扣搭配分享额度) |
AI 推荐(2)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /ai-recommend |
生成 AI 穿搭推荐(扣 AI 额度) |
| POST | /ai-recommend/save |
保存 AI 推荐为我的穿搭 |
衣柜分享(5)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /closet-share |
创建衣柜分享(扣衣柜分享额度) |
| GET | /closet-share |
我的分享列表 |
| GET | /closet-share/received |
分享给我的列表 |
| GET | /closet-share/:uuid |
分享详情(属主 / 访客视角) |
| POST | /closet-share/:uuid/cancel |
取消分享 |
穿搭分享(5)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /outfit-share |
创建穿搭分享(无需公开到社区;私密穿搭扣 1 次衣柜分享额度) |
| GET | /outfit-share |
我的穿搭分享列表 |
| GET | /outfit-share/received |
分享给我的穿搭分享 |
| GET | /outfit-share/:uuid |
穿搭分享详情(属主 / 访客视角) |
| POST | /outfit-share/:uuid/cancel |
取消穿搭分享 |
收藏 / 点赞(5)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /collect |
收藏 / 取消收藏 |
| GET | /collect |
我的收藏列表 |
| GET | /collect/:targetUUID |
查询目标收藏状态与数量 |
| POST | /like |
点赞 / 取消点赞 |
| GET | /like/:targetUUID |
查询目标点赞状态与数量 |
通知(4)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /notification |
通知列表 |
| GET | /notification/unread |
未读通知数 |
| GET | /notification/:uuid |
通知详情 |
| POST | /notification/:uuid/read |
标记单条已读 |
订单(4)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /order |
创建订单(微信 JSAPI 支付) |
| POST | /order/virtual |
创建虚拟支付订单(小程序虚拟支付,iOS/Android 全终端) |
| GET | /order |
我的订单列表 |
| POST | /order/:orderNo/cancel |
取消订单 |
| POST | /order/refund |
申请退款 |
说明:社区广场列表接口为
GET /outfit/community(分页返回其他用户已公开且审核通过的穿搭,含作者信息与点赞/收藏计数)。
4. 公共数据结构
以下对象在多个接口中复用,后文接口章节直接引用,不再重复列出字段。
4.1 用户对象 user_info
| 字段 | 类型 | 说明 |
|---|---|---|
| username | string | 昵称(新用户默认"微信用户") |
| avatar | string | 头像地址 |
| sex | int | 性别:1=男 2=女(0 或未设置时不展示) |
| birth_day | int64 | 生日(秒级时间戳);为 0 表示未设置,运势接口会要求先完善 |
| phone | string | 手机号 |
| is_new_user | bool | 是否本次新注册用户(用于引导页) |
| status | int | 1=正常 2=临时禁用 3=永久禁用 4=已注销 |
| ban_reason | string | 封禁原因 |
| ban_start | int64 | 封禁开始时间(秒) |
| ban_expire | int64 | 封禁到期时间(秒) |
| vip_type | int | 1=非会员 2=月度会员 3=年度会员 4=体验会员(新用户注册赠送) |
| vip_expire | int64 | VIP 到期时间(秒) |
4.2 衣物对象 ClothesItem
| 字段 | 类型 | 说明 |
|---|---|---|
| clothes_uuid | string | 衣物 UUID(后续查询/更新/删除均用此值) |
| category_main | string | 大类:上装 / 下装 / 连衣裙 / 外套 / 鞋 / 配饰 |
| category_sub | string | 小类:T恤 / 牛仔裤 / 运动鞋 / 发带 / 戒指 等 |
| style | string | 风格 |
| color_main | string | 主颜色 |
| color_sub | string | 副颜色 |
| thickness | string | 厚度 |
| version | string | 版型:宽松 / 修身 / 阔腿 / 紧身 / 常规合身 |
| material | string | 材质:牛仔布 / 针织 / 皮质 / 纯棉 / 羊毛 等 |
| pattern | string | 花纹 |
| sleeve_type | string | 袖型 |
| clothes_type | string | 衣长:短款 / 常规 / 中长 / 长款 |
| neckline_type | string | 领型 |
| formality | string | 正式程度 1-5 |
| occasions | string | 适用场合 |
| visual_impact | string | 视觉冲击力 1-5 |
| name | string | 名称 |
| remark | string | 备注 |
| processed_image | string | 抠图后的图片完整 URL(可直接展示) |
| image_process_status | int | 处理状态:1=抠图中 2=分类中 3=排队中 4=成功 5=失败 |
| fail_reason | string | 处理失败原因;仅属主可见,非属主(穿搭分享 / 衣柜分享只读)场景为空串 |
衣物列表与详情响应均带
image_process_status:前端应据此区分「处理中(1/2/3)」与「处理失败(5)」,不要用属性/图片是否为空来推断。fail_reason仅在属主场景返回。
4.3 穿搭对象 OutfitItem
| 字段 | 类型 | 说明 |
|---|---|---|
| outfit_uuid | string | 穿搭 UUID |
| outfit_name | string | 穿搭名称(≤32) |
| outfit_desc | string | 穿搭描述(≤500) |
| scene | string | 适合场景(≤100) |
| 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=3(AI 待保存):AI 推荐生成的穿搭会以status=3出现在GET /outfit列表中,前端应据此把它归到「AI 推荐」入口,不要混入「我的搭配」。这类穿搭超过 7 天未被用户保存,会由后端定时任务自动清理(软删除,列表与详情立即不可见)。用户超期后再调
POST /ai-recommend/save会返回40000穿搭不存在,属预期行为。
4.4 商品对象 GoodsItem
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int64 | 商品 ID |
| goods_code | string | 商品编码(下单时用这个) |
| goods_name | string | 商品名称 |
| goods_type | int | 1=会员包 2=上传包 3=AI 推荐包 4=衣柜分享包 5=搭配分享包 |
| price | int64 | 原价(分) |
| discount_price | int64 | 售价(分,实际支付金额) |
| member_days | int64 | 赠送会员天数;>=365 发放年度会员,否则月度会员 |
| upload_quota | int64 | 赠送上传额度(对应后端 cos_quota) |
| closet_share_quota | int64 | 赠送衣柜分享额度 |
| outfit_share_quota | int64 | 赠送搭配分享额度 |
| description | string | 商品描述 |
商品对象不含 AI 推荐额度展示字段,但
goods_type=3(AI 推荐包)会发放 AI 额度。
4.5 订单对象 OrderItem
| 字段 | 类型 | 说明 |
|---|---|---|
| order_no | string | 订单号 |
| goods_name | string | 商品名称 |
| unit_price | int64 | 单价(分) |
| pay_amount | int64 | 实付金额(分) |
| pay_channel | int | 支付渠道 1=微信支付(JSAPI) 2=虚拟支付 |
| order_status | int | 1=待支付 2=已支付 3=已取消 4=已过期 5=已退款 |
| member_days | int64 | 订单快照:会员天数 |
| expire_time | int64 | 支付截止时间(秒),前端倒计时用(15 分钟) |
| pay_time | int64 | 支付时间(秒),未支付为 0 |
| refund_time | int64 | 退款时间(秒),未退款为 0 |
| refund_amount | int64 | 退款金额(分) |
| created_at | int64 | 创建时间(秒) |
4.6 通知对象 NotificationItem
| 字段 | 类型 | 说明 |
|---|---|---|
| notification_uuid | string | 通知 UUID |
| msg_type | int | 1=点赞通知 2=订单通知 3=系统通知 |
| title | string | 标题 |
| content | string | 内容 |
| is_read | int | ⚠️ 1=未读 2=已读(不是布尔值,也不是 0/1) |
| created_at | int64 | 创建时间(秒) |
4.7 额度流水对象 QuotaFlowItem
| 字段 | 类型 | 说明 |
|---|---|---|
| quota_flow_uuid | string | 流水 UUID |
| quota_type | int | 1=上传 2=AI 3=社区分享 4=衣柜分享 |
| change_target | int | 1=配额调整 2=使用 |
| change_type | int | 1=购买获赠 2=活动赠送 3=上传消耗 4=删除回退 5=人工调整 6=退款回收 7=到期失效(固定有效期的额度凭证过期,由后台任务每小时补记) |
| change_num | int64 | 变更数量(正数为增加,负数为减少;到期失效为负数) |
| new_value | int64 | 变更后的数值 |
| reason | string | 变更原因 / 备注 |
| order_no | string | 关联订单号:购买/退款类为真实订单号;赠送/消耗/回退/到期类由后端填充流水 UUID 或凭证 ref_id,用于满足唯一索引幂等 |
| created_at | int64 | 创建时间(秒) |
4.8 微信支付参数 pay_params
| 字段 | 类型 | 说明 |
|---|---|---|
| timeStamp | string | ⚠️ 注意大小写(微信原生命名),直接透传给 wx.requestPayment |
| nonceStr | string | 随机字符串 |
| package | string | 形如 prepay_id=wx... |
| signType | string | 签名方式,如 RSA |
| paySign | string | 签名 |
pay_params是微信原样返回的字段,整体透传给wx.requestPayment即可,不要改名或转换大小写。
5. 接口详解
参数位置标注:
json= JSON 请求体、query= URL 查询参数、path= 路径参数、form= multipart 表单。 路径中的:xxx是占位参数名,调用时替换为真实值(注意targetUUID、orderNo的驼峰写法)。
5.1 用户模块
POST /user/wxlogin — 微信登录(公开)
业务逻辑
-
前端
wx.login()获取code,传给后端;后端用code2session换openid。 -
首次登录自动注册:昵称默认"微信用户",
status=1正常,返回is_new_user=true,随后依次赠送两样东西:- 免费额度(
UserDefaultQuota段,与会员身份无关、所有用户长期享有):内置默认上传 20 / AI 推荐 3 / 搭配分享 10 / 衣柜分享 5,允许显式配置为 0; - 体验会员及其额度(
NewUserGift段):开通vip_type=4体验会员(内置默认 3 天,配 0 表示不赠送会员),并按该段配置发放体验会员额度凭证(source=4,如右上表;有效期是固定值 = 注册时刻 + 体验天数,不随后续续费延长)。内置默认上传 0 / AI 3 / 搭配分享 3 / 衣柜分享 3。这部分额度与上面的免费额度叠加生效。
两段配置都支持显式配 0 表示不赠送;
GiftVIPDay配 0 时,体验会员额度一律不发放。赠送成功后下发一条系统通知「欢迎礼包已到账」。因此新用户返回的vip_type通常为4(配置为 0 时为1)。 - 免费额度(
-
并发首次登录不会注册重复账号(依赖
wx_open_id唯一索引)。 -
账号被禁用/注销(
status != 1)时返回21001,HTTP 仍是 200。 -
额度初始化失败不影响登录(仅后端告警)。
请求(json)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| code | string | ✅ | required | wx.login 获取的临时登录凭证,一次性 |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| access_token | string | 访问令牌,放入 Authorization: Bearer |
| refresh_token | string | 刷新令牌,用于续期 |
| expire_time | int64 | access_token 过期时间戳(秒) |
| user_info | object | 用户对象 |
错误码:10000 参数错误 · 21002 微信登录失败 · 21003 微信 Code 无效 · 21001 账号已被禁用(HTTP 200)· 29000 用户创建失败 · 19002 缓存错误
POST /user/refresh-token — 刷新 Token(公开)
业务逻辑
- ⚠️
refresh_token与access_token两个字段都必须传,缺少 access_token 会返回10000+details:["缺少旧 access token"]。 - 刷新成功后,服务端立即吊销旧的 access_token 与 refresh_token,并返回一对全新的 token —— 前端必须整体替换本地存储。
- refresh_token 旋转更新,不可重复使用同一返回值多次刷新。
请求(json)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| refresh_token | string | ✅ | required | 当前有效的 refresh_token |
| access_token | string | ✅ | 业务必填 | 当前(可能已过期)的 access_token,用于刷新后立即吊销 |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| access_token | string | 新访问令牌 |
| refresh_token | string | 新刷新令牌 |
| expire_time | int64 | 新 access_token 过期时间戳(秒) |
错误码:10000 + 缺少旧 access token · 11001 Token 鉴权失败(无效/已被吊销)· 11002 Token 已过期 · 19002 缓存错误
GET /user/info — 当前用户信息(鉴权)
无请求参数。
响应 data = 用户对象。
新注册用户
birth_day = 0,此时调用运势接口会提示"请先完善生日信息",建议在首页做一次引导。
错误码:11001/11002/11004 鉴权 · 22000 用户不存在 · 29003 用户查询失败
PUT /user/profile — 更新个人资料(鉴权)
业务逻辑
- 只更新非空字段(传什么改什么),全空则直接返回成功且不写库。
- 昵称按字符数(汉字算 1)限制 ≤ 32,超长返回
10000+details:["昵称长度不能超过 32 个字符"]。 birth_day不能晚于当前时间。- 更新后自动失效缓存,下次
GET /user/info即为最新值。
请求(json,全部可选)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| username | string | 否 | max=32 |
昵称 |
| avatar | string | 否 | max=500 |
头像地址(建议先自行上传到 COS 再传 URL) |
| sex | int | 否 | oneof=0 1 2 |
0=未知(忽略) 1=男 2=女 |
| birth_day | int64 | 否 | — | 生日(秒级时间戳),决定星座与运势 |
响应 data = {}
错误码:10000 参数错误(含 昵称长度不能超过 32 个字符、头像地址过长、性别参数不合法、生日不能晚于当前时间)· 29001 用户更新失败
POST /user/avatar/upload — 上传头像(鉴权)
业务逻辑
- multipart 上传,字段名
file;仅支持图片(jpeg/png/gif/webp),复用全站图片扩展名白名单与大小限制(MaxSizeMB)。 - 上传成功后写入 COS(key 规则
{用户UUID}/{用户ID}/avatar/{随机名}.{扩展名}),并将 COS key 存入users.avatar。 - 头像上传不消耗 COS 上传额度。
- 更新后自动失效缓存,
GET /user/info返回的头像字段为可直接访问的 URL。
请求:multipart/form-data,字段名 file。
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| avatar | string | 头像可访问 URL |
错误码:11001/11002/11004 鉴权 · 30000 上传文件为空 · 30001 不支持的文件类型 · 30002 文件大小超过限制 · 39000 文件保存失败 · 22000 用户不存在 · 29001 用户更新失败
5.2 额度模块
全站共 4 类额度:上传额度为永久额度(不每天重置);AI 推荐 / 社区分享 / 衣柜分享为每日额度(每天 0 点重置)。
额度有两个来源,叠加生效:
① 免费额度 —— 由服务端配置 UserDefaultQuota 控制,所有用户长期享有、与会员身份无关(下表「新用户默认」为内置兜底值,允许配置为 0,表示不赠送该类免费额度):
| quota_type | 名称 | 类型 | 新用户默认 | 消耗场景 |
|---|---|---|---|---|
| 1 | 上传额度 | 永久 | 20 | 上传一件衣物扣 1 |
| 2 | AI 推荐额度 | 每日 | 3 | 生成一次 AI 穿搭推荐扣 1 |
| 3 | 社区分享额度(搭配分享) | 每日 | 10 | 穿搭首次公开扣 1 |
| 4 | 衣柜分享额度 | 每日 | 5 | 创建一次衣柜分享扣 1 |
② 额度凭证(quota_grants)—— 有有效期的赠送额度,按来源分三类:
| source | 来源 | 有效期 |
|---|---|---|
| 1 | 会员包(购买) | expire_time=0,跟随 VIP:VIP 到期自动失效,续期自动延长 |
| 2 | 加量包(购买) | 独立有效期 goods.valid_days,到期失效 |
| 3 | 广告激励 | 当天 23:59:59 到期(金额由 AdReward 配置固定,开关控制) |
| 4 | 体验会员赠送(新用户注册) | 固定有效期 = 注册时刻 + 体验天数,不随后续续费延长 |
新用户注册时,
NewUserGift段配置的体验会员额度以 source=4 凭证发放;GiftVIPDay配 0(不赠送会员)时这部分额度一律不发放。 ⚠️ 体验额度不会被后续充值「继承」:用户 3 天体验期后再购买会员,体验额度照常失效,只剩免费额度 + 新购会员包额度。 Cos 消费先扣「即将过期凭证」再扣免费额度;每日额度总额 = 免费配置 + 有效凭证赠送额。
GET /quota/info — 额度概览(鉴权)
无请求参数。
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| quotas | array | 额度数组,元素见下表 |
quotas[]:
| 字段 | 类型 | 说明 |
|---|---|---|
| quota_type | int | 1=上传 2=AI 3=社区分享 4=衣柜分享 |
| total | int64 | 总额度(上传=永久总额度;AI/社区/衣柜=每日总额度) |
| used | int64 | 已使用(上传=永久累计已用;AI/社区/衣柜=当日已用) |
| remain | int64 | 剩余(max(total-used, 0),不会为负;每日额度次日 0 点重置) |
错误码:19001 数据库错误 · 89000 额度更新失败
GET /quota/flow — 额度流水(鉴权)
请求(query)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| page / page_size | int | 否 | 见分页约定 | 分页 |
| quota_type | int | 否 | oneof=1 2 3 4 |
额度类型筛选;不传 = 全部 |
响应 data.list[] = 额度流水对象
错误码:10000 参数错误(quota_type 非 1~4)· 19001 数据库错误
POST /ad-reward/grant — 广告激励额度发放(鉴权)
看广告后发放单次激励额度,当天有效(当天 23:59:59 到期),通过 ref_id 幂等防重复领取。
业务逻辑:广告激励由服务端 AdReward 配置开关控制——未开启时返回 80004 广告激励未开启;发放金额由服务端配置固定值(cos/ai/outfit_share/closet_share 各类型分别配置),前端不传金额,只传类型与广告记录 ID。
请求(JSON body)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| reward_type | string | ✅ | oneof=cos ai outfit_share closet_share |
激励类型,映射到额度类型 |
| ref_id | string | ✅ | — | 广告记录ID(幂等键,同 ref_id+类型 重复发放返回 80002) |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| ref_id | string | 广告记录ID |
| quota_type | int | 1=上传 2=AI 3=社区分享 4=衣柜分享 |
| amount | int64 | 发放数量(由服务端配置决定) |
错误码:80002 广告激励已领取 · 80003 无效的激励类型 · 80004 广告激励未开启 · 10000 参数错误 · 19001 数据库错误
5.3 运势模块
GET /horoscope/today — 今日星座运势(鉴权)
业务逻辑
- 星座由用户
birth_day自动推算,请求不需要传星座。 - 未设置生日 →
10000+details:["请先完善生日信息"],前端应引导跳转资料页。 - 读取顺序:Redis 缓存(有效期至次日 0 点)→ MySQL → 都没有时异步触发生成,并当次返回
90300。此时前端应提示"运势生成中,请稍后重试",3~5 秒后重试即可。 - 十二星座运势由后端每日 0 点定时任务批量生成(Redis 分布式锁保证多实例只生成一次)。
- 运势是 AI 推荐的输入之一,缺失不影响 AI 推荐(会降级为仅星座或空)。
请求:无参数。
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| stat_date | int64 | 日期,格式 YYYYMMDD(如 20260907) |
| zodiac | string | 星座中文名(摩羯座 / 水瓶座 / … / 射手座) |
| overall_score | int | 综合运势 1-5 |
| love_score | int | 爱情运势 1-5 |
| career_score | int | 事业运势 1-5 |
| wealth_score | int | 财运运势 1-5 |
| daily_text | string | 今日运势文案 |
| lucky_color | string | 今日幸运色 |
| lucky_number | string | 今日幸运数字 |
错误码:10000 + 请先完善生日信息 · 90300 星座运势不存在(生成中,请重试)· 19001 数据库错误
5.4 衣物模块
图片处理状态枚举 image_process_status(全模块通用)
| 值 | 含义 | 前端处理 |
|---|---|---|
| 1 | 抠图中 | 继续轮询 |
| 2 | 分类中 | 继续轮询 |
| 3 | 排队中 | 继续轮询 |
| 4 | 成功 | 终态:可读取识别结果、可用于 AI 推荐 |
| 5 | 失败 | 终态:查看 fail_reason,可重新上传 |
POST /clothes/upload — 上传衣物图片(鉴权)
业务逻辑
- 同步校验:文件非空 → 扩展名白名单(默认
jpg/jpeg/png)→ 大小 ≤ 20MB(配置Upload.MaxSizeMB)→ magic bytes 文件头真图校验(防伪造扩展名)。 - 同步扣 1 个上传额度(
quota_type=1),额度不足直接80000,不会创建记录。 - 创建衣物记录,
image_process_status = 3(排队中),立即返回。 - 后端异步流水线(与请求无关,最多并发 10,单任务超时 3 分钟):
3 排队中→1 抠图中(BiRefNet 抠图,失败重试 3 次,间隔 1s)→ 上传 COS →2 分类中(Fashion-CLIP 识别 15 个属性,失败重试 3 次)→4 成功(回填属性 + 抠图 URL)。 - 任一环节失败 → 置
5 失败并写fail_reason("图片抠图失败"/"图片保存失败"/"图片上传失败"/"图片分类失败"/"内部错误"),并自动回退 1 个上传额度。 - 分类失败但抠图成功时仍会保留抠好的图(
processed_image),用户可用PUT /clothes/:uuid手动补全属性;但状态仍是5。
前端必做:拿到
clothes_uuid后轮询GET /clothes/:uuid/status直到4或5。状态 1/2/3 时不要展示"完成"。
请求(form,multipart/form-data)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file | file | ✅ | 衣物图片,字段名固定为 file,无其他表单字段 |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| clothes_uuid | string | 衣物 UUID,后续查询/更新/删除/轮询都用它 |
| image_process_status | int | 初始恒为 3(排队中) |
错误码:30000 上传文件为空 · 30001 不支持的文件类型(含 magic bytes 校验失败)· 30002 文件大小超过限制 · 39000 文件保存失败 · 50002 创建衣物失败(额度已自动回退)· 80000 额度不足
GET /clothes — 我的衣物列表(鉴权)
请求(query)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / page_size | int | 否 | 分页 |
| category_main | string | 否 | 按大类筛选(上装 / 下装 / 连衣裙 / 外套 / 鞋 / 配饰) |
| category_sub | string | 否 | 按小类筛选 |
响应 data:total / page / page_size + list(衣物对象 数组)
列表包含处理中的衣物(状态 1/2/3),其属性字段为空字符串。前端应识别并展示"处理中"占位,或按
image_process_status过滤(注意:列表响应不含该字段,如需判断请结合上传后的本地状态或详情接口)。
错误码:10000 参数错误 · 19001 数据库错误
GET /clothes/:uuid — 衣物详情(鉴权)
请求(path + query)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 衣物 UUID |
| share_uuid | string | 否 | 穿搭分享 UUID:非属主通过穿搭分享只读查看该分享穿搭内的衣物时必传 |
| outfit_uuid | string | 否 | 穿搭 UUID:非属主通过社区公开穿搭只读查看该穿搭内的衣物时必传 |
响应 data = 衣物对象
业务逻辑(可见性)
- 属主:始终可见。
- 非属主:两个只读入口,二选一携带即可,都不携带则
50005。share_uuid:该分享正常、穿搭未被删除 / 封禁、且该衣物属于该分享的穿搭明细 → 返回只读衣物信息。outfit_uuid:该穿搭公开可见(is_public=2且audit_status=2且status=1,与GET /outfit/:uuid非属主口径一致)且该衣物属于该穿搭明细 → 返回只读衣物信息。
- 只读场景下响应不含失败原因(
fail_reason恒为空);通过outfit_uuid(社区公开穿搭)进入时,属主私有备注remark也不下发,避免暴露给陌生人(share_uuid好友分享场景保持原样)。
错误码:10000 参数错误(uuid 为空)· 50000 衣物不存在 · 50005 无权操作该衣物(含未携带凭证 / 穿搭不可见 / 衣物不属于该穿搭)· 90103 穿搭分享不存在(含穿搭已删除 / 已封禁)· 90104 穿搭分享已取消
GET /clothes/:uuid/status — 查询 AI 处理状态(鉴权,轮询用)
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 衣物 UUID |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| image_process_status | int | 处理状态,见 5.4 节枚举表 |
| fail_reason | string | 失败原因(状态为 5 时有效,其余为空) |
错误码:10000 参数错误 · 50000 衣物不存在 · 50005 无权操作该衣物
PUT /clothes/:uuid — 更新衣物属性(鉴权)
业务逻辑
- 用于修正 AI 识别结果或用户手动补全(尤其分类失败但抠图成功时)。
- 只更新非空字段,全空返回成功但不写库。
- 字段无枚举限制,允许自定义值(如自定义分类)。
- ⚠️ 主颜色的请求字段是
color,不是color_main(响应里才是color_main),这是最容易写错的地方。 - 更新不会改变
image_process_status(识别失败仍是5,但属性已可用)。
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 衣物 UUID(路径参数优先,body 中的 clothes_uuid 会被覆盖) |
请求体(json,全部可选,传什么改什么)
| 参数 | 类型 | 约束 | 说明 |
|---|---|---|---|
| category_main | string | ≤64 | 大类 |
| category_sub | string | ≤64 | 小类 |
| style | string | ≤64 | 风格 |
| color | string | ≤64 | ⚠️ 主颜色(请求字段名是 color) |
| color_sub | string | ≤64 | 副颜色 |
| thickness | string | ≤64 | 厚度 |
| version | string | ≤64 | 版型 |
| material | string | ≤64 | 材质 |
| pattern | string | ≤64 | 花纹 |
| sleeve_type | string | ≤64 | 袖型 |
| clothes_type | string | ≤64 | 衣长类型 |
| neckline_type | string | ≤64 | 领型 |
| formality | string | ≤16 | 正式程度 1-5 |
| occasions | string | ≤255 | 适用场合 |
| visual_impact | string | ≤16 | 视觉冲击力 1-5 |
| name | string | ≤64 | 名称 |
| remark | string | ≤500 | 备注 |
响应 data = {}
错误码:10000 参数错误 · 50000 衣物不存在 · 50004 更新衣物失败 · 50005 无权操作该衣物
DELETE /clothes/:uuid — 删除衣物(鉴权)
业务逻辑
- 软删除,无恢复接口,视为不可逆操作,前端需二次确认。
- 额度回退规则(重要):
- 衣物状态为
1/2/3/4(处理中或成功)且已扣额度 → 事务内回退 1 个上传额度; - 衣物状态为
5(处理失败)→ 不回退(失败时已回退过,防止双退)。
- 衣物状态为
- 已被穿搭引用的衣物无法删除 →
50001衣服已被搭配使用,无法删除(需先从穿搭中移除)。 - 删除后,已创建的衣柜分享里该衣物会自动过滤掉(不会崩)。
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 衣物 UUID |
响应 data = {}
错误码:10000 参数错误 · 50000 衣物不存在 · 50001 衣服已被搭配使用,无法删除 · 50003 删除衣物失败 · 50005 无权操作该衣物
5.5 穿搭模块
枚举速查:source 1=用户创建 2=AI 推荐 · is_public 1=不公开 2=公开 · audit_status 1=待审核 2=审核通过 3=审核不通过 · status 1=正常 2=封禁 3=待用户保存(AI 推荐生成后未保存)
POST /outfit — 创建穿搭(鉴权)
业务逻辑
- 1~10 件衣物,必须全部属于当前用户(非本人 →
50005,不存在 →50000),同一穿搭内重复衣物会自动去重。 - 创建后默认
is_public=1私有、audit_status=1待审核、status=1正常。 source传非 1/2 的值时服务端兜底为1(用户创建)。- 创建不扣任何额度,公开时才扣。
请求(json)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| outfit_name | string | ✅ | required, ≤32 | 穿搭名称 |
| outfit_desc | string | 否 | ≤500 | 穿搭描述 |
| scene | string | 否 | ≤100 | 适合场景 |
| source | int | 否 | oneof=1 2 |
1=用户创建 2=AI 推荐 |
| items | array | ✅ | min=1, max=10 |
衣物列表,元素 {"clothes_uuid": string} |
响应 data = 穿搭对象
错误码:10000 参数错误(名称超长 / items 为空或超过 10 件)· 40001 创建穿搭失败 · 50000 衣物不存在 · 50005 无权操作该衣物
GET /outfit — 我的穿搭列表(鉴权)
请求(query)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / page_size | int | 否 | 分页 |
| is_public | int | 否 | 1=不公开 2=公开;不传 = 全部 |
| source | int | 否 | 1=用户创建 2=AI 推荐;不传 = 全部 |
响应 data.list[] = 穿搭对象
⚠️ 该接口不会过滤
status=3(AI 待保存)的穿搭,但响应中已带上status字段。前端应据此把 AI 未保存的穿搭归到「AI 推荐」入口,不要混入「我的搭配」。这类穿搭超过 7 天未保存会被后端自动清理(软删除),详见 穿搭对象。
错误码:10000 参数错误 · 19001 数据库错误
GET /outfit/community — 社区穿搭列表(鉴权)
业务逻辑
- 返回其他用户已公开的穿搭:
is_public=2+audit_status=2+status=1(与详情页他人可见性口径一致),并排除本人的穿搭(自己的公开穿搭在"我的穿搭"中查看)。 - 列表按发布时间倒序(
id DESC)。 - 每项聚合作者信息与互动计数:
owner_name/owner_avatar(无头像为空串)、like_count/collect_count(仅统计有效点赞/收藏)。
请求(query)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / page_size | int | 否 | 分页,page_size 上限 50 |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| total | int64 | 总条数 |
| page | int | 当前页 |
| page_size | int | 每页条数 |
| list | array | 社区穿搭数组,见下表 |
list[]:
| 字段 | 类型 | 说明 |
|---|---|---|
| outfit_uuid | string | 穿搭 UUID |
| outfit_name | string | 穿搭名称 |
| outfit_desc | string | 穿搭描述 |
| scene | string | 适合场景 |
| source | int | 1=用户创建 2=AI 推荐 |
| created_at | int64 | 发布时间(秒级时间戳) |
| items | array | 衣物明细 [{clothes_uuid, processed_image}],processed_image 为可访问 URL |
| owner_name | string | 作者昵称 |
| owner_avatar | string | 作者头像 URL(无则空串) |
| like_count | int64 | 点赞数 |
| collect_count | int64 | 收藏数 |
错误码:10000 参数错误 · 19001 数据库错误
GET /outfit/:uuid — 穿搭详情(鉴权)
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 穿搭 UUID |
业务逻辑(可见性)
- 本人穿搭:始终可见。
- 他人穿搭:必须同时满足
is_public=2+audit_status=2+status=1,否则返回40004。 - 通过好友分享链接查看穿搭请使用
GET /outfit-share/:uuid(不要求穿搭已公开)。
响应 data = 穿搭对象
错误码:10000 参数错误 · 40000 穿搭不存在 · 40004 无权操作该穿搭(他人未公开/未过审)
PUT /outfit/:uuid — 更新穿搭(鉴权)
业务逻辑
- 只更新
outfit_name/outfit_desc/scene与明细。 - 明细采用"先删后建"(事务内),中途失败不会留下空明细。
outfit_uuid以路径参数为准(body 中传了也会被覆盖)。- 更新不影响公开状态与审核状态(已公开的仍是公开,不重复扣额度)。
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 穿搭 UUID |
请求体(json)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| outfit_name | string | ✅ | required, ≤32 | 穿搭名称 |
| outfit_desc | string | 否 | ≤500 | 穿搭描述 |
| scene | string | 否 | ≤100 | 适合场景 |
| items | array | ✅ | min=1, max=10 |
衣物列表,元素 {"clothes_uuid": string} |
响应 data = {}
错误码:10000 参数错误 · 40000 穿搭不存在 · 40003 更新穿搭失败 · 40004 无权操作该穿搭 · 50000 衣物不存在
DELETE /outfit/:uuid — 删除穿搭(鉴权)
业务逻辑
- 软删穿搭 + 物理删除明细,无恢复接口,需二次确认。
- 不返还任何额度(已扣的搭配分享额度不退)。
- 删除后,他人收藏列表与点赞记录不会自动清理。
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 穿搭 UUID |
响应 data = {}
错误码:10000 参数错误 · 40000 穿搭不存在 · 40002 删除穿搭失败 · 40004 无权操作该穿搭
PUT /outfit/:uuid/public — 设置公开状态(鉴权)
业务逻辑(额度相关,重点)
- 公开(
is_public=2):条件更新is_public 1→2且audit_status → 2(自动审核通过,无需等待人工)→ 成功后扣 1 个社区分享额度(quota_type=3)。- 额度不足 → 回滚为私有并返回
80000,穿搭不会公开。 - 重复公开(已是公开态)→ 幂等成功,不重复扣额度。
- ⚠️ 取消公开后再次公开,会再扣 1 个额度(因为
is_public已回到 1,首次公开条件重新成立)。
- 额度不足 → 回滚为私有并返回
- 取消公开(
is_public=1):直接置为私有,不返还已扣额度。 - 公开成功后,其他用户可通过
POST /like、POST /collect按 UUID 互动。
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 穿搭 UUID |
请求体(json)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| is_public | int | ✅ | required, oneof=1 2 |
1=不公开 2=公开 |
响应 data = {}
错误码:10000 参数错误 · 40000 穿搭不存在 · 40003 更新穿搭失败 · 40004 无权操作该穿搭 · 80000 额度不足(已自动回滚为私有)
5.6 AI 推荐模块
AI 推荐是异步接口。
POST /ai-recommend只提交任务并立即返回task_id,真正的生成在后台执行;前端用GET /ai-recommend/status轮询结果。这样设计的好处:接口不再长时间挂起,前端退出、切页面、断网都不会中断生成 —— 结果仍会落库为"待保存"穿搭,用户重新进入也能看到。
POST /ai-recommend — 提交 AI 穿搭推荐任务(鉴权)
业务逻辑
- 提交幂等:同一用户已有任务在生成中时,直接返回该任务的
task_id(不会重复扣额度),前端继续轮询原任务即可。 - 提交时先扣 1 个 AI 额度(
quota_type=2);额度不足返回90401AI 推荐次数已达上限(注意不是80000)。 - 接口秒级返回(实测 ~30ms),不等大模型。
- 生成在后台执行,输入为:当前用户**处理成功(状态 4)**的全部衣物 + 生日推算的星座 + 今日运势(星座/运势缺失会降级,不影响生成)。
- 失败时额度自动回退,失败原因写入任务状态(
error_code/error_msg),详见下方状态接口:90400+衣橱暂无可用衣物90400+大模型调用失败90400+未生成有效搭配90400+生成任务异常终止(超过 3 分钟未完成)
- ⚠️ 生成成功但未调用 save,不退还 AI 额度(额度在提交时已消耗)。
请求(json)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| scene_type | int | ✅ | required, oneof=1 2 |
1=每日推荐 2=个性化场景 |
| scene | string | 否 | — | 用户自定义场景描述(scene_type=2 时使用,如"参加朋友的婚礼") |
响应 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(保存时要用) |
| outfit_name | string | 穿搭名称 |
| outfit_desc | string | 穿搭描述 |
| scene | string | 场景 |
| source | int | 2=AI 推荐 |
| status | int | ⚠️ 穿搭状态 3=待用户保存(与顶层的任务状态 status 同名但含义不同) |
| outfit_item | array | ⚠️ 注意字段名是 outfit_item(单数 item),元素 {"clothes_uuid": string} |
错误码:10000 参数错误 · 90402 任务不存在或已过期 · 19002 缓存错误
POST /ai-recommend/save — 保存 AI 推荐为我的穿搭(鉴权)
业务逻辑
- 仅
status=3(待用户保存)的穿搭可保存 → 置为status=1。 - 已是
status=1→ 幂等成功(重复保存安全)。 - 其它状态(如已封禁)→
40003。 - 保存不扣任何额度;后续若要公开到社区,仍需调
PUT /outfit/:uuid/public,届时扣 1 个搭配分享额度。
请求(json)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| outfit_uuid | string | ✅ | required | 由 GET /ai-recommend/status 返回的 result.outfit_uuid |
响应 data = {}
错误码:10000 参数错误 · 40000 穿搭不存在 · 40003 更新穿搭失败(状态不允许保存)· 40004 无权操作该穿搭
5.7 衣柜分享模块
状态枚举 status:1=正常 2=已取消 3=已过期 4=已封禁
分享类型 share_type:1=部分衣物分享 2=全量快照分享
POST /closet-share — 创建衣柜分享(鉴权)
业务逻辑
expire_time:0表示永不过期;非 0 时必须晚于当前时间(否则10000+details:["过期时间必须晚于当前时间"])。share_type=1:必须传非空clothes_uuids(否则10000+details:["部分分享需选择衣物"]),且衣物必须全部属于本人。share_type=2:自动快照当前全部衣物(后端分页遍历,无需传列表)。- 先扣 1 个衣柜分享额度(
quota_type=4),不足 →80000。 - 创建分享(
status=1正常)→ 写入明细;明细写入失败会回退额度并返回19001(避免产生空分享)。 - 创建即自动审核通过。
请求(json)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| share_type | int | ✅ | required, oneof=1 2 |
1=部分衣物 2=全量快照 |
| clothes_uuids | array | 条件必填 | — | 衣物 UUID 数组;share_type=1 时必填且非空 |
| expire_time | int64 | 否 | — | 过期时间戳(秒);0 或省略 = 永不过期 |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| share_uuid | string | 分享 UUID,供他人访问分享详情 |
错误码:10000 参数错误(含 部分分享需选择衣物、过期时间必须晚于当前时间)· 80000 额度不足 · 19001 数据库错误(额度已回退)· 50000 衣物不存在 · 50005 无权操作该衣物
GET /closet-share — 我的分享列表(鉴权)
请求(query)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / page_size | int | 否 | 分页 |
| status | int | 否 | 1=正常 2=已取消 3=已过期 4=已封禁;不传 = 全部 |
响应 data.list[]
| 字段 | 类型 | 说明 |
|---|---|---|
| share_uuid | string | 分享 UUID |
| share_type | int | 1=部分衣物 2=全量快照 |
| clothes_count | int | 衣物数量 |
| expire_time | int64 | 过期时间戳(秒),0=永不过期 |
| status | int | 1=正常 2=已取消 3=已过期 4=已封禁 |
| owner_name | string | 分享者昵称(本接口暂不返回,恒为空) |
| owner_avatar | string | 分享者头像(本接口暂不返回,恒为空) |
| created_at | int64 | 分享创建时间(秒,前端合并排序用) |
⚠️ 过期是"惰性"的:
expire_time到了但没人访问时,列表里status可能仍是1,直到有人访问详情才变成3。前端应同时判断status与expire_time来展示"已过期"。
错误码:10000 参数错误 · 19001 数据库错误
GET /closet-share/received — 分享给我的列表(鉴权)
业务逻辑
- "访问即视为收到":登录用户(非分享属主)成功访问他人分享详情时,自动记录接收关系;重复访问不重复记录,属主访问不记录。
- 列表按接收记录倒序分页返回,支持按
status筛选。
请求(query)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / page_size | int | 否 | 分页 |
| status | int | 否 | 1=正常 2=已取消 3=已过期 4=已封禁;不传 = 全部 |
响应 data.list[]
| 字段 | 类型 | 说明 |
|---|---|---|
| share_uuid | string | 分享 UUID |
| share_type | int | 1=部分衣物 2=全量快照 |
| clothes_count | int | 衣物数量 |
| expire_time | int64 | 过期时间戳(秒),0=永不过期 |
| status | int | 1=正常 2=已取消 3=已过期 4=已封禁 |
| owner_name | string | 分享者昵称 |
| owner_avatar | string | 分享者头像 |
⚠️ 过期与状态判定同"我的分享列表",前端应同时判断
status与expire_time。
错误码:10000 参数错误 · 19001 数据库错误
GET /closet-share/:uuid — 分享详情(鉴权,属主 / 访客视角)
业务逻辑
- 属主与访客都可访问(需已登录,
is_owner区分视角)。 - 访客访问会 +1 访问计数,属主自己访问不计数。
- 状态处理:
2已取消 →90102;4已封禁 →90100(故意伪装成"不存在");3已过期 →90101;expire_time已过 → 惰性置为3并返回90101。 - 已被软删除的衣物会自动从分享中过滤。
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 分享 UUID |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| share_uuid | string | 分享 UUID |
| owner_name | string | 分享者昵称 |
| owner_avatar | string | 分享者头像 |
| clothes | array | 衣物对象 数组 |
| expire_time | int64 | 过期时间戳(秒) |
| status | int | 1=正常 2=已取消 3=已过期 4=已封禁 |
| is_owner | bool | 是否本人分享(true 时隐藏"收藏/点赞"入口) |
错误码:10000 参数错误 · 90100 分享不存在(含已封禁)· 90101 分享已过期 · 90102 分享已取消
POST /closet-share/:uuid/cancel — 取消分享(鉴权)
业务逻辑
- 仅属主可操作,他人 →
12000(HTTP 403)。 - 置
status=2,不可逆,且不返还衣柜分享额度,前端需二次确认。
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 分享 UUID |
响应 data = {}
错误码:10000 参数错误 · 12000 无权限访问(403)· 90100 分享不存在 · 19001 数据库错误
5.8 穿搭分享模块
状态枚举 status:1=正常 2=用户取消
穿搭分享用于把「自己的穿搭」以链接形式分享给好友,与「公开到社区」完全解耦:仅自己可见的穿搭同样可以分享,好友凭
share_uuid只读查看整套穿搭与单品图。额度:分享仅自己可见的穿搭扣 1 次衣柜分享额度(
quota_type=4,与衣柜分享共用同一个额度池);分享已公开的穿搭不扣(公开到社区时已扣过社区分享额度)。额度不足 →80000且不创建分享。分享一旦发出即独立于穿搭的公开状态——取消公开不影响已发出的分享,仅「取消分享」「删除穿搭」「封禁穿搭」才使分享失效。
POST /outfit-share — 创建穿搭分享(鉴权)
业务逻辑
- 仅能分享本人的穿搭(他人 →
40004)。 - 无需先公开到社区:仅自己可见的穿搭同样可分享(分享与公开解耦);穿搭已被删除 →
40000,已被封禁 →40004。 - 额度:
is_public=1(仅自己可见)→ 扣 1 次衣柜分享额度(quota_type=4,与衣柜分享共用池),额度不足 →80000且不创建分享;is_public=2(已公开)→ 不扣。扣额成功后分享落库失败会自动回退 1 次额度。 - 创建成功后返回
share_uuid,分享链接携带该值。
请求(json)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| outfit_uuid | string | ✅ | 穿搭 UUID |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| share_uuid | string | 分享 UUID,供他人访问分享详情 |
错误码:10000 参数错误 · 40000 穿搭不存在 · 40004 无权操作该穿搭 · 80000 额度不足 · 19001 数据库错误
GET /outfit-share — 我的穿搭分享列表(鉴权)
请求(query)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page | int | 否 | 页码,默认 1 |
| page_size | int | 否 | 每页数量,默认 10,最大 50 |
| status | int | 否 | 1=正常 2=已取消;不传 = 全部 |
响应 data.list[]
| 字段 | 类型 | 说明 |
|---|---|---|
| share_uuid | string | 分享 UUID |
| outfit_uuid | string | 穿搭 UUID |
| outfit_name | string | 穿搭名称 |
| cover_image | string | 封面(首件衣物抠图 URL,无则空) |
| items_count | int | 包含衣物数量 |
| status | int | 1=正常 2=已取消 |
| owner_name | string | 分享者昵称(本接口不返回,恒为空) |
| owner_avatar | string | 分享者头像(本接口不返回,恒为空) |
| created_at | int64 | 分享创建时间(秒) |
错误码:10000 参数错误 · 19001 数据库错误
GET /outfit-share/received — 分享给我的穿搭分享(鉴权)
业务逻辑
- "访问即视为收到":登录用户(非分享属主)成功访问他人穿搭分享详情时,自动记录接收关系;重复访问不重复记录,属主访问不记录。
- 列表按接收记录倒序分页返回,支持按
status筛选;仅排除已删除 / 已封禁的穿搭(穿搭是否公开不影响,分享与公开解耦)。 - 字段同「我的穿搭分享列表」,并额外返回
owner_name/owner_avatar。
错误码:10000 参数错误 · 19001 数据库错误
GET /outfit-share/:uuid — 穿搭分享详情(鉴权,属主 / 访客视角)
业务逻辑
- 属主与访客都可访问(需已登录,
is_owner区分视角)。 - 访客访问成功即记录接收关系;属主访问不记录。
- 分享已取消 →
90104;分享不存在或对应穿搭已删除 / 已封禁 →90103(穿搭取消公开不影响已发出的分享)。
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 分享 UUID |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| share_uuid | string | 分享 UUID |
| owner_name | string | 分享者昵称 |
| owner_avatar | string | 分享者头像 |
| status | int | 1=正常 2=已取消 |
| is_owner | bool | 是否本人分享 |
| outfit | object | 穿搭对象,items[] 含 processed_image(可直接展示) |
错误码:10000 参数错误 · 90103 穿搭分享不存在(含穿搭已删除 / 已封禁)· 90104 穿搭分享已取消
POST /outfit-share/:uuid/cancel — 取消穿搭分享(鉴权)
业务逻辑:仅属主可操作,他人 → 12000(HTTP 403);置 status=2,不可逆。
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 分享 UUID |
响应 data = {}
错误码:10000 参数错误 · 12000 无权限访问(403)· 90103 穿搭分享不存在 · 19001 数据库错误
5.9 收藏与点赞模块
动作枚举 action:1=执行(点赞/收藏)2=取消。两个接口都幂等,重复提交无副作用。
POST /collect — 收藏 / 取消收藏(鉴权)
业务逻辑
- 目标为社区穿搭 UUID。目标不存在 →
90200;非本人且未公开可见 →40004。 - 取消时仅当当前为"已收藏"才更新为已取消;记录不会物理删除,可再次收藏。
- 收藏不触发任何通知。
请求(json)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| target_uuid | string | ✅ | required | 目标穿搭 UUID |
| action | int | ✅ | required, oneof=1 2 |
1=收藏 2=取消收藏 |
响应 data = {}
错误码:10000 参数错误 · 90200 目标内容不存在 · 90202 收藏操作失败 · 40004 无权操作该穿搭 · 19001 数据库错误
GET /collect — 我的收藏列表(鉴权)
请求(query):仅 page / page_size。
响应 data.list[] = 穿搭对象
错误码:10000 参数错误 · 19001 数据库错误
GET /collect/:targetUUID — 查询收藏状态与数量(鉴权)
进入穿搭详情时用于初始化收藏按钮状态。
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| targetUUID | string | ✅ | ⚠️ 路径参数名是 targetUUID(驼峰),不是 uuid |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| is_collected | bool | 当前用户是否已收藏 |
| collect_count | int64 | 该穿搭收藏总数 |
错误码:10000 参数错误 · 90200 目标内容不存在 · 40004 无权操作该穿搭 · 19001 数据库错误
POST /like — 点赞 / 取消点赞(鉴权)
业务逻辑
- 目标为社区穿搭 UUID。目标不存在 →
90200;非本人且未公开可见 →40004。 - 通知触发条件:仅当状态从"无记录或已取消"变为"已点赞"、且不是给自己点赞时,才向穿搭作者发一条
msg_type=1通知(标题"收到新的点赞",内容"你的穿搭「{名称}」收到了一个赞")。重复点赞不会重复通知。
请求(json)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| target_uuid | string | ✅ | required | 目标穿搭 UUID |
| action | int | ✅ | required, oneof=1 2 |
1=点赞 2=取消点赞 |
响应 data = {}
错误码:10000 参数错误 · 90200 目标内容不存在 · 90201 点赞操作失败 · 40004 无权操作该穿搭 · 19001 数据库错误
GET /like/:targetUUID — 查询点赞状态与数量(鉴权)
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| targetUUID | string | ✅ | ⚠️ 路径参数名是 targetUUID(驼峰),不是 uuid |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| is_liked | bool | 当前用户是否已点赞 |
| like_count | int64 | 该穿搭点赞总数 |
错误码:10000 参数错误 · 19001 数据库错误
5.10 通知模块
is_read 取值:⚠️ 1=未读 2=已读(非常规的 0/1,也不是布尔值)
msg_type 取值:1=点赞通知 2=订单通知 3=系统通知(当前代码无发送点,预留)
通知触发点(仅 3 处)
| 触发动作 | msg_type | 标题 | 内容 | 接收者 |
|---|---|---|---|---|
| 他人点赞我的公开穿搭 | 1 |
收到新的点赞 | 你的穿搭「{OutfitName}」收到了一个赞 | 穿搭作者(非本人点赞才触发) |
| 支付成功 | 2 |
订单支付成功 | 你购买的「{GoodsName}」已到账 | 买家(sender=0 系统) |
| 退款成功 | 2 |
退款成功 | 订单「{GoodsName}」已退款 | 买家(sender=0 系统) |
通知无推送能力(无 WebSocket/订阅消息),前端需轮询
GET /notification/unread刷新红点。
GET /notification — 通知列表(鉴权)
请求(query)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| page / page_size | int | 否 | — | 分页 |
| is_read | int | 否 | oneof=1 2 |
⚠️ 1=未读 2=已读;不传 = 全部(不是布尔值) |
| msg_type | int | 否 | oneof=1 2 3 |
1=点赞 2=订单 3=系统;不传 = 全部(可与 is_read 组合筛选) |
响应 data.list[] = 通知对象
错误码:10000 参数错误 · 19001 数据库错误
GET /notification/unread — 未读通知数(鉴权)
业务逻辑:优先读 Redis 计数;Redis 为 0 或异常时回落 MySQL 统计并回填缓存。计数为 best-effort,可能短暂偏小,展示红点时以列表接口为准更稳妥。
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| unread_count | int64 | 未读通知数 |
错误码:19001 数据库错误 · 19002 缓存错误
GET /notification/:uuid — 通知详情(鉴权)
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 通知 UUID |
响应 data = 通知对象
错误码:10000 参数错误 · 90000 通知不存在 · 12000 无权限访问
POST /notification/:uuid/read — 标记已读(鉴权)
业务逻辑
- 条件更新(仅"未读→已读"),防并发重复扣减未读数;已读时直接返回当前未读数,幂等。
- ⚠️ 没有"全部已读"接口,需要全标已读时前端需逐条调用。
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uuid | string | ✅ | 通知 UUID |
响应 data = {}
错误码:10000 参数错误 · 90000 通知不存在 · 19001 数据库错误
5.11 商品模块(公开,无需登录)
goods_type 枚举:1=会员包 2=上传包 3=AI 推荐包 4=衣柜分享包 5=搭配分享包
status:1=上架 2=下架(列表只返回上架商品)
GET /goods — 商品列表(公开)
请求(query)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / page_size | int | 否 | 分页 |
| goods_type | int | 否 | 商品类型筛选;不传或 0 = 全部 |
响应 data.list[] = 商品对象(仅上架商品)
错误码:10000 参数错误 · 19001 数据库错误
GET /goods/:code — 按编码查询商品(公开)
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | string | ✅ | 商品编码 goods_code(不是数字 ID) |
响应 data = 商品对象
错误码:10000 参数错误 · 60000 商品不存在 · 60001 商品已下架
5.12 订单与退款模块
order_status 枚举:1=待支付 2=已支付 3=已取消 4=已过期 5=已退款
退款 status 枚举:0=待处理 1=退款中 2=退款成功 3=退款失败
POST /order — 创建订单并发起微信支付(鉴权)
业务逻辑
- 只用
goods_code下单,金额由服务端按商品售价discount_price计算,前端不要传金额。 - 商品必须存在且上架(否则
60000/60001)。 - 用户必须有
wx_open_id(即必须通过微信登录),否则79000+details:["缺少微信 openid"]。 - 支付有效期 15 分钟,
expire_time用于倒计时。 - 下单时把商品权益(会员天数 / 各类额度)快照进订单,后续商品改价不影响已下单权益。
- 先落本地待支付订单,再调微信 JSAPI。微信下单失败返回
79000,但本地订单保留为待支付,15 分钟后自动过期。 - 同一用户可并存多个待支付订单(无防重限制),前端应避免重复点击。
请求(json)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| goods_code | string | ✅ | required | 商品编码(唯一请求字段) |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| order_no | string | 订单号(后续取消/退款/查询用) |
| pay_amount | int64 | 应付金额(分) |
| expire_time | int64 | 支付截止时间戳(秒),倒计时用(15 分钟) |
| pay_params | object | 微信支付参数,见此,整体透传给 wx.requestPayment |
错误码:10000 参数错误 · 60000 商品不存在 · 60001 商品已下架 · 79000 创建订单失败(含 缺少微信 openid)· 19001 数据库错误
POST /order/virtual — 创建虚拟支付订单(鉴权)
小程序虚拟商品(会员 / 额度包)必须走虚拟支付(微信 2026-04-01 起全终端强制)。本接口本地落单(pay_channel=2)后返回前端调起 wx.requestVirtualPayment 所需的签名参数,不调用微信 JSAPI 下单。
业务逻辑
- 服务端校验虚拟支付已开通、商品上架且已发布为虚拟支付道具(
product_id非空、product_status=2)。 - 下单时把商品权益快照进订单;
outTradeNo复用本地订单号。 - 服务端用
offerId+ 现网appKey生成signData/paySig,用登录缓存的session_key生成signature,原样返回前端。 - 支付结果不由前端回调决定:前端
success可能丢失,最终以服务端收到的「道具发货推送」为准。
请求(json)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| goods_code | string | ✅ | required | 商品编码 |
| platform | string | ✅ | oneof=android ios harmony windows |
支付平台,前端按 wx.getSystemInfoSync().platform 上报 |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| order_no | string | 订单号(即 outTradeNo) |
| pay_amount | int64 | 实付金额(分) |
| expire_time | int64 | 支付截止时间戳(秒),15 分钟 |
| sign_data | string | 参与签名的 JSON 字符串,原样传给 wx.requestVirtualPayment,勿 JSON.parse/stringify |
| pay_sig | string | 支付签名(服务端生成) |
| signature | string | 用户态签名(服务端生成) |
前端调用契约(示例)
const res = await post('/order/virtual', { goods_code, platform: wx.getSystemInfoSync().platform })
wx.requestVirtualPayment({
signData: res.data.sign_data, // 原样透传,勿重新序列化
paySig: res.data.pay_sig,
signature: res.data.signature,
success: () => { /* 仅表示拉起成功,仍需轮询 GET /order 确认 order_status=2 */ },
fail: (err) => { /* requestVirtualPayment:fail cancel = 用户取消 */ },
})
错误码:10000 参数错误 · 60000 商品不存在 · 60001 商品已下架 · 70005 虚拟支付未开通 · 70006 商品尚未发布为虚拟支付道具 · 70007 虚拟支付订单不支持取消 · 70008 iOS 虚拟支付最低 1 元 · 79003 创建虚拟支付订单失败 · 19001 数据库错误
iOS 虚拟支付限制:最低 1 元(低于 100 分返回
70008)、iOS 15+、微信 8.0.68+、不支持沙箱;结算由 Apple(45~60 天)。iOS 订单退款用户走 App Store 申请,服务端会收到退款问询推送并应答。运营约定:虚拟支付道具的发布与改价由管理后台负责(服务端不会自动同步道具)。商品发布为道具后回填
product_id/product_status=2才可下单;运营改价后必须通过管理后台同步微信侧道具价格,否则微信侧道具价格与本地不一致:微信扣款多于订单金额时发货金额校验会拒绝(用户付款拿不到权益),少于订单金额时产生差价资损。
GET /order — 我的订单列表(鉴权)
请求(query)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| page / page_size | int | 否 | — | 分页 |
| status | int | 否 | oneof=1 2 3 4 5 |
订单状态筛选;不传 = 全部 |
响应 data.list[] = 订单对象
错误码:10000 参数错误 · 19001 数据库错误
POST /order/:orderNo/cancel — 取消订单(鉴权)
业务逻辑
- 虚拟支付订单(
pay_channel=2)不可取消 →70007(虚拟支付无服务端关单 API,取消后微信侧单仍可支付,会造成付款不到账)。 - 仅
1待支付订单可取消;已支付 →70002;其它状态 →70001。 - 条件更新
1→3,失败 →70001。 - 本地取消后后端尽力调用微信关单(失败仅告警,不影响结果)。
- 不可撤销,但可重新下单。
请求(path)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| orderNo | string | ✅ | ⚠️ 路径参数名是 orderNo(驼峰),不是 uuid |
响应 data = {}
错误码:10000 参数错误 · 70000 订单不存在 · 70001 订单状态不正确 · 70002 订单已支付 · 79001 取消订单失败 · 12000 无权限访问
POST /order/refund — 申请退款(鉴权)
业务逻辑(重要:人工审核模式)
- iOS 虚拟支付订单不可主动退款 →
71004,请引导用户走 App Store 申请退款(Apple 通过退款问询推送回收权益)。 - 仅
2已支付订单可申请,其它状态 →71001。 - ⚠️ 系统不会自动调微信退款:接口只是登记退款申请(
status=0待处理),需运营人工审核并手动发起退款——普通支付订单在微信支付商户后台退款,虚拟支付订单(pay_channel=2)请在虚拟支付后台(Midas)发起退款,退款结果由推送回调同步。 - 前端应提示"退款审核中",并允许轮询订单状态直到
5已退款;不要承诺实时到账。 - 同一订单已有退款记录且状态不是
3(失败)→71001;上次失败可重新申请(复用记录、换新退款单号)。 - 退款成功后,订单置
5,会员天数与已发放额度会被回收(已消耗部分不回收),并发退款通知。
请求(json)
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| order_no | string | ✅ | required | 订单号 |
| reason | string | ✅ | required, ≤200 | 退款原因 |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| refund_no | string | 退款单号 |
| refund_amount | int64 | 退款金额(分) |
| status | int | 0=待处理 1=退款中 2=退款成功 3=退款失败 |
| apply_time | int64 | 申请时间(秒) |
错误码:10000 参数错误 · 70000 订单不存在 · 71000 退款记录不存在 · 71001 当前状态不允许退款 · 71002 退款金额不合法 · 71003 退款申请失败 · 71004 iOS 订单请通过 App Store 申请退款
POST /order/pay/notify · POST /order/refund/notify — 微信回调(公开)
🚫 这是微信服务端回调地址,前端绝对不要调用。
- 不走统一
{code,msg,data}响应包装,按微信 V3 规范返回:成功 HTTP 200{"code":"SUCCESS","message":"成功"};失败 HTTP 500{"code":"FAIL","message":"<原因>"}。 - 服务端处理:验签 + 解密 → 幂等锁(30 秒)→ 金额校验(与订单金额不一致则拒绝流转、人工介入)→ 流转订单状态 → 发放/回收权益 → 发站内通知。
- 竞态兜底:已过期但已付款 → 补发货;已取消但已付款 → 自动发起微信退款。
6. 业务逻辑与状态机
6.1 七条业务主线(总览)
flowchart TD
A["微信登录<br/>新用户自动注册"] --> B["初始化 4 类免费额度<br/>默认 上传 20 / AI 3 / 搭配分享 10 / 衣柜分享 5<br/>(可配置,允许为 0)"]
B --> B2["赠送体验会员 + 会员额度<br/>默认 3 天;AI 3 / 分享 3 / 衣柜 3<br/>(体验会员凭证 source=4,固定到期)"]
B2 --> C["上传衣物<br/>校验 → 扣上传额度 → 异步 AI 抠图 + 分类"]
C --> D["衣橱管理<br/>查看 / 修正属性 / 删除回退额度"]
D --> E["创建穿搭"]
D --> F["AI 穿搭推荐<br/>扣 AI 额度 → 大模型生成 → 待用户保存"]
D --> G["衣柜分享<br/>部分衣物 / 全量快照"]
E --> H["公开到社区<br/>自动审核通过 → 扣搭配分享额度"]
H --> I["社区互动<br/>点赞 → 触发站内通知"]
F --> E
B --> J{"额度不足?"}
J -- "是" --> K["选购商品 → 微信支付 → 权益到账"]
K --> B
- 注册与额度:微信登录首次自动注册,同步初始化 4 类免费额度(内置默认 上传 20 / AI 3 / 搭配分享 10 / 衣柜分享 5,可由
UserDefaultQuota配置,允许为 0)。 - 衣物生命周期:上传(校验 + 扣额)→ 异步 AI 处理(抠图 → 分类)→ 成功入库可被选用 → 删除软删并回退额度。
- 穿搭与社区:创建穿搭 → 公开(自动审核通过 + 扣额度)→ 他人点赞触发通知。
- 衣柜分享:部分衣物或全量快照 → 扣额度 → 属主/访客查看 → 取消或惰性过期。
- 交易与权益:选购商品 → 下单(15 分钟有效)→ 微信支付回调发放权益 → 超时过期 / 退款回收。
- AI 推荐:扣 AI 额度 → 结合星座运势与衣橱生成 → 落库"待用户保存" → 保存转正式穿搭。
- 通知:点赞、支付成功、退款成功触发站内通知,未读计数实时维护(需前端轮询)。
6.2 衣物 AI 处理状态机
stateDiagram-v2
[*] --> queued : 上传成功(同步扣上传额度 1 点)
queued --> matting : 异步任务开始
matting --> classifying : 抠图完成(上传 COS)
classifying --> success : 分类完成,回填 15 维属性
classifying --> failed : 分类失败(保留抠图结果)
matting --> failed : 抠图失败
queued --> failed : 保存/上传 COS 异常
success --> [*] : 可被穿搭/AI 推荐选用
failed --> [*] : 用户可修正属性或删除(额度已自动回退)
state "1 抠图中" as matting
state "2 分类中" as classifying
state "3 排队中" as queued
state "4 成功" as success
state "5 失败(写 fail_reason)" as failed
关键规则
- 上传接口同步返回(不等待 AI),返回即
3 排队中。 - 异步流水线并发上限 10,单任务超时 3 分钟;抠图/分类各带重试(默认 3 次,间隔 1 秒)。
- 失败时
fail_reason取值:图片抠图失败/图片保存失败/图片上传失败/图片分类失败/内部错误。 - 失败自动回退上传额度,三重防重防护:状态已是 5 不再退、衣物已删除不再退、删除接口用行锁 + 状态判断。
- 只有
4 成功的衣物才会被 AI 推荐采用。
6.3 穿搭状态机(公开 / 审核 / 保存)
stateDiagram-v2
[*] --> created : 创建穿搭(1~10 件本人衣物)
created --> private : 默认 is_public=1, audit_status=1, status=1
private --> public : 公开(审核自动通过 + 扣搭配分享额度 1 点)
public --> private : 取消公开(不返还额度,再次公开会再扣)
public --> interacted : 他人点赞(触发通知)/ 收藏(不通知)
private --> [*] : 修改(明细先删后建)/ 删除(软删 + 明细物理删)
public --> [*] : 修改 / 删除(均不返还额度)
state "1 私有(待审核)" as private
state "2 公开(审核通过)" as public
state "社区互动" as interacted
AI 推荐穿搭的额外状态:status=3 待用户保存 → 调 /ai-recommend/save → status=1 正常。
可见性判定:他人可见需同时满足 is_public=2 且 audit_status=2 且 status=1,否则 40004。
6.4 衣柜分享状态机
stateDiagram-v2
[*] --> normal : 创建分享(扣衣柜分享额度 1 点 + 自动审核通过)
normal --> cancelled : 用户取消(仅属主,不返还额度,不可逆)
normal --> expired : 惰性过期(访问详情时判定 now > ExpireTime)
normal --> banned : 封禁(预留状态,对访问者隐藏为"不存在")
state "1 正常" as normal
state "2 已取消" as cancelled
state "3 已过期" as expired
state "4 已封禁" as banned
关键规则
expire_time = 0表示永不过期。- 惰性过期:到时间但无人访问时列表
status仍为1,前端必须同时判断status与expire_time。 - 访客访问 +1 访问计数,属主访问不计数。
- 删除衣物不会清理分享明细,被软删的衣物在分享详情中自动过滤。
6.5 订单状态机
stateDiagram-v2
[*] --> pending : 创建订单(快照权益 + 微信 JSAPI 下单,15 分钟有效)
pending --> paid : 支付回调 SUCCESS(验签 + 金额校验 → 发放权益 → 通知)
pending --> expired : 超时 15 分钟(后台每分钟扫描)
pending --> cancelled : 用户主动取消(同步关闭微信订单)
expired --> paid : 过期后付款到账(补发货)
cancelled --> refunded : 已取消但已付款 → 生成待审核退款单,管理员手动退款
paid --> refunded : 退款成功回调(回收权益 → 通知)
state "1 待支付" as pending
state "2 已支付" as paid
state "3 已取消" as cancelled
state "4 已过期" as expired
state "5 已退款" as refunded
关键规则
- 金额校验:回调金额与订单
pay_amount不一致 → 拒绝流转、不发货、转人工。 - 权益发放:会员天数按快照实际发放,会员类型按续费后最终到期时长推导(剩余 ≥365 天为年度,否则月度,避免年度会员续月包被降级);上传 / AI / 衣柜分享 / 搭配分享额度改为写入额度凭证
quota_grants(会员包expire_time=0跟随 VIP、加量包按goods.valid_days独立到期)。 - 幂等 + 补偿:
benefit_grant_tasks唯一索引 + "发放中"状态防并发;失败自动落任务,每分钟重试,重试 3 次仍失败则标记失败并告警人工介入。 - 退款回收:会员天数按快照回退(不回退到过去);赠送额度通过失效凭证回收未用部分,已消耗部分不追回。
6.6 额度规则全景
扣减 / 回退 / 发放 / 回收全部走事务 + 幂等流水(唯一索引 (order_no, quota_type, change_type) 兜底),前端无需做补偿。
| 场景 | 额度处理 | 前端提示 |
|---|---|---|
| 上传衣物(建记录成功) | 扣 1 个上传额度 | — |
| 上传衣物但建记录失败 | 自动回退 | 重试即可,不会丢额度 |
| 衣物抠图/分类/保存失败 | 自动回退(状态非 5 且未删除) | 提示识别失败,可重传 |
| 删除"处理中/成功"的衣物 | 回退 1 个上传额度 | 可提示"已返还 1 次上传额度" |
| 删除"处理失败"的衣物 | 不回退(失败时已退过) | 不要提示返还 |
| 穿搭首次公开 | 扣 1 个搭配分享额度 | 公开前展示额度消耗确认 |
| 穿搭重复公开 | 不重复扣(幂等) | — |
| 取消公开后再次公开 | 再扣 1 个额度 | ⚠️ 重点提示用户 |
| 穿搭取消公开 | 不返还 | — |
| 穿搭删除 | 不返还 | — |
| 分享「仅自己可见」的穿搭给好友 | 扣 1 个衣柜分享额度(与衣柜分享共用池) | 额度不足时引导购买 |
| 分享「已公开」的穿搭给好友 | 不扣(公开到社区时已扣社区分享额度) | — |
| 私密穿搭分享但分享记录落库失败 | 自动回退 | 重试即可 |
| AI 推荐生成(成功) | 扣 1 个 AI 额度 | 生成前展示剩余次数 |
| AI 推荐失败(衣橱为空/模型失败/无有效单品/落库失败) | 自动回退 | 可安全重试 |
| AI 推荐成功但未保存 | 不返还 | ⚠️ 重点提示用户及时保存 |
| 创建衣柜分享 | 扣 1 个衣柜分享额度 | — |
| 创建衣柜分享但明细写入失败 | 自动回退 | 重试即可 |
| 取消衣柜分享 | 不返还 | 二次确认 |
| 新用户注册 | 发放:免费额度 + 体验会员额度(NewUserGift,source=4 凭证,固定有效期=体验期结束) |
「欢迎礼包已到账」 |
| 固定有效期额度过期 | 自动失效:到期即不再计入有效额度;后续充值续费不会延长它(跟随 VIP 的凭证则不受影响,续期自动延长) | 无感知,剩余额度同步下降 |
| 额度过期记账 | 后台任务每小时为已过期的固定有效期凭证补记 change_type=7(到期失效)流水,变更量为负数(幂等,重复执行不会重复记账) |
额度明细里能看到 -N 到期失效,账目闭环 |
| 购买商品支付成功 | 发放(按订单快照) | 展示"已到账" |
| 退款成功 | 回收(total >= used,已用部分不退) |
提示额度已回收 |
额度不足时:前端跳商品列表引导购买。注意 AI 额度不足返回的是 90401(不是 80000)。私密穿搭分享额度不足返回 80000,此时不要回退为普通穿搭详情链接(私密穿搭对他人不可见,对方打开会是无权限的空页),应提示用户补充衣柜分享额度。
6.7 通知触发规则
| 触发动作 | msg_type | 发送者 | 接收者 | 触发条件与防重 |
|---|---|---|---|---|
| 点赞 | 1 |
点赞者 | 穿搭作者 | 仅"从取消/无记录 变为 已点赞"且非本人时触发;重复点赞不重复通知 |
| 支付成功 | 2 |
系统(sender=0) | 买家 | 权益发放成功后触发;取消/过期不发 |
| 退款成功 | 2 |
系统(sender=0) | 买家 | 权益回收后触发 |
| 系统通知 | 3 |
预留 | — | 常量已定义,当前代码无发送点 |
- 通知写入 MySQL + Redis 未读计数自增,best-effort(失败仅告警,不影响主业务)。
- 无推送能力,前端需轮询
GET /notification/unread。
7. 核心调用时序图
7.1 登录与 Token 刷新
sequenceDiagram
participant F as 小程序前端
participant B as 后端服务
participant W as 微信服务端
F->>B: POST /user/wxlogin {code}
B->>W: code2session(code)
W-->>B: openid / session_key
B->>B: 首次登录:注册用户 + 初始化 4 类免费额度 + 赠送体验会员及会员额度
B-->>F: {access_token, refresh_token, expire_time, user_info}
Note over F,B: 后续请求携带 Authorization: Bearer <access_token>
F->>B: 任意鉴权接口
B-->>F: code 11002 / HTTP 401(Token 已过期)
F->>B: POST /user/refresh-token {refresh_token, access_token}
B-->>F: 全新 {access_token, refresh_token, expire_time}
Note over F: 必须整体替换本地 token(旧的已立即失效)
F->>B: 携带新 token 重试原请求
7.2 衣物上传与 AI 处理轮询
sequenceDiagram
participant F as 小程序前端
participant B as 后端服务
participant M as 抠图服务(8001)
participant C as 分类服务(8000)
F->>B: POST /clothes/upload (multipart, 字段名 file)
B->>B: 校验扩展名/大小/magic bytes + 扣 1 个上传额度
B-->>F: {clothes_uuid, image_process_status: 3}
Note over B,C: 异步:3 排队 → 1 抠图 → 2 分类 → 4 成功 / 5 失败
B->>M: 抠图请求(失败重试 ≤3 次,间隔 1s)
B->>C: 15 维分类请求(失败重试 ≤3 次)
loop 前端轮询(建议 1~2 秒)
F->>B: GET /clothes/:uuid/status
B-->>F: {image_process_status, fail_reason}
end
alt 状态 = 4 成功
F->>B: GET /clothes/:uuid 读取识别结果
opt 识别不准
F->>B: PUT /clothes/:uuid {color: "...", ...}
end
else 状态 = 5 失败
Note over F: 展示 fail_reason,可重新上传(额度已自动回退)
end
7.3 AI 穿搭推荐与保存
sequenceDiagram
participant F as 小程序前端
participant B as 后端服务
participant R as Redis
participant L as 大模型网关(Tokenhub)
F->>B: POST /ai-recommend {scene_type, scene?}
B->>R: 抢占用户活跃位(已有任务则返回原 task_id,幂等)
B->>B: 扣 1 个 AI 额度(不足 → 90401)
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->>R: 任务快照 status=3 + error_code/error_msg
end
F->>F: 用户预览、确认
F->>B: POST /ai-recommend/save {outfit_uuid}
B-->>F: 保存成功(status 3 → 1,不扣额度)
opt 公开到社区
F->>B: PUT /outfit/:uuid/public {is_public: 2}
B->>B: 自动审核通过 + 扣 1 个搭配分享额度
end
7.4 下单到支付完成
sequenceDiagram
participant F as 小程序前端
participant B as 后端服务
participant W as 微信支付
F->>B: GET /goods 选品
F->>B: POST /order {goods_code}
B->>B: 按售价计算金额 + 快照权益,创建订单(15 分钟有效)
B->>W: 微信 JSAPI 下单
B-->>F: {order_no, pay_amount, expire_time, pay_params}
F->>W: wx.requestPayment(pay_params)
W-->>B: POST /order/pay/notify(验签 + 解密)
B->>B: 金额校验 → 订单 1→2 → 发放权益 → 发站内通知
loop 前端轮询(1 秒起)
F->>B: GET /order
B-->>F: order_status
end
Note over F: 以服务端 order_status=2 为准,<br/>不要只信支付组件回调
opt 超时未支付
B->>B: 后台任务每分钟扫描,超 15 分钟置为 4 已过期
end
7.5 点赞 / 收藏切换
sequenceDiagram
participant F as 小程序前端
participant B as 后端服务
F->>B: GET /like/:targetUUID 获取初始状态
B-->>F: {is_liked, like_count}
F->>B: POST /like {target_uuid, action: 1}
B->>B: 状态 无/已取消 → 已点赞,且非本人 → 向作者发通知
B-->>F: 成功(幂等)
F->>B: POST /like {target_uuid, action: 2}
B-->>F: 成功(幂等,记录保留可再次点赞)
Note over F,B: 收藏同理:POST /collect {target_uuid, action},但收藏不触发通知
8. 错误码全表
前端统一以响应体
code为准(HTTP 状态码仅供参考)。全量 83 个错误码如下。
8.1 通用 / 鉴权 / 权限 / 系统(16)
| code | 含义 | HTTP | 前端建议 |
|---|---|---|---|
| 0 | 成功 | 200 | — |
| 10000 | 请求参数错误 | 200 | 展示 details[0] |
| 10001 | 资源不存在 | 200 | 通用兜底 |
| 10002 | 请求方法不允许 | 200 | 检查 Method |
| 10003 | 请求过于频繁 | 429 | 稍后重试(AI 推荐 180s 并发锁等) |
| 11000 | 未授权 | 401 | 跳登录 |
| 11001 | Token 鉴权失败 | 401 | 尝试刷新,失败则重新登录 |
| 11002 | Token 已过期 | 401 | 刷新 Token 后重试 |
| 11003 | Token 生成失败 | 200 | 重新登录 |
| 11004 | Token 缺失 | 401 | 跳登录 |
| 12000 | 无权限访问 | 403 | 提示无权操作 |
| 19000 | 服务内部错误 | 500 | "服务繁忙,请稍后重试" |
| 19001 | 数据库错误 | 500 | 同上 |
| 19002 | 缓存错误 | 500 | 同上 |
| 19003 | 下游服务调用失败 | 500 | 同上(AI 服务不可用) |
| 19999 | 未知错误 | 500 | 同上 |
8.2 用户(13)
| code | 含义 | HTTP |
|---|---|---|
| 20000 | 用户参数错误 | 200 |
| 21000 | 用户名或密码错误 | 200 |
| 21001 | 账号已被禁用 | 200 ⚠️ 非 401,需单独处理 |
| 21002 | 微信登录失败 | 200 |
| 21003 | 微信 Code 无效 | 200 |
| 21004 | 注册暂未开放 | 200 |
| 22000 | 用户不存在 | 200 |
| 23000 | 用户已存在 | 200 |
| 23001 | 邮箱已被注册 | 200 |
| 29000 | 用户创建失败 | 200 |
| 29001 | 用户更新失败 | 200 |
| 29002 | 用户删除失败 | 200 |
| 29003 | 用户查询失败 | 200 |
8.3 上传(10)
| code | 含义 | HTTP |
|---|---|---|
| 30000 | 上传文件为空 | 200 |
| 30001 | 不支持的文件类型 | 200 |
| 30002 | 文件大小超过限制 | 200 |
| 30004 | 文件不存在 | 200 |
| 30005 | 文件不是公开的 | 200 |
| 30006 | 权限不足 | 200 |
| 30007 | 文件处理中 | 200 |
| 39000 | 文件保存失败 | 200 |
| 39001 | 图片抠图处理失败 | 500 |
| 39002 | 图片分类处理失败 | 500 |
8.4 穿搭(5)
| code | 含义 | HTTP |
|---|---|---|
| 40000 | 穿搭不存在 | 200 |
| 40001 | 创建穿搭失败 | 200 |
| 40002 | 删除穿搭失败 | 200 |
| 40003 | 更新穿搭失败 | 200 |
| 40004 | 无权操作该穿搭 | 200 |
8.5 衣物(6)
| code | 含义 | HTTP |
|---|---|---|
| 50000 | 衣物不存在 | 200 |
| 50001 | 衣服已被搭配使用,无法删除 | 200 |
| 50002 | 创建衣物失败 | 200 |
| 50003 | 删除衣物失败 | 200 |
| 50004 | 更新衣物失败 | 200 |
| 50005 | 无权操作该衣物 | 200 |
8.6 商品(5)
| code | 含义 | HTTP |
|---|---|---|
| 60000 | 商品不存在 | 200 |
| 60001 | 商品已下架 | 200 |
| 69000 | 创建商品失败 | 200 |
| 69001 | 更新商品失败 | 200 |
| 69002 | 删除商品失败 | 200 |
8.7 订单 / 退款(12)
| code | 含义 | HTTP |
|---|---|---|
| 70000 | 订单不存在 | 200 |
| 70001 | 订单状态不正确 | 200 |
| 70002 | 订单已支付 | 200 |
| 70003 | 订单已取消 | 200 |
| 70004 | 订单已过期 | 200 |
| 70005 | 虚拟支付未开通 | 200 |
| 70006 | 商品尚未发布为虚拟支付道具 | 200 |
| 70007 | 虚拟支付订单不支持取消 | 200 |
| 70008 | iOS 虚拟支付最低 1 元 | 200 |
| 79000 | 创建订单失败 | 200 |
| 79001 | 取消订单失败 | 200 |
| 79002 | 更新订单失败 | 200 |
| 79003 | 创建虚拟支付订单失败 | 200 |
| 71000 | 退款记录不存在 | 200 |
| 71001 | 当前状态不允许退款 | 200 |
| 71002 | 退款金额不合法 | 200 |
| 71003 | 退款申请失败 | 200 |
| 71004 | iOS 订单请通过 App Store 申请退款 | 200 |
8.8 额度 / VIP(9)
| code | 含义 | HTTP |
|---|---|---|
| 80000 | 额度不足 | 200 |
| 80001 | 额度记录不存在 | 200 |
| 80002 | 广告激励已领取 | 200 |
| 80003 | 无效的激励类型 | 200 |
| 80004 | 广告激励未开启 | 200 |
| 89000 | 额度更新失败 | 200 |
| 81000 | 会员记录不存在 | 200 |
| 81001 | 会员已过期 | 200 |
| 81002 | 会员开通失败 | 200 |
8.9 通知 / 分享 / 社区 / 星座 / AI(10)
| code | 含义 | HTTP |
|---|---|---|
| 90000 | 通知不存在 | 200 |
| 90100 | 分享不存在 | 200 |
| 90101 | 分享已过期 | 200 |
| 90102 | 分享已取消 | 200 |
| 90103 | 穿搭分享不存在 | 200 |
| 90104 | 穿搭分享已取消 | 200 |
| 90200 | 目标内容不存在 | 200 |
| 90201 | 点赞操作失败 | 200 |
| 90202 | 收藏操作失败 | 200 |
| 90300 | 星座运势不存在(生成中,稍后重试) | 200 |
| 90400 | AI 推荐失败 | 200 |
| 90401 | AI 推荐次数已达上限(AI 额度不足) | 200 |
| 90402 | AI 推荐任务不存在或已过期(轮询用) | 200 |
9. 前端联调 FAQ 与检查清单
9.1 高频问题
Q:接口返回 HTTP 200,但业务是失败的?
A:正常。除 401/403/429/500 外,所有业务错误都返回 HTTP 200。必须解析 code。
Q:21001 账号已被禁用 为什么不是 401?
A:它在鉴权中间件最后一步(查用户状态)返回,HTTP 是 200。拦截器里需单独判断 code === 21001 并跳封禁页。
Q:刷新 Token 一直报 10000 缺少旧 access token?
A:/user/refresh-token 必须同时传 refresh_token 和 access_token,只传前者会失败。
Q:上传成功但衣物没有属性?
A:上传是异步的。用返回的 clothes_uuid 轮询 GET /clothes/:uuid/status,直到 4(成功)再拉详情。
Q:更新衣物颜色没生效?
A:主颜色的请求字段名是 color(响应才是 color_main)。写成 color_main 会被忽略(因为只更新非空字段,且不报错)。
Q:AI 推荐报 10003 请求过于频繁?
A:同一用户 180 秒内只能有一个生成任务(Redis 锁)。前端应禁用按钮并提示"生成中",生成完成后再放开。
Q:AI 推荐扣了额度但用户没保存,能退吗? A:不能。额度在生成时已消耗,只有生成失败才回退。前端应在生成前明确提示消耗。
Q:支付组件回调 success,但订单还是待支付?
A:以服务端为准。微信回调是异步的,轮询 GET /order 直到 order_status=2。
Q:退款申请后一直没到账?
A:退款是人工审核模式,接口只登记申请(status=0)。前端提示"退款审核中",并允许轮询订单状态到 5。
Q:衣柜分享列表里显示"正常",但对方打不开?
A:过期是惰性的。前端需同时判断 status 与 expire_time。
Q:GET /outfit 里混进了 AI 推荐的穿搭?
A:列表不过滤 status=3(待用户保存)。前端用 source 区分来源,AI 未保存的单独入口展示。
Q:如何浏览"社区广场"的公开穿搭?
A:调用 GET /outfit/community 分页获取其他用户已公开(公开 + 审核通过 + 正常)的穿搭列表,含作者信息与点赞/收藏计数;点击卡片进入 GET /outfit/:uuid 详情(他人视角)互动。
9.2 联调检查清单
- 统一封装请求:自动带
Authorization: Bearer,统一解析code - 实现 401 自动刷新 Token(同时传两个 token),失败则清登录态
- 单独处理
code === 21001(账号禁用,HTTP 200) - 金额展示
÷100,时间展示×1000 - 上传后轮询状态(1~2 秒,最多 60 次),展示处理中/失败态
- 支付后轮询订单状态,不依赖支付组件回调
- 退款提示"人工审核中"
- 公开穿搭前提示将消耗 1 次搭配分享额度
- AI 推荐前提示将消耗 1 次 AI 额度,且不保存不退
- 点赞/收藏按钮做乐观更新 + 失败回滚(接口幂等,可重试)
- 通知红点通过轮询
GET /notification/unread刷新(无推送) - 不可逆操作(删除衣物/穿搭、取消订单/分享)加二次确认
- 列表分页
page_size不超过 50 - 排障时请求头自带
X-Request-ID(跨域读不到响应头)
10. 接口调用示例(curl)
示例中的 token、UUID、订单号均为示意值;
BASE=http://<host>/api/v1。
微信登录
curl -X POST $BASE/user/wxlogin \
-H "Content-Type: application/json" \
-d '{"code": "0b3abcxxxxxxxx"}'
{
"code": 0,
"msg": "成功",
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"expire_time": 1756742400,
"user_info": {
"username": "微信用户",
"avatar": "",
"sex": 0,
"birth_day": 0,
"phone": "",
"is_new_user": true,
"status": 1,
"ban_reason": "",
"ban_start": 0,
"ban_expire": 0,
"vip_type": 4,
"vip_expire": 1744761600
}
}
}
刷新 Token
curl -X POST $BASE/user/refresh-token \
-H "Content-Type: application/json" \
-d '{"refresh_token":"<refresh_token>","access_token":"<旧 access_token>"}'
上传衣物(multipart)
curl -X POST $BASE/clothes/upload \
-H "Authorization: Bearer <access_token>" \
-F "file=@/path/to/t-shirt.png"
{
"code": 0,
"msg": "成功",
"data": {
"clothes_uuid": "c8f2e1a0-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"image_process_status": 3
}
}
轮询处理状态
curl "$BASE/clothes/c8f2e1a0-xxxx-xxxx-xxxx-xxxxxxxxxxxx/status" \
-H "Authorization: Bearer <access_token>"
{ "code": 0, "msg": "成功", "data": { "image_process_status": 4, "fail_reason": "" } }
修正衣物属性(注意 color)
curl -X PUT $BASE/clothes/c8f2e1a0-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"color":"白色","category_main":"上装","name":"我的白T"}'
额度概览
curl $BASE/quota/info -H "Authorization: Bearer <access_token>"
{
"code": 0,
"msg": "成功",
"data": {
"quotas": [
{ "quota_type": 1, "total": 20, "used": 1, "remain": 19 },
{ "quota_type": 2, "total": 3, "used": 0, "remain": 3 },
{ "quota_type": 3, "total": 10, "used": 0, "remain": 10 },
{ "quota_type": 4, "total": 5, "used": 0, "remain": 5 }
]
}
}
AI 穿搭推荐与保存
curl -X POST $BASE/ai-recommend \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"scene_type": 2, "scene": "参加朋友的婚礼"}'
{
"code": 0,
"msg": "成功",
"data": {
"outfit_uuid": "of-xxxxxxxxxxxx",
"outfit_name": "婚礼宾客穿搭",
"outfit_desc": "简约大方,正式又不失亲和力",
"scene": "参加朋友的婚礼",
"source": 2,
"status": 3,
"outfit_item": [{ "clothes_uuid": "c8f2e1a0-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }]
}
}
curl -X POST $BASE/ai-recommend/save \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"outfit_uuid":"of-xxxxxxxxxxxx"}'
创建订单(微信支付)
curl -X POST $BASE/order \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"goods_code": "AI_3"}'
{
"code": 0,
"msg": "成功",
"data": {
"order_no": "OD20260907000123",
"pay_amount": 990,
"expire_time": 1756743300,
"pay_params": {
"timeStamp": "1756742400",
"nonceStr": "1a2b3c4d5e6f",
"package": "prepay_id=wx09153000123456",
"signType": "RSA",
"paySign": "xxxxxxxxxxxxxxxx"
}
}
}
小程序侧:
wx.requestPayment({
...pay_params, // timeStamp / nonceStr / package / signType / paySign 原样透传
success: () => { /* 仅表示拉起成功,仍需轮询 GET /order 确认 order_status=2 */ },
fail: () => { /* 用户取消或失败 */ }
})
点赞 / 取消点赞
curl -X POST $BASE/like \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"target_uuid":"of-xxxxxxxxxxxx","action":1}'
curl "$BASE/like/of-xxxxxxxxxxxx" -H "Authorization: Bearer <access_token>"
# {"code":0,"msg":"成功","data":{"is_liked":true,"like_count":12}}
创建衣柜分享
curl -X POST $BASE/closet-share \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"share_type":1,"clothes_uuids":["c8f2e1a0-xxxx"],"expire_time":1756828800}'
错误响应示例(额度不足)
{ "code": 80000, "msg": "额度不足" }
错误响应示例(参数校验)
{
"code": 10000,
"msg": "请求参数错误",
"details": ["Key: 'OutfitCreateRequest.OutfitName' Error:Field validation for 'OutfitName' failed on the 'required' tag"]
}
文档维护说明:本文档由后端源码(
internal/routers/router.go、internal/dto/、internal/service/、pkg/errcode/)逐字段核对生成。接口变更时请同步更新本文件;工程视角的部署/配置说明见README.md。