zt-closet-backend
智能衣橱微信小程序后端服务。基于 Go + Gin 构建,提供衣物管理、AI 衣物识别(分类/抠图)、穿搭与 AI 推荐、每日星座运势、社区互动(分享/收藏/点赞/通知)、额度体系、商品与微信支付订单等完整能力,并内置 OpenTelemetry 全链路可观测性与结构化日志。
核心功能
- 用户体系:微信小程序登录(code2session,首次自动注册并初始化免费额度)、JWT 双 token(access/refresh)、个人资料维护。
- 衣物管理:图片上传(扩展名 + magic bytes 双重校验)→ 异步 AI 流水线(抠图 → 15 维分类)→ 衣物 CRUD 与处理状态查询。
- 穿搭与 AI 推荐:自建穿搭(含社区公开与审核)、基于星座运势 + 衣橱衣物的 AI 穿搭推荐(异步提交 + 轮询结果,前端断开不中断生成;落库为"待用户保存",超期未保存自动清理)。
- 运势:每日 0 点定时生成十二星座运势,Redis 分布式锁保证多实例下仅执行一次,查询时支持按需兜底生成。
- 社区互动:衣柜分享(部分衣物 / 全量快照)、穿搭点赞与收藏(Redis 集合 + MySQL 双写)、站内通知(未读计数)。
- 额度体系:四类额度(上传 / AI 推荐 / 穿搭分享 / 衣柜分享)的扣减、回退、发放、回收;上传为永久额度,AI 推荐 / 穿搭分享 / 衣柜分享为每日额度(每天 0 点重置);购买赠送额度以凭证(
quota_grants)发放——会员包随 VIP 有效期、加量包独立有效期(goods.valid_days)、广告激励当天有效;Cos 消费先扣即将过期凭证再扣免费额度,全部走事务 + 幂等流水。 - 商品与订单:商品(会员包 / 上传包 / AI 推荐包 / 分享包)、微信支付 V3 下单、支付与退款回调(验签 + 解密)、订单 15 分钟超时自动过期、权益发放失败自动补偿。
- 可观测性:OpenTelemetry Trace / Metric / Log 三信号 OTLP gRPC 上报;结构化 JSON 日志注入
trace_id/span_id。
技术栈
| 分类 | 技术 |
|---|---|
| 语言 | Go 1.27 |
| Web 框架 | Gin v1.12 |
| ORM / 数据库 | GORM + MySQL(含 soft_delete 软删除插件) |
| 缓存 / 锁 | go-redis v9(会话存储、缓存、分布式锁、计数) |
| 配置 | spf13/viper(仅读 YAML 文件) |
| 鉴权 | golang-jwt/v5(HS256 双 token) |
| 对象存储 | 腾讯云 COS SDK v5(含 CDN 签名 URL) |
| 微信生态 | 小程序 code2session 登录、微信支付 APIv3(wechatpay-go 官方 SDK)、小程序虚拟支付(XPay) |
| 可观测性 | OpenTelemetry v1.46(otelgin、otelslog、OTLP gRPC 导出) |
| 日志 | log/slog + lumberjack(JSON、按大小轮转压缩) |
| AI 推理服务 | Python FastAPI:Fashion-CLIP 衣物分类、BiRefNet 抠图 |
目录结构
zt-closet-backend/
├── main.go # 入口:初始化配置/日志/DB/Redis/COS/支付,启动后台任务与 HTTP 服务
├── cmd/
│ └── migrate/ # 独立数据库迁移命令入口(up/down/steps/version/force)
├── configs/
│ ├── config.example.yaml # 配置模板(含注释说明)
│ └── config.yaml # 实际配置(已被 .gitignore 忽略,需自行复制创建)
├── global/ # 全局单例:配置指针、DB/Redis/COS 引擎、OTel Tracer/Meter
├── internal/
│ ├── dto/ # 请求/响应 DTO 与分页结构
│ ├── middleware/ # RequestID / OTel / 请求日志(脱敏) / CORS / JWT 鉴权
│ ├── mysqldao/ # MySQL 数据访问层(tracing span 命名 mysqldao.Xxx)
│ ├── mysqlmodel/ # GORM 数据表模型(18 张表)
│ ├── redisdao/ # Redis 数据访问层(tracing span 命名 redisdao.Xxx)
│ ├── redismodel/ # Redis 引擎初始化与 key 常量
│ ├── routers/ # 路由注册(router.go)与各 handler(api/v1)
│ └── service/ # 业务逻辑层(16 个 service)
├── pkg/
│ ├── app/ # HTTP 统一响应封装 {code, msg, data}
│ ├── classification/ # 衣物分类服务客户端
│ ├── cos/ # 腾讯云 COS 客户端
│ ├── errcode/ # 统一业务错误码(按业务域分段)
│ ├── lzcwyjwt/ # JWT 生成/解析
│ ├── matting/ # 抠图服务客户端
│ ├── migration/ # 数据库迁移:embed 迁移脚本 + Up/Down/Steps/Version/Force
│ │ # (目录下只存放单一 000001_init,up 建全量表/down 删全量表)
│ ├── setting/ # viper 配置读取与配置结构体
│ ├── tokenhub/ # 腾讯 Tokenhub 大模型网关客户端(运势/AI 推荐生成 + 调度)
│ ├── tracing/ # OpenTelemetry 初始化与日志桥接
│ ├── upload/ # 文件上传校验与存储
│ └── wechat/ # 微信登录与微信支付
├── thirdparty/ # Python AI 推理服务
│ ├── classification_server/ # Fashion-CLIP 衣物分类(端口 8000)
│ └── matting_server/ # BiRefNet 抠图(端口 8001)
├── design/ # 数据库 ER 图(drawio)
├── sql/ # 占位目录(建表 SQL 已统一收归 pkg/migration)
├── scripts/ # 占位目录(暂无部署脚本)
└── storage/ # 本地存储(日志、上传临时文件,已被 gitignore)
说明:
sql/、scripts/、docs/目前为空占位目录;建表与索引由pkg/migration中的版本化迁移脚本维护,表模型见internal/mysqlmodel/,ER 图见design/zt-closet-er.drawio。
快速开始
1. 环境依赖
- Go 1.27+
- MySQL 5.7+ / 8.0
- Redis 5+
- Python 3.9+(运行 AI 推理服务时需要;若仅做基础调试可先跳过)
2. 配置准备
cp configs/config.example.yaml configs/config.yaml
编辑 configs/config.yaml,至少修改以下内容:
Mysql:数据库连接信息Redis:Redis 连接信息JWT.SecretKey:必填,须为强随机字符串,留空会导致启动失败Server.HttpPort:服务监听端口(模板默认为 80)Cos/WechatMini/WechatPay/WechatVirtualPay:按需填写;未配置的第三方能力(COS、微信支付、虚拟支付)启动时仅告警、不阻断服务
重要:
config.example.yaml中不包含Tokenhub段,但服务启动时会强制读取该段(缺失将导致启动 panic)。请手动在config.yaml中补充:
Tokenhub:
APIKey: your_tokenhub_api_key # 腾讯 Tokenhub 大模型网关 APIKey,用于生成运势与 AI 穿搭推荐
Model: hy4 # 模型代号,留空时默认 hy4
APIKey 留空不会阻断启动,但运势与 AI 推荐功能将不可用(后台任务仅记录错误日志)。
3. 准备数据库
先创建 config.yaml 中 Mysql.DBName 对应的空数据库(迁移脚本只建表,不建库):
CREATE DATABASE IF NOT EXISTS `zt-closet` DEFAULT CHARSET utf8mb4;
再用独立命令执行迁移(服务启动时不会自动迁移):
go run ./cmd/migrate up # 建表到最新版本(幂等,可重复执行)
go run ./cmd/migrate version # 查看当前版本与是否为脏库
其他命令:
go run ./cmd/migrate steps 1 # 向上 1 步(负数向下)
go run ./cmd/migrate down # 全量回滚(按依赖倒序删表,慎用)
go run ./cmd/migrate force 3 # 迁移中断导致 dirty 时,强制修正版本号
迁移脚本位于 pkg/migration,单一版本(000001_init)包含全部 27 张业务表,以 go:embed 打包进二进制;迁移记录保存在 schema_migrations 表。
维护约定:项目当前无线上数据,因此不使用增量迁移(ALTER/UPDATE)。结构变更直接修改
000001_init.up.sql里对应表的CREATE TABLE语句,并同步000001_init.down.sql。
字段与索引以 internal/mysqlmodel/ 的 GORM 模型为准,ER 图见 design/zt-closet-er.drawio。
4. 启动 AI 推理服务(可选,推荐)
衣物上传后的抠图与分类依赖两个 Python 服务,地址与端口在配置的 MattingModel / ClassificationModel 段中约定(默认分别为 8001 / 8000)。
抠图服务(BiRefNet):
cd thirdparty/matting_server
pip install -r requirements.txt
python main.py # 监听 0.0.0.0:8001
- 模型使用
ZhengPeng7/BiRefNet_lite(small 变体),本地缓存优先、缺失时自动下载;可用环境变量BIREFNET_REPO_ID覆盖模型来源。 - 默认从
https://hf-mirror.com(国内镜像)下载权重。
分类服务(Fashion-CLIP):
cd thirdparty/classification_server
pip install -r requirements.txt
python main.py # 监听 0.0.0.0:8000
- 模型使用
patrickjohncyh/fashion-clip,一次前向识别衣物 15 个维度(大类/子类别/风格/主副颜色/厚度/版型/材质/花纹/袖型/衣长/领型/正式程度/场合/视觉冲击力)。 - 首次运行需下载模型权重,同样走 hf-mirror 镜像。
建议在带 GPU 的环境中部署两个推理服务;CPU 环境亦可运行(抠图服务自动退回 fp32)。
5. 编译运行
# 必须在项目根目录执行(配置路径固定为相对路径 configs/config.yaml)
go run .
服务启动后验证健康检查:
curl http://localhost:<HttpPort>/ping
# {"message":"pong"}
配置说明
配置文件为 configs/config.yaml(YAML),仅从文件读取,不支持环境变量覆盖。各配置段说明如下:
| 配置段 | 说明 | 关键字段 |
|---|---|---|
Server |
HTTP 服务 | Domain(CORS 允许域名,* 或不填允许所有来源)、RunMode(debug/release)、HttpPort、ReadTimeout/WriteTimeout(秒) |
Log |
日志 | LogSavePath、LogFileName、MaxSize/MaxBackups/MaxAge(MB/份数/天数)、LogLevel(Debug/Info/Warn/Error) |
Mysql |
MySQL 连接池 | 用户名/密码/Host/Port/DBName 及连接池参数 |
Redis |
Redis 连接 | Host/Port/Password/DB 及连接池、重试、超时参数 |
Otel |
OpenTelemetry | Enabled、ServiceName、ExporterEndpoint(OTLP gRPC 端点)、Insecure、SamplerRatio(0~1) |
JWT |
鉴权 | SecretKey(必填,空则启动失败)、Issuer、AccessTokenExpire(默认 7200s)、RefreshTokenExpire(默认 86400s) |
Cos |
腾讯云 COS | SecretID/SecretKey、上传/下载域名、CDN 下载鉴权密钥 |
Upload |
上传限制 | TmpSavePath、MaxSizeMB(默认 20)、AllowedExtensions 扩展名白名单 |
MattingModel |
抠图服务地址 | Host/Port(默认 8001)、MaxRetry(默认 3) |
ClassificationModel |
分类服务地址 | Host/Port(默认 8000)、MaxRetry(默认 3) |
WechatMini |
微信小程序 | AppID/AppSecret(用于 code2session 登录) |
WechatPay |
微信支付 V3 | Enable(是否启用,未开通置 false 不加载)/AppID/MchID/APIv3Key(必须 32 字节)/SerialNo/PrivateKeyPath/NotifyURL/RefundNotifyURL |
WechatVirtualPay |
小程序虚拟支付(XPay) | Enable(是否启用)/OfferID/EnvAppKey(现网 AppKey)/SandboxAppKey(沙箱 AppKey)/Token(消息推送验签);与普通微信支付独立商户号、独立结算,虚拟商品(会员/额度包)须走此通道。环境自动选择:只填 SandboxAppKey 走沙箱测试(不真实扣款),只填 EnvAppKey 或两把都填走现网(现网优先) |
虚拟支付道具同步由管理后台负责,服务端不会自动同步:商品发布为虚拟支付道具(上传/发布、改价)由管理后台完成,并将微信侧返回的
product_id与product_status回填到goods表后,虚拟商品才可下单。运营改价后必须通过管理后台同步道具价格,否则微信侧道具价格与本地不一致:微信扣款多于订单金额时发货金额校验会拒绝(用户付款拿不到权益),少于订单金额时产生差价资损。 |UserDefaultQuota| 新用户免费额度 |Cos(永久额度,未配置回落 20)、AI/OutfitShare/ClosetShare(每日额度,未配置回落 3/10/5,每天 0 点重置;可显式配 0 表示不赠送)。与会员身份无关,所有用户长期享有 | |NewUserGift| 新用户注册赠送的体验会员及其额度 |GiftVIPDay(体验会员天数,未配置回落 3 天,可配 0 表示不赠送会员)、Cos/AI/OutfitShare/ClosetShare(体验会员额度,未配置回落 0/3/3/3)。额度以体验会员凭证(source=4)发放,有效期为固定值 = 注册时刻 + 体验天数(⚠️ 不跟随 VIP 延长,否则用户后续充值会把体验额度一直「继承」下去);GiftVIPDay配 0 时额度一律不发放。与UserDefaultQuota的免费额度叠加生效 | |AdReward| 广告激励额度 |Enable(是否开启,关闭时接口返回「广告激励未开启」)、Cos/AI/OutfitShare/ClosetShare(各类型固定金额,服务端定价,前端不传金额;可删或置 0 表示不发放该类型) | |Tokenhub| 大模型网关 |APIKey、Model(默认 hy4);模板中缺失,需手动补充(见快速开始) |
未配置(整段缺失或留空)的第三方能力:COS、微信支付与虚拟支付在启动时降级为告警并禁用对应功能;微信登录(
WechatMini)未配置时服务可正常启动,但登录接口会返回"未配置"错误。Tokenhub、MattingModel、ClassificationModel三段为启动强依赖(缺失会导致 panic),请务必配置。
业务流程
本章从业务视角介绍各业务域的闭环流转、状态机与关键业务规则(额度扣减/回退、审核、幂等、补偿等);接口层面的调用顺序与时序图见下文 核心业务调用流程,接口入参/出参细节见 接口调用详解。
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
全系统共 7 条业务主线:
- 注册与额度:微信登录首次自动注册,先初始化免费额度(
UserDefaultQuota:Cos 永久默认 20,AI 推荐 / 搭配分享 / 衣柜分享为每日默认 3/10/5,允许显式配 0;未配置或负数回落默认,每日额度不落库、按当天计数每天 0 点重置),再赠送体验会员及其额度(NewUserGift:开通vip_type=4默认 3 天、允许配 0 关闭,并发放source=4体验会员额度凭证,固定有效期为体验期结束、续费不会延长,与免费额度叠加),同时下发一条系统通知;会员已过期时接口返回的vip_type归一为 1。 - 衣物生命周期:上传(校验 + 扣额)→ 异步 AI 处理(抠图 → 分类)→ 成功入库可被选用;失败自动回退额度;删除软删并回退额度。
- 穿搭与社区:创建穿搭 → 公开(审核自动通过 + 扣分享额度)→ 其他用户点赞/收藏 → 触发通知。
- 衣柜分享:部分衣物或全量快照分享 → 扣额度 → 属主/访客查看(访客访问即记录接收关系)→ 取消或到期懒过期;"分享给我的"列表联表查询接收关系。
- 交易与权益:选购商品 → 下单(15 分钟有效)→ 微信支付回调发放权益 → 超时过期 / 退款回收,发放失败自动补偿。
- AI 推荐:异步提交(抢占活跃位 + 扣 AI 额度 + 秒回
task_id)→ 后台生成 → 前端轮询结果 → 落库"待用户保存" → 保存转正式穿搭(公开仍需扣分享额度);超期未保存的由定时任务自动清理。 - 通知:点赞、支付成功、退款成功等业务动作触发站内通知,未读计数实时维护。
2. 衣物生命周期
衣物图片处理状态机(image_process_status):
stateDiagram-v2
[*] --> queued : 上传成功(同步扣上传额度 1 点)
queued --> matting : 异步任务开始
matting --> classifying : 抠图完成
classifying --> success : 分类完成
classifying --> failed : 分类失败
matting --> failed : 抠图失败
queued --> failed : 保存/上传异常
success --> deleted : 用户删除(回退额度)
failed --> deleted : 用户删除(不回退,失败时已回退)
state "3 排队中" as queued
state "1 抠图中" as matting
state "2 分类中" as classifying
state "4 成功" as success
state "5 失败" as failed
处理流程:
- 上传校验:扩展名白名单
jpg/jpeg/png+ magic bytes(文件头)双重校验 + 大小 ≤ 20MB,任一项不通过即拒绝上传。 - 同步扣额:先扣减上传额度 1 点,扣减成功后才创建衣物记录(初始状态
3 排队中)并立即返回clothes_uuid;建记录失败自动回退额度。 - 异步 AI 流水线:抠图 → 15 维分类在后台 goroutine 中执行;并发信号量上限 10、单任务超时 3 分钟;抠图/分类调用各带重试(默认 3 次、间隔 1s)。
- 失败处理:任一环节异常即置
5 失败并写入fail_reason(如"图片抠图失败/图片分类失败");失败自动回退上传额度(仅当衣物仍未被删除,防止与删除回退双重回退);分类失败但抠图成功时保留抠好的图,供用户手动补全属性。 - 修正与删除:
update支持 18 个属性字段(分类/风格/颜色/材质/袖型等,无枚举限制,支持自定义分类);删除为软删除,处理状态非失败(失败态已在失败路径回退过)且已扣额度时,在事务内(行锁 → 软删 → 条件扣减 → 写回退流水)回退额度。
3. 穿搭与社区
穿搭公开与审核状态机:
stateDiagram-v2
[*] --> created : 创建穿搭(1~10 件本人衣物)
created --> private : 默认私有 is_public=1
private --> public : 公开 is_public=2(审核自动通过 + 扣搭配分享额度 1 点)
public --> private : 取消公开(不返还已扣额度)
public --> interacted : 其他用户点赞 / 收藏(触发点赞通知)
private --> [*] : 修改(明细先删后建)/ 删除(软删 + 明细物理删)
public --> [*] : 修改(明细先删后建)/ 删除(软删 + 明细物理删)
state "创建(私有/待审核)" as created
state "1 私有" as private
state "2 公开(审核通过)" as public
state "社区互动" as interacted
处理流程:
- 创建:1~10 件衣物(必须属于当前用户)、名称必填;默认
is_public=1私有、audit_status=1待审核、status=1正常。 - 公开:置
is_public=2时审核自动通过(audit_status 1→2)并扣搭配分享额度 1 点;扣额失败则回退为私有。当前无人工驳回流程(audit_status=3不通过为预留值)。 - 可见性:
is_public=2且audit_status=2且status=1三者同时满足才对他人可见;他人仅可点赞/收藏公开可见的穿搭。 - 点赞 / 收藏:先写 MySQL(联合唯一索引 upsert 幂等)再写 Redis 集合;取消时 MySQL 记录状态置 0(不删记录),可再次点赞/收藏;仅"从取消/无记录变为点赞"且非本人时才触发通知。
- 实现备注:当前无独立"社区广场"浏览列表接口(仅"我的穿搭"列表 + 按 UUID 查看公开穿搭)。
4. 衣柜分享
分享状态机(status):
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
处理流程:
- 创建:
share_type=1部分衣物(指定衣物 UUID 列表,逐件校验归属 + 去重)或share_type=2全量快照(当前全部衣物 ID 的引用列表,非属性数据副本)。 - 扣额:扣衣柜分享额度 1 点,明细落库失败自动回退(避免产生空分享);创建即自动审核通过。
- 过期机制:
ExpireTime由创建者指定(0 表示永不);采用懒过期——访问详情时判定过期并更新状态,无定时扫描任务。 - 查看:详情支持属主与访客视角(
is_owner),访客计入访问统计;封禁状态对访问者隐藏(返回"不存在");已取消/已过期返回对应错误。 - 删除衣物影响:不联动清理分享明细,被软删除的衣物在分享详情中自动过滤。
- 接收关系:采用"访问即视为收到"——登录用户(非属主)成功访问他人分享详情时,幂等写入
closet_share_receivers(share_uuid + receiver_user_id联合唯一);一个分享可被多个用户接收,重复访问不重复记录。 - 分享给我的列表:
GET /closet-share/received联表分页返回当前用户收到的分享,额外返回分享者昵称/头像(owner_name/owner_avatar)。 - 实现备注:service 层已支持访客(
userID=0)视角,但当前路由注册在鉴权组内,未登录访客的免登录访问需结合 HTTP 层路由确认。
5. 交易与权益
订单状态机(order_status):
stateDiagram-v2
[*] --> pending : 创建订单(快照权益 + 微信 JSAPI 下单)
pending --> paid : 支付回调 SUCCESS(验签 + 金额校验 → 发放权益 → 通知)
pending --> expired : 超时 15 分钟(后台每分钟扫描)
pending --> cancelled : 用户主动取消(同步关闭微信订单)
expired --> paid : 过期后付款到账(补发货)
cancelled --> refunded : 已付款的取消订单生成待审核退款单(管理员手动退款)
paid --> refunding : 用户申请退款(同事务写待审核退款单)
refunding --> refunded : 退款成功回调(回收权益 → 通知)
refunding --> paid : 退款失败/被驳回(流水置失败,可重新申请)
paid --> refunded : 退款成功回调(回收权益 → 通知)
state "1 待支付" as pending
state "2 已支付" as paid
state "3 已取消" as cancelled
state "4 已过期" as expired
state "5 已退款" as refunded
state "6 申请退款中" as refunding
处理流程:
- 商品:5 类商品(
1会员包 /2上传包 /3AI 推荐包 /4衣柜分享包 /5搭配分享包),价格单位为分;仅1 上架状态可购买。 - 下单:实际支付金额取
discount_price;下单时把商品权益快照(会员天数/各类额度)冗余进订单,防止商品后续变更导致错发;先落本地待支付订单再调微信 JSAPI,微信下单失败时订单保留并靠超时自动过期。同一用户可并存多个待支付订单(无防重限制)。 - 支付回调:验签 + 解密 → 仅处理
SUCCESS→ 幂等锁 30s → 金额校验(回调金额与订单不一致则拒绝流转、人工介入)→ 待支付 → 已支付 → 发放权益 + 写支付信息 + 发订单通知。竞态兜底:已过期但已付款的订单补发货;已取消但已付款的订单生成待审核退款单,由管理员手动退款。 - 权益发放:会员天数实际发放(叠加规则
vip_expire = max(vip_expire, now) + 天数×86400,续费顺延,会员类型按续费后最终到期时长推导——剩余 ≥365 天为年度会员、否则月度会员,避免年度会员续月包被降级);Cos / AI / 分享额度改为写入额度凭证quota_grants——会员包expire_time=0(跟随 VIP)、加量包按goods.valid_days独立到期,幂等由(ref_id, quota_type)唯一索引保证;补偿任务order_no + benefit_type唯一保证不重发。会员包来源(source=1)额度不叠加:月度包/年度包同时持有时同类额度只取 amount 最大的一份(用尽后自动顺延下一份),避免额度翻倍。 - 超时过期:后台任务每分钟扫描
待支付且 expire_time < now的订单批量置为已过期(条件更新天然幂等);过期后无权益可回收。 - 退款:所有退款均需管理员操作——用户申请退款受理成功后将订单置为
6 申请退款中并登记待审核退款单(流水0 待处理,同一事务 + 订单行锁,重复/并发申请仅受理一次),运营审核后在微信后台手动退款,退款回调更新流水状态(0 待处理 → 2 成功 / 3 失败);退款成功 → 订单6 → 5,退款失败 → 订单回退2 已支付(可重新申请)。虚拟支付退款推送(xpay_refund_notify)按官方字段解析:订单号取MchOrderId、结果取RetCode(0=成功,失败原因见RetMsg),关键字段缺失或结果无法判定时应答失败触发微信重试(最多 15 次)而非静默成功;成功后仍反查query_order确认真实退款与金额一致。查单兜底任务每 60 秒反查停留超 10 分钟的「申请退款中」订单:微信侧已退款自动置5并回收权益,仍未退款则保持状态、超 24 小时告警人工。「已取消但已付款」的订单同样生成待审核退款单而非自动退款,订单保持3 已取消。退款成功回调将订单置已退款、回收权益(会员天数不回退到过去;赠送额度通过失效凭证回收未用部分,已用不追回;已取消订单权益从未发放,跳过回收)并发通知。 - 发放失败补偿:权益发放失败自动落 pending 任务,后台任务每分钟扫描重试(Redis 锁保证单实例),每轮最多 100 个,重试 3 次仍失败则标记失败 + 告警人工介入;重试前先查发放流水幂等。
- 额度变更统一收口:Cos 永久额度扣减/回退封装在事务内「凭证扣减(非会员包先到期先用 → 会员包生效份)+ 免费额度扣减 + 写流水」强一致(会员包来源不叠加,未生效份不参与扣减/回退);每日额度(AI/分享/衣柜分享)由 Redis 原子限流计数(当天 0 点自然过期),MySQL
user_daily_usages兜底统计。
6. AI 推荐
flowchart TD
A["POST /ai-recommend<br/>(异步提交)"] --> B{"抢占用户活跃位<br/>(已有任务则返回原 task_id)"}
B -- 已有任务 --> X["幂等返回该 task_id<br/>(不重复扣额度)"]
B -- 抢占成功 --> C["扣 AI 额度 1 点"]
C --> D["写任务快照 status=1<br/>立即返回 {task_id, status:1}"]
D --> E["后台 goroutine 生成<br/>(独立 ctx,前端断开不中断)"]
E --> F["组装输入:处理成功的全部衣物 + 星座 + 今日运势"]
F --> G["调用大模型生成<br/>(tokenhub)"]
G --> H{"校验结果<br/>(过滤模型幻觉衣物 ID)"}
H -- 有效 --> I["落库:source=2 AI、status=3 待用户保存<br/>任务快照置 status=2 + result"]
H -- 无效 / 任一步失败 --> Y["回退 AI 额度 + 写失败流水<br/>任务快照置 status=3 + error"]
I --> J["前端轮询 GET /ai-recommend/status<br/>拿到 result"]
J --> K["用户确认 Save:<br/>status 3→1 转正式穿搭(不扣额)"]
K --> L["公开到社区仍需 Public<br/>(扣搭配分享额度)"]
I -. "超过 7 天未保存" .-> M["定时任务自动清理<br/>(软删除穿搭 + 明细)"]
处理流程:
- 异步提交:
POST /ai-recommend只做「抢占活跃位 → 扣额度 → 写任务快照」三步,秒级返回task_id;生成在后台 goroutine 完成,使用独立 context,前端退出、切页面或断网都不会中断。 - 提交幂等:同一用户已有任务在执行时直接返回该任务的
task_id,不会重复扣额度;活跃位 TTL 4 分钟,略长于任务超时 3 分钟,避免任务未收尾就被放行重复提交。 - 输入:当前用户处理成功(4) 的全部衣物(分页遍历)+ 由生日推算的星座 + 今日运势(无生日/运势时降级为仅星座或空)。
- 扣额与回退:提交时扣减 AI 额度 1 点;衣橱为空、模型调用失败、无有效单品、落库失败、任务超时等任一情况均统一回退额度并写失败流水(回退与写流水使用不受任务取消影响的 context,避免断连时收尾失败)。
- 结果校验:校正模型返回的衣物 ID(过滤幻觉)、纠正品类、同品类去重(每类最多 1 件)。
- 搭配完整性保障:按衣橱实际存在的品类校验——必须含「上装」或「连衣裙」(外套不可孤立)、未选连衣裙时必须有下装、衣橱有鞋必须有鞋、配饰可选、连衣裙与下装互斥;不满足时带纠偏提示重试一次模型(重试前检查剩余时间预算,不足则跳过),仍不满足则由服务端从衣橱按「同风格 → 同主色 → 视觉冲击力接近 → 最近上传」的确定性规则自动补齐(
reason标注"系统按搭配完整性规则补齐"),避免出现"只有外套+下装"这类不完整搭配。 - 落库:生成穿搭以
source=2 AI、status=3 待用户保存入库(私有、待审核);明细落库失败则删除穿搭 + 回退额度。 - 结果获取:前端轮询
GET /ai-recommend/status?task_id=...(建议 2 秒一次、上限 90 次);status=2取result渲染,status=3读error_msg提示用户。任务快照在 Redis 保留 15 分钟,过期返回90402。 - 保存转正:仅
status=3可保存(重复保存幂等),保存仅将状态 3→1,不扣分享额度;后续公开到社区仍走 Public 流程扣搭配分享额度。 - 自动清理:未被保存的
status=3穿搭超过 7 天,由后台定时任务(启动即执行一次,之后每小时一次,单批 500 条)连同明细一起软删除,避免长期堆积;清理后列表与详情立即不可见。
7. 通知触发规则
| 触发动作 | 通知类型 | 发送者 | 接收者 | 触发条件与防重 |
|---|---|---|---|---|
| 点赞 | 1 点赞 |
点赞者 | 穿搭作者 | 仅当"从取消/无记录变为点赞"且非本人点赞时触发 |
| 支付成功 | 2 订单 |
系统(sender=0) | 买家 | 支付回调发放权益成功后触发;取消/过期不发 |
| 退款成功 | 2 订单 |
系统(sender=0) | 买家 | 退款回调回收权益后触发 |
| 系统通知 | 3 系统 |
预留 | — | 常量已定义,当前代码无发送点(需运营/后台触发) |
- 发送实现:写入 MySQL 通知表(
is_read=1未读)+ Redis 未读计数自增,best-effort(失败仅告警,不影响主业务)。 - 未读/已读:
is_read取值1 未读 / 2 已读;标记已读为单条接口(条件更新"仅未读→已读"防并发重复扣减,随后 Redis 计数自减),无"全部已读"接口。 - 未读计数:Redis 优先,Redis 为 0/异常时回落 MySQL 统计并回填缓存。
以上 7 条业务主线的接口调用顺序(时序图)见下文 核心业务调用流程。
API 概览
- 统一前缀:
/api/v1 - 统一响应结构:成功
{"code":0,"msg":"成功","data":{...}};失败{"code":<业务错误码>,"msg":<文案>,"details":[...]}。HTTP 状态码大多为 200(按业务 code 处理),鉴权类错误返回 401/403,限流 429,系统错误 500。 - 鉴权方式:
Authorization: Bearer <access_token>(登录/刷新接口获取)。
通用调用约定
鉴权
- 需登录接口统一携带请求头
Authorization: Bearer <access_token>;access_token 由登录接口返回,默认有效期 7200 秒(JWT.AccessTokenExpire)。 - access_token 过期(业务码
11002,HTTP 401)时,前端调用POST /user/refresh-token换新,成功后携带新 access_token 重试原请求。 - 鉴权中间件除校验 JWT 外,还会校验 Redis 会话状态与用户状态(必须为
status=1正常,否则拒绝);Redis 故障时降级为仅校验 JWT。
统一响应结构
成功(HTTP 200):
{
"code": 0,
"msg": "成功",
"data": {}
}
- 无业务数据的接口(删除、取消、标记已读等)
data为空对象{}。
失败:
{
"code": 10000,
"msg": "请求参数错误",
"details": ["field: 错误说明"]
}
details仅在参数校验类错误时返回错误明细数组,非校验类错误无此字段。- 判断业务结果一律以
code为准,不能仅凭 HTTP 状态码。
HTTP 状态码与业务码映射
| HTTP 状态码 | 业务 code | 场景 |
|---|---|---|
| 200 | 0 及其余全部业务码 | 成功与绝大多数业务错误(按 code 区分) |
| 401 | 11000 / 11001 / 11002 / 11004 | 未授权、Token 无效 / 已过期 / 缺失 |
| 403 | 12000 | 越权访问他人资源 |
| 429 | 10003 | 请求过于频繁(限流) |
| 500 | 19000–19003 / 19999 / 39001 / 39002 | 系统 / 数据库 / 缓存 / 下游 AI 服务错误 |
微信支付回调专用响应
/order/pay/notify、/order/refund/notify 是微信服务端回调,不走统一响应包装,按微信 V3 规范返回:
- 受理成功:HTTP 200,
{"code":"SUCCESS","message":"成功"} - 受理失败:HTTP 500,
{"code":"FAIL","message":"<失败原因>"}
分页约定
- 所有列表接口使用 GET query 参数:
page(可选,≥1)、page_size(可选,1~50)。 - 列表响应统一包含:
total(总条数,int64)、page(当前页)、page_size(每页条数)、list(数据数组)。
金额单位
- 所有金额字段(
price、discount_price、unit_price、pay_amount、refund_amount)单位均为分(int64),前端展示需除以 100。
幂等与动作切换
- 点赞 / 收藏 / 取消分享等写操作幂等,重复提交不会产生重复数据。
- 点赞与收藏通过
action字段切换:1=执行(点赞/收藏)、2=取消。 - 删除衣物回退上传额度、权益发放等均由服务端事务 + 幂等流水保证一致,前端无需补偿处理。
健康检查
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /ping |
存活探针,返回 {"message":"pong"} |
公开路由(无需登录)
| 方法 | 路径 | 说明 |
|---|---|---|
| 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 | /user/info |
查询个人信息 |
| PUT | /user/profile |
更新个人资料 |
| GET | /quota/info |
额度概览(四类额度余量) |
| GET | /quota/flow |
额度流水 |
| GET | /horoscope/today |
今日星座运势(按生日推算星座) |
衣物 / 穿搭
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /clothes/upload |
上传衣物图片(触发 AI 抠图/分类流水线) |
| GET | /clothes |
衣物列表 |
| GET | /clothes/:uuid |
衣物详情 |
| GET | /clothes/:uuid/status |
衣物 AI 处理状态 |
| PUT | /clothes/:uuid |
更新衣物信息 |
| DELETE | /clothes/:uuid |
删除衣物(返还上传额度) |
| POST | /outfit |
创建穿搭 |
| GET | /outfit |
穿搭列表 |
| GET | /outfit/:uuid |
穿搭详情 |
| PUT | /outfit/:uuid |
更新穿搭 |
| DELETE | /outfit/:uuid |
删除穿搭 |
| PUT | /outfit/:uuid/public |
公开穿搭(进入社区,需审核) |
分享 / 社区 / 通知
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /closet-share |
创建衣柜分享 |
| GET | /closet-share |
我的衣柜分享列表 |
| GET | /closet-share/received |
分享给我的列表 |
| GET | /closet-share/:uuid |
分享详情 |
| POST | /closet-share/:uuid/cancel |
取消分享 |
| POST | /collect |
收藏/取消收藏(由请求 Action 决定) |
| GET | /collect |
收藏列表 |
| GET | /collect/:targetUUID |
查询目标收藏状态与数量 |
| POST | /like |
点赞/取消点赞(由请求 Action 决定) |
| GET | /like/:targetUUID |
查询目标点赞情况 |
| GET | /notification |
通知列表 |
| GET | /notification/unread |
未读通知数 |
| GET | /notification/:uuid |
通知详情 |
| POST | /notification/:uuid/read |
标记已读 |
订单 / AI 推荐
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /order |
创建订单(微信 JSAPI 支付) |
| GET | /order |
订单列表 |
| POST | /order/:orderNo/cancel |
取消订单 |
| POST | /order/refund |
申请退款 |
| POST | /ad-reward/grant |
广告激励额度发放(当天有效,幂等;由 AdReward 配置开关控制,金额服务端定价) |
| POST | /ai-recommend |
生成 AI 穿搭推荐(扣减 AI 额度) |
| POST | /ai-recommend/save |
保存 AI 推荐结果为用户穿搭 |
接口调用详解
本节为路由总表的字段级补充。请求参数位置标注:
json= JSON 请求体、query= URL 查询参数、path= 路径参数、form= multipart 文件。路径中的:xxx为占位参数名,调用时替换为真实值。字段类型按 JSON 语义标注(string / int / int64 / bool / object / array)。
公共数据结构
以下对象在多处接口中出现,统一在此定义,后文不再重复。
用户对象(user_info)
| 字段 | 类型 | 说明 |
|---|---|---|
| username | string | 昵称 |
| avatar | string | 头像地址 |
| sex | int | 1=男 2=女 |
| birth_day | int64 | 生日(秒级时间戳) |
| 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 到期时间 |
衣物对象(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 | 处理后图片地址 |
穿搭对象(OutfitItem)
| 字段 | 类型 | 说明 |
|---|---|---|
| outfit_uuid | string | 穿搭 UUID |
| outfit_name | string | 穿搭名称 |
| outfit_desc | string | 穿搭描述 |
| scene | string | 适合场景 |
| source | int | 1=用户创建 2=AI 推荐 |
| is_public | int | 1=不公开 2=公开 |
| audit_status | int | 1=待审核 2=审核通过 3=审核不通过 |
| items | array | 衣物列表,元素:{"clothes_uuid": string} |
商品对象(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 | 会员天数 |
| upload_quota | int64 | 上传额度 |
| closet_share_quota | int64 | 衣柜分享额度 |
| outfit_share_quota | int64 | 搭配分享额度 |
| description | string | 商品描述 |
订单对象(OrderItem)
| 字段 | 类型 | 说明 |
|---|---|---|
| order_no | string | 订单号 |
| goods_name | string | 商品名称 |
| unit_price | int64 | 单价(分) |
| pay_amount | int64 | 实付金额(分) |
| order_status | int | 1=待支付 2=已支付 3=已取消 4=已过期 5=已退款 6=申请退款中 |
| member_days | int64 | 会员天数 |
| expire_time | int64 | 支付截止时间戳(秒) |
| pay_time | int64 | 支付时间戳(秒) |
| refund_time | int64 | 退款时间戳(秒) |
| refund_amount | int64 | 退款金额(分) |
| created_at | int64 | 创建时间戳(秒) |
通知对象(NotificationItem)
| 字段 | 类型 | 说明 |
|---|---|---|
| notification_uuid | string | 消息 UUID |
| msg_type | int | 1=点赞通知 2=订单通知 3=系统通知 |
| title | string | 标题 |
| content | string | 内容 |
| is_read | int | 1=未读 2=已读 |
| created_at | int64 | 创建时间戳(秒) |
额度流水对象(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 | 创建时间戳(秒) |
1. 用户模块
POST /user/wxlogin —— 微信登录(公开)
请求(json):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | string | 是 | 微信登录 code(wx.login 获取),首次登录自动注册:初始化免费额度 + 赠送体验会员及额度 |
响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
| access_token | string | 访问令牌,后续请求放入 Authorization: Bearer 头 |
| refresh_token | string | 刷新令牌,用于 access 过期后续期 |
| expire_time | int64 | access_token 过期时间(秒) |
| user_info | object | 用户对象(见公共结构) |
相关错误码:21002 微信登录失败、21003 Code 无效。
POST /user/refresh-token —— 刷新令牌(公开)
请求(json):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| refresh_token | string | 是 | 有效的 refresh_token |
| access_token | string | 是 | 旧 access_token,必须与 refresh_token 一起传入;服务端校验通过后立即吊销旧 access + 旧 refresh,缺少返回 10000 |
响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
| access_token | string | 新访问令牌 |
| refresh_token | string | 新刷新令牌(旋转更新) |
| expire_time | int64 | 新 access_token 过期时间(秒) |
GET /user/info —— 查询当前用户信息(需鉴权)
无请求参数。响应 data = 用户对象(见公共结构)。
PUT /user/profile —— 更新个人资料(需鉴权)
请求(json):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| username | string | 否 | 昵称,最长 32 |
| avatar | string | 否 | 头像地址,最长 500 |
| sex | int | 否 | 0=未知 1=男 2=女 |
| birth_day | int64 | 否 | 生日(秒级时间戳) |
响应:data 为空对象 {}。
POST /user/avatar/upload —— 上传头像(需鉴权)
multipart 上传,字段名 file;仅支持图片(jpeg/png/gif/webp),复用全站图片扩展名白名单与大小限制。上传成功后写入 COS(key 规则 {用户UUID}/{用户ID}/avatar/{随机名}.{扩展名})并将头像 key 存入 users.avatar,不消耗 COS 上传额度。响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
| avatar | string | 头像可访问 URL |
2. 额度模块
GET /quota/info —— 额度概览(需鉴权)
无请求参数。响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
| quotas | array | 额度数组,元素见下表 |
数组元素:
| 字段 | 类型 | 说明 |
|---|---|---|
| quota_type | int | 1=上传 2=AI 3=社区分享 4=衣柜分享 |
| total | int64 | 总配额 |
| used | int64 | 已使用 |
| remain | int64 | 剩余配额 |
GET /quota/flow —— 额度流水(需鉴权)
请求(query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / page_size | int | 否 | 分页(见通用约定) |
| quota_type | int | 否 | 1=上传 2=AI 3=社区分享 4=衣柜分享;不传 = 全部 |
响应 data.list 元素 = 额度流水对象(见公共结构)。
3. 运势模块
GET /horoscope/today —— 今日星座运势(需鉴权)
无请求参数(星座按用户生日自动推算)。响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
| stat_date | int64 | 日期(格式 YYYYMMDD) |
| 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 | 今日幸运数字 |
4. 衣物模块
图片处理状态枚举(image_process_status,全模块通用):
| 值 | 含义 |
|---|---|
| 1 | 抠图中 |
| 2 | 分类中 |
| 3 | 排队中 |
| 4 | 成功 |
| 5 | 失败(配合 fail_reason 查看原因) |
POST /clothes/upload —— 上传衣物图片(需鉴权)
请求(form,multipart/form-data):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file | file | 是 | 衣物图片文件(字段名固定为 file,无其他表单字段) |
服务端校验:扩展名白名单(配置 Upload.AllowedExtensions)+ 大小上限(Upload.MaxSizeMB,默认 20MB)+ magic bytes 真图校验;上传即扣减一次上传额度。
响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
| clothes_uuid | string | 衣物 UUID,后续查询/更新/删除均用此值 |
| image_process_status | int | 处理状态(见枚举表),初始为 3=排队中 |
相关错误码:30000 文件为空、30001 类型不支持、30002 超限、39000 保存失败、80000 额度不足。
GET /clothes —— 衣物列表(需鉴权)
请求(query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / page_size | int | 否 | 分页 |
| category_main | string | 否 | 按大类筛选 |
| category_sub | string | 否 | 按小类筛选 |
响应 data:total / page / page_size + list(衣物对象数组,见公共结构)。
GET /clothes/:uuid —— 衣物详情(需鉴权)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| uuid | path | string | 是 | 衣物 UUID |
响应 data = 衣物对象(见公共结构)。
相关错误码:50000 衣物不存在、50005 无权操作该衣物。
GET /clothes/:uuid/status —— 查询图片处理状态(需鉴权)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| uuid | path | string | 是 | 衣物 UUID |
响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
| image_process_status | int | 处理状态(见枚举表) |
| fail_reason | string | 失败原因(状态为 5 时有效) |
PUT /clothes/:uuid —— 更新衣物信息(需鉴权)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| uuid | path | string | 是 | 衣物 UUID |
请求(json,全部可选,传什么改什么):
| 参数 | 类型 | 说明 |
|---|---|---|
| category_main | string | 大类,最长 64 |
| category_sub | string | 小类,最长 64 |
| style | string | 风格,最长 64 |
| color | string | 主颜色,最长 64(注意:请求字段名为 color,非 color_main) |
| 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 | 正式程度(1-5),最长 16 |
| occasions | string | 适用场合,最长 255 |
| visual_impact | string | 视觉冲击力(1-5),最长 16 |
| name | string | 名称,最长 64 |
| remark | string | 备注,最长 500 |
响应:data 为空对象 {}。
DELETE /clothes/:uuid —— 删除衣物(需鉴权)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| uuid | path | string | 是 | 衣物 UUID |
响应:data 为空对象 {}。删除成功自动回退上传额度。
相关错误码:50000 衣物不存在、50001 已被搭配使用无法删除。
5. 穿搭模块
穿搭枚举汇总:来源 source 1=用户创建 2=AI 推荐;公开 is_public 1=不公开 2=公开;审核 audit_status 1=待审核 2=通过 3=不通过。
POST /outfit —— 创建穿搭(需鉴权)
请求(json):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| outfit_name | string | 是 | 穿搭名称,最长 32 |
| outfit_desc | string | 否 | 穿搭描述,最长 500 |
| scene | string | 否 | 适合场景,最长 100 |
| source | int | 否 | 1=用户创建 2=AI 推荐 |
| items | array | 是 | 衣物列表,1~10 项,元素:{"clothes_uuid": string} |
响应 data = 穿搭对象(见公共结构)。
GET /outfit —— 我的穿搭列表(需鉴权)
请求(query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / page_size | int | 否 | 分页 |
| is_public | int | 否 | 1=不公开 2=公开 |
| source | int | 否 | 1=用户创建 2=AI 推荐 |
响应 data.list 元素 = 穿搭对象(见公共结构)。
GET /outfit/:uuid —— 穿搭详情(需鉴权)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| uuid | path | string | 是 | 穿搭 UUID |
响应 data = 穿搭对象(见公共结构)。
相关错误码:40000 穿搭不存在、40004 无权操作该穿搭。
PUT /outfit/:uuid —— 更新穿搭(需鉴权)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| uuid | path | string | 是 | 穿搭 UUID |
请求(json):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| outfit_name | string | 是 | 穿搭名称,最长 32 |
| outfit_desc | string | 否 | 穿搭描述,最长 500 |
| scene | string | 否 | 适合场景,最长 100 |
| items | array | 是 | 衣物列表,1~10 项,元素:{"clothes_uuid": string} |
响应:data 为空对象 {}。
DELETE /outfit/:uuid —— 删除穿搭(需鉴权)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| uuid | path | string | 是 | 穿搭 UUID |
响应:data 为空对象 {}。
PUT /outfit/:uuid/public —— 设置公开状态(需鉴权)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| uuid | path | string | 是 | 穿搭 UUID |
请求(json):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| is_public | int | 是 | 1=不公开 2=公开(公开进入社区,需审核) |
响应:data 为空对象 {}。
6. 衣柜分享模块
分享状态枚举:status 1=正常 2=已取消 3=已过期 4=已封禁。
POST /closet-share —— 创建衣柜分享(需鉴权)
请求(json):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| share_type | int | 是 | 1=部分衣物分享 2=全量快照分享 |
| clothes_uuids | array | 部分分享必填 | 衣物 UUID 数组(share_type=1 时必填) |
| expire_time | int64 | 否 | 过期时间戳(秒) |
响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
| share_uuid | string | 分享 UUID,供他人访问分享详情 |
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 | 过期时间戳(秒) |
| status | int | 1=正常 2=已取消 3=已过期 4=已封禁 |
GET /closet-share/:uuid —— 分享详情(需鉴权,访客视角)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| uuid | path | 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 | 是否本人分享 |
相关错误码:90100 分享不存在、90101 分享已过期、90102 分享已取消。
POST /closet-share/:uuid/cancel —— 取消分享(需鉴权)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| uuid | path | string | 是 | 分享 UUID |
无请求体,响应为操作结果(空对象)。
7. 点赞 / 收藏模块
动作枚举:action 1=执行(点赞/收藏)2=取消,重复调用幂等。
POST /collect —— 收藏 / 取消收藏(需鉴权)
请求(json):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| target_uuid | string | 是 | 目标(社区穿搭)UUID |
| action | int | 是 | 1=收藏 2=取消收藏 |
响应:data 为空对象 {}。
相关错误码:90200 目标内容不存在、90202 收藏操作失败。
GET /collect —— 我的收藏列表(需鉴权)
请求(query):仅 page / page_size 分页参数。
响应 data.list 元素 = 穿搭对象(见公共结构)。
POST /like —— 点赞 / 取消点赞(需鉴权)
请求(json):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| target_uuid | string | 是 | 目标穿搭 UUID |
| action | int | 是 | 1=点赞 2=取消点赞 |
响应:data 为空对象 {}。
相关错误码:90201 点赞操作失败。
GET /like/:targetUUID —— 查询点赞状态与数量(需鉴权)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| targetUUID | path | string | 是 | 目标穿搭 UUID(参数名为 targetUUID,非 uuid) |
响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
| is_liked | bool | 当前用户是否已点赞 |
| like_count | int64 | 点赞总数 |
8. 通知模块
GET /notification —— 通知列表(需鉴权)
请求(query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / page_size | int | 否 | 分页 |
| is_read | int | 否 | 1=未读 2=已读;不传 = 全部(注意:不是布尔值) |
响应 data.list 元素 = 通知对象(见公共结构)。
GET /notification/unread —— 未读通知数(需鉴权)
无请求参数。响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
| unread_count | int64 | 未读通知数 |
GET /notification/:uuid —— 通知详情(需鉴权)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| uuid | path | string | 是 | 通知 UUID |
响应 data = 通知对象(见公共结构)。
相关错误码:90000 通知不存在。
POST /notification/:uuid/read —— 标记已读(需鉴权)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| uuid | path | string | 是 | 通知 UUID |
无请求体,响应为操作结果(空对象)。
9. 商品模块(公开)
商品类型枚举:goods_type 1=会员包 2=上传包 3=AI 推荐包 4=衣柜分享包 5=搭配分享包。
GET /goods —— 商品列表(公开)
请求(query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / page_size | int | 否 | 分页 |
| goods_type | int | 否 | 商品类型(见枚举),不传 = 全部 |
响应 data.list 元素 = 商品对象(见公共结构,仅返回上架商品)。
GET /goods/:code —— 按编码查询商品(公开)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| code | path | string | 是 | 商品编码(goods_code) |
响应 data = 商品对象(见公共结构)。
相关错误码:60000 商品不存在、60001 商品已下架。
10. 订单模块
订单状态枚举:order_status 1=待支付 2=已支付 3=已取消 4=已过期 5=已退款 6=申请退款中;退款状态 status 0=待处理 1=退款中 2=退款成功 3=退款失败。
POST /order —— 创建订单并发起微信支付(需鉴权)
请求(json):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| goods_code | string | 是 | 商品编码(唯一请求字段,金额由服务端按商品售价计算,客户端不传金额) |
响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
| order_no | string | 订单号 |
| pay_amount | int64 | 应付金额(分) |
| expire_time | int64 | 支付截止时间戳(秒),前端倒计时用(15 分钟) |
| pay_params | object | 小程序拉起支付参数(微信原生命名,注意大小写):timeStamp、nonceStr、package、signType、paySign |
相关错误码:60000 商品不存在、60001 已下架、79000 创建订单失败。
GET /order —— 我的订单列表(需鉴权)
请求(query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / page_size | int | 否 | 分页 |
| status | int | 否 | 1=待支付 2=已支付 3=已取消 4=已过期 5=已退款 6=申请退款中;不传 = 全部 |
响应 data.list 元素 = 订单对象(见公共结构)。
POST /order/:orderNo/cancel —— 取消订单(需鉴权)
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| orderNo | path | string | 是 | 订单号(参数名为 orderNo,非 uuid) |
无请求体,响应为操作结果(空对象)。仅待支付订单可取消。
POST /order/refund —— 申请退款(需鉴权)
请求(json):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| order_no | string | 是 | 订单号 |
| reason | string | 是 | 退款原因,最长 200 |
响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
| refund_no | string | 退款单号 |
| refund_amount | int64 | 退款金额(分) |
| status | int | 0=待处理 1=退款中 2=退款成功 3=退款失败 |
| apply_time | int64 | 申请时间戳(秒) |
相关错误码:71000 退款记录不存在、71001 当前状态不允许退款、71002 退款金额不合法、71003 退款申请失败。
POST /order/pay/notify —— 微信支付回调(公开,微信服务端调用)
请求体为微信支付回调原始报文(服务端验签 + 解密)。响应遵循微信规范(见通用调用约定),前端不要手动调用。
POST /order/refund/notify —— 微信退款回调(公开,微信服务端调用)
请求体为微信退款回调原始报文。响应遵循微信规范(见通用调用约定),前端不要手动调用。
11. AI 推荐模块
POST /ai-recommend —— 提交 AI 穿搭推荐任务(需鉴权)
AI 推荐为异步接口:提交后立即返回
task_id,需轮询GET /ai-recommend/status获取结果。生成在后台执行,前端退出、切页面、断网都不会中断。
请求(json):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| scene_type | int | 是 | 1=每日推荐 2=个性化场景 |
| scene | string | 否 | 用户自定义场景描述(scene_type=2 时使用) |
响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
| task_id | string | 任务 ID,用于轮询状态接口 |
| status | int | 1=生成中 |
相关错误码:90401 AI 推荐次数已达上限、19002 缓存错误。
GET /ai-recommend/status —— 查询 AI 推荐任务状态(需鉴权)
请求(query):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:
| 字段 | 类型 | 说明 |
|---|---|---|
| outfit_uuid | string | 生成结果 UUID |
| outfit_name | string | 穿搭名称 |
| outfit_desc | string | 穿搭描述 |
| scene | string | 场景 |
| source | int | 1=用户创建 2=AI 推荐 |
| status | int | 1=正常 2=封禁 3=待用户保存 |
| outfit_item | array | 衣物列表,元素:{"clothes_uuid": string} |
相关错误码:90400 AI 推荐失败、90402 AI 推荐任务不存在或已过期。
POST /ai-recommend/save —— 保存 AI 推荐为我的穿搭(需鉴权)
请求(json):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| outfit_uuid | string | 是 | AI 推荐结果 UUID(由 GET /ai-recommend/status 的 result.outfit_uuid 返回) |
响应:data 为空对象 {}。
错误码
业务错误码按业务域分段(pkg/errcode),常见分段如下:
| 段位 | 业务域 |
|---|---|
| 0 | 成功 |
| 10000–10999 | 通用/参数(10000 参数错误、10001 不存在、10003 限流) |
| 11000–11999 | 鉴权(11000 未授权、11001 Token 鉴权失败、11002 Token 已过期、11004 Token 缺失) |
| 12000–12999 | 权限(12000 无权限) |
| 19000–19999 | 系统错误(19000 服务器错误、19001 数据库、19002 缓存) |
| 20000–29999 | 用户(21000 用户名或密码错误、21002 微信登录失败、22000 用户不存在) |
| 30000–39999 | 上传(30000 空文件、30001 扩展名不允许、30002 超限、39001 抠图失败、39002 分类失败) |
| 40000–49999 | 穿搭 |
| 50000–59999 | 衣物 |
| 60000–69999 | 商品 |
| 70000–79999 | 订单/退款(70000 订单不存在、70004 订单已过期) |
| 80000–89999 | 额度/VIP(80000 额度不足、80004 广告激励未开启) |
| 90000–99999 | 通知/分享/社区/星座/AI 推荐(90000 通知不存在、90400 AI 推荐失败、90401 AI 推荐次数已达上限) |
核心业务调用流程
以下为前后端联调时的关键调用时序(业务视角的状态机与业务规则见上文 业务流程)。参与方:小程序前端、后端服务、微信服务端、AI 推理服务与大模型网关。
流程 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: 首次登录:注册用户 + 初始化免费额度 + 赠送体验会员及会员额度
B-->>F: {access_token, refresh_token, expire_time, user_info}
Note over F,B: access_token 过期(业务码 11002 / HTTP 401)
F->>B: POST /user/refresh-token {refresh_token, access_token?}
B-->>F: 新 {access_token, refresh_token, expire_time}
F->>B: 携带新 token 重试原请求
- 前端调用
wx.login获取 code,POST 到/user/wxlogin;后端经微信 code2session 换取 openid,首次登录自动注册并初始化免费额度。 - 前端保存双 token;此后所有需鉴权接口携带
Authorization: Bearer <access_token>。 - 收到业务码 11002(HTTP 401)时调用
/user/refresh-token换新(可带上旧 access_token 使其立即吊销),成功后用新 token 重试。
流程 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 + 扣上传额度
B-->>F: {clothes_uuid, image_process_status: 3 排队中}
Note over B,M: 异步:排队(3) → 抠图(1) → 分类(2) → 成功(4)/失败(5)
B->>M: 抠图请求(失败重试 ≤ MaxRetry)
B->>C: 15 维分类请求(失败重试 ≤ MaxRetry)
loop 前端轮询(建议 1~2 秒间隔)
F->>B: GET /clothes/:uuid/status
B-->>F: {image_process_status, fail_reason}
end
alt 状态为 4 成功
F->>B: GET /clothes/:uuid 读取识别结果
else 状态为 5 失败
Note over F: 查看 fail_reason,可重新上传
end
- 上传接口同步返回(不等待 AI 完成):
clothes_uuid+ 初始状态3(排队中)。 - 后端异步流水线:3 排队 → 1 抠图中(BiRefNet,端口 8001)→ 2 分类中(Fashion-CLIP,端口 8000)→ 4 成功 / 5 失败。
- 前端轮询
GET /clothes/:uuid/status(建议间隔 1~2 秒),直到状态为 4 或 5。 - 成功后调用
GET /clothes/:uuid读取识别结果,可用PUT /clothes/:uuid修正识别值(注意该接口主颜色字段名为color)。 - 失败时根据
fail_reason判断,可重新上传。
流程 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: 抢占用户活跃位 + 写入任务快照(status=1)
B->>B: 校验并扣减 AI 额度
B-->>F: {task_id, status: 1} · 秒级返回
par 后台生成(独立 ctx,前端退出不中断)
B->>L: 结合星座运势 + 衣橱衣物生成推荐
L-->>B: 推荐结果
B->>B: 落库(status=3 待用户保存)
B->>R: 任务快照 status=2 + result
and 前端轮询
loop 每 2 秒,上限 90 次
F->>B: GET /ai-recommend/status?task_id
B-->>F: {status, result?}
end
end
F->>F: 用户预览、确认
F->>B: POST /ai-recommend/save {outfit_uuid}
B-->>F: 保存成功,转入我的穿搭
- 请求必带
scene_type(1=每日推荐 2=个性化场景),提交时扣减一次 AI 额度,不足返回 90401。 - 接口秒级返回
task_id,不等大模型;生成在后台执行,前端退出、切页面或断网都不会中断。 - 前端轮询
GET /ai-recommend/status(建议 2 秒一次),直到status为 2(成功)或 3(失败);失败时额度已自动回退。 - 成功后结果先落库为"待用户保存"状态(status=3),用户确认后调用
/ai-recommend/save转为正式穿搭;未保存的结果不进入穿搭列表。
流程 4:创建订单到支付完成
sequenceDiagram
participant F as 小程序前端
participant B as 后端服务
participant W as 微信支付
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: 订单置为已支付,发放权益(会员/额度)
F->>B: GET /order 确认支付结果
B-->>F: {order_status: 2 已支付}
opt 超时未支付
B->>B: 后台任务每分钟扫描,超 15 分钟置为已过期
end
- 创建订单只传
goods_code,金额由服务端按商品售价计算(单位:分),前端不传金额。 - 前端用响应中的
pay_params调wx.requestPayment;expire_time用于支付倒计时(15 分钟)。 - 支付结果以微信回调为准(公开路由,服务端验签 + 解密);前端支付完成后应通过
GET /order确认order_status=2,不要自行判定支付成功。 - 超时未支付订单由后台任务每分钟扫描并自动置为已过期(4)。
- 退款:
POST /order/refund申请成功后订单置6 申请退款中(人工审核,非实时到账)→ 运营在微信后台手动退款 → 退款回调将订单置5 已退款(失败则回退2 已支付,可重新申请);虚拟支付退款推送字段为MchOrderId/MchRefundId/RetCode(0=成功),推送丢失时由查单兜底任务自动订正;可通过订单列表查看refund_time/refund_amount。
流程 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-->>F: 成功(幂等)
F->>B: POST /like {target_uuid, action: 2 取消}
B-->>F: 成功(幂等)
Note over F,B: 收藏同理:POST /collect {target_uuid, action}
- 进入详情页先调
GET /like/:targetUUID获取is_liked/like_count渲染状态。 - 点击切换调用
POST /like(action1=点赞 2=取消),服务端幂等,重复提交安全。 - 收藏同理:
GET /collect查看收藏列表,POST /collect切换收藏状态。
接口调用示例
以下示例中的 token、UUID、时间戳等业务数据均为示意值;实际以接口返回为准。
微信登录:
curl -X POST http://localhost:80/api/v1/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
}
}
}
上传衣物图片(multipart):
curl -X POST http://localhost:80/api/v1/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 http://localhost:80/api/v1/clothes/c8f2e1a0-xxxx-xxxx-xxxx-xxxxxxxxxxxx/status \
-H "Authorization: Bearer <access_token>"
{
"code": 0,
"msg": "成功",
"data": {
"image_process_status": 4,
"fail_reason": ""
}
}
创建订单(微信支付):
curl -X POST http://localhost:80/api/v1/order \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"goods_code": "<商品编码,如 AI_3>"}'
{
"code": 0,
"msg": "成功",
"data": {
"order_no": "20260907000123",
"pay_amount": 990,
"expire_time": 1756743300,
"pay_params": {
"timeStamp": "1756742400",
"nonceStr": "1a2b3c4d5e6f",
"package": "prepay_id=wx09153000123456",
"signType": "RSA",
"paySign": "xxxxxxxxxxxxxxxx"
}
}
}
AI 穿搭推荐:
curl -X POST http://localhost:80/api/v1/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" }
]
}
}
可观测性与后台任务
可观测性
- Trace/Metric/Log 三信号通过 OTLP gRPC 上报(
Otel.Enabled=true时);初始化失败自动降级为 no-op,不阻断启动。 - 请求链路:每请求生成
X-Request-ID,Gin 中间件自动开启 server span,日志中注入trace_id/span_id。 - 结构化日志(JSON)同时输出到文件(lumberjack 滚动压缩)与 stdout;请求日志对 query 中的
token/code/secret/password等参数自动脱敏。
后台任务
| 任务 | 周期 | 说明 |
|---|---|---|
| 每日运势生成 | 每日 0 点 + 启动兜底 | 大模型生成十二星座运势;Redis 分布式锁保证多实例仅执行一次;当日不足 12 条时按需兜底生成 |
| 订单超时过期 | 每 60 秒 | 将超过 15 分钟未支付的订单批量置为"已过期" |
| 权益发放补偿 | 每 60 秒 | 重试发放失败的权益任务(恢复僵死任务、幂等防重复发放、超限告警) |
| 虚拟支付退款查单兜底 | 每 60 秒 | 反查停留超 10 分钟的「申请退款中」虚拟支付订单:已退款自动置「已退款」并回收权益,仍未退款保持状态、超 24 小时告警人工 |
部署提示
- 工作目录:服务启动时必须位于项目根目录(配置路径固定为
configs/config.yaml),容器化部署时注意挂载配置文件与日志目录。 - 多实例:支持水平扩展;写操作幂等保护与关键后台任务(运势生成、权益补偿)均通过 Redis 分布式锁协调。
- 推理服务:
thirdparty/下的两个 Python 服务建议独立部署(GPU 优先),并通过MattingModel/ClassificationModel配置段指向其地址。 - 可观测性后端:按需配置
Otel.ExporterEndpoint指向 OTLP Collector;无需可观测性时设置Enabled: false即可零开销关闭。
安全注意事项
JWT.SecretKey必须设置为强随机值,代码会强制校验,为空直接启动失败;请勿使用默认占位值。WechatPay.APIv3Key必须为 32 字节随机密钥;微信支付/退款回调依赖官方 SDK 验签与解密,勿绕过。- 所有密钥仅存放于
configs/config.yaml(已被.gitignore忽略),严禁提交到版本库或写入数据库。 - 后端对 AI 大模型输入(衣物信息、用户数据)做了清洗与截断,防 prompt 注入;上传文件除扩展名外还校验 magic bytes,防伪造类型。
- 生产环境请将
Server.RunMode设为release,并通过反向代理对外提供 HTTPS。