整轮 doc 跟齐 2026-05 落地但 doc 没补全/没更新的内容. 按 P0~P3 优先级处理.
P0 - 新 API doc + index 更新:
* docs/api/user-profile.md (新) PATCH /api/v1/user/profile 改昵称
* docs/api/user-avatar.md (新) POST /api/v1/user/avatar 上传头像(魔数嗅探/旧文件清理)
* docs/api/user-delete.md (新) DELETE /api/v1/user 注销账号(软删+匿名化)
* docs/api/feedback.md (新) POST /api/v1/feedback 提交反馈(含截图)
* docs/api/README.md 加 User/Feedback 组 + /media 静态服务段 +
修 UserOut "无接口可改恒为 null" 过时描述
P0 - 美团降级 / 现有 doc 校准:
* docs/api/meituan-coupons.md 加 "未配 MT_CPS 凭证降级返空" 说明
* docs/api/meituan-referral-link.md 同上 + 客户端兜底建议
* docs/api/meituan-feed.md 区分两种空结果路径(未配 vs 调用失败) +
/health.mt_cps_configured 排障入口
* docs/integrations/meituan.md "未配凭证降级" + "feed 静默吞错" 写进集成层
* ad-reward-status.md PR #5 commit 已自带 doc 更新, 复核无需再改
P1 - 主文档大改:
* docs/后端技术实现.md
- §1 能力表加 用户资料/外卖比价/钱包/福利/广告/反馈 (从 3 条扩到 9 条)
- §1 数据模型纠正 "仅 1 张 user 表" -> 现有 10 张 + alembic_version
- §2 DB 行 PG 主 / SQLite 兜底 + 加 psycopg3 行
- §3 目录树补 user.py / feedback.py / media.py / repositories/feedback.py /
scripts 子项 / docs/postgres-migration.md
- §7 数据模型重写: 10 张业务表速查表 + alembic 合并迁移 f01db5d77dac 始末
- §8 部署: 加 init_postgres.py 一句话流程 + checklist 加 "DATABASE_URL 切 PG"
+ "psycopg3 已装" + "MEDIA_ROOT 可写"
- §10 已知问题: 加 美团降级 / agent 不鉴权 / MEDIA_ROOT serve / init_postgres bug /
alembic 多 head 风险; 删过时 SQLite 行
- 接口计数 11 -> 38
* docs/待办与技术债.md
- 已解决追加: PG 切换 / 外卖比价透传 / 用户资料反馈接口 / 美团降级 /
看广告 4 态冷却
- 新增 "P2 DB 与 Alembic 治理" 段: alembic 多 head 团队规约 (含本次撞坑实录) +
init_postgres.py 已知 5 处小 bug + MEDIA_ROOT serve
P2 - PG 迁移文档互指 + 迁移链补完:
* docs/数据库迁移.md
- 顶部加 postgres-migration.md / 后端技术实现.md 互指
- §6 迁移链 9 条 -> 12 条 (含 feedback_table / convert_dishes_jsonb /
merge_pg_jsonb_and_feedback_heads)
- §7 踩坑里 "多 head" 一行扩成完整故事 + 修复命令 + 团队预防规约
* docs/postgres-migration.md
- 顶部加 数据库迁移.md / 后端技术实现.md §10 互指
P3 - 上线清单:
* docs/看广告赚金币上线清单.md
- 设计要点加 PR #5 4 态 CTA + 后端权威派生说明 +
VIDEO_ROUND_REQUIRED_COUNT/VIDEO_ROUND_COOLDOWN_SECONDS 常量
- §E 端到端验收加 本轮冷却 / 弹窗 limit note / 跨设备一致 三项
所有最后更新日期同步到 2026-05-31.
This commit is contained in:
+28
-4
@@ -3,7 +3,7 @@
|
||||
> Base URL:生产 `https://app-api.shaguabijia.com`;本地联调 `http://<开发机>:8770`
|
||||
> 协议:HTTP / JSON,请求与响应体均 `application/json`,字段统一 **snake_case**
|
||||
> 鉴权:需鉴权的接口在请求头带 `Authorization: Bearer <access_token>`
|
||||
> 最后更新:2026-05-27
|
||||
> 最后更新:2026-05-31
|
||||
> 架构:`app/api/v1/` 只放很轻的接口层;穿山甲/微信支付/极光/短信/美团等 SDK 集成的重逻辑在 `app/integrations/`,实现细节见 [docs/integrations/](../integrations/README.md)。
|
||||
|
||||
---
|
||||
@@ -52,6 +52,15 @@
|
||||
| 32 | `GET /api/v1/ad/pangle-callback` | 验签 | [详情](./ad-pangle-callback.md) |
|
||||
| 33 | `GET /api/v1/ad/reward-status` | Bearer | [详情](./ad-reward-status.md) |
|
||||
| 34 | `POST /api/v1/ad/test-grant` | Bearer | [详情](./ad-test-grant.md) |
|
||||
| **用户资料**(前缀 `/api/v1/user`) |||
|
||||
| 35 | `PATCH /api/v1/user/profile` | Bearer | [详情](./user-profile.md) |
|
||||
| 36 | `POST /api/v1/user/avatar` | Bearer | [详情](./user-avatar.md) |
|
||||
| 37 | `DELETE /api/v1/user` | Bearer | [详情](./user-delete.md) |
|
||||
| **帮助与反馈**(前缀 `/api/v1/feedback`) |||
|
||||
| 38 | `POST /api/v1/feedback` | Bearer | [详情](./feedback.md) |
|
||||
| **静态资源**(StaticFiles 挂载,见下方 `/media` 静态服务) |||
|
||||
| - | `GET /media/avatars/<file>` | 无 | 用户头像;返回二进制图片 |
|
||||
| - | `GET /media/feedback/<file>` | 无 | 反馈截图;返回二进制图片 |
|
||||
|
||||
> ⚠️ 美团三个接口当前**无鉴权**,且 `referral-link` 的 `sid` 允许客户端传值覆盖默认渠道——见各接口"备注"。
|
||||
> `coupon/step` 及外卖比价的 `intent/recognize` / `price/step` 都透传到 pricebot-backend,**MVP 阶段均不鉴权**(device_id 透传,待补 JWT——见各接口详情)。
|
||||
@@ -99,9 +108,9 @@
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `id` | int | 用户主键 |
|
||||
| `phone` | string | 手机号 |
|
||||
| `nickname` | string \| null | 昵称(当前无接口可改,恒为 null) |
|
||||
| `avatar_url` | string \| null | 头像(同上) |
|
||||
| `phone` | string | 手机号(注销账号后变 `deleted_<id>` 占位释放唯一约束) |
|
||||
| `nickname` | string \| null | 昵称,经 [`PATCH /api/v1/user/profile`](./user-profile.md) 修改 |
|
||||
| `avatar_url` | string \| null | 头像相对 URL(`/media/avatars/...`),经 [`POST /api/v1/user/avatar`](./user-avatar.md) 上传 |
|
||||
| `register_channel` | string | 注册渠道:`jverify` / `sms` |
|
||||
| `status` | string | `active` / `disabled` / `deleted` |
|
||||
| `created_at` | datetime | 注册时间 |
|
||||
@@ -135,6 +144,21 @@
|
||||
|
||||
---
|
||||
|
||||
## /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`。
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# POST /api/v1/feedback — 提交反馈
|
||||
|
||||
> 所属:Feedback 组(前缀 `/api/v1/feedback`) | 鉴权:Bearer access_token | [← 返回 API 索引](./README.md)
|
||||
|
||||
## 入参
|
||||
**multipart/form-data**:
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `content` | string | ✓ | 反馈正文,**1-2000 字**(strip 后) |
|
||||
| `contact` | string | ✓ | 联系方式(微信/QQ/手机号),**1-128 字**,便于回访 |
|
||||
| `images` | file[] | ✗ | 截图,**最多 4 张**,每张走头像同款校验(JPEG/PNG/WebP,≤ 5 MB,魔数嗅探) |
|
||||
|
||||
## 出参
|
||||
响应 `200`:
|
||||
```json
|
||||
{
|
||||
"id": 123,
|
||||
"status": "new",
|
||||
"created_at": "2026-05-29T12:34:56Z"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `id` | int | 反馈单 ID |
|
||||
| `status` | string | `new`(待处理)/ `handled`(已处理) |
|
||||
| `created_at` | datetime | 提交时间(UTC) |
|
||||
|
||||
> 不返回上传的 image URL——这是给运营后台看的,客户端通常不需要。
|
||||
|
||||
## 错误码
|
||||
- `400` 内容为空 / 内容超 2000 字 / 联系方式为空 / 联系方式超 128 字 / 图片超 4 张 / 单图非法(空/过大/格式不对)
|
||||
- `401` 未带 token / token 无效或过期 / 用户被禁用
|
||||
- `422` 缺 `content` 或 `contact` 字段
|
||||
|
||||
## 说明
|
||||
- **反馈绑用户**:`feedback.user_id = current_user.id`,便于回访
|
||||
- **截图落盘**:存到 `settings.MEDIA_ROOT/feedback/`,URL 写进 `feedback.images`(JSON 列,SQLite 是 TEXT、PG 是 JSON/JSONB——为不影响现有迁移用 JSON 而非 JSONB)
|
||||
- **截图可读**:`GET /media/feedback/<file>` 静态服务暴露,详见 [API 索引 — /media 静态服务](./README.md#media-静态服务)
|
||||
- 文件名随机 `u<user_id>_<rand>.<ext>`,杜绝路径穿越
|
||||
@@ -23,7 +23,8 @@
|
||||
响应 `200`:`{ items: CouponCard[], has_next: bool, search_id: string|null }`。`CouponCard` 结构见 [API 索引](./README.md#复用数据结构)。
|
||||
|
||||
## 错误码
|
||||
- `502` 美团接口失败
|
||||
- `502` 美团接口失败(仅在**已配置 `MT_CPS_APP_KEY/APP_SECRET` 但调用失败**时;未配凭证场景见下)
|
||||
|
||||
## 说明
|
||||
当前 Android 客户端**未调用**此接口(搜索功能尚未实现),仅后端实现完整。
|
||||
- 当前 Android 客户端**未调用**此接口(搜索功能尚未实现),仅后端实现完整
|
||||
- **未配置 MT_CPS 凭证时降级**:`settings.mt_cps_configured == false` 时直接返 `{ items: [], has_next: false, search_id: null }`,**不报 502**。设计目的是让新开发机首屏不炸——后端业务层先不依赖 CPS。已配凭证但调美团失败时才走 502
|
||||
|
||||
@@ -17,9 +17,15 @@
|
||||
响应 `200`:`{ items: CouponCard[], has_next: bool, page: int }`。`CouponCard` 结构见 [API 索引](./README.md#复用数据结构)。
|
||||
|
||||
## 错误码
|
||||
无(见"说明"中的可观测性盲区)
|
||||
无(任何失败场景都返 200 + 空 items,见"说明"中的可观测性盲区)
|
||||
|
||||
## 说明
|
||||
后端在 `coupons` 之上封装的"伪推荐"——每页并发拉一次外卖、一次到店(到餐)榜单,按 **2 外卖 + 1 到店** 交叉去重。榜单组合写死 3 页(第1页爆款、第2页今日必推、第3页外卖精选+到店限时),**第 3 页起 `has_next=false`、第 4 页返空**。
|
||||
|
||||
⚠️ **可观测性盲区**:`feed` 内部对美团调用异常是静默吞掉返回空列表(不报 502、不打日志)。若 `items` 为空但无错误,优先用 `coupons` 接口逼出真实错误(它会以 502 暴露,如"MT_CPS_APP_KEY not configured")。
|
||||
**两种空结果路径**:
|
||||
- **未配置 `MT_CPS_APP_KEY/APP_SECRET`**:开头 `settings.mt_cps_configured` 判定后**直接返空**,完全不调美团
|
||||
- **已配置但美团调用失败**:`_fetch_topic` 的 `except MeituanCpsError` **静默返空**(不报 502、不打日志)
|
||||
|
||||
⚠️ **可观测性盲区**:两种路径在响应上**无法区分**。若 `items` 为空想区分:
|
||||
- 看 `settings.mt_cps_configured`(`/health` 接口有暴露此字段)→ 区分"配置缺失" vs "调用失败"
|
||||
- 改调 `coupons` 接口——它在"已配凭证但调用失败"时会 502 暴露错误细节(未配凭证它也降级返空,跟 feed 一致)
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
| `link_map` | object | 各类型链接 `{ "linkType": "url" }`,如 `{"1": "<H5>", "3": "<deeplink>"}` |
|
||||
|
||||
## 错误码
|
||||
- `502` 美团接口失败
|
||||
- `502` 美团接口失败(仅在**已配置 `MT_CPS_APP_KEY/APP_SECRET` 但调用失败**时;未配凭证场景见下)
|
||||
|
||||
## 说明
|
||||
客户端实际优先用 `link_map["3"]`(deeplink)拉起美团 App,失败降级 `link_map["1"]`(H5)。
|
||||
|
||||
⚠️ **安全**:`sid` 允许客户端传值覆盖服务端默认渠道,理论上他人可借本接口刷自己渠道的分佣;建议服务端锁定 `sid`、忽略客户端传值。
|
||||
- 客户端实际优先用 `link_map["3"]`(deeplink)拉起美团 App,失败降级 `link_map["1"]`(H5)
|
||||
- **未配置 MT_CPS 凭证时降级**:`settings.mt_cps_configured == false` 时直接返 `{ link: "", link_map: {} }`,**不报 502**。客户端拿到空 link 时跳转会失败——客户端应做兜底(toast "暂无可用链接"或不显示「抢」按钮)
|
||||
- ⚠️ **安全**:`sid` 允许客户端传值覆盖服务端默认渠道,理论上他人可借本接口刷自己渠道的分佣;建议服务端锁定 `sid`、忽略客户端传值
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# POST /api/v1/user/avatar — 上传头像
|
||||
|
||||
> 所属:User 组(前缀 `/api/v1/user`) | 鉴权:Bearer access_token | [← 返回 API 索引](./README.md)
|
||||
|
||||
## 入参
|
||||
**multipart/form-data**:
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `file` | file | 头像图片,**仅 JPEG / PNG / WebP**,**≤ 5 MB** |
|
||||
|
||||
> 服务端**按文件头魔数嗅探真实类型**(`FF D8 FF` / `89 50 4E 47` / `RIFF…WEBP`),不信任 Content-Type 与文件名;不在三种之内的一律拒。
|
||||
|
||||
## 出参
|
||||
响应 `200`:`UserOut`(`avatar_url` 字段已更新为新地址,形如 `/media/avatars/u<uid>_<rand>.jpg`)。结构见 [API 索引](./README.md#复用数据结构)。
|
||||
|
||||
> 返回**相对路径**——客户端按自己的 `BASE_URL` 拼绝对地址(dev 下 `10.0.2.2`/LAN IP 都可能,服务端不知道客户端怎么访问到自己)。读图走 [/media 静态服务](./README.md#media-静态服务) 的 `GET /media/...`。
|
||||
|
||||
## 错误码
|
||||
- `400` 空文件 / 超过 5 MB / 非 JPEG/PNG/WebP
|
||||
- `401` 未带 token / token 无效或过期 / 用户被禁用
|
||||
- `422` 缺 `file` 字段
|
||||
|
||||
## 说明
|
||||
- **旧头像清理**:落库成功后才删旧文件,避免"删了新的没存上"的丢图风险;非本服务托管的 URL(如微信头像)不动
|
||||
- **文件名服务端随机生成** `u<user_id>_<16 位 hex>.<ext>`,杜绝路径穿越与覆盖
|
||||
- 存储目录:`settings.MEDIA_ROOT/avatars/`,默认 `data/media/avatars/`(生产 nginx 注意把它放共享卷或对象存储,见 [后端技术实现.md](../后端技术实现.md))
|
||||
@@ -0,0 +1,18 @@
|
||||
# DELETE /api/v1/user — 注销账号
|
||||
|
||||
> 所属:User 组(前缀 `/api/v1/user`) | 鉴权:Bearer access_token | [← 返回 API 索引](./README.md)
|
||||
|
||||
## 入参
|
||||
无(身份取自 Header token)
|
||||
|
||||
## 出参
|
||||
响应 `200`:`{ "ok": true }`
|
||||
|
||||
## 错误码
|
||||
- `401` 未带 token / token 无效或过期 / 用户被禁用(已注销账号再次调返 401,因 `status=deleted` 不再 active)
|
||||
|
||||
## 说明
|
||||
- **软删除 + 匿名化**:`status="deleted"` + `phone="deleted_<id>"`(释放 phone 唯一约束,允许同号码重新注册成全新账号)+ `nickname`/`avatar_url` 清空。行保留用于审计 / 关联表外键完整性
|
||||
- **头像文件物理删除**:本服务托管的头像文件(`/media/avatars/...`)同时删盘;微信头像等外部 URL 不动
|
||||
- **后续登录**:`status=deleted` 的行登录接口校验不过,所以"注销账号"后旧 token 仍能用直到自然过期(2h),但一过期就再也换不出新 token。客户端应在调本接口后立刻清本地 token
|
||||
- **未来若加 jti 黑名单**:本接口应同时把当前 access/refresh 加入黑名单实现"立即吊销"
|
||||
@@ -0,0 +1,20 @@
|
||||
# PATCH /api/v1/user/profile — 修改昵称
|
||||
|
||||
> 所属:User 组(前缀 `/api/v1/user`) | 鉴权:Bearer access_token | [← 返回 API 索引](./README.md)
|
||||
|
||||
## 入参
|
||||
请求体 JSON:
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `nickname` | string | 昵称,**1-16 字**(前后空格自动 strip,strip 后为空判 400) |
|
||||
|
||||
## 出参
|
||||
响应 `200`:`UserOut`(返回更新后的完整用户对象,`nickname` 字段已更新)。结构见 [API 索引](./README.md#复用数据结构)。
|
||||
|
||||
## 错误码
|
||||
- `401` 未带 token / token 无效或过期 / 用户被禁用
|
||||
- `422` 字段校验失败(`nickname` 缺失 / 长度超 16 字 / 全空白)
|
||||
|
||||
## 说明
|
||||
- **服务端持久化**:写入 `user.nickname` 列。这是昵称的唯一数据源——客户端之前的本地缓存退登即丢,改完后下次登录走 [`/auth/me`](./auth-me.md) 与登录响应回传仍生效
|
||||
- **strip 行为**:服务端会先去除前后空白再校验长度,所以 `" bob "` 等价于 `"bob"`
|
||||
Reference in New Issue
Block a user