Files
guke a563c1ca4b @ (#111)
docs/api目录文档分类和补全

---------

Co-authored-by: guke <guke@autohome.com.cn>
Reviewed-on: #111
2026-07-03 15:00:37 +08:00

269 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 傻瓜比价 App 后端 — API 接口文档(索引)
> Base URL:生产 `https://app-api.shaguabijia.com`;本地联调 `http://<开发机>:8770`
> 协议:HTTP / JSON,请求与响应体均 `application/json`,字段统一 **snake_case**
> 鉴权:需鉴权的接口在请求头带 `Authorization: Bearer <access_token>`
> 最后更新:2026-07-03(补全缺失文档:ad/watch-report, wallet/transfer-auth 族, coupon/session+stats+completed-today+prompt 族, invite 族, user/onboarding, platform/flags+ad-config+app-version, intent/step+precoupon/step, analytics/events, order/report, report 族, feedback/config+records, trace/finalize。文档移至分类子目录,新增 mock 入参/出参示例)
> 架构:`app/api/v1/` 只放很轻的接口层;穿山甲/微信支付/极光/短信/美团等 SDK 集成的重逻辑在 `app/integrations/`,实现细节见 [docs/integrations/](../integrations/README.md)。
---
## 接口总览
| # | 方法 + 路径 | 鉴权 | 详情 |
|---|---|---|---|
| 1 | `GET /health` | 无 | [详情](./other/health.md) |
| 2 | `POST /api/v1/auth/jverify-login` | 无 | [详情](./auth/auth-jverify-login.md) |
| 3 | `POST /api/v1/auth/sms/send` | 无 | [详情](./auth/auth-sms-send.md) |
| 4 | `POST /api/v1/auth/sms/login` | 无 | [详情](./auth/auth-sms-login.md) |
| 5 | `POST /api/v1/auth/refresh` | 无 | [详情](./auth/auth-refresh.md) |
| 6 | `GET /api/v1/auth/me` | Bearer | [详情](./auth/auth-me.md) |
| 7 | `POST /api/v1/auth/logout` | Bearer | [详情](./auth/auth-logout.md) |
| 8 | `POST /api/v1/coupon/step` | 无 | [详情](./coupon/coupon-step.md)(透传 pricebot + best-effort 写 `coupon_*` 三表) |
| 8a | `POST /api/v1/coupon/prompt/shown` | 无 | [详情](./coupon/coupon-prompt.md)(引导窗弹出即上报) |
| 8b | `POST /api/v1/coupon/prompt/dismiss` | 无 | [详情](./coupon/coupon-prompt.md)(用户拒绝/关闭引导窗) |
| 8c | `GET /api/v1/coupon/prompt/should-show` | 无 | [详情](./coupon/coupon-prompt.md)(切到外卖 App 时是否还应弹引导窗) |
| 8d | `POST /api/v1/coupon/prompt/reset` | 无 | [详情](./coupon/coupon-prompt.md)(重置今日引导窗 engagement,开发测频控用) |
| 8e | `GET /api/v1/coupon/completed-today` | 无 | [详情](./coupon/coupon-completed-today.md)(这台设备今天是否已跑完整轮领券) |
| 8f | `POST /api/v1/coupon/completed-today/reset` | 无 | [详情](./coupon/coupon-completed-today.md)(重置今日已完成,开发用) |
| 8g | `GET /api/v1/coupon/stats` | Bearer | [详情](./coupon/coupon-stats.md)(累计领券数,「我的」页战绩卡) |
| 8h | `POST /api/v1/coupon/session` | 无 | [详情](./coupon/coupon/coupon-session.md)(领券流水上报,admin 看板数据源) |
| 9 | `POST /api/v1/meituan/coupons` | 无 | [详情](./meituan/meituan-coupons.md) |
| 10 | `POST /api/v1/meituan/feed` | 无 | [详情](./meituan/meituan-feed.md) |
| 11 | `POST /api/v1/meituan/referral-link` | 无 | [详情](./meituan/meituan-referral-link.md) |
| 11a | `POST /api/v1/meituan/top-sales` | 无 | [详情](./meituan/meituan-top-sales.md)(销量榜:离线库 `meituan_coupon` 按销量降序 + 跨源去重,不实时打美团) |
| **比价透传**(前缀 `/api/v1`,外卖 MVP;与 `coupon/step` 同为透传 pricebot-backend;下按 Phase 流程列,均不鉴权) |||
| 12 | `POST /api/v1/intent/recognize` | 无 | [详情](./intent/compare-intent-recognize.md)(Phase 1 意图识别,单次,多数源) |
| 12a | `POST /api/v1/intent/precoupon/step` | 无 | [详情](./intent/intent-step.md)(Phase 0 意图识别前先用券,仅美团源) |
| 12b | `POST /api/v1/intent/step` | 无 | [详情](./intent/intent-step.md)(Phase 1 多帧意图识别,仅淘宝源,循环到 done) |
| 13 | `POST /api/v1/price/step` | 无 | [详情](./intent/compare-price-step.md)Phase 2 步进) |
| 13a | `POST /api/v1/trace/finalize` | 无 | [详情](./other/trace-finalize.md)(比价 trace 收尾上云,终止/未识别拿 trace_url |
| **比价记录**(前缀 `/api/v1/compare`;按用户落库,**鉴权**,区别于上面不鉴权的透传) |||
| 12a | `POST /api/v1/compare/record` | Bearer | [详情](./compare/compare-record-report.md) |
| 12b | `GET /api/v1/compare/records` | Bearer | [详情](./compare/compare-records.md) |
| 12c | `GET /api/v1/compare/records/{id}` | Bearer | [详情](./compare/compare-record-detail.md) |
| 12e | `GET /api/v1/compare/stats` | Bearer | [详情](./compare/compare-stats.md)(「我的」页省钱战绩卡:完成比价数 + 累计发现可省) |
| **比价战绩里程碑**(前缀 `/api/v1/compare`;福利页「记录比价战绩」,按成功比价数解锁逐档发金币) |||
| 12d | `GET /api/v1/compare/milestones` | Bearer | [详情](./compare/compare-milestones.md) |
| 12e | `POST /api/v1/compare/milestones/{milestone}/claim` | Bearer | [详情](./compare/compare-milestone-claim.md) |
| **设备 / 无障碍存活监控**(前缀 `/api/v1/device`;心跳超时检出 + 掉线召回,#65 |||
| D1 | `POST /api/v1/device/register` | Bearer | [详情](./device/device-liveness.md)(注册设备/更新极光 push token |
| D2 | `POST /api/v1/device/heartbeat` | Bearer | [详情](./device/device-liveness.md)(无障碍服务存活心跳,心跳也能自注册) |
| D3 | `GET /api/v1/device/liveness` | Bearer | [详情](./device/device-liveness.md)(进 App 查本机是否被判掉线过) |
| D4 | `POST /api/v1/device/liveness/ack` | Bearer | [详情](./device/device-liveness.md)(确认已弹引导,清掉线告警) |
| **上报更低价**(前缀 `/api/v1/report`;众包纠偏,人工审核发奖) |||
| R1 | `POST /api/v1/report` | Bearer | [详情](./other/report-submit.md)(提交上报更低价,multipart:比价记录ID+平台+价格+截图1-4张) |
| R2 | `GET /api/v1/report/records` | Bearer | [详情](./other/report-records.md)(上报记录列表,?status=pending/approved/rejected 可选筛选) |
| **好友邀请**(前缀 `/api/v1/invite`;注册即生效,双方各发 1 万金币) |||
| I1 | `GET /api/v1/invite/me` | Bearer | [详情](./invite/invite-me.md)(我的邀请码+分享链接+已邀人数/已得金币) |
| I2 | `GET /api/v1/invite/invitees` | Bearer | [详情](./invite/invite-invitees.md)(我邀请的人列表,limit/offset 分页) |
| I3 | `POST /api/v1/invite/landing-track` | 无 | [详情](./invite/invite-bind.md)(落地页 dl.html 访问上报指纹,剪贴板归因兜底;浏览器无 token) |
| I4 | `POST /api/v1/invite/bind` | Bearer | [详情](./invite/invite-bind.md)(绑定邀请人;支持 clipboard/manual 邀请码+fingerprint 指纹反查三种归因) |
| **钱包 / 我的资产**(前缀 `/api/v1/wallet` |||
| 14 | `GET /api/v1/wallet/account` | Bearer | [详情](./wallet/wallet-account.md) |
| 15 | `GET /api/v1/wallet/coin-transactions` | Bearer | [详情](./wallet/wallet-coin-transactions.md) |
| 16 | `GET /api/v1/wallet/cash-transactions` | Bearer | [详情](./wallet/wallet-cash-transactions.md) |
| 17 | `GET /api/v1/wallet/exchange-info` | 无 | [详情](./wallet/wallet-exchange-info.md) |
| 18 | `POST /api/v1/wallet/exchange` | Bearer | [详情](./wallet/wallet-exchange.md) |
| 19 | `POST /api/v1/wallet/bind-wechat` | Bearer | [详情](./wallet/wallet-bind-wechat.md) |
| 20 | `POST /api/v1/wallet/unbind-wechat` | Bearer | [详情](./wallet/wallet-unbind-wechat.md) |
| 21 | `GET /api/v1/wallet/withdraw-info` | Bearer | [详情](./wallet/wallet-withdraw-info.md) |
| 22 | `POST /api/v1/wallet/withdraw` | Bearer | [详情](./wallet/wallet-withdraw.md) |
| 23 | `GET /api/v1/wallet/withdraw/status` | Bearer | [详情](./wallet/wallet-withdraw-status.md) |
| 24 | `GET /api/v1/wallet/withdraw-orders` | Bearer | [详情](./wallet/wallet-withdraw-orders.md) |
| 24a | `POST /api/v1/wallet/transfer-auth` | Bearer | [详情](./wallet/wallet-transfer-auth.md)(开启免确认到账,申请授权,返回拉起微信授权页的 package) |
| 24b | `GET /api/v1/wallet/transfer-auth/status` | Bearer | [详情](./wallet/wallet-transfer-auth.md)(查免确认授权状态,从微信授权页返回后轮询) |
| 24c | `POST /api/v1/wallet/transfer-auth/close` | Bearer | [详情](./wallet/wallet-transfer-auth.md)(关闭免确认到账,解除授权) |
| **签到**(前缀 `/api/v1/signin` |||
| 25 | `GET /api/v1/signin/status` | Bearer | [详情](./signin/signin-status.md) |
| 26 | `POST /api/v1/signin` | Bearer | [详情](./signin/signin-do.md) |
| 26a | `POST /api/v1/signin/boost` | Bearer | [详情](./signin/signin-boost.md) |
| **任务**(前缀 `/api/v1/tasks` |||
| 27 | `GET /api/v1/tasks` | Bearer | [详情](./tasks/tasks-list.md) |
| 28 | `POST /api/v1/tasks/{task_key}/claim` | Bearer | [详情](./tasks/tasks-claim.md) |
| **省钱**(前缀 `/api/v1/savings` |||
| 29 | `GET /api/v1/savings/summary` | Bearer | [详情](./savings/savings-summary.md) |
| 30 | `GET /api/v1/savings/battle` | Bearer | [详情](./savings/savings-battle.md) |
| 31 | `GET /api/v1/savings/records` | Bearer | [详情](./savings/savings-records.md) |
| **看广告发奖**(前缀 `/api/v1/ad` |||
| 32 | `GET /api/v1/ad/pangle-callback` | 验签 | [详情](./ad/ad-pangle-callback.md) |
| 33 | `GET /api/v1/ad/reward-status` | Bearer | [详情](./ad/ad-reward-status.md) |
| 34 | `POST /api/v1/ad/test-grant` | Bearer | [详情](./ad/ad-test-grant.md) |
| 35 | `POST /api/v1/ad/ecpm-report` | Bearer | [详情](./ad/ad-ecpm-report.md) |
| 35a | `POST /api/v1/ad/feed-reward` | Bearer | [详情](./ad/ad-feed-reward.md) |
| 35b | `POST /api/v1/ad/reward-noshow` | Bearer | [详情](./ad/ad-reward-noshow.md)(激励视频提前关闭/未发奖留痕,只记原因不发币) |
| 35c | `GET /api/v1/ad/feed-reward/units` | Bearer | [详情](./ad/ad-feed-reward.md)(信息流广告今日已发份数/上限,配合 feed-reward 看进度) |
| 35d | `POST /api/v1/ad/watch-report` | Bearer | [详情](./ad/ad-watch-report.md)(上报激励视频观看时长,旧客户端兼容) |
| **用户资料**(前缀 `/api/v1/user` |||
| 35 | `PATCH /api/v1/user/profile` | Bearer | [详情](./user/user-profile.md) |
| 36 | `POST /api/v1/user/avatar` | Bearer | [详情](./user/user-avatar.md) |
| 36a | `POST /api/v1/user/onboarding/complete` | Bearer | [详情](./user/user-onboarding.md)(标记新手引导完成,按 账号+device_id 幂等,跨卸载重装持久) |
| 36b | `GET /api/v1/user/onboarding/status` | Bearer | [详情](./user/user-onboarding.md)(查该 账号+设备 是否走过引导,运营在 admin 删记录即触发重走) |
| 37 | `DELETE /api/v1/user` | Bearer | [详情](./user/user-delete.md) |
| **帮助与反馈**(前缀 `/api/v1/feedback` |||
| 38 | `POST /api/v1/feedback` | Bearer | [详情](./other/feedback.md) |
| 38a | `GET /api/v1/feedback/config` | Bearer | [详情](./other/feedback-config.md)(反馈页「加群二维码」卡配置:开关+二维码图+三行文案) |
| 38b | `GET /api/v1/feedback/records` | Bearer | [详情](./other/feedback-records.md)(我的反馈历史,pending/adopted/rejected |
| **埋点 & 订单上报**(前缀分散;全部 Bearer 除 analytics/events 不强制登录) |||
| E1 | `POST /api/v1/analytics/events` | 无 | [详情](./other/analytics-events.md)(批量上报埋点事件,不强制登录,每批最多200条) |
| E2 | `POST /api/v1/order/report` | Bearer | [详情](./other/order-report.md)(上报归因订单,比价后5分钟内点链接+支付金额与比价价相差≤1元) |
| **首页门面数据 / 客户端配置**(前缀 `/api/v1/platform`;全平台展示数字 + 运营开关,**全部不鉴权**,登录前可读) |||
| 39 | `GET /api/v1/platform/stats` | 无 | [详情](./platform/platform-stats.md) |
| 40 | `GET /api/v1/platform/savings-feed` | 无 | [详情](./savings/platform-savings-feed.md) |
| 40a | `GET /api/v1/platform/flags` | 无 | [详情](./platform/platform-flags.md)(客户端运营 feature flag,比价/领券期广告开关等,拉取后缓存) |
| 40b | `GET /api/v1/platform/ad-config` | 无 | [详情](./platform/platform-ad-config.md)(客户端拉广告配置:穿山甲 app_id+各位ID+各场景开关;不含验签密钥) |
| 40c | `GET /api/v1/platform/app-version` | 无 | [详情](./platform/platform-app-version.md)(最新 App 版本,OTA 检查更新;与本机 versionCode 比) |
| **微信支付回调**(前缀 `/api/v1/wxpay` |||
| W1 | `POST /api/v1/wxpay/transfer-auth-notify` | 无 | 免确认收款授权结果通知(一期 stub:仅应答 200 不验签不改账,授权状态靠主动查询兜底)(无单独文档) |
| **CPS 群发短链落地**(**无前缀**,挂域名根;公网不鉴权) |||
| C1 | `GET /c/{code}` | 无 | [详情](./other/cps-redirect.md)(短链落地:微信授权拿 openid + 记点击 + 302 跳/淘宝 H5 落地页) |
| C2 | `POST /c/{code}/copy` | 无 | [详情](./other/cps-redirect.md)(淘宝落地页点「复制口令」记 `copy` |
| C3 | `GET /wx/oauth/cb` | 无 | [详情](./other/cps-redirect.md)(微信网页授权回调;upsert `cps_wx_user` + 种 cookie,`include_in_schema=False` |
| C4 | `GET /MP_verify_*.txt` | 无 | [详情](./other/cps-redirect.md)(微信「网页授权域名」归属校验文件,`include_in_schema=False` |
| **内部回写端点**(前缀 `/internal`;pricebot/发布流程→app-server,**`X-Internal-Secret` 头**,非客户端接口) |||
| N1 | `POST /internal/price-observation` | 内部密钥 | [详情](./internal/internal.md)(比价价格事实批量落 `price_observation` |
| N2 | `GET /internal/store-mapping/lookup` | 内部密钥 | [详情](./internal/internal.md)(按源平台店名反查目标平台已沉淀店铺 id/deeplink |
| N3 | `POST /internal/store-mapping` | 内部密钥 | [详情](./internal/internal.md)(跨平台店铺身份映射落 `store_mapping` |
| N4 | `POST /internal/store-mapping/invalidate` | 内部密钥 | [详情](./internal/internal.md)(标记某平台 shopId 缓存 deeplink 失效) |
| N5 | `POST /internal/launch-confirm-sample` | 内部密钥 | [详情](./internal/internal.md)(启动确认窗兜底样本落 `launch_confirm_sample` |
| N6 | `POST /internal/app-version` | 内部密钥 | [详情](./internal/internal.md)(发布流程写最新 App 版本,落 `app_config` |
| **静态资源**StaticFiles 挂载,见下方 `/media` 静态服务) |||
| - | `GET /media/avatars/<file>` | 无 | 用户头像;返回二进制图片 |
| - | `GET /media/feedback/<file>` | 无 | 反馈截图;返回二进制图片 |
| **运营后台 Admin**(独立子应用 `app/admin/`,前缀 `/admin/api`,独立进程 + 独立 admin JWT。鉴权列:`admin`=任意已登录管理员,`operator`/`finance`/`super_admin`=需对应角色(`super_admin` 恒通过)) |||
| A1 | `POST /admin/api/auth/login` | 无 | [详情](./admin/auth/admin-auth-login.md) |
| A2 | `GET /admin/api/auth/me` | admin | [详情](./admin/auth/admin-auth-me.md) |
| A3 | `GET /admin/api/stats/overview` | admin | [详情](./admin/admin-stats-overview.md) |
| A4 | `GET /admin/api/users` | admin | [详情](./admin/users/admin-users-list.md) |
| A5 | `GET /admin/api/users/{user_id}` | admin | [详情](./admin/users/admin-user-detail.md) |
| A6 | `POST /admin/api/users/{user_id}/status` | operator | [详情](./admin/users/admin-user-status.md) |
| A7 | `POST /admin/api/users/{user_id}/coins` | finance | [详情](./admin/users/admin-user-coins.md) |
| A8 | `POST /admin/api/users/{user_id}/cash` | finance | [详情](./admin/users/admin-user-cash.md) |
| A9 | `GET /admin/api/wallet/coin-transactions` | admin | [详情](./admin/wallet/admin-wallet-coin-transactions.md) |
| A10 | `GET /admin/api/wallet/cash-transactions` | admin | [详情](./admin/wallet/admin-wallet-cash-transactions.md) |
| A11 | `GET /admin/api/withdraws` | admin | [详情](./admin/withdraws/admin-withdraws-list.md) |
| A12 | `POST /admin/api/withdraws/reconcile` | finance | [详情](./admin/withdraws/admin-withdraw-reconcile.md) |
| A13 | `POST /admin/api/withdraws/{out_bill_no}/refresh` | finance | [详情](./admin/withdraws/admin-withdraw-refresh.md) |
| A14 | `GET /admin/api/feedbacks` | admin | [详情](./admin/feedbacks/admin-feedbacks-list.md) |
| A15 | `POST /admin/api/feedbacks/{feedback_id}/handle` | operator | [详情](./admin/feedbacks/admin-feedback-handle.md) |
| A16 | `GET /admin/api/admins` | super_admin | [详情](./admin/admins/admin-admins-list.md) |
| A17 | `POST /admin/api/admins` | super_admin | [详情](./admin/admins/admin-admin-create.md) |
| A18 | `PATCH /admin/api/admins/{admin_id}` | super_admin | [详情](./admin/admins/admin-admin-update.md) |
| A19 | `GET /admin/api/audit-logs` | admin | [详情](./admin/admin-audit-logs.md) |
| A20 | `GET /admin/api/dashboard-display` | admin | [详情](./admin/admin-dashboard-display.md) |
| A21 | `PATCH /admin/api/dashboard-display/{metric}` | operator | [详情](./admin/admin-dashboard-display.md) |
| A22 | `GET /admin/api/marquee-seeds` | admin | [详情](./admin/admin-marquee-seeds.md) |
| A23 | `POST /admin/api/marquee-seeds` | operator | [详情](./admin/admin-marquee-seeds.md) |
| A24 | `PATCH /admin/api/marquee-seeds/{seed_id}` | operator | [详情](./admin/admin-marquee-seeds.md) |
| A25 | `DELETE /admin/api/marquee-seeds/{seed_id}` | operator | [详情](./admin/admin-marquee-seeds.md) |
| A26 | `POST /admin/api/marquee-seeds/bulk` | operator | [详情](./admin/admin-marquee-seeds.md) |
| A27 | `GET /admin/api/marquee-seeds/preview` | admin | [详情](./admin/admin-marquee-seeds.md) |
| A28 | `GET /admin/api/ad-coin-audit` | admin | [详情](./admin/ad/admin-ad-coin-audit.md)(看广告金币公式复算对账,只读) |
| A29 | `GET /admin/api/ad-revenue-report` | admin | [详情](./admin/ad/admin-ad-revenue-report.md)(广告收益报表:按用户/日期/类型/应用/代码位 聚合 条数/收益/金币,只读) |
| - | `GET /admin/api/health` | 无 | admin 健康检查(无单独文档) |
> ⚠️ 美团三个接口当前**无鉴权**,且 `referral-link` 的 `sid` 允许客户端传值覆盖默认渠道——见各接口"备注"。
> `coupon/step` 及外卖比价的 `intent/recognize`、`intent/precoupon/step`、`intent/step`、`price/step`、`trace/finalize` 都透传到 pricebot-backend,**MVP 阶段均不鉴权**(device_id 透传,待补 JWT——见 `app/api/v1/compare.py`)。
> 福利相关业务接口(wallet/signin/tasks/savings、`ad/reward-status`、`ad/feed-reward`)均需 **Bearer**;`wallet/exchange-info` 是静态规则无鉴权;`ad/pangle-callback` 不走 JWT、靠穿山甲**验签**;`ad/test-grant` **仅本地联调**(开关控制,生产 404)。
> 金额字段一律以**分**为单位(`*_cents`)。
---
## 通用约定
- **时间格式**:ISO 8601 UTC(如 `2026-05-27T12:34:56Z`
- **错误响应**:FastAPI 标准结构 `{"detail": "<错误信息>"}`,配合语义化 HTTP 状态码:
- `400` 业务参数错(如验证码不对)
- `401` 未认证 / token 无效或过期 / 用户被禁(响应头带 `WWW-Authenticate: Bearer`
- `403` 账号被禁用
- `422` 请求体字段校验失败(FastAPI 自动校验,如手机号格式)
- `429` 触发限流(短信发送过频)
- `502` 上游调用失败(极光验证/解密失败、美团接口失败)
- **接口文档(交互式)**:`APP_ENV` 非 prod 时开放 `GET /docs`Swagger UI)、`GET /redoc`;生产环境关闭。
### 游标分页约定
钱包流水 / 提现单 / 省钱明细等列表接口统一用**游标分页**:
- 请求 query:`limit`(每页条数,1100,默认 20)、`cursor`(上一页返回的 `next_cursor`,首页不传)。
- 响应:`{ "items": [...], "next_cursor": <int|null> }``next_cursor` 为**末条记录的 id**;为 `null` 表示已到底,无更多数据。
- `items``id` 倒序(最新在前)。
---
## 复用数据结构
### TokenPair
| 字段 | 类型 | 说明 |
|---|---|---|
| `access_token` | string | 访问令牌,之后每个鉴权请求带 `Authorization: Bearer <它>` |
| `refresh_token` | string | 刷新令牌 |
| `token_type` | string | 固定 `"Bearer"` |
| `expires_in` | int | access 剩余秒数(默认 7200 = 2h |
| `refresh_expires_in` | int | refresh 剩余秒数(默认 2592000 = 30d |
### TokenWithUser
继承 `TokenPair` 全部字段,额外多一个 `user``UserOut`)。登录类接口成功时返回此结构。
### UserOut
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | int | 用户主键 |
| `phone` | string | 手机号(注销账号后变 `deleted_<id>` 占位释放唯一约束) |
| `nickname` | string \| null | 昵称,经 [`PATCH /api/v1/user/profile`](./user/user-profile.md) 修改 |
| `avatar_url` | string \| null | 头像相对 URL(`/media/avatars/...`),经 [`POST /api/v1/user/avatar`](./user/user-avatar.md) 上传 |
| `register_channel` | string | 注册渠道:`jverify` / `sms` |
| `status` | string | `active` / `disabled` / `deleted` |
| `created_at` | datetime | 注册时间 |
| `last_login_at` | datetime | 最近登录时间 |
### CouponCard(券卡片,`coupons` 与 `feed` 的 `items` 元素)
| 字段 | 类型 | 说明 |
|---|---|---|
| `product_view_sign` | string | **换链主键**(传给 `referral-link` |
| `platform` | int | `1`=外卖/到家, `2`=到店 |
| `biz_line` | int \| null | 到店子类:1到餐 2到综 3酒店 4门票 |
| `name` | string | 商品名 |
| `head_image_url` | string | 头图;后端已**统一**拼缩放参数 `@375w_375h_1e_1c.webp`(美团 CDN 服务端缩到 124dp + 转 WebP,实测省 7699%),客户端直接用 |
| `brand_name` | string \| null | 品牌名 |
| `brand_logo_url` | string \| null | 品牌 logo |
| `sell_price` | string | 现价 |
| `original_price` | string | 原价 |
| `discount_amount` | string | 优惠额 = 原价 现价 |
| `commission_rate` | string | 佣金比例,如 `"1.4%"` |
| `commission_amount` | string \| null | 预估佣金(元) |
| `sale_volume` | string \| null | 销量 |
| `poi_name` | string \| null | 最近门店名 |
| `distance_text` | string \| null | 格式化距离,如 `"600m"` / `"1.5km"` |
| `distance_meters` | float \| null | 原始距离(米) |
| `available_poi_num` | int \| null | 可用门店数 |
| `coupon_num` | int \| null | 券张数 |
| `valid_days` | int \| null | 券有效天数 |
| `price_label` | string \| null | 如 `"15天低价"` |
| `rank_label` | string \| null | 如 `"2小时北京外卖销量榜第1名"` |
| `rating_label` | string \| null | 如 `"4.6分"` |
---
## /media 静态服务
用户上传文件(头像/反馈截图)落盘到 `settings.MEDIA_ROOT`(默认 `./data/media/`),由 FastAPI 的 `StaticFiles` 挂在 `settings.MEDIA_URL_PREFIX`(默认 `/media`)对外暴露:
- `GET /media/avatars/<file>` — 用户头像(JPEG/PNG/WebP)
- `GET /media/feedback/<file>` — 反馈截图(同上)
**生产建议**:由 nginx 直接 serve `MEDIA_ROOT` 目录,绕过应用进程减少压力。
**URL 格式**:服务端返回**相对路径**(如 `/media/avatars/u1_a4f2b3c8e9d2e0a1.jpg`),客户端按自己的 `BASE_URL` 拼绝对地址——dev 下可能是 `10.0.2.2`/LAN IP/`127.0.0.1`,服务端不知道客户端怎么访问到自己。
**文件名服务端随机生成** `u<user_id>_<16 位 hex>.<ext>`,杜绝路径穿越与覆盖。
---
## 附:鉴权与刷新机制
- **签发**:登录成功后签发 access(HS256,2h) + refresh(30d),payload 含 `sub`(user_id)、`typ`(access/refresh)、`iat``exp`
- **携带**:客户端对需鉴权接口自动加 `Authorization: Bearer <access>`(登录类接口跳过)。
- **校验**`/me``/logout`:验签 → 验过期 → 验 `typ=access` → 查库确认用户存在且 `status=active`,任一不过 → 401。
- **续期**:access 过期触发 401 → 客户端用 refresh 调 `/refresh` 换新 token 对并重放原请求;refresh 也失效 → 清本地、跳登录。
- **无状态**:token 不落库,服务端无法主动吊销(logout 为占位),靠短 access 有效期限制泄露风险。JWT 密钥 `JWT_SECRET_KEY` 必须为高熵随机串(生产严禁用默认值)。