diff --git a/docs/README.md b/docs/README.md index 503dd92..bf74c75 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,32 +1,83 @@ -# 后端文档库(docs/) +# 文档索引 -`shaguabijia-app-server` 的文档都在这里。结构:**根目录放总览,其余按领域/用途分目录**。 +项目文档结构说明。后续大模型增补/更新文档时,按此分类找到对应目录。 -## 根目录 +--- -| 文档 | 作用 | -|---|---| -| [后端技术实现.md](./后端技术实现.md) | **后端技术方案总览**:业务概览、分层架构与目录、登录链路、美团 CPS、领券/比价透传、数据模型、配置与部署、已知问题。想了解"整个后端怎么回事"先看这份。 | -| README.md | 本文件:文档库索引/传送门。 | +## API 接口文档 (`api/`) -## 子目录 +按业务领域分类,每个子目录对应一类接口。 -| 目录 | 作用 | -|---|---| -| [api/](./api/) | **API 接口文档**:"索引 + 一接口一文件"(组织方式见下)。想查"某个接口的协议"看这里。 | -| [database/](./database/) | **数据库文档**:每张表一个文件(表结构/字段/索引/约定) + 两份迁移指南——[数据库迁移.md](./database/数据库迁移.md)(Alembic 操作:clone 后建表/日常升级/新增迁移/多 head 排查) + [postgres-migration.md](./database/postgres-migration.md)(SQLite → PostgreSQL 切换步骤,配套 `scripts/init_postgres.py`)。想"把库跑起来 / 改表结构 / 查某张表"看这里。 | -| [integrations/](./integrations/) | **集成层实现文档**:穿山甲验签 / 微信支付 / 极光 / 短信 / 美团 CPS 等 SDK 集成的签名、加解密、协议细节与踩坑。想知道"接外部服务那块到底怎么实现"看这里。 | -| [guides/](./guides/) | **开发 / 上线 / 功能 指南**:[待办与技术债.md](./guides/待办与技术债.md)(跨前后端 backlog,P1 鉴权/用户绑定、引擎移植待办等,"还欠什么、以后要补什么"看这份) + [看广告赚金币上线清单.md](./guides/看广告赚金币上线清单.md)(看广告发奖上线 checklist) + [邀请功能-实现原理与本地测试.md](./guides/邀请功能-实现原理与本地测试.md)(invite-mvp 实现原理 + 本地内网全链路测试) + [CPS发券分发与微信授权.md](./guides/CPS发券分发与微信授权.md)(**CPS 发券系统交接文档**:设群/建活动/生成落地页短链/美团对账/统计 + 微信网页授权拿 openid 做用户级统计;含数据流/表/平台差异/端点/配置/代码地图/排障/技术债)。 | +### 分类目录 -## api/ 目录是怎么组织的(传送门式) +| 目录 | 分类 | URL 前缀 | 说明 | +|------|------|----------|------| +| [api/auth/](api/auth/) | 认证 | `/api/v1/auth` | 用户登录(极光一键登录/短信验证码)、Token 刷新(access + refresh)、登出、当前用户信息查询 | +| [api/ad/](api/ad/) | 广告 | `/api/v1/ad` | 穿山甲 S2S 回调验签发奖、激励视频/信息流广告奖励结算、eCPM 上报、发奖状态查询、测试发奖(仅本地) | +| [api/wallet/](api/wallet/) | 钱包 | `/api/v1/wallet` | 账户资产查询(金币/现金余额)、金币与现金流水、兑换规则与执行、绑定/解绑微信、提现申请/状态/记录、免确认收款授权 | +| [api/coupon/](api/coupon/) | 领券 | `/api/v1/coupon` | CPS 领券透传(step/session)、引导窗频控(should-show/shown/dismiss/reset)、每日完成状态、累计领券统计 | +| [api/compare/](api/compare/) | 比价记录 | `/api/v1/compare` | 比价记录上报/列表/详情/统计、比价战绩里程碑查询及奖励领取(按成功比价数解锁金币) | +| [api/savings/](api/savings/) | 省钱 | `/api/v1/savings` + `/api/v1/platform/savings-feed` | 省钱大作战(battle)、省钱明细/汇总、平台省钱动态 Feed | +| [api/signin/](api/signin/) | 签到 | `/api/v1/signin` | 每日签到执行、签到加速(看广告多领)、签到状态查询 | +| [api/tasks/](api/tasks/) | 任务 | `/api/v1/tasks` | 任务列表查询、任务奖励领取(按 task_key) | +| [api/invite/](api/invite/) | 邀请 | `/api/v1/invite` | 邀请码/分享链接生成、已邀请列表、邀请绑定(支持 clipboard/manual/fingerprint 三种归因) | +| [api/user/](api/user/) | 用户 | `/api/v1/user` | 个人资料编辑(昵称)、头像上传、新手引导状态/完成标记、注销账号 | +| [api/device/](api/device/) | 设备 | `/api/v1/device` | 设备注册/推送 token 更新、无障碍存活心跳、掉线检测(后置 pull)、告警确认 | +| [api/platform/](api/platform/) | 平台配置 | `/api/v1/platform` | 平台统计数据、Feature Flag 开关、广告配置(穿山甲 ID)、App 版本更新检查(OTA);全部不鉴权 | +| [api/intent/](api/intent/) | 意图/电商 | `/api/v1/intent` + `/api/v1/price` + `/api/v1/ecom` | 比价意图识别(Phase 1 单次/多帧/预券)、电商意图识别、比价步进(Phase 2) | +| [api/meituan/](api/meituan/) | 美团 CPS | `/api/v1/meituan` | 美团券列表/信息流/推广链接/销量榜;全部无鉴权 | +| [api/other/](api/other/) | 其它 | 分散 | 健康检查、CPS 短链重定向(含微信 OAuth)、反馈提交/配置/记录、埋点上报、订单上报、更低价上报、Trace 收尾 | +| [api/admin/](api/admin/) | 管理后台 | `/admin/api` | Admin 独立子应用,二级目录按子资源拆分:`auth/`(登录)、`users/`(用户管理/财务操作)、`wallet/`(流水查询)、`withdraws/`(提现审核/对账)、`feedbacks/`(反馈处理)、`admins/`(管理员管理)、`ad/`(广告对账/收益);单文件留根:审计日志、数据大盘、跑马灯种子、统计概览 | +| [api/internal/](api/internal/) | 内部接口 | `/internal` | 服务间调用(pricebot→app-server):价格事实回写、店铺映射、启动确认样本、App 版本写入;X-Internal-Secret 鉴权 | -接口多了之后,单个大文件会臃肿、难维护,所以拆成 **一个索引 + 一个接口一个文件**: +### 接口索引入口 -- **[api/README.md](./api/README.md) — 入口/传送门** - 一张总览表列出**全部接口**(方法、路径、鉴权),每行链接到该接口的独立文档;另含**通用约定**(错误码、时间格式等)和**复用数据结构**(`TokenPair` / `UserOut` / `CouponCard` 等)。它本身不展开每个接口的细节,只负责"指路"。 -- **api/<模块>-<接口>.md — 单接口文档** - 每个接口一个文件,只写自己的入参 / 出参 / 错误码 / 说明。命名按 `<模块>-<接口>`,如 `auth-jverify-login.md`、`coupon-step.md`、`meituan-feed.md`。 +完整接口列表(含路径、方法、鉴权方式)见 [api/README.md](api/README.md)。 -**查某个接口的协议**:先打开 [api/README.md](./api/README.md) 的总览表 → 找到接口 → 点链接进对应文档。 +### 文档命名规则 -> **新增接口时**:在 `api/` 下加一个 `<模块>-<接口>.md`,并到 `api/README.md` 总览表里补一行链接。这样索引始终是唯一的"传送门",细节各自独立、互不干扰。 +单端点文档按 URL 路径命名:`{prefix}-{resource}.md`(如 `wallet-withdraw.md`、`auth-sms-send.md`)。 +含子资源的合并文档按族命名(如 `device-liveness.md` 覆盖 register/heartbeat/liveness/liveness-ack 四个端点)。 +文件名唯一确定文档位置:大类目录 + 文件名前缀 → 直接匹配。 + +--- + +## 数据库文档 (`database/`) + +数据库表结构说明,每表一个文件,[database/OVERVIEW.md](database/OVERVIEW.md) 为索引入口。 + +--- + +## 第三方集成 (`integrations/`) + +外部 SDK/API 集成架构与实现说明:[integrations/README.md](integrations/README.md)。 + +| 文件 | 说明 | +|------|------| +| [jiguang.md](integrations/jiguang.md) | 极光一键登录(REST 验证 + RSA 解密) | +| [pangle.md](integrations/pangle.md) | 穿山甲广告 S2S 回调 | +| [wxpay.md](integrations/wxpay.md) | 微信支付 V3(提现/授权) | +| [meituan.md](integrations/meituan.md) | 美团 CPS 网关 | + +--- + +## 开发指南 (`guides/`) + +| 文件 | 说明 | +|------|------| +| [邀请功能-实现原理与本地测试.md](guides/邀请功能-实现原理与本地测试.md) | 邀请系统实现细节 | +| [CPS发券分发与微信授权.md](guides/CPS发券分发与微信授权.md) | CPS 发券 + 微信网页授权流程 | +| [看广告赚金币上线清单.md](guides/看广告赚金币上线清单.md) | 广告功能上线检查清单 | +| [待办与技术债.md](guides/待办与技术债.md) | 待办事项与技术债务 | + +--- + +## 架构与设计 (`superpowers/`) + +需求规格与设计文档,按 `YYYY-MM-DD--design.md` 命名。 + +--- + +## 后端技术实现 (`后端技术实现.md`) + +后端整体技术架构说明。 diff --git a/docs/api/README.md b/docs/api/README.md index 6c7bcd0..b1d3377 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -3,7 +3,7 @@ > Base URL:生产 `https://app-api.shaguabijia.com`;本地联调 `http://<开发机>:8770` > 协议:HTTP / JSON,请求与响应体均 `application/json`,字段统一 **snake_case** > 鉴权:需鉴权的接口在请求头带 `Authorization: Bearer ` -> 最后更新:2026-06-23(补全此前缺整族的端点:device 无障碍存活监控、internal 内部回写、CPS 短链落地、report/invite/wxpay,及 platform/coupon/feedback/user 的零散读端点;device/internal/cps-redirect 三族新建单文件文档。上一次 2026-06-18 补录比价透传端点与 `meituan/top-sales`) +> 最后更新: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)。 --- @@ -12,146 +12,154 @@ | # | 方法 + 路径 | 鉴权 | 详情 | |---|---|---|---| -| 1 | `GET /health` | 无 | [详情](./health.md) | -| 2 | `POST /api/v1/auth/jverify-login` | 无 | [详情](./auth-jverify-login.md) | -| 3 | `POST /api/v1/auth/sms/send` | 无 | [详情](./auth-sms-send.md) | -| 4 | `POST /api/v1/auth/sms/login` | 无 | [详情](./auth-sms-login.md) | -| 5 | `POST /api/v1/auth/refresh` | 无 | [详情](./auth-refresh.md) | -| 6 | `GET /api/v1/auth/me` | Bearer | [详情](./auth-me.md) | -| 7 | `POST /api/v1/auth/logout` | Bearer | [详情](./auth-logout.md) | -| 8 | `POST /api/v1/coupon/step` | 无 | [详情](./coupon-step.md)(透传 pricebot + best-effort 写 `coupon_*` 三表) | -| 8a | `POST /api/v1/coupon/prompt/shown` | 无 | 引导窗弹出即上报(按 device+package+日记 `shown`,今天这个 App 不再自动弹)(无单独文档) | -| 8b | `POST /api/v1/coupon/prompt/dismiss` | 无 | 用户拒绝/关闭引导窗(透传链路看不到拒绝,客户端通知)(无单独文档) | -| 8c | `GET /api/v1/coupon/prompt/should-show` | 无 | 切到外卖 App 时是否还应弹引导窗(`device_id`+`package`)(无单独文档) | -| 8d | `POST /api/v1/coupon/prompt/reset` | 无 | 重置今日引导窗 engagement(开发测频控用)(无单独文档) | -| 8e | `GET /api/v1/coupon/completed-today` | 无 | 这台设备今天是否已跑完整轮领券(首页「去领取」卡置灰源)(无单独文档) | -| 8f | `POST /api/v1/coupon/completed-today/reset` | 无 | 重置今日已完成(开发用)(无单独文档) | -| 8g | `GET /api/v1/coupon/stats` | Bearer | 累计领券数(「我的」页战绩卡;按 user_id 聚合,**鉴权**)(无单独文档) | -| 9 | `POST /api/v1/meituan/coupons` | 无 | [详情](./meituan-coupons.md) | -| 10 | `POST /api/v1/meituan/feed` | 无 | [详情](./meituan-feed.md) | -| 11 | `POST /api/v1/meituan/referral-link` | 无 | [详情](./meituan-referral-link.md) | -| 11a | `POST /api/v1/meituan/top-sales` | 无 | [详情](./meituan-top-sales.md)(销量榜:离线库 `meituan_coupon` 按销量降序 + 跨源去重,不实时打美团) | +| 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` | 无 | [详情](./compare-intent-recognize.md)(Phase 1 意图识别,单次,多数源) | -| 12a | `POST /api/v1/intent/precoupon/step` | 无 | Phase 0 意图识别前先用券,仅美团源(透传,无单独文档) | -| 12b | `POST /api/v1/intent/step` | 无 | Phase 1 多帧意图识别,仅淘宝源,循环到 done(透传,无单独文档) | -| 13 | `POST /api/v1/price/step` | 无 | [详情](./compare-price-step.md)(Phase 2 步进) | -| 13a | `POST /api/v1/trace/finalize` | 无 | 比价 trace 收尾上云,终止/未识别拿 trace_url(透传,无单独文档) | +| 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-record-report.md) | -| 12b | `GET /api/v1/compare/records` | Bearer | [详情](./compare-records.md) | -| 12c | `GET /api/v1/compare/records/{id}` | Bearer | [详情](./compare-record-detail.md) | -| 12e | `GET /api/v1/compare/stats` | Bearer | [详情](./compare-stats.md)(「我的」页省钱战绩卡:完成比价数 + 累计发现可省) | +| 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-milestones.md) | -| 12e | `POST /api/v1/compare/milestones/{milestone}/claim` | Bearer | [详情](./compare-milestone-claim.md) | +| 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-liveness.md)(注册设备/更新极光 push token) | -| D2 | `POST /api/v1/device/heartbeat` | Bearer | [详情](./device-liveness.md)(无障碍服务存活心跳,心跳也能自注册) | -| D3 | `GET /api/v1/device/liveness` | Bearer | [详情](./device-liveness.md)(进 App 查本机是否被判掉线过) | -| D4 | `POST /api/v1/device/liveness/ack` | Bearer | [详情](./device-liveness.md)(确认已弹引导,清掉线告警) | +| 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 | 提交上报(multipart:`comparison_record_id`/`reported_platform_id`/`reported_price`(元) + 1~4 张截图;原最低价反查 `comparison_record.best_*` 校验须更低)(无单独文档) | -| R2 | `GET /api/v1/report/records` | Bearer | 上报记录列表(`?status=` pending/approved/rejected 可选筛选)(无单独文档) | +| 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 | 我的邀请码 + 分享链接 + 已邀人数/已得金币(无单独文档) | -| I2 | `GET /api/v1/invite/invitees` | Bearer | 我邀请的人列表(`limit`/`offset` 分页)(无单独文档) | -| I3 | `POST /api/v1/invite/landing-track` | 无 | 落地页 `dl.html` 访问上报指纹(剪贴板归因兜底;浏览器无 token)(无单独文档) | -| I4 | `POST /api/v1/invite/bind` | Bearer | 绑定邀请人;支持 clipboard/manual 邀请码 + fingerprint 指纹反查三种归因(无单独文档) | +| 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-account.md) | -| 15 | `GET /api/v1/wallet/coin-transactions` | Bearer | [详情](./wallet-coin-transactions.md) | -| 16 | `GET /api/v1/wallet/cash-transactions` | Bearer | [详情](./wallet-cash-transactions.md) | -| 17 | `GET /api/v1/wallet/exchange-info` | 无 | [详情](./wallet-exchange-info.md) | -| 18 | `POST /api/v1/wallet/exchange` | Bearer | [详情](./wallet-exchange.md) | -| 19 | `POST /api/v1/wallet/bind-wechat` | Bearer | [详情](./wallet-bind-wechat.md) | -| 20 | `POST /api/v1/wallet/unbind-wechat` | Bearer | [详情](./wallet-unbind-wechat.md) | -| 21 | `GET /api/v1/wallet/withdraw-info` | Bearer | [详情](./wallet-withdraw-info.md) | -| 22 | `POST /api/v1/wallet/withdraw` | Bearer | [详情](./wallet-withdraw.md) | -| 23 | `GET /api/v1/wallet/withdraw/status` | Bearer | [详情](./wallet-withdraw-status.md) | -| 24 | `GET /api/v1/wallet/withdraw-orders` | Bearer | [详情](./wallet-withdraw-orders.md) | +| 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-status.md) | -| 26 | `POST /api/v1/signin` | Bearer | [详情](./signin-do.md) | -| 26a | `POST /api/v1/signin/boost` | Bearer | [详情](./signin-boost.md) | +| 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-list.md) | -| 28 | `POST /api/v1/tasks/{task_key}/claim` | Bearer | [详情](./tasks-claim.md) | +| 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-summary.md) | -| 30 | `GET /api/v1/savings/battle` | Bearer | [详情](./savings-battle.md) | -| 31 | `GET /api/v1/savings/records` | Bearer | [详情](./savings-records.md) | +| 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-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) | -| 35 | `POST /api/v1/ad/ecpm-report` | Bearer | [详情](./ad-ecpm-report.md) | -| 35a | `POST /api/v1/ad/feed-reward` | Bearer | [详情](./ad-feed-reward.md) | -| 35b | `POST /api/v1/ad/reward-noshow` | Bearer | [详情](./ad-reward-noshow.md)(激励视频提前关闭/未发奖留痕,只记原因不发币) | -| 35c | `GET /api/v1/ad/feed-reward/units` | Bearer | 信息流广告今日已发份数/上限(配合 `feed-reward` 看进度)(无单独文档) | +| 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-profile.md) | -| 36 | `POST /api/v1/user/avatar` | Bearer | [详情](./user-avatar.md) | -| 36a | `POST /api/v1/user/onboarding/complete` | Bearer | 标记新手引导完成(按 账号+device_id 幂等,跨卸载重装持久)(无单独文档) | -| 36b | `GET /api/v1/user/onboarding/status` | Bearer | 查该 (账号,设备) 是否走过引导(运营在 admin 删记录即触发重走)(无单独文档) | -| 37 | `DELETE /api/v1/user` | Bearer | [详情](./user-delete.md) | +| 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 | [详情](./feedback.md) | -| 38a | `GET /api/v1/feedback/config` | Bearer | 反馈页「加群二维码」卡配置(开关 + 二维码图 + 三行文案)(无单独文档) | -| 38b | `GET /api/v1/feedback/records` | Bearer | 我的反馈历史(pending/adopted/rejected)(无单独文档) | +| 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-stats.md) | -| 40 | `GET /api/v1/platform/savings-feed` | 无 | [详情](./platform-savings-feed.md) | -| 40a | `GET /api/v1/platform/flags` | 无 | 客户端运营 feature flag(比价/领券期广告开关等),拉取后缓存(无单独文档) | -| 40b | `GET /api/v1/platform/ad-config` | 无 | 客户端拉广告配置(穿山甲 app_id + 各位 ID + 各场景开关;不含验签密钥)(无单独文档) | -| 40c | `GET /api/v1/platform/app-version` | 无 | 最新 App 版本(OTA 检查更新;与本机 versionCode 比)(无单独文档) | +| 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}` | 无 | [详情](./cps-redirect.md)(短链落地:微信授权拿 openid + 记点击 + 302 跳/淘宝 H5 落地页) | -| C2 | `POST /c/{code}/copy` | 无 | [详情](./cps-redirect.md)(淘宝落地页点「复制口令」记 `copy`) | -| C3 | `GET /wx/oauth/cb` | 无 | [详情](./cps-redirect.md)(微信网页授权回调;upsert `cps_wx_user` + 种 cookie,`include_in_schema=False`) | -| C4 | `GET /MP_verify_*.txt` | 无 | [详情](./cps-redirect.md)(微信「网页授权域名」归属校验文件,`include_in_schema=False`) | +| 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.md)(比价价格事实批量落 `price_observation`) | -| N2 | `GET /internal/store-mapping/lookup` | 内部密钥 | [详情](./internal.md)(按源平台店名反查目标平台已沉淀店铺 id/deeplink) | -| N3 | `POST /internal/store-mapping` | 内部密钥 | [详情](./internal.md)(跨平台店铺身份映射落 `store_mapping`) | -| N4 | `POST /internal/store-mapping/invalidate` | 内部密钥 | [详情](./internal.md)(标记某平台 shopId 缓存 deeplink 失效) | -| N5 | `POST /internal/launch-confirm-sample` | 内部密钥 | [详情](./internal.md)(启动确认窗兜底样本落 `launch_confirm_sample`) | -| N6 | `POST /internal/app-version` | 内部密钥 | [详情](./internal.md)(发布流程写最新 App 版本,落 `app_config`) | +| 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/` | 无 | 用户头像;返回二进制图片 | | - | `GET /media/feedback/` | 无 | 反馈截图;返回二进制图片 | | **运营后台 Admin**(独立子应用 `app/admin/`,前缀 `/admin/api`,独立进程 + 独立 admin JWT。鉴权列:`admin`=任意已登录管理员,`operator`/`finance`/`super_admin`=需对应角色(`super_admin` 恒通过)) ||| -| A1 | `POST /admin/api/auth/login` | 无 | [详情](./admin-auth-login.md) | -| A2 | `GET /admin/api/auth/me` | admin | [详情](./admin-auth-me.md) | -| A3 | `GET /admin/api/stats/overview` | admin | [详情](./admin-stats-overview.md) | -| A4 | `GET /admin/api/users` | admin | [详情](./admin-users-list.md) | -| A5 | `GET /admin/api/users/{user_id}` | admin | [详情](./admin-user-detail.md) | -| A6 | `POST /admin/api/users/{user_id}/status` | operator | [详情](./admin-user-status.md) | -| A7 | `POST /admin/api/users/{user_id}/coins` | finance | [详情](./admin-user-coins.md) | -| A8 | `POST /admin/api/users/{user_id}/cash` | finance | [详情](./admin-user-cash.md) | -| A9 | `GET /admin/api/wallet/coin-transactions` | admin | [详情](./admin-wallet-coin-transactions.md) | -| A10 | `GET /admin/api/wallet/cash-transactions` | admin | [详情](./admin-wallet-cash-transactions.md) | -| A11 | `GET /admin/api/withdraws` | admin | [详情](./admin-withdraws-list.md) | -| A12 | `POST /admin/api/withdraws/reconcile` | finance | [详情](./admin-withdraw-reconcile.md) | -| A13 | `POST /admin/api/withdraws/{out_bill_no}/refresh` | finance | [详情](./admin-withdraw-refresh.md) | -| A14 | `GET /admin/api/feedbacks` | admin | [详情](./admin-feedbacks-list.md) | -| A15 | `POST /admin/api/feedbacks/{feedback_id}/handle` | operator | [详情](./admin-feedback-handle.md) | -| A16 | `GET /admin/api/admins` | super_admin | [详情](./admin-admins-list.md) | -| A17 | `POST /admin/api/admins` | super_admin | [详情](./admin-admin-create.md) | -| A18 | `PATCH /admin/api/admins/{admin_id}` | super_admin | [详情](./admin-admin-update.md) | -| A19 | `GET /admin/api/audit-logs` | admin | [详情](./admin-audit-logs.md) | -| A20 | `GET /admin/api/dashboard-display` | admin | [详情](./admin-dashboard-display.md) | -| A21 | `PATCH /admin/api/dashboard-display/{metric}` | operator | [详情](./admin-dashboard-display.md) | -| A22 | `GET /admin/api/marquee-seeds` | admin | [详情](./admin-marquee-seeds.md) | -| A23 | `POST /admin/api/marquee-seeds` | operator | [详情](./admin-marquee-seeds.md) | -| A24 | `PATCH /admin/api/marquee-seeds/{seed_id}` | operator | [详情](./admin-marquee-seeds.md) | -| A25 | `DELETE /admin/api/marquee-seeds/{seed_id}` | operator | [详情](./admin-marquee-seeds.md) | -| A26 | `POST /admin/api/marquee-seeds/bulk` | operator | [详情](./admin-marquee-seeds.md) | -| A27 | `GET /admin/api/marquee-seeds/preview` | admin | [详情](./admin-marquee-seeds.md) | -| A28 | `GET /admin/api/ad-coin-audit` | admin | [详情](./admin-ad-coin-audit.md)(看广告金币公式复算对账,只读) | -| A29 | `GET /admin/api/ad-revenue-report` | admin | [详情](./admin-ad-revenue-report.md)(广告收益报表:按用户/日期/类型/应用/代码位 聚合 条数/收益/金币,只读) | +| 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` 允许客户端传值覆盖默认渠道——见各接口"备注"。 @@ -201,8 +209,8 @@ |---|---|---| | `id` | int | 用户主键 | | `phone` | string | 手机号(注销账号后变 `deleted_` 占位释放唯一约束) | -| `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) 上传 | +| `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 | 注册时间 | diff --git a/docs/api/ad-ecpm-report.md b/docs/api/ad/ad-ecpm-report.md similarity index 98% rename from docs/api/ad-ecpm-report.md rename to docs/api/ad/ad-ecpm-report.md index 6359b00..f7f03a6 100644 --- a/docs/api/ad-ecpm-report.md +++ b/docs/api/ad/ad-ecpm-report.md @@ -1,6 +1,6 @@ # POST /api/v1/ad/ecpm-report — 上报本次广告展示的 eCPM(内部收益统计) -> 所属:Ad 组(前缀 `/api/v1/ad`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Ad 组(前缀 `/api/v1/ad`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参 请求体:`EcpmReportIn` diff --git a/docs/api/ad-feed-reward.md b/docs/api/ad/ad-feed-reward.md similarity index 100% rename from docs/api/ad-feed-reward.md rename to docs/api/ad/ad-feed-reward.md diff --git a/docs/api/ad-pangle-callback.md b/docs/api/ad/ad-pangle-callback.md similarity index 98% rename from docs/api/ad-pangle-callback.md rename to docs/api/ad/ad-pangle-callback.md index 3d8e13c..b059afd 100644 --- a/docs/api/ad-pangle-callback.md +++ b/docs/api/ad/ad-pangle-callback.md @@ -1,6 +1,6 @@ # GET /api/v1/ad/pangle-callback — 穿山甲 GroMore 激励视频发奖回调(S2S) -> 所属:Ad 组(前缀 `/api/v1/ad`) | 鉴权:**无 JWT,靠验签**(穿山甲 GroMore 服务器调用) | 限流:同 IP ≤300 次/分 | [← 返回 API 索引](./README.md) +> 所属:Ad 组(前缀 `/api/v1/ad`) | 鉴权:**无 JWT,靠验签**(穿山甲 GroMore 服务器调用) | 限流:同 IP ≤300 次/分 | [← 返回 API 索引](../README.md) > > ⚠️ 我们客户端用 `useMediation(true)`(GroMore 融合),回调走 **GroMore 广告位层级**(规范见 supportcenter/26240),**不是**联盟代码位层级(5416)。两者密钥、响应格式都不同,别混。后台配置入口:**GroMore 聚合管理 → 搜广告位ID → 编辑 → 勾选「服务端激励回调」**(广告位层级配了就别再在代码位层级重复配,会冲突)。 > diff --git a/docs/api/ad-reward-noshow.md b/docs/api/ad/ad-reward-noshow.md similarity index 100% rename from docs/api/ad-reward-noshow.md rename to docs/api/ad/ad-reward-noshow.md diff --git a/docs/api/ad-reward-status.md b/docs/api/ad/ad-reward-status.md similarity index 98% rename from docs/api/ad-reward-status.md rename to docs/api/ad/ad-reward-status.md index 54f8f7f..44bfd9a 100644 --- a/docs/api/ad-reward-status.md +++ b/docs/api/ad/ad-reward-status.md @@ -1,6 +1,6 @@ # GET /api/v1/ad/reward-status — 今日看广告发奖进度 -> 所属:Ad 组(前缀 `/api/v1/ad`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Ad 组(前缀 `/api/v1/ad`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参 无(用户由 token 确定)。 diff --git a/docs/api/ad-test-grant.md b/docs/api/ad/ad-test-grant.md similarity index 97% rename from docs/api/ad-test-grant.md rename to docs/api/ad/ad-test-grant.md index c4a0638..490f96b 100644 --- a/docs/api/ad-test-grant.md +++ b/docs/api/ad/ad-test-grant.md @@ -1,6 +1,6 @@ # POST /api/v1/ad/test-grant — [仅本地联调]模拟穿山甲回调发奖 -> 所属:Ad 组(前缀 `/api/v1/ad`) | 鉴权:Bearer | 限流:同 IP ≤60 次/分 | [← 返回 API 索引](./README.md) +> 所属:Ad 组(前缀 `/api/v1/ad`) | 鉴权:Bearer | 限流:同 IP ≤60 次/分 | [← 返回 API 索引](../README.md) > > ⚠️ **仅本地联调**,受 `AD_REWARD_TEST_GRANT_ENABLED` 开关控制,**生产必须关闭**(默认 False → 一律 404)。 diff --git a/docs/api/ad/ad-watch-report.md b/docs/api/ad/ad-watch-report.md new file mode 100644 index 0000000..36ac112 --- /dev/null +++ b/docs/api/ad/ad-watch-report.md @@ -0,0 +1,46 @@ +# POST /api/v1/ad/watch-report — 上报激励视频观看时长 + +> 所属:Ad 组(前缀 `/api/v1/ad`) | 鉴权:Bearer | 限流:同 IP ≤120 次/分 | [← 返回 API 索引](../README.md) + +客户端在激励视频关闭(onAdClose)后上报本次实际观看秒数,服务端累计到当日总时长。当前产品只保留每日 500 次上限,DAILY_AD_WATCH_SECONDS_LIMIT=0 表示时长闸不启用;该接口仍保留用于旧客户端兼容和排查观看时长。 + +## 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `seconds` | int | ✅ (≥0) | 本次观看秒数,服务端夹 [0, MAX_SINGLE_WATCH_SECONDS] | + +Mock 入参: +```json +{ + "seconds": 28 +} +``` + +## 出参 + +响应 `200`:`WatchReportOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `watched_seconds_today` | int | 今日累计观看秒数 | +| `watch_seconds_limit` | int | 每日上限(秒);0 表示当前未启用时长闸 | +| `watch_seconds_remaining` | int | 今日剩余可观看秒数 | + +Mock 出参: +```json +{ + "watched_seconds_today": 128, + "watch_seconds_limit": 0, + "watch_seconds_remaining": 0 +} +``` + +## 错误码 +- `401` 未鉴权 / token 失效 +- `422` `seconds` 缺或为负 + +## 说明 +- 鉴权靠 Bearer,`user_id` 取自 JWT(不信 body) +- 后端只做累计 + 上限裁切,不据此发奖(发奖靠 S2S 回调) +- `watch_seconds_limit=0` 时客户端不据此拦截,按每日 500 次上限走 diff --git a/docs/api/admin-ad-coin-audit.md b/docs/api/admin/ad/admin-ad-coin-audit.md similarity index 99% rename from docs/api/admin-ad-coin-audit.md rename to docs/api/admin/ad/admin-ad-coin-audit.md index 2956994..65c39b8 100644 --- a/docs/api/admin-ad-coin-audit.md +++ b/docs/api/admin/ad/admin-ad-coin-audit.md @@ -1,6 +1,6 @@ # Admin 看广告金币审计 -> 所属:Admin 组(前缀 `/admin/api/ad-coin-audit`) | 鉴权:Admin Bearer(任意已登录 admin,只读) | [← 返回 API 索引](./README.md) +> 所属:Admin 组(前缀 `/admin/api/ad-coin-audit`) | 鉴权:Admin Bearer(任意已登录 admin,只读) | [← 返回 API 索引](../../README.md) 把「看视频赚金币」(`ad_reward_record`)和「比价信息流广告」(`ad_feed_reward_record`)两类发奖记录,用与**正式发奖完全相同**的公式 [`app/core/rewards.py` `calculate_ad_reward_coin`](../../app/core/rewards.py) 复算一遍 `expected_coin`,与实际入账的 `actual_coin` 对比,核对金币公式是否生效。**纯只读对账**,不发币、不改任何数据。 diff --git a/docs/api/admin-ad-revenue-report.md b/docs/api/admin/ad/admin-ad-revenue-report.md similarity index 99% rename from docs/api/admin-ad-revenue-report.md rename to docs/api/admin/ad/admin-ad-revenue-report.md index 898f9d4..b4c2b07 100644 --- a/docs/api/admin-ad-revenue-report.md +++ b/docs/api/admin/ad/admin-ad-revenue-report.md @@ -1,6 +1,6 @@ # Admin 广告收益报表 -> 所属:Admin 组(前缀 `/admin/api/ad-revenue-report`) | 鉴权:Admin Bearer(任意已登录 admin,只读) | [← 返回 API 索引](./README.md) +> 所属:Admin 组(前缀 `/admin/api/ad-revenue-report`) | 鉴权:Admin Bearer(任意已登录 admin,只读) | [← 返回 API 索引](../../README.md) 按 **用户 × 日期 × 广告类型 × 我们的应用 × 我们的代码位** 聚合,回答「每个用户某天、每类广告(激励视频 / 信息流 / 历史 Draw)分别**看了多少条**、**收益多少**、按现算法**发了多少金币**、广告来自**哪个应用的哪个代码位**」。**纯只读**,不发币、不改数据,也**不改发奖逻辑**。 diff --git a/docs/api/admin-audit-logs.md b/docs/api/admin/admin-audit-logs.md similarity index 98% rename from docs/api/admin-audit-logs.md rename to docs/api/admin/admin-audit-logs.md index bc93d9f..0ea9b19 100644 --- a/docs/api/admin-audit-logs.md +++ b/docs/api/admin/admin-audit-logs.md @@ -1,6 +1,6 @@ # GET /admin/api/audit-logs — 审计日志(谁改了什么,游标分页) -> 所属:Admin·Audit 组(前缀 `/admin/api/audit-logs`) | 鉴权:Bearer admin_token(角色:任意已登录 admin) | [← 返回 API 索引](./README.md) +> 所属:Admin·Audit 组(前缀 `/admin/api/audit-logs`) | 鉴权:Bearer admin_token(角色:任意已登录 admin) | [← 返回 API 索引](../README.md) ## 入参(query) | 字段 | 类型 | 必填 | 默认 | 说明 | diff --git a/docs/api/admin-dashboard-display.md b/docs/api/admin/admin-dashboard-display.md similarity index 99% rename from docs/api/admin-dashboard-display.md rename to docs/api/admin/admin-dashboard-display.md index 93eec20..e088e9b 100644 --- a/docs/api/admin-dashboard-display.md +++ b/docs/api/admin/admin-dashboard-display.md @@ -1,6 +1,6 @@ # Admin 首页数据配置 — 三统计展示模式 -> 所属:Admin 组(前缀 `/admin/api/dashboard-display`) | 鉴权:Admin Bearer(改需 operator/super) | [← 返回 API 索引](./README.md) +> 所属:Admin 组(前缀 `/admin/api/dashboard-display`) | 鉴权:Admin Bearer(改需 operator/super) | [← 返回 API 索引](../README.md) 配置客户端首页三个门面数字(帮助用户 / 完成比价 / 累计节省)的展示模式。每个指标可独立选 real/manual/random。用户侧读取见 [platform-stats](./platform-stats.md);表见 [ops_stat_config](../database/ops_stat_config.md)。 diff --git a/docs/api/admin-marquee-seeds.md b/docs/api/admin/admin-marquee-seeds.md similarity index 99% rename from docs/api/admin-marquee-seeds.md rename to docs/api/admin/admin-marquee-seeds.md index cc1e8dd..8478cb7 100644 --- a/docs/api/admin-marquee-seeds.md +++ b/docs/api/admin/admin-marquee-seeds.md @@ -1,6 +1,6 @@ # Admin 首页轮播种子管理 -> 所属:Admin 组(前缀 `/admin/api/marquee-seeds`) | 鉴权:Admin Bearer(改需 operator/super) | [← 返回 API 索引](./README.md) +> 所属:Admin 组(前缀 `/admin/api/marquee-seeds`) | 鉴权:Admin Bearer(改需 operator/super) | [← 返回 API 索引](../README.md) 管理首页轮播「真实+种子混播」的兜底种子。种子是「生成规则」:`masked_user` 可空(空→feed 随机合成名)、金额是 `[min_cents, max_cents]` 区间(feed 每次随机取值)。用户侧 feed 见 [platform-savings-feed](./platform-savings-feed.md);表见 [ops_marquee_seed](../database/ops_marquee_seed.md)。金额单位:分(前端 ÷100 显示元)。 diff --git a/docs/api/admin-stats-overview.md b/docs/api/admin/admin-stats-overview.md similarity index 98% rename from docs/api/admin-stats-overview.md rename to docs/api/admin/admin-stats-overview.md index b618eb5..6165445 100644 --- a/docs/api/admin-stats-overview.md +++ b/docs/api/admin/admin-stats-overview.md @@ -1,6 +1,6 @@ # GET /admin/api/stats/overview — 大盘核心指标 -> 所属:Admin·数据大盘 组(前缀 `/admin/api/stats`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,无 require_role) | [← 返回 API 索引](./README.md) +> 所属:Admin·数据大盘 组(前缀 `/admin/api/stats`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,无 require_role) | [← 返回 API 索引](../README.md) ## 入参 无 diff --git a/docs/api/admin-admin-create.md b/docs/api/admin/admins/admin-admin-create.md similarity index 98% rename from docs/api/admin-admin-create.md rename to docs/api/admin/admins/admin-admin-create.md index f93e2b7..f1d013c 100644 --- a/docs/api/admin-admin-create.md +++ b/docs/api/admin/admins/admin-admin-create.md @@ -1,6 +1,6 @@ # POST /admin/api/admins — 创建管理员 -> 所属:Admin·Accounts 组(前缀 `/admin/api/admins`) | 鉴权:Bearer admin_token(角色:super_admin) | [← 返回 API 索引](./README.md) +> 所属:Admin·Accounts 组(前缀 `/admin/api/admins`) | 鉴权:Bearer admin_token(角色:super_admin) | [← 返回 API 索引](../../README.md) ## 入参 **application/json**: diff --git a/docs/api/admin-admin-update.md b/docs/api/admin/admins/admin-admin-update.md similarity index 98% rename from docs/api/admin-admin-update.md rename to docs/api/admin/admins/admin-admin-update.md index a86117a..9b2247d 100644 --- a/docs/api/admin-admin-update.md +++ b/docs/api/admin/admins/admin-admin-update.md @@ -1,6 +1,6 @@ # PATCH /admin/api/admins/{admin_id} — 改角色/启停/重置密码 -> 所属:Admin·Accounts 组(前缀 `/admin/api/admins`) | 鉴权:Bearer admin_token(角色:super_admin) | [← 返回 API 索引](./README.md) +> 所属:Admin·Accounts 组(前缀 `/admin/api/admins`) | 鉴权:Bearer admin_token(角色:super_admin) | [← 返回 API 索引](../../README.md) ## 入参 **路径参数**: diff --git a/docs/api/admin-admins-list.md b/docs/api/admin/admins/admin-admins-list.md similarity index 97% rename from docs/api/admin-admins-list.md rename to docs/api/admin/admins/admin-admins-list.md index 951917e..3e01544 100644 --- a/docs/api/admin-admins-list.md +++ b/docs/api/admin/admins/admin-admins-list.md @@ -1,6 +1,6 @@ # GET /admin/api/admins — 管理员列表 -> 所属:Admin·Accounts 组(前缀 `/admin/api/admins`) | 鉴权:Bearer admin_token(角色:super_admin) | [← 返回 API 索引](./README.md) +> 所属:Admin·Accounts 组(前缀 `/admin/api/admins`) | 鉴权:Bearer admin_token(角色:super_admin) | [← 返回 API 索引](../../README.md) ## 入参 无(按 `id` 升序返回全部,无分页) diff --git a/docs/api/admin-auth-login.md b/docs/api/admin/auth/admin-auth-login.md similarity index 97% rename from docs/api/admin-auth-login.md rename to docs/api/admin/auth/admin-auth-login.md index c228bfa..db7e8d1 100644 --- a/docs/api/admin-auth-login.md +++ b/docs/api/admin/auth/admin-auth-login.md @@ -1,6 +1,6 @@ # POST /admin/api/auth/login — 管理员登录 -> 所属:Admin·Auth 组(前缀 `/admin/api/auth`) | 鉴权:无 | [← 返回 API 索引](./README.md) +> 所属:Admin·Auth 组(前缀 `/admin/api/auth`) | 鉴权:无 | [← 返回 API 索引](../../README.md) ## 入参 **application/json**: diff --git a/docs/api/admin-auth-me.md b/docs/api/admin/auth/admin-auth-me.md similarity index 96% rename from docs/api/admin-auth-me.md rename to docs/api/admin/auth/admin-auth-me.md index 057f2e5..dfa6764 100644 --- a/docs/api/admin-auth-me.md +++ b/docs/api/admin/auth/admin-auth-me.md @@ -1,6 +1,6 @@ # GET /admin/api/auth/me — 当前管理员 -> 所属:Admin·Auth 组(前缀 `/admin/api/auth`) | 鉴权:Bearer admin_token(角色:任意已登录 admin) | [← 返回 API 索引](./README.md) +> 所属:Admin·Auth 组(前缀 `/admin/api/auth`) | 鉴权:Bearer admin_token(角色:任意已登录 admin) | [← 返回 API 索引](../../README.md) ## 入参 无(身份取自 Header token) diff --git a/docs/api/admin-feedback-handle.md b/docs/api/admin/feedbacks/admin-feedback-handle.md similarity index 93% rename from docs/api/admin-feedback-handle.md rename to docs/api/admin/feedbacks/admin-feedback-handle.md index fff4b5f..c0ea532 100644 --- a/docs/api/admin-feedback-handle.md +++ b/docs/api/admin/feedbacks/admin-feedback-handle.md @@ -1,6 +1,6 @@ # POST /admin/api/feedbacks/{feedback_id}/handle — 标记反馈已处理 -> 所属:Admin·反馈 组(前缀 `/admin/api/feedbacks`) | 鉴权:Bearer admin_token(角色:`operator`,`super_admin` 恒通过,`require_role("operator")`) | [← 返回 API 索引](./README.md) +> 所属:Admin·反馈 组(前缀 `/admin/api/feedbacks`) | 鉴权:Bearer admin_token(角色:`operator`,`super_admin` 恒通过,`require_role("operator")`) | [← 返回 API 索引](../../README.md) ## 入参 - 路径:`feedback_id`(int) diff --git a/docs/api/admin-feedbacks-list.md b/docs/api/admin/feedbacks/admin-feedbacks-list.md similarity index 99% rename from docs/api/admin-feedbacks-list.md rename to docs/api/admin/feedbacks/admin-feedbacks-list.md index af0460f..277d593 100644 --- a/docs/api/admin-feedbacks-list.md +++ b/docs/api/admin/feedbacks/admin-feedbacks-list.md @@ -1,6 +1,6 @@ # GET /admin/api/feedbacks — 反馈工单列表(offset 分页 + 筛选/排序) -> 所属:Admin·反馈 组(前缀 `/admin/api/feedbacks`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,无 `require_role`,仅 `get_current_admin`) | [← 返回 API 索引](./README.md) +> 所属:Admin·反馈 组(前缀 `/admin/api/feedbacks`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,无 `require_role`,仅 `get_current_admin`) | [← 返回 API 索引](../../README.md) ## 入参(query) | 字段 | 类型 | 必填 | 默认 | 说明 | diff --git a/docs/api/admin-user-cash.md b/docs/api/admin/users/admin-user-cash.md similarity index 98% rename from docs/api/admin-user-cash.md rename to docs/api/admin/users/admin-user-cash.md index c9ea739..7cbc25b 100644 --- a/docs/api/admin-user-cash.md +++ b/docs/api/admin/users/admin-user-cash.md @@ -1,6 +1,6 @@ # POST /admin/api/users/{user_id}/cash — 手动增减/设值现金(带审计) -> 所属:Admin·用户 组(前缀 `/admin/api/users`) | 鉴权:Bearer admin_token(角色:`finance`,`super_admin` 恒通过) | [← 返回 API 索引](./README.md) +> 所属:Admin·用户 组(前缀 `/admin/api/users`) | 鉴权:Bearer admin_token(角色:`finance`,`super_admin` 恒通过) | [← 返回 API 索引](../../README.md) 主要用于给无现金用户直接发钱、好让其测试提现链路。 diff --git a/docs/api/admin-user-coins.md b/docs/api/admin/users/admin-user-coins.md similarity index 98% rename from docs/api/admin-user-coins.md rename to docs/api/admin/users/admin-user-coins.md index c126afc..1513b91 100644 --- a/docs/api/admin-user-coins.md +++ b/docs/api/admin/users/admin-user-coins.md @@ -1,6 +1,6 @@ # POST /admin/api/users/{user_id}/coins — 手动增减/设值金币(带审计) -> 所属:Admin·用户 组(前缀 `/admin/api/users`) | 鉴权:Bearer admin_token(角色:`finance`,`super_admin` 恒通过) | [← 返回 API 索引](./README.md) +> 所属:Admin·用户 组(前缀 `/admin/api/users`) | 鉴权:Bearer admin_token(角色:`finance`,`super_admin` 恒通过) | [← 返回 API 索引](../../README.md) ## 入参 - 路径:`user_id`(int) diff --git a/docs/api/admin-user-detail.md b/docs/api/admin/users/admin-user-detail.md similarity index 97% rename from docs/api/admin-user-detail.md rename to docs/api/admin/users/admin-user-detail.md index 36571fe..d0d14ab 100644 --- a/docs/api/admin-user-detail.md +++ b/docs/api/admin/users/admin-user-detail.md @@ -1,6 +1,6 @@ # GET /admin/api/users/{user_id} — 用户 360 详情 -> 所属:Admin·用户 组(前缀 `/admin/api/users`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,无 require_role) | [← 返回 API 索引](./README.md) +> 所属:Admin·用户 组(前缀 `/admin/api/users`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,无 require_role) | [← 返回 API 索引](../../README.md) ## 入参 - 路径:`user_id`(int) diff --git a/docs/api/admin-user-status.md b/docs/api/admin/users/admin-user-status.md similarity index 96% rename from docs/api/admin-user-status.md rename to docs/api/admin/users/admin-user-status.md index 68d496e..3645c33 100644 --- a/docs/api/admin-user-status.md +++ b/docs/api/admin/users/admin-user-status.md @@ -1,6 +1,6 @@ # POST /admin/api/users/{user_id}/status — 封禁/解封用户 -> 所属:Admin·用户 组(前缀 `/admin/api/users`) | 鉴权:Bearer admin_token(角色:`operator`,`super_admin` 恒通过) | [← 返回 API 索引](./README.md) +> 所属:Admin·用户 组(前缀 `/admin/api/users`) | 鉴权:Bearer admin_token(角色:`operator`,`super_admin` 恒通过) | [← 返回 API 索引](../../README.md) ## 入参 - 路径:`user_id`(int) diff --git a/docs/api/admin-users-list.md b/docs/api/admin/users/admin-users-list.md similarity index 97% rename from docs/api/admin-users-list.md rename to docs/api/admin/users/admin-users-list.md index f4ec2ba..b5df4a6 100644 --- a/docs/api/admin-users-list.md +++ b/docs/api/admin/users/admin-users-list.md @@ -1,6 +1,6 @@ # GET /admin/api/users — 用户列表(筛选+排序+分页) -> 所属:Admin·用户 组(前缀 `/admin/api/users`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,无 require_role) | [← 返回 API 索引](./README.md) +> 所属:Admin·用户 组(前缀 `/admin/api/users`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,无 require_role) | [← 返回 API 索引](../../README.md) ## 入参(query) | 字段 | 类型 | 必填 | 默认 | 说明 | diff --git a/docs/api/admin-wallet-cash-transactions.md b/docs/api/admin/wallet/admin-wallet-cash-transactions.md similarity index 98% rename from docs/api/admin-wallet-cash-transactions.md rename to docs/api/admin/wallet/admin-wallet-cash-transactions.md index e3942be..c35a1bd 100644 --- a/docs/api/admin-wallet-cash-transactions.md +++ b/docs/api/admin/wallet/admin-wallet-cash-transactions.md @@ -1,6 +1,6 @@ # GET /admin/api/wallet/cash-transactions — 现金流水(游标分页) -> 所属:Admin·钱包 组(前缀 `/admin/api/wallet`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,仅需 `get_current_admin`,无 `require_role`) | [← 返回 API 索引](./README.md) +> 所属:Admin·钱包 组(前缀 `/admin/api/wallet`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,仅需 `get_current_admin`,无 `require_role`) | [← 返回 API 索引](../../README.md) 跨用户查询全量现金流水,可按 `user_id` / `biz_type` 过滤。游标分页(`id` 倒序)。金额单位一律为**分**。 diff --git a/docs/api/admin-wallet-coin-transactions.md b/docs/api/admin/wallet/admin-wallet-coin-transactions.md similarity index 98% rename from docs/api/admin-wallet-coin-transactions.md rename to docs/api/admin/wallet/admin-wallet-coin-transactions.md index fc40802..a0a7fcf 100644 --- a/docs/api/admin-wallet-coin-transactions.md +++ b/docs/api/admin/wallet/admin-wallet-coin-transactions.md @@ -1,6 +1,6 @@ # GET /admin/api/wallet/coin-transactions — 金币流水(游标分页) -> 所属:Admin·钱包 组(前缀 `/admin/api/wallet`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,仅需 `get_current_admin`,无 `require_role`) | [← 返回 API 索引](./README.md) +> 所属:Admin·钱包 组(前缀 `/admin/api/wallet`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,仅需 `get_current_admin`,无 `require_role`) | [← 返回 API 索引](../../README.md) 跨用户查询全量金币流水,可按 `user_id` / `biz_type` 过滤。游标分页(`id` 倒序)。 diff --git a/docs/api/admin-withdraw-reconcile.md b/docs/api/admin/withdraws/admin-withdraw-reconcile.md similarity index 97% rename from docs/api/admin-withdraw-reconcile.md rename to docs/api/admin/withdraws/admin-withdraw-reconcile.md index 0cba53c..aa2e4ce 100644 --- a/docs/api/admin-withdraw-reconcile.md +++ b/docs/api/admin/withdraws/admin-withdraw-reconcile.md @@ -1,6 +1,6 @@ # POST /admin/api/withdraws/reconcile — 批量对账(扫超时 pending 单) -> 所属:Admin·提现 组(前缀 `/admin/api/withdraws`) | 鉴权:Bearer admin_token(角色:`finance`,`super_admin` 恒通过) | [← 返回 API 索引](./README.md) +> 所属:Admin·提现 组(前缀 `/admin/api/withdraws`) | 鉴权:Bearer admin_token(角色:`finance`,`super_admin` 恒通过) | [← 返回 API 索引](../../README.md) 扫描创建时间超过 `older_than_minutes` 分钟、仍为 `pending` 的提现单,逐单调微信查单并归一化(成功落 `success`;失败/已撤销则退款落 `failed`;查到 `WAIT_USER_CONFIRM` 视为用户放弃,撤单+退款)。用于解开"扣了款但转账没发起/没确认"的孤儿单。单笔失败不影响其余(内部 rollback 后继续,下轮再试)。 diff --git a/docs/api/admin-withdraw-refresh.md b/docs/api/admin/withdraws/admin-withdraw-refresh.md similarity index 97% rename from docs/api/admin-withdraw-refresh.md rename to docs/api/admin/withdraws/admin-withdraw-refresh.md index 6e4261c..145fa6a 100644 --- a/docs/api/admin-withdraw-refresh.md +++ b/docs/api/admin/withdraws/admin-withdraw-refresh.md @@ -1,6 +1,6 @@ # POST /admin/api/withdraws/{out_bill_no}/refresh — 单笔提现重试查单 -> 所属:Admin·提现 组(前缀 `/admin/api/withdraws`) | 鉴权:Bearer admin_token(角色:`finance`,`super_admin` 恒通过) | [← 返回 API 索引](./README.md) +> 所属:Admin·提现 组(前缀 `/admin/api/withdraws`) | 鉴权:Bearer admin_token(角色:`finance`,`super_admin` 恒通过) | [← 返回 API 索引](../../README.md) 对单笔提现单调微信查单并归一化:`SUCCESS`→`success`;`FAIL`/`CANCELLED`/`CLOSED`→退款+`failed`;查到 `WAIT_USER_CONFIRM` 视为用户放弃(`cancel_if_unconfirmed=True`),撤单+退款;`ACCEPTED`/`PROCESSING` 等仍在途则保持 `pending`。已是终态的单直接返回、不再查。 diff --git a/docs/api/admin-withdraws-list.md b/docs/api/admin/withdraws/admin-withdraws-list.md similarity index 97% rename from docs/api/admin-withdraws-list.md rename to docs/api/admin/withdraws/admin-withdraws-list.md index f90e6c6..12ae85d 100644 --- a/docs/api/admin-withdraws-list.md +++ b/docs/api/admin/withdraws/admin-withdraws-list.md @@ -1,6 +1,6 @@ # GET /admin/api/withdraws — 提现单列表(游标分页) -> 所属:Admin·提现 组(前缀 `/admin/api/withdraws`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,列表为只读,仅需 `get_current_admin`,无 `require_role`) | [← 返回 API 索引](./README.md) +> 所属:Admin·提现 组(前缀 `/admin/api/withdraws`) | 鉴权:Bearer admin_token(角色:任意已登录管理员,列表为只读,仅需 `get_current_admin`,无 `require_role`) | [← 返回 API 索引](../../README.md) 跨用户查询全量提现单,可按 `user_id` / `status` 过滤。游标分页(`id` 倒序)。 diff --git a/docs/api/auth-jverify-login.md b/docs/api/auth/auth-jverify-login.md similarity index 96% rename from docs/api/auth-jverify-login.md rename to docs/api/auth/auth-jverify-login.md index 182ea6e..c8cae76 100644 --- a/docs/api/auth-jverify-login.md +++ b/docs/api/auth/auth-jverify-login.md @@ -1,6 +1,6 @@ # POST /api/v1/auth/jverify-login — 极光一键登录 -> 所属:Auth 组(前缀 `/api/v1/auth`) | 鉴权:无 | [← 返回 API 索引](./README.md) +> 所属:Auth 组(前缀 `/api/v1/auth`) | 鉴权:无 | [← 返回 API 索引](../README.md) > > 集成实现:见 [integrations/jiguang](../integrations/jiguang.md)(极光核验链路、RSA 解密策略、私钥配对踩坑)。 diff --git a/docs/api/auth-logout.md b/docs/api/auth/auth-logout.md similarity index 86% rename from docs/api/auth-logout.md rename to docs/api/auth/auth-logout.md index 041c0f6..59b9333 100644 --- a/docs/api/auth-logout.md +++ b/docs/api/auth/auth-logout.md @@ -1,6 +1,6 @@ # POST /api/v1/auth/logout — 登出 -> 所属:Auth 组(前缀 `/api/v1/auth`) | 鉴权:Bearer access_token | [← 返回 API 索引](./README.md) +> 所属:Auth 组(前缀 `/api/v1/auth`) | 鉴权:Bearer access_token | [← 返回 API 索引](../README.md) ## 入参 无 diff --git a/docs/api/auth-me.md b/docs/api/auth/auth-me.md similarity index 87% rename from docs/api/auth-me.md rename to docs/api/auth/auth-me.md index 01eae87..fc69cb6 100644 --- a/docs/api/auth-me.md +++ b/docs/api/auth/auth-me.md @@ -1,6 +1,6 @@ # GET /api/v1/auth/me — 获取当前登录用户 -> 所属:Auth 组(前缀 `/api/v1/auth`) | 鉴权:Bearer access_token | [← 返回 API 索引](./README.md) +> 所属:Auth 组(前缀 `/api/v1/auth`) | 鉴权:Bearer access_token | [← 返回 API 索引](../README.md) ## 入参 无(身份取自 Header token) diff --git a/docs/api/auth-refresh.md b/docs/api/auth/auth-refresh.md similarity index 91% rename from docs/api/auth-refresh.md rename to docs/api/auth/auth-refresh.md index c2860df..b2c9dd2 100644 --- a/docs/api/auth-refresh.md +++ b/docs/api/auth/auth-refresh.md @@ -1,6 +1,6 @@ # POST /api/v1/auth/refresh — 刷新 token -> 所属:Auth 组(前缀 `/api/v1/auth`) | 鉴权:无(凭 body 里的 refresh_token) | [← 返回 API 索引](./README.md) +> 所属:Auth 组(前缀 `/api/v1/auth`) | 鉴权:无(凭 body 里的 refresh_token) | [← 返回 API 索引](../README.md) ## 入参 diff --git a/docs/api/auth-sms-login.md b/docs/api/auth/auth-sms-login.md similarity index 95% rename from docs/api/auth-sms-login.md rename to docs/api/auth/auth-sms-login.md index a858ef0..643db01 100644 --- a/docs/api/auth-sms-login.md +++ b/docs/api/auth/auth-sms-login.md @@ -1,6 +1,6 @@ # POST /api/v1/auth/sms/login — 手机号 + 验证码登录 -> 所属:Auth 组(前缀 `/api/v1/auth`) | 鉴权:无 | [← 返回 API 索引](./README.md) +> 所属:Auth 组(前缀 `/api/v1/auth`) | 鉴权:无 | [← 返回 API 索引](../README.md) > > 集成实现:见 [integrations/sms](../integrations/sms.md)(验证码校验逻辑)。 diff --git a/docs/api/auth-sms-send.md b/docs/api/auth/auth-sms-send.md similarity index 95% rename from docs/api/auth-sms-send.md rename to docs/api/auth/auth-sms-send.md index c2cb4df..9c046eb 100644 --- a/docs/api/auth-sms-send.md +++ b/docs/api/auth/auth-sms-send.md @@ -1,6 +1,6 @@ # POST /api/v1/auth/sms/send — 发送短信验证码 -> 所属:Auth 组(前缀 `/api/v1/auth`) | 鉴权:无 | [← 返回 API 索引](./README.md) +> 所属:Auth 组(前缀 `/api/v1/auth`) | 鉴权:无 | [← 返回 API 索引](../README.md) > > 集成实现:见 [integrations/sms](../integrations/sms.md)(mock 模式、频控、接真供应商 TODO)。 diff --git a/docs/api/compare-milestone-claim.md b/docs/api/compare/compare-milestone-claim.md similarity index 96% rename from docs/api/compare-milestone-claim.md rename to docs/api/compare/compare-milestone-claim.md index 97f2fd3..eae8f5e 100644 --- a/docs/api/compare-milestone-claim.md +++ b/docs/api/compare/compare-milestone-claim.md @@ -1,6 +1,6 @@ # POST /api/v1/compare/milestones/{milestone}/claim — 领取比价战绩里程碑奖励 -> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) 领取某一档(第 `milestone` 次)。⚠️ **当前不真发金币**(产品定,后续整体删除该功能):仍写 `comparison_milestone_claim`((user_id, milestone) 唯一)标记该档已领、**每档只能领一次**,但不调 diff --git a/docs/api/compare-milestones.md b/docs/api/compare/compare-milestones.md similarity index 97% rename from docs/api/compare-milestones.md rename to docs/api/compare/compare-milestones.md index 7933913..e8953e0 100644 --- a/docs/api/compare-milestones.md +++ b/docs/api/compare/compare-milestones.md @@ -1,6 +1,6 @@ # GET /api/v1/compare/milestones — 比价战绩里程碑进度 -> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) 福利页「记录比价战绩」的数据源。返回各档(第 1~6 次)解锁/领取状态。 diff --git a/docs/api/compare-record-detail.md b/docs/api/compare/compare-record-detail.md similarity index 95% rename from docs/api/compare-record-detail.md rename to docs/api/compare/compare-record-detail.md index eed1bcd..44c7075 100644 --- a/docs/api/compare-record-detail.md +++ b/docs/api/compare/compare-record-detail.md @@ -1,6 +1,6 @@ # GET /api/v1/compare/records/{record_id} — 比价记录详情 -> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) 单条比价记录详情,在列表项基础上额外带 `raw_payload`(客户端上报的原始全量),供未来 UI 展示任意细节。 diff --git a/docs/api/compare-record-report.md b/docs/api/compare/compare-record-report.md similarity index 98% rename from docs/api/compare-record-report.md rename to docs/api/compare/compare-record-report.md index 21085a4..cdd4523 100644 --- a/docs/api/compare-record-report.md +++ b/docs/api/compare/compare-record-report.md @@ -1,6 +1,6 @@ # POST /api/v1/compare/record — 上报一次比价结果(幂等) -> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) 比价 `done` 帧后,客户端用**带 JWT 的通道**上报一条比价结果,落 `comparison_record` 表,作为「我的比价记录」数据源 + 用户级行为画像。 diff --git a/docs/api/compare-records.md b/docs/api/compare/compare-records.md similarity index 97% rename from docs/api/compare-records.md rename to docs/api/compare/compare-records.md index 70d3889..b1051bf 100644 --- a/docs/api/compare-records.md +++ b/docs/api/compare/compare-records.md @@ -1,6 +1,6 @@ # GET /api/v1/compare/records — 比价记录列表(游标分页) -> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) 「我的比价记录」列表页数据源。按 `id` 倒序(最新在前)。 diff --git a/docs/api/compare-stats.md b/docs/api/compare/compare-stats.md similarity index 96% rename from docs/api/compare-stats.md rename to docs/api/compare/compare-stats.md index 454f5a7..89eeee0 100644 --- a/docs/api/compare-stats.md +++ b/docs/api/compare/compare-stats.md @@ -1,6 +1,6 @@ # GET /api/v1/compare/stats — 比价口径战绩(「我的」页省钱战绩卡) -> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参 无(用户由 token 确定)。 diff --git a/docs/api/coupon/coupon-completed-today.md b/docs/api/coupon/coupon-completed-today.md new file mode 100644 index 0000000..98183d5 --- /dev/null +++ b/docs/api/coupon/coupon-completed-today.md @@ -0,0 +1,67 @@ +# 今日领券完成状态(completed-today 族) + +> 所属:Coupon 组(前缀 `/api/v1/coupon`) | 鉴权:无(按 device_id 判断) | [← 返回 API 索引](../README.md) + +--- + +## GET /completed-today — 今天是否已完成整轮领券 + +判断这台设备今天是否跑到 done 帧。已完成 → 首页「去领取」卡置灰。 + +### 入参(query) + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `device_id` | string | ✅ | 设备 ID(需与领券循环上报的一致) | + +Mock 请求: +``` +GET /api/v1/coupon/completed-today?device_id=android_abc123def456 +``` + +### 出参 + +响应 `200`:`CouponCompletedTodayOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `completed` | bool | 今天是否已跑完整轮 | + +Mock 出参: +```json +{"completed": true} +``` + +--- + +## POST /completed-today/reset — 重置今日已完成 + +删这台设备今天的 completion → `has_completed_today` 变 false,首页「去领取」卡恢复可点。开发设置用。 + +### 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `device_id` | string | ✅ | 设备 ID | +| `package` | string | ❌ | App 包名(默认 "") | +| `user_id` | int \| null | ❌ | 登录用户 ID | + +Mock 入参: +```json +{ + "device_id": "android_abc123def456" +} +``` + +### 出参 + +```json +{"ok": true} +``` + +--- + +## 说明 +- 判断维度 `device_id`(客户端两端都用 ANDROID_ID) +- 用户决策 A 方案:到 done 即算完成,不管单券成败 +- MVP 不鉴权 diff --git a/docs/api/coupon/coupon-prompt.md b/docs/api/coupon/coupon-prompt.md new file mode 100644 index 0000000..7adcb17 --- /dev/null +++ b/docs/api/coupon/coupon-prompt.md @@ -0,0 +1,128 @@ +# 领券引导窗频控(prompt 族) + +> 所属:Coupon 组(前缀 `/api/v1/coupon`) | 鉴权:无(按 device_id 判断,MVP 阶段) | [← 返回 API 索引](../README.md) + +领券引导窗频控:今天这台设备**这个 App** 已弹过/领过/拒过 → 不再弹。各 App 独立(美团弹过不压淘宝/京东)。 + +--- + +## GET /prompt/should-show — 是否还应弹引导窗 + +客户端切到外卖 App 时查。 + +### 入参(query) + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `device_id` | string | ✅ | 设备 ID | +| `package` | string | ❌ | App 包名(默认 "",老客户端兼容全局态) | + +Mock 请求: +``` +GET /api/v1/coupon/prompt/should-show?device_id=android_abc&package=com.sankuai.meituan +``` + +### 出参 + +响应 `200`:`CouponPromptShouldShowOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `should_show` | bool | 今天是否还应弹引导窗 | + +Mock 出参: +```json +{"should_show": true} +``` + +--- + +## POST /prompt/shown — 引导窗弹出即上报 + +客户端弹出引导窗那刻调 → 记一条今日 engagement(shown),今天这个 App 不再自动弹。频控主判据管跨重装。 + +### 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `device_id` | string | ✅ | 设备 ID | +| `package` | string | ✅ | App 包名 | +| `user_id` | int \| null | ❌ | 登录用户 ID | + +Mock 入参: +```json +{ + "device_id": "android_abc123def456", + "package": "com.sankuai.meituan", + "user_id": 42 +} +``` + +### 出参 + +```json +{"ok": true} +``` + +--- + +## POST /prompt/dismiss — 用户拒绝/关闭引导窗 + +客户端点关闭时调 → 记 dismissed,今天这个 App 不再弹。 + +### 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `device_id` | string | ✅ | 设备 ID | +| `package` | string | ❌ | App 包名(默认 "") | +| `user_id` | int \| null | ❌ | 登录用户 ID | + +Mock 入参: +```json +{ + "device_id": "android_abc123def456", + "package": "com.sankuai.meituan" +} +``` + +### 出参 + +```json +{"ok": true} +``` + +--- + +## POST /prompt/reset — 重置今日引导窗状态 + +删这台设备今天的 engagement → 今天又能弹。开发测频控用。 + +### 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `device_id` | string | ✅ | 设备 ID | +| `package` | string | ❌ | App 包名(默认 "") | +| `user_id` | int \| null | ❌ | 登录用户 ID | + +Mock 入参: +```json +{ + "device_id": "android_abc123def456" +} +``` + +### 出参 + +```json +{"ok": true} +``` + +--- + +## 说明 +- 频控按 `(device, package, 日)`,各 App 独立 +- 弹出即占用今天一次(管跨重装),后续领取/拒绝再升级 type +- user_id 可选,登录态带上就一并记(资产留痕) +- MVP 不鉴权 diff --git a/docs/api/coupon/coupon-session.md b/docs/api/coupon/coupon-session.md new file mode 100644 index 0000000..7a27168 --- /dev/null +++ b/docs/api/coupon/coupon-session.md @@ -0,0 +1,75 @@ +# POST /api/v1/coupon/session — 领券任务流水上报 + +> 所属:Coupon 组(前缀 `/api/v1/coupon`) | 鉴权:无(按 device_id/trace_id 区分) | [← 返回 API 索引](../README.md) + +客户端两段上报一次领券流水(发起 / 收尾),按 `trace_id` upsert 到 `coupon_session`。供 admin「领券数据」看板算发起/完成数、耗时分位、机型维度。 + +## 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `trace_id` | string | ✅ | 领券 trace 标识(同领券循环 step 的 trace_id) | +| `device_id` | string | ✅ | 设备 ID(与领券循环一致) | +| `status` | string | ✅ | `started` / `completed` / `failed` / `abandoned` | +| `started_at_ms` | int | ✅ | 发起墙钟毫秒(客户端 `System.currentTimeMillis`) | +| `user_id` | int \| null | ❌ | 登录用户 ID(未登录可空) | +| `platforms` | list[string] \| null | ❌ | 勾选的平台列表 | +| `origin_package` | string \| null | ❌ | 发起领券的 App 包名 | +| `device_model` | string \| null | ❌ | 机型(如 `24115RA8EC`) | +| `rom` | string \| null | ❌ | ROM 信息(如 `MIUI 14.0.8`) | +| `app_env` | string \| null | ❌ | 应用环境(`prod` / `test`) | +| `elapsed_ms` | int \| null | ❌ | 全程耗时(ms),收尾帧必带 | +| `platform_elapsed` | dict \| null | ❌ | 各平台耗时(如 `{"meituan": 3200, "jd": 2800}`),收尾帧带 | +| `claimed_count` | int \| null | ❌ | 本场实际领到的券张数 | +| `trace_url` | string \| null | ❌ | trace 云端 URL(done 帧带) | + +Mock 入参(started): +```json +{ + "trace_id": "tr_20260703_a1b2c3d4", + "device_id": "android_abc123def456", + "status": "started", + "started_at_ms": 1719993600000, + "user_id": 42, + "platforms": ["meituan", "jd"], + "origin_package": "com.sankuai.meituan", + "device_model": "24115RA8EC", + "rom": "MIUI 14.0.8", + "app_env": "prod" +} +``` + +Mock 入参(completed): +```json +{ + "trace_id": "tr_20260703_a1b2c3d4", + "device_id": "android_abc123def456", + "status": "completed", + "started_at_ms": 1719993600000, + "user_id": 42, + "platforms": ["meituan", "jd"], + "origin_package": "com.sankuai.meituan", + "device_model": "24115RA8EC", + "rom": "MIUI 14.0.8", + "app_env": "prod", + "elapsed_ms": 12500, + "platform_elapsed": {"meituan": 5200, "jd": 4800}, + "claimed_count": 3, + "trace_url": "https://trace.shaguabijia.com/tr_20260703_a1b2c3d4" +} +``` + +## 出参 + +响应 `200`: +```json +{"ok": true} +``` + +## 错误码 +- `422` 必填字段缺失或类型不符 + +## 说明 +- 不鉴权(同领券循环 MVP,按 `device_id`/`trace_id`) +- 写库失败不连累客户端(fire-and-forget,吞掉返回 ok) +- 一次领券两段上报:发起(started)→ 收尾(completed/failed/abandoned) diff --git a/docs/api/coupon/coupon-stats.md b/docs/api/coupon/coupon-stats.md new file mode 100644 index 0000000..cdace56 --- /dev/null +++ b/docs/api/coupon/coupon-stats.md @@ -0,0 +1,33 @@ +# GET /api/v1/coupon/stats — 累计领券数 + +> 所属:Coupon 组(前缀 `/api/v1/coupon`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) + +「我的」页战绩卡「领取优惠券 X 张」数据源。该登录用户累计领到的券数(`SUM(claimed_count)`,与领券完成时给用户看的「本次领了 N 张」同源)。 + +**鉴权(CurrentUser)** — 区别于同文件不鉴权的 `/step` 透传与 `/prompt` 频控:个人战绩按 `user_id` 聚合,必须有登录态。 + +## 入参 + +无(`user_id` 从 JWT 取)。 + +## 出参 + +响应 `200`:`CouponStatsOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `coupon_count` | int | 累计领到的券张数 | + +Mock 出参: +```json +{ + "coupon_count": 42 +} +``` + +## 错误码 +- `401` 未鉴权 / token 失效 + +## 说明 +- 口径:`SUM(claimed_count)` — 各成功领券记录 pricebot 展示张数之和 +- 只算登录用户,按 `user_id` 聚合(非 device_id) diff --git a/docs/api/coupon-step.md b/docs/api/coupon/coupon-step.md similarity index 97% rename from docs/api/coupon-step.md rename to docs/api/coupon/coupon-step.md index 809183d..74442a0 100644 --- a/docs/api/coupon-step.md +++ b/docs/api/coupon/coupon-step.md @@ -1,6 +1,6 @@ # POST /api/v1/coupon/step — 一键领券任务步进(透传到 pricebot) -> 所属:Coupon 组(前缀 `/api/v1/coupon`) | 鉴权:Bearer access_token(客户端契约;Server MVP 阶段不强校验) | [← 返回 API 索引](./README.md) +> 所属:Coupon 组(前缀 `/api/v1/coupon`) | 鉴权:Bearer access_token(客户端契约;Server MVP 阶段不强校验) | [← 返回 API 索引](../README.md) ## 入参 任意 JSON body,**不做 schema 校验**,原样透传给上游。后端仅从中读 `device_id`、`trace_id`、`step` 用于日志。 diff --git a/docs/api/device-liveness.md b/docs/api/device/device-liveness.md similarity index 98% rename from docs/api/device-liveness.md rename to docs/api/device/device-liveness.md index 6021b5d..e0c34d5 100644 --- a/docs/api/device-liveness.md +++ b/docs/api/device/device-liveness.md @@ -1,6 +1,6 @@ # 设备 / 无障碍存活监控(device 族) -> 所属:device 组(前缀 `/api/v1/device`,源 `app/api/v1/device.py`) | 鉴权:**全部 Bearer**(设备绑登录用户) | [← 返回 API 索引](./README.md) +> 所属:device 组(前缀 `/api/v1/device`,源 `app/api/v1/device.py`) | 鉴权:**全部 Bearer**(设备绑登录用户) | [← 返回 API 索引](../README.md) > > 落库:[`device_liveness`](../database/device_liveness.md) 表;后台 `heartbeat_monitor_worker` 据此检出心跳超时的设备并召回。设计见 spec `accessibility-liveness-push.md`(推送版)+ `accessibility-liveness-pull-prompt.md`(后置 pull 提醒版)。#65 新增。 diff --git a/docs/api/compare-intent-recognize.md b/docs/api/intent/compare-intent-recognize.md similarity index 95% rename from docs/api/compare-intent-recognize.md rename to docs/api/intent/compare-intent-recognize.md index 51cb30e..e1a3d2c 100644 --- a/docs/api/compare-intent-recognize.md +++ b/docs/api/intent/compare-intent-recognize.md @@ -1,6 +1,6 @@ # POST /api/v1/intent/recognize — 外卖比价 Phase 1 意图识别(透传到 pricebot) -> 所属:Compare 组(前缀 `/api/v1`,外卖比价) | 鉴权:**无(MVP 阶段不鉴权)** | [← 返回 API 索引](./README.md) +> 所属:Compare 组(前缀 `/api/v1`,外卖比价) | 鉴权:**无(MVP 阶段不鉴权)** | [← 返回 API 索引](../README.md) ## 入参 任意 JSON body,**不做 schema 校验**,原样透传给上游。后端仅从中读 `device_id`、`trace_id`、`step` 用于日志。 diff --git a/docs/api/compare-price-step.md b/docs/api/intent/compare-price-step.md similarity index 95% rename from docs/api/compare-price-step.md rename to docs/api/intent/compare-price-step.md index 4bd3f42..ce904eb 100644 --- a/docs/api/compare-price-step.md +++ b/docs/api/intent/compare-price-step.md @@ -1,6 +1,6 @@ # POST /api/v1/price/step — 外卖比价 Phase 2 步进(透传到 pricebot) -> 所属:Compare 组(前缀 `/api/v1`,外卖比价) | 鉴权:**无(MVP 阶段不鉴权)** | [← 返回 API 索引](./README.md) +> 所属:Compare 组(前缀 `/api/v1`,外卖比价) | 鉴权:**无(MVP 阶段不鉴权)** | [← 返回 API 索引](../README.md) ## 入参 任意 JSON body,**不做 schema 校验**,原样透传给上游。后端仅从中读 `device_id`、`trace_id`、`step` 用于日志。 diff --git a/docs/api/intent/intent-step.md b/docs/api/intent/intent-step.md new file mode 100644 index 0000000..f651178 --- /dev/null +++ b/docs/api/intent/intent-step.md @@ -0,0 +1,113 @@ +# 比价意图多帧步进(intent/step + intent/precoupon/step) + +> 所属:Intent 组(前缀 `/api/v1`,外卖比价透传) | 鉴权:无(MVP 阶段不鉴权) | [← 返回 API 索引](../README.md) + +透传到 pricebot-backend。请求体原样转发、不做 schema 校验。 + +--- + +## POST /intent/step — Phase 1 多帧意图识别(淘宝源) + +淘宝源走多帧版意图识别(展开+滚动采集→提取):循环调用直到 done(done 帧顶层带 `result` + `calibration`)。其它源走单次 `/intent/recognize`。 + +### 入参 + +透传 pricebot,客户端按 pricebot 协议组装。关键字段: + +| 字段 | 类型 | 说明 | +|---|---|---| +| `device_id` | string | 设备 ID | +| `trace_id` | string | 比价 trace 标识 | +| `frame` | int | 帧序号(0 起始) | +| `continue` | bool | 是否继续(pricebot 回传,客户端 next 调用时带上帧) | +| `image` | string | 当前帧截图 base64 | +| `platform` | string | 源平台 | +| `package` | string | 目标平台包名 | +| *(透传)* | | 其余字段由 pricebot 定义,本端点不做校验 | + +Mock 入参(首帧): +```json +{ + "device_id": "android_abc123def456", + "trace_id": "tr_20260703_e5f6g7h8", + "frame": 0, + "continue": true, + "image": "/9j/4AAQSkZJRgABAQEAYABgAAD/...(base64)", + "platform": "taobao", + "package": "com.sankuai.meituan" +} +``` + +### 出参 + +透传 pricebot 原始响应。通常包含 `action.command`(`continue` / `done`)、`action.params` 等。 + +Mock 出参: +```json +{ + "trace_id": "tr_20260703_e5f6g7h8", + "frame": 0, + "continue": true, + "action": { + "command": "continue", + "params": { + "next_frame": 1, + "scroll_distance": 300 + } + } +} +``` + +--- + +## POST /intent/precoupon/step — Phase 0 意图识别前先用券(美团源) + +美团源平台「意图识别前先用券」多帧循环:客户端在调 `/intent/recognize` 之前先循环调本端点到 done(`continue=false`)。订单页底部有「点击使用X红包」就自动选最大免费券用上,已用券/无券则首帧秒过。与 `/intent/step` 同属 intent 域,复用同一透传壳。 + +### 入参 + +同 `/intent/step`,透传 pricebot。 + +Mock 入参(首帧): +```json +{ + "device_id": "android_abc123def456", + "trace_id": "tr_20260703_i9j0k1l2", + "frame": 0, + "continue": true, + "image": "/9j/4AAQSkZJRgABAQEAYABgAAD/...(base64)", + "platform": "meituan", + "package": "com.sankuai.meituan" +} +``` + +### 出参 + +透传 pricebot 原始响应。 + +Mock 出参(无券首帧秒过): +```json +{ + "trace_id": "tr_20260703_i9j0k1l2", + "frame": 0, + "continue": false, + "action": { + "command": "done", + "params": { + "coupon_used": false, + "reason": "no_coupon_available" + } + } +} +``` + +--- + +## 错误码 +- `502` pricebot 不可达或返回 5xx +- `400` 请求体不是合法 JSON + +## 说明 +- 两个端点是同域透传,区别是 pricebot 后端路由不同(`/api/intent/step` vs `/api/intent/precoupon/step`) +- 一致性 hash 按 `trace_id` 路由到同一 pricebot 实例 +- MVP 阶段不鉴权(待补 JWT) diff --git a/docs/api/internal.md b/docs/api/internal/internal.md similarity index 98% rename from docs/api/internal.md rename to docs/api/internal/internal.md index 8c0af60..a5dd18c 100644 --- a/docs/api/internal.md +++ b/docs/api/internal/internal.md @@ -1,6 +1,6 @@ # 内部回写端点(internal 族,pricebot / 发布流程 → app-server) -> 所属:internal 组(前缀 `/internal`,源 `app/api/internal/`) | 鉴权:**`X-Internal-Secret` 头**(== `settings.INTERNAL_API_SECRET`) | [← 返回 API 索引](./README.md) +> 所属:internal 组(前缀 `/internal`,源 `app/api/internal/`) | 鉴权:**`X-Internal-Secret` 头**(== `settings.INTERNAL_API_SECRET`) | [← 返回 API 索引](../README.md) **不是给客户端的接口**:不走用户 JWT。`§6` 是 app-server 透传给 pricebot,这里反过来——pricebot(及发布流程)把少量数据 server→server 回写 app-server 落库。 diff --git a/docs/api/invite/invite-bind.md b/docs/api/invite/invite-bind.md new file mode 100644 index 0000000..afe9bcf --- /dev/null +++ b/docs/api/invite/invite-bind.md @@ -0,0 +1,100 @@ +# 邀请绑定(bind + landing-track) + +> 所属:Invite 组(前缀 `/api/v1/invite`) | 鉴权:bind 需 Bearer / landing-track 无需鉴权 | [← 返回 API 索引](../README.md) + +--- + +## POST /bind — 绑定邀请人 + +把当前登录用户(被邀请人)绑定到某邀请码。支持三种归因路径:clipboard(首启读剪贴板)、manual(手动输入邀请码)、fingerprint(指纹兜底反查)。绑定成功双方各发 1 万金币。 + +### 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `invite_code` | string \| null | ❌ | 邀请码;走指纹兜底时为空 | +| `channel` | string | ✅ | 归因来源:`clipboard` / `manual` / `fingerprint` | +| `fingerprint` | object \| null | ❌ | 指纹兜底时必传 | +| `fingerprint.screen` | string | - | 屏幕分辨率(如 `1080x2400`) | +| `fingerprint.device_model` | string | - | 设备型号(如 `24115RA8EC`) | + +Mock 入参(clipboard 归因): +```json +{ + "invite_code": "A3F8K2", + "channel": "clipboard", + "fingerprint": null +} +``` + +Mock 入参(指纹兜底): +```json +{ + "invite_code": null, + "channel": "fingerprint", + "fingerprint": { + "screen": "1080x2400", + "device_model": "24115RA8EC" + } +} +``` + +### 出参 + +响应 `200`:`BindInviteOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `status` | string | `success` / `already_bound` / `invalid_code` / `self_invite` / `not_eligible` / `fp_not_found` | +| `coins_awarded` | int | 本次给当前用户(被邀请人)发的金币 | +| `message` | string | 给前端直接展示的文案 | + +Mock 出参: +```json +{ + "status": "success", + "coins_awarded": 10000, + "message": "邀请绑定成功" +} +``` + +--- + +## POST /landing-track — 落地页指纹采集 + +B 浏览器打开 `dl.html?ref=xxx` 时上报指纹(剪贴板归因失败时兜底)。**无需鉴权**(浏览器没 token)。 + +### 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `ref` | string | ✅(4-16 位) | 邀请码(落地页 `?ref=`) | +| `screen` | string | ❌ | 屏幕分辨率(如 `1080x2400`) | + +Mock 入参: +```json +{ + "ref": "A3F8K2", + "screen": "1080x2400" +} +``` + +### 出参 + +响应 `200`:`LandingTrackOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `status` | string | `ok` / `invalid_code` / `no_ip` | + +Mock 出参: +```json +{"status": "ok"} +``` + +--- + +## 说明 +- `bind`: 绑定只在注册后首次有效(`not_eligible` = 已过新人期),自邀屏蔽 +- `landing-track`: IP/UA 服务端从 HTTP 头自动拿,JS 无需上报 +- 指纹反查窗口期由 `INVITE_FP_WINDOW_DAYS` 控制 diff --git a/docs/api/invite/invite-invitees.md b/docs/api/invite/invite-invitees.md new file mode 100644 index 0000000..7709457 --- /dev/null +++ b/docs/api/invite/invite-invitees.md @@ -0,0 +1,60 @@ +# GET /api/v1/invite/invitees — 我邀请的人列表 + +> 所属:Invite 组(前缀 `/api/v1/invite`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) + +分页查询当前用户成功邀请的人列表。邀请页小窗(取前几条)+ 完整列表页(分页加载)共用。 + +## 入参(query) + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `limit` | int | ❌ | 每页条数(1–50,默认 20) | +| `offset` | int | ❌ | 偏移量(≥0,默认 0) | + +Mock 请求: +``` +GET /api/v1/invite/invitees?limit=5&offset=0 +``` + +## 出参 + +响应 `200`:`InviteeListOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `items` | list[InviteeItem] | 被邀请人列表 | +| `items[].display_name` | string | 显示名(昵称 → 微信昵称 → 脱敏手机号,后端已兜底) | +| `items[].avatar_url` | string \| null | 头像 URL;null = 前端画默认色块 | +| `items[].coins` | int | 这次邀请给邀请人发的金币 | +| `items[].invited_at` | datetime | 邀请绑定时间(ISO 8601 UTC) | +| `total` | int | 我邀请的总人数 | +| `has_more` | bool | 还有下一页吗 | + +Mock 出参: +```json +{ + "items": [ + { + "display_name": "省钱小王", + "avatar_url": "/media/avatars/u2_f1e2d3c4b5a60708.jpg", + "coins": 10000, + "invited_at": "2026-06-28T14:30:00Z" + }, + { + "display_name": "138****1234", + "avatar_url": null, + "coins": 10000, + "invited_at": "2026-07-01T09:15:00Z" + } + ], + "total": 5, + "has_more": false +} +``` + +## 错误码 +- `401` 未鉴权 / token 失效 + +## 说明 +- 名字/头像降级兜底在后端算好:昵称 → 微信昵称 → 脱敏手机号 +- `limit` 钳到 [1, 50],`offset` 钳到 ≥0 diff --git a/docs/api/invite/invite-me.md b/docs/api/invite/invite-me.md new file mode 100644 index 0000000..167c021 --- /dev/null +++ b/docs/api/invite/invite-me.md @@ -0,0 +1,48 @@ +# GET /api/v1/invite/me — 我的邀请信息 + +> 所属:Invite 组(前缀 `/api/v1/invite`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) + +获取当前用户的邀请码、分享链接、已邀人数、累计获得金币/奖励金,以及 7 天一轮的倒计时信息。 + +## 入参 + +无(`user_id` 从 JWT 取)。 + +## 出参 + +响应 `200`:`InviteInfoOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `invite_code` | string | 我的邀请码(6-8 位) | +| `share_url` | string | 落地页链接(含 `?ref=`),前端据此生成二维码 + 复制分享 | +| `invited_count` | int | 已成功邀请人数 | +| `coins_earned` | int | 累计从邀请获得的金币(v1 口径) | +| `reward_balance_cents` | int | v2 可提现邀请奖励金(分) | +| `reward_withdrawn_cents` | int | v2 累计提现成功的邀请奖励金(分) | +| `countdown_days_left` | int | v2 本轮剩余天数(7 天 1 轮) | +| `countdown_is_fresh_round` | bool | 是否刚进入新一轮(非首轮第 1 天) | +| `countdown_text` | string | 倒计时展示文案(前端直接显示,新轮含换行) | + +Mock 出参: +```json +{ + "invite_code": "A3F8K2", + "share_url": "https://app.shaguabijia.com/dl.html?ref=A3F8K2", + "invited_count": 5, + "coins_earned": 50000, + "reward_balance_cents": 3200, + "reward_withdrawn_cents": 1800, + "countdown_days_left": 4, + "countdown_is_fresh_round": false, + "countdown_text": "还剩 4 天" +} +``` + +## 错误码 +- `401` 未鉴权 / token 失效 + +## 说明 +- 首次调用自动生成邀请码(幂等) +- v2 奖励金与现金隔离(`reward_balance_cents` 独立于 `cash_balance_cents`) +- 7 天 1 轮倒计时:新用户从注册日起算 diff --git a/docs/api/meituan-coupons.md b/docs/api/meituan/meituan-coupons.md similarity index 96% rename from docs/api/meituan-coupons.md rename to docs/api/meituan/meituan-coupons.md index c38966b..1d3be3d 100644 --- a/docs/api/meituan-coupons.md +++ b/docs/api/meituan/meituan-coupons.md @@ -1,6 +1,6 @@ # POST /api/v1/meituan/coupons — 券列表 / 搜索 -> 所属:美团 CPS 组(前缀 `/api/v1/meituan`,**全部无鉴权**) | 鉴权:无 | [← 返回 API 索引](./README.md) +> 所属:美团 CPS 组(前缀 `/api/v1/meituan`,**全部无鉴权**) | 鉴权:无 | [← 返回 API 索引](../README.md) > > 集成实现:见 [integrations/meituan](../integrations/meituan.md)(CPS S-Ca 签名、入参换算坑)。 diff --git a/docs/api/meituan-feed.md b/docs/api/meituan/meituan-feed.md similarity index 98% rename from docs/api/meituan-feed.md rename to docs/api/meituan/meituan-feed.md index 9377ab8..5fd974f 100644 --- a/docs/api/meituan-feed.md +++ b/docs/api/meituan/meituan-feed.md @@ -1,6 +1,6 @@ # POST /api/v1/meituan/feed — 首页推荐流(多 tab) -> 所属:美团 CPS 组(前缀 `/api/v1/meituan`,**全部无鉴权**) | 鉴权:无 | [← 返回 API 索引](./README.md) +> 所属:美团 CPS 组(前缀 `/api/v1/meituan`,**全部无鉴权**) | 鉴权:无 | [← 返回 API 索引](../README.md) > > 集成实现:见 [integrations/meituan](../integrations/meituan.md)(CPS S-Ca 签名、入参换算坑);离线库见 [database/meituan_coupon](../database/meituan_coupon.md)。 diff --git a/docs/api/meituan-referral-link.md b/docs/api/meituan/meituan-referral-link.md similarity index 96% rename from docs/api/meituan-referral-link.md rename to docs/api/meituan/meituan-referral-link.md index 289b73a..b0d0ff3 100644 --- a/docs/api/meituan-referral-link.md +++ b/docs/api/meituan/meituan-referral-link.md @@ -1,6 +1,6 @@ # POST /api/v1/meituan/referral-link — 换取推广链接 -> 所属:美团 CPS 组(前缀 `/api/v1/meituan`,**全部无鉴权**) | 鉴权:无 | [← 返回 API 索引](./README.md) +> 所属:美团 CPS 组(前缀 `/api/v1/meituan`,**全部无鉴权**) | 鉴权:无 | [← 返回 API 索引](../README.md) > > 集成实现:见 [integrations/meituan](../integrations/meituan.md)(CPS S-Ca 签名、入参换算坑)。 diff --git a/docs/api/meituan-top-sales.md b/docs/api/meituan/meituan-top-sales.md similarity index 96% rename from docs/api/meituan-top-sales.md rename to docs/api/meituan/meituan-top-sales.md index 3f91f66..2e7df8e 100644 --- a/docs/api/meituan-top-sales.md +++ b/docs/api/meituan/meituan-top-sales.md @@ -1,6 +1,6 @@ # POST /api/v1/meituan/top-sales — 销量最高(离线库) -> 所属:美团 CPS 组(前缀 `/api/v1/meituan`,**全部无鉴权**) | 鉴权:无 | [← 返回 API 索引](./README.md) +> 所属:美团 CPS 组(前缀 `/api/v1/meituan`,**全部无鉴权**) | 鉴权:无 | [← 返回 API 索引](../README.md) > > 数据来自离线库 [database/meituan_coupon](../database/meituan_coupon.md);**不实时打美团**(美团搜索对销量排序支持差、且有 402 限流)。 diff --git a/docs/api/other/analytics-events.md b/docs/api/other/analytics-events.md new file mode 100644 index 0000000..76dc7de --- /dev/null +++ b/docs/api/other/analytics-events.md @@ -0,0 +1,82 @@ +# POST /api/v1/analytics/events — 批量上报埋点事件 + +> 所属:Analytics 组(前缀 `/api/v1/analytics`) | 鉴权:无(不强制登录,未登录态也要采集行为) | [← 返回 API 索引](../README.md) + +批量接收新手引导(及后续)埋点,append 落 `analytics_event` 表。`user_id` 由客户端在 body 可选带上,不靠 Bearer。服务端补 `client_ip`(X-Forwarded-For)与 `server_at`(接收时间)。 + +## 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `device_id` | string | ✅(≤64) | 设备 ID | +| `user_id` | int \| null | ❌ | 登录用户 ID(未登录可空) | +| `sent_at` | int \| null | ❌ | 批次发送时间(epoch ms) | +| `oem` | string \| null | ❌ | 厂商(如 `Xiaomi`) | +| `os` | string \| null | ❌ | 操作系统(如 `Android 14`) | +| `model` | string \| null | ❌ | 机型(如 `24115RA8EC`) | +| `app_ver` | string \| null | ❌ | App 版本 | +| `channel` | string \| null | ❌ | 渠道 | +| `events` | list[object] | ✅(1-200 条) | 事件数组 | +| `events[].event` | string | ✅(≤64) | 事件名(如 `onboarding_start`) | +| `events[].client_ts` | int | ✅ | 端事件发生时间(epoch ms) | +| `events[].session_id` | string \| null | ❌ | 会话 ID | +| `events[].page` | string \| null | ❌ | 页面标识 | +| `events[].network` | string \| null | ❌ | 网络类型(如 `wifi`) | +| `events[].props` | dict | ❌ | 事件属性(key-value) | + +Mock 入参: +```json +{ + "device_id": "android_abc123def456", + "user_id": 42, + "sent_at": 1719993700000, + "oem": "Xiaomi", + "os": "Android 14", + "model": "24115RA8EC", + "app_ver": "0.1.5", + "channel": "official", + "events": [ + { + "event": "onboarding_start", + "client_ts": 1719993600000, + "session_id": "sess_a1b2c3", + "page": "onboarding", + "network": "wifi", + "props": {"step": "1", "source": "fresh_install"} + }, + { + "event": "onboarding_step_complete", + "client_ts": 1719993615000, + "session_id": "sess_a1b2c3", + "page": "onboarding", + "network": "wifi", + "props": {"step": "1", "duration_ms": "15000"} + } + ] +} +``` + +## 出参 + +响应 `200`:`AnalyticsIngestOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `ok` | bool | 固定 `true` | +| `received` | int | 成功写入的条数 | + +Mock 出参: +```json +{ + "ok": true, + "received": 2 +} +``` + +## 错误码 +- `422` `events` 为空或超过 200 条 / 字段类型不符 + +## 说明 +- `user_id` 不靠 JWT:未登录态也要采集行为(新手引导可能在登录前) +- 每批最多 200 条,建议客户端攒到一定量再批量上报 +- `client_ts` 是端侧时间(客户端时钟),`server_at` 由服务端补(可靠时间轴) diff --git a/docs/api/cps-redirect.md b/docs/api/other/cps-redirect.md similarity index 97% rename from docs/api/cps-redirect.md rename to docs/api/other/cps-redirect.md index bcb659b..ee00719 100644 --- a/docs/api/cps-redirect.md +++ b/docs/api/other/cps-redirect.md @@ -1,6 +1,6 @@ # CPS 群发短链落地(cps-redirect 族) -> 所属:cps-redirect 组(**无前缀**,挂域名根;源 `app/api/v1/cps_redirect.py`) | 鉴权:**公网无鉴权**(群里任何人点都要能跳/能领) | [← 返回 API 索引](./README.md) +> 所属:cps-redirect 组(**无前缀**,挂域名根;源 `app/api/v1/cps_redirect.py`) | 鉴权:**公网无鉴权**(群里任何人点都要能跳/能领) | [← 返回 API 索引](../README.md) > > 落库:点击落 [`cps_click`](../database/cps_click.md)、微信落地页用户落 [`cps_wx_user`](../database/cps_wx_user.md);短链由 [`cps_link`](../database/cps_link.md) 解析。 diff --git a/docs/api/other/feedback-config.md b/docs/api/other/feedback-config.md new file mode 100644 index 0000000..77e733f --- /dev/null +++ b/docs/api/other/feedback-config.md @@ -0,0 +1,40 @@ +# GET /api/v1/feedback/config — 反馈页二维码卡配置 + +> 所属:Feedback 组(前缀 `/api/v1/feedback`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) + +运营后台配的反馈页「加群二维码」卡配置(开关 + 二维码图 + 三行文案)。客户端进反馈页时拉取,据此渲染整张「加群二维码」卡。 + +## 入参 + +无(`user_id` 从 JWT 取)。 + +## 出参 + +响应 `200`:`FeedbackQrConfigOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `enabled` | bool | 是否展示加群二维码卡 | +| `image_url` | string \| null | 二维码图片 URL(相对 `/media` 路径);空 → 客户端走本地兜底 | +| `title` | string | 卡片标题(如 `加入用户反馈群`) | +| `group_name` | string | 群名称(如 `傻瓜比价用户群`) | +| `subtitle` | string | 副标题/说明文案(如 `扫码加入,你的声音我们听得见`) | + +Mock 出参: +```json +{ + "enabled": true, + "image_url": "/media/feedback_qr/qr_default.png", + "title": "加入用户反馈群", + "group_name": "傻瓜比价用户群", + "subtitle": "扫码加入,你的声音我们听得见" +} +``` + +## 错误码 +- `401` 未鉴权 / token 失效 + +## 说明 +- `image_url` 是相对路径,客户端按自己的 `BASE_URL` 拼绝对地址 +- `enabled=false` 时客户端隐藏整张卡 +- 配置在 admin 后台 `feedback_qr` 表维护 diff --git a/docs/api/other/feedback-records.md b/docs/api/other/feedback-records.md new file mode 100644 index 0000000..6f15e6f --- /dev/null +++ b/docs/api/other/feedback-records.md @@ -0,0 +1,71 @@ +# GET /api/v1/feedback/records — 我的反馈历史 + +> 所属:Feedback 组(前缀 `/api/v1/feedback`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) + +查询当前用户提交的反馈历史,支持按状态筛选。 + +## 入参(query) + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `status` | string \| null | ❌ | 筛选状态:`pending` / `adopted` / `rejected`;不传 = 全部 | + +Mock 请求: +``` +GET /api/v1/feedback/records?status=adopted +``` + +## 出参 + +响应 `200`:`FeedbackRecordsOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `records` | list[FeedbackRecordOut] | 反馈记录列表 | +| `records[].id` | int | 反馈 ID | +| `records[].content` | string | 反馈正文 | +| `records[].scene` | string \| null | 问题场景(比价结果页反馈时带,如 `找错商品`) | +| `records[].images` | list[string] | 截图 URL 列表 | +| `records[].status` | string | `pending` / `adopted` / `rejected` | +| `records[].reject_reason` | string \| null | 驳回原因 | +| `records[].reward_coins` | int \| null | 采纳后发的金币数 | +| `records[].admin_reply` | string \| null | 管理员回复 | +| `records[].created_at` | datetime | 提交时间 | +| `counts` | object | 三态计数(不受 status 筛选影响) | +| `counts.all` | int | 总数 | +| `counts.pending` | int | 待处理 | +| `counts.adopted` | int | 已采纳 | +| `counts.rejected` | int | 已驳回 | + +Mock 出参: +```json +{ + "records": [ + { + "id": 56, + "content": "比价结果显示美团 28.5 元,但实际下单时涨到了 32 元", + "scene": "价格不一致", + "images": ["/media/feedback/u42_f1e2d3c4b5a60708.jpg"], + "status": "adopted", + "reject_reason": null, + "reward_coins": 500, + "admin_reply": "感谢反馈,已核实并修复", + "created_at": "2026-07-02T15:20:00Z" + } + ], + "counts": { + "all": 3, + "pending": 1, + "adopted": 2, + "rejected": 0 + } +} +``` + +## 错误码 +- `400` 无效的 `status` 值(仅 `pending`/`adopted`/`rejected` 合法) +- `401` 未鉴权 + +## 说明 +- `counts` 始终基于全量(不受 `status` 筛选影响),供前端筛选 chip +- `scene` 仅比价结果页反馈时带(区分普通反馈 vs 比价场景反馈) diff --git a/docs/api/feedback.md b/docs/api/other/feedback.md similarity index 96% rename from docs/api/feedback.md rename to docs/api/other/feedback.md index e59e157..8473aef 100644 --- a/docs/api/feedback.md +++ b/docs/api/other/feedback.md @@ -1,6 +1,6 @@ # POST /api/v1/feedback — 提交反馈 -> 所属:Feedback 组(前缀 `/api/v1/feedback`) | 鉴权:Bearer access_token | [← 返回 API 索引](./README.md) +> 所属:Feedback 组(前缀 `/api/v1/feedback`) | 鉴权:Bearer access_token | [← 返回 API 索引](../README.md) ## 入参 **multipart/form-data**: diff --git a/docs/api/health.md b/docs/api/other/health.md similarity index 65% rename from docs/api/health.md rename to docs/api/other/health.md index 26d4e61..58d9a43 100644 --- a/docs/api/health.md +++ b/docs/api/other/health.md @@ -1,6 +1,6 @@ # GET /health — 健康检查 -> 所属:Meta | 鉴权:无 | [← 返回 API 索引](./README.md) +> 所属:Meta | 鉴权:无 | [← 返回 API 索引](../README.md) ## 入参 无 diff --git a/docs/api/other/order-report.md b/docs/api/other/order-report.md new file mode 100644 index 0000000..47e70e8 --- /dev/null +++ b/docs/api/other/order-report.md @@ -0,0 +1,74 @@ +# POST /api/v1/order/report — 上报归因订单 + +> 所属:Order 组(前缀 `/api/v1/order`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) + +比价后 5 分钟内点链接下单、支付金额与比价价相差 ≤1 元时,客户端上报归因订单。落 `savings_record`(`source='compare'`),客户端幂等键防重。 + +## 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `client_event_id` | string | ✅(≤64) | 客户端幂等键(UUID) | +| `platform` | string | ✅(≤32) | 平台展示名(如 `美团`) | +| `platform_package` | string \| null | ❌ | 平台包名 | +| `pay_channel` | string | ✅(≤16) | 支付渠道(`wechat` / `alipay`) | +| `compared_price_cents` | int | ✅(≥0) | 我们给出的比价价(分) | +| `paid_amount_cents` | int | ✅(≥0) | 实际支付金额(分) | +| `device_id` | string \| null | ❌ | 设备 ID | +| `shop_name` | string \| null | ❌ | 门店名(如 `肯德基宅急送(天北路店)`) | +| `dishes` | list[string] | ❌ | 菜品名列表 | +| `original_price_cents` | int \| null | ❌ | 源平台原价(分),省额 = 原价 − 实付 | +| `source_platform_name` | string \| null | ❌ | 源平台展示名(如 `美团`) | +| `source_deeplink` | string \| null | ❌ | 源平台重进链接(预留,本期只存不展示) | + +Mock 入参: +```json +{ + "client_event_id": "550e8400-e29b-41d4-a716-446655440000", + "platform": "美团", + "platform_package": "com.sankuai.meituan", + "pay_channel": "wechat", + "compared_price_cents": 2850, + "paid_amount_cents": 2800, + "device_id": "android_abc123def456", + "shop_name": "肯德基宅急送(天北路店)", + "dishes": ["香辣鸡腿堡套餐", "可口可乐(中)"], + "original_price_cents": 4200, + "source_platform_name": "美团", + "source_deeplink": null +} +``` + +## 出参 + +响应 `200`:`OrderReportOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | int | 省钱记录 ID | +| `platform` | string | 平台 | +| `pay_channel` | string | 支付渠道 | +| `compared_price_cents` | int | 比价价(分) | +| `paid_amount_cents` | int | 实付金额(分) | +| `duplicated` | bool | 是否为重复上报(幂等命中) | + +Mock 出参: +```json +{ + "id": 1234, + "platform": "美团", + "pay_channel": "wechat", + "compared_price_cents": 2850, + "paid_amount_cents": 2800, + "duplicated": false +} +``` + +## 错误码 +- `401` 未鉴权 / token 失效 +- `422` 必填字段缺失或类型不符 + +## 说明 +- 记账唯一真相表是 `savings_record`(`source='compare'`) +- `client_event_id` 幂等防重(网络重试不重复记) +- 省额 = `original_price_cents − paid_amount_cents`(若原价可用) diff --git a/docs/api/other/report-records.md b/docs/api/other/report-records.md new file mode 100644 index 0000000..b50bc91 --- /dev/null +++ b/docs/api/other/report-records.md @@ -0,0 +1,81 @@ +# GET /api/v1/report/records — 上报更低价记录列表 + +> 所属:Report 组(前缀 `/api/v1/report`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) + +当前用户的上报更低价记录列表,支持按状态筛选。 + +## 入参(query) + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `status` | string \| null | ❌ | 筛选状态:`pending` / `approved` / `rejected`;不传 = 全部 | + +Mock 请求: +``` +GET /api/v1/report/records?status=pending +``` + +## 出参 + +响应 `200`:`ReportRecordsOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `records` | list[ReportRecordOut] | 上报记录列表 | +| `records[].id` | int | 记录 ID | +| `records[].store_name` | string \| null | 门店名 | +| `records[].dish_summary` | string \| null | 菜品摘要(如 `香辣鸡腿堡套餐、可口可乐`) | +| `records[].original_platform_id` | string \| null | 原最低价来源平台 ID | +| `records[].original_platform_name` | string \| null | 原最低价来源平台名 | +| `records[].original_price_cents` | int \| null | 原最低价(分) | +| `records[].reported_platform_id` | string | 上报平台标识 | +| `records[].reported_platform_name` | string | 上报平台名(如 `京东外卖`) | +| `records[].reported_price_cents` | int | 上报更低价(分) | +| `records[].images` | list[string] | 截图 URL 列表 | +| `records[].status` | string | `pending` / `approved` / `rejected` | +| `records[].reject_reason` | string \| null | 驳回原因(rejected 时) | +| `records[].reward_coins` | int \| null | 通过后发的金币数 | +| `records[].created_at` | datetime | 提交时间 | +| `counts` | object | 四态计数(不受 status 筛选影响,供前端 chip 展示) | +| `counts.all` | int | 总数 | +| `counts.pending` | int | 审核中 | +| `counts.approved` | int | 已通过 | +| `counts.rejected` | int | 未通过 | + +Mock 出参: +```json +{ + "records": [ + { + "id": 89, + "store_name": "肯德基宅急送(天北路店)", + "dish_summary": "香辣鸡腿堡套餐、可口可乐(中)", + "original_platform_id": "meituan-waimai", + "original_platform_name": "美团外卖", + "original_price_cents": 2850, + "reported_platform_id": "jd-waimai", + "reported_platform_name": "京东外卖", + "reported_price_cents": 1880, + "images": ["/media/price_report/u42_a1b2c3d4e5f6g7h8.jpg"], + "status": "pending", + "reject_reason": null, + "reward_coins": null, + "created_at": "2026-07-03T10:30:00Z" + } + ], + "counts": { + "all": 3, + "pending": 1, + "approved": 1, + "rejected": 1 + } +} +``` + +## 错误码 +- `400` 无效的 `status` 值(仅 `pending`/`approved`/`rejected` 合法) +- `401` 未鉴权 + +## 说明 +- `counts` 始终基于全量(不受 `status` 筛选影响),供前端筛选 chip 显示各状态数量 +- 价格单位均为分(`*_cents`),客户端 ÷100 显示元 diff --git a/docs/api/other/report-submit.md b/docs/api/other/report-submit.md new file mode 100644 index 0000000..48417de --- /dev/null +++ b/docs/api/other/report-submit.md @@ -0,0 +1,57 @@ +# POST /api/v1/report — 提交上报更低价 + +> 所属:Report 组(前缀 `/api/v1/report`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) + +众包纠偏:用户发现比价记录中某平台有更低价格时提交上报。需附截图证明,原最低价由 `comparison_record_id` 反查(不信任客户端传的快照),提交价必须 < 原最低价。提交后 `status=pending`,人工审核通过后发奖。 + +## 入参 + +**multipart/form-data**: + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `comparison_record_id` | int | ✅ | 比价记录 ID | +| `reported_platform_id` | string | ✅ | 上报平台标识:`meituan-waimai` / `jd-waimai` / `taobao-shanguang` | +| `reported_price` | string | ✅ | 用户填的更低价(元,如 `23.5`) | +| `images` | file[] | ✅(1–4 张) | 截图证明 | + +Mock 入参(curl 示例): +```bash +curl -X POST https://app-api.shaguabijia.com/api/v1/report \ + -H "Authorization: Bearer " \ + -F "comparison_record_id=5678" \ + -F "reported_platform_id=jd-waimai" \ + -F "reported_price=18.8" \ + -F "images=@screenshot1.png" \ + -F "images=@screenshot2.png" +``` + +## 出参 + +响应 `200`:`ReportSubmitOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | int | 上报记录 ID | +| `status` | string | 固定 `pending`(待审核) | +| `created_at` | datetime | 提交时间(ISO 8601 UTC) | + +Mock 出参: +```json +{ + "id": 89, + "status": "pending", + "created_at": "2026-07-03T10:30:00Z" +} +``` + +## 错误码 +- `400` 价格不合法(≤0 / 格式错)/ 上报价 ≥ 原最低价 / 图片问题(空/超 4 张/格式不对)/ 不支持的上报平台 +- `401` 未鉴权 +- `404` 比价记录不存在或不属于当前用户 + +## 说明 +- 原最低价由 `comparison_record_id` → `ComparisonRecord.best_price_cents` 反查 +- 校验(D):`reported_price_cents < original_price_cents`,否则 400 +- 截图落盘 `MEDIA_ROOT/price_report/`,文件名随机防覆盖 +- 发奖走人工审核(admin 后台操作),不在此端点 diff --git a/docs/api/other/trace-finalize.md b/docs/api/other/trace-finalize.md new file mode 100644 index 0000000..19b7fc1 --- /dev/null +++ b/docs/api/other/trace-finalize.md @@ -0,0 +1,45 @@ +# POST /api/v1/trace/finalize — 比价 trace 收尾上云 + +> 所属:透传端点(前缀 `/api/v1`,外卖比价) | 鉴权:无(MVP 阶段不鉴权) | [← 返回 API 索引](../README.md) + +透传到 pricebot-backend。用户终止 / Phase 1 未识别没走到 done 帧时,pricebot 没上云也没回传 `trace_url`。客户端收尾时打这个,pricebot 按 `trace_id` 一致性 hash 落到处理这条 trace 的同一进程(dir_cache 在那才能算对 trace 目录),打包上云返回 `{trace_url}`。 + +## 入参 + +透传 pricebot,客户端按 pricebot 协议组装。关键字段: + +| 字段 | 类型 | 说明 | +|---|---|---| +| `device_id` | string | 设备 ID | +| `trace_id` | string | 比价 trace 标识 | +| *(透传)* | | 其余字段由 pricebot 定义,本端点不做校验 | + +Mock 入参: +```json +{ + "device_id": "android_abc123def456", + "trace_id": "tr_20260703_m3n4o5p6", + "reason": "user_cancelled" +} +``` + +## 出参 + +透传 pricebot 原始响应,通常包含 `trace_url`。 + +Mock 出参: +```json +{ + "trace_url": "https://trace.shaguabijia.com/tr_20260703_m3n4o5p6", + "ok": true +} +``` + +## 错误码 +- `502` pricebot 不可达或返回 5xx +- `400` 请求体不是合法 JSON + +## 说明 +- 一致性 hash 按 `trace_id` 路由到同一 pricebot 实例(确保 dir_cache 命中) +- MVP 阶段不鉴权 +- 与 `/intent/recognize`、`/price/step` 等同属外卖比价透传族 diff --git a/docs/api/platform/platform-ad-config.md b/docs/api/platform/platform-ad-config.md new file mode 100644 index 0000000..fbf0f42 --- /dev/null +++ b/docs/api/platform/platform-ad-config.md @@ -0,0 +1,43 @@ +# GET /api/v1/platform/ad-config — 客户端广告配置 + +> 所属:Platform 组(前缀 `/api/v1/platform`) | 鉴权:无 | [← 返回 API 索引](../README.md) + +客户端启动 / 每场广告前拉取,缓存后用:穿山甲 `app_id` + 各位 ID + 各场景开关。不含验签密钥(密钥只在后端验 S2S 回调用)。 + +## 入参 + +无。 + +## 出参 + +响应 `200`:`AdConfigPublicOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `app_id` | string | 穿山甲应用 ID(改了需冷启才生效,SDK init 一次性读) | +| `reward_code_id` | string | 福利页激励视频位 | +| `compare_draw_code_id` | string | 比价 Draw 代码位 | +| `coupon_draw_code_id` | string | 领券 Draw 代码位(与比价共用同一位,靠 `feed_scene` 区分收益) | +| `reward_enabled` | bool | 福利激励视频开关 | +| `compare_ad_enabled` | bool | 比价广告开关 | +| `coupon_ad_enabled` | bool | 领券广告开关 | +| `withdrawal_ad_enabled` | bool | 提现激励视频开关(关 = 客户端直接放行提现) | + +Mock 出参: +```json +{ + "app_id": "5123456", + "reward_code_id": "104001", + "compare_draw_code_id": "104002", + "coupon_draw_code_id": "104002", + "reward_enabled": true, + "compare_ad_enabled": true, + "coupon_ad_enabled": true, + "withdrawal_ad_enabled": false +} +``` + +## 说明 +- 空库回退默认值(= 客户端内置值,维持现状) +- 不含验签密钥(`m-key`),安全边界 +- `coupon_draw_code_id` 与 `compare_draw_code_id` 通常同值,客户端按场景调不同端点区分 diff --git a/docs/api/platform/platform-app-version.md b/docs/api/platform/platform-app-version.md new file mode 100644 index 0000000..9e63f2b --- /dev/null +++ b/docs/api/platform/platform-app-version.md @@ -0,0 +1,39 @@ +# GET /api/v1/platform/app-version — 最新 App 版本 + +> 所属:Platform 组(前缀 `/api/v1/platform`) | 鉴权:无 | [← 返回 API 索引](../README.md) + +客户端启动 / 手动检查更新时拉取。用 `latest_version_code` 与本机 `versionCode` 比;未配置(返回默认 0)时客户端视为已是最新。 + +## 入参 + +无。 + +## 出参 + +响应 `200`:`AppVersionOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `latest_version_code` | int | 最新版 versionCode;0 = 未配置(无更新) | +| `latest_version_name` | string | 展示用版本号(如 `0.1.4`) | +| `apk_url` | string | 下载链接(发车产出的永久版本化链接) | +| `update_note` | string | 更新说明,弹窗展示(如「修复了部分机型闪退问题」) | +| `min_supported_version_code` | int | 本机低于此版本 = 强制更新;0 = 不强更(全可选) | +| `apk_size_bytes` | int | 包大小(字节),展示「约 x MB」 | + +Mock 出参: +```json +{ + "latest_version_code": 42, + "latest_version_name": "0.1.5", + "apk_url": "https://cdn.shaguabijia.com/releases/app-v0.1.5.apk", + "update_note": "1. 修复了部分机型闪退问题\n2. 优化了比价速度\n3. 新增省钱大作战功能", + "min_supported_version_code": 35, + "apk_size_bytes": 18350080 +} +``` + +## 说明 +- 不鉴权(版本信息非敏感,检查更新可能在登录前) +- `latest_version_code = 0` → 无更新,客户端无需提示 +- `min_supported_version_code > 本机 versionCode` → 强制更新弹窗(不可跳过) diff --git a/docs/api/platform/platform-flags.md b/docs/api/platform/platform-flags.md new file mode 100644 index 0000000..f4997d3 --- /dev/null +++ b/docs/api/platform/platform-flags.md @@ -0,0 +1,28 @@ +# GET /api/v1/platform/flags — 客户端 Feature Flags + +> 所属:Platform 组(前缀 `/api/v1/platform`) | 鉴权:无 | [← 返回 API 索引](../README.md) + +客户端拉取运营开关并缓存(app 启动 / 每场比价开始时刷新)。不鉴权:开关非敏感,且比价无障碍服务取值时未必有登录态。值来自 `app_config`(admin 可改),空库回退默认。 + +## 入参 + +无。 + +## 出参 + +响应 `200`:`AppFlagsOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `comparing_ad_enabled` | bool | 比价/领券期是否展示信息流广告(远程 kill-switch) | + +Mock 出参: +```json +{ + "comparing_ad_enabled": true +} +``` + +## 说明 +- 客户端拉取后本地缓存,避免每次请求 +- 空库回退默认值(false) diff --git a/docs/api/platform-stats.md b/docs/api/platform/platform-stats.md similarity index 99% rename from docs/api/platform-stats.md rename to docs/api/platform/platform-stats.md index 9ddaef6..b95f793 100644 --- a/docs/api/platform-stats.md +++ b/docs/api/platform/platform-stats.md @@ -1,6 +1,6 @@ # GET /api/v1/platform/stats — 首页三统计(全平台门面数字) -> 所属:Platform 组(前缀 `/api/v1/platform`) | 鉴权:**无**(登录前首页也要展示) | [← 返回 API 索引](./README.md) +> 所属:Platform 组(前缀 `/api/v1/platform`) | 鉴权:**无**(登录前首页也要展示) | [← 返回 API 索引](../README.md) 客户端首页顶部「帮助用户 / 完成比价 / 累计节省」三个平台级数字。每个指标的展示模式由运营后台配(见 [admin-dashboard-display](./admin-dashboard-display.md)),本接口只返回算好的结果值。 diff --git a/docs/api/platform-savings-feed.md b/docs/api/savings/platform-savings-feed.md similarity index 98% rename from docs/api/platform-savings-feed.md rename to docs/api/savings/platform-savings-feed.md index 511e8c4..fc1d93d 100644 --- a/docs/api/platform-savings-feed.md +++ b/docs/api/savings/platform-savings-feed.md @@ -1,6 +1,6 @@ # GET /api/v1/platform/savings-feed — 首页轮播 feed(真实+种子混播) -> 所属:Platform 组(前缀 `/api/v1/platform`) | 鉴权:**无** | [← 返回 API 索引](./README.md) +> 所属:Platform 组(前缀 `/api/v1/platform`) | 鉴权:**无** | [← 返回 API 索引](../README.md) 客户端首页顶部「某用户 比价后节省 xx 元」滚动条数据源。全平台真实比价记录优先;不足时用运营配的种子([ops_marquee_seed](../database/ops_marquee_seed.md))补齐到 `limit` 条「混播」,保证轮播不空。 diff --git a/docs/api/savings-battle.md b/docs/api/savings/savings-battle.md similarity index 96% rename from docs/api/savings-battle.md rename to docs/api/savings/savings-battle.md index 1911b99..be19bbb 100644 --- a/docs/api/savings-battle.md +++ b/docs/api/savings/savings-battle.md @@ -1,6 +1,6 @@ # GET /api/v1/savings/battle — 省钱战绩 -> 所属:Savings 组(前缀 `/api/v1/savings`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Savings 组(前缀 `/api/v1/savings`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参 无(用户由 token 确定)。 diff --git a/docs/api/savings-records.md b/docs/api/savings/savings-records.md similarity index 95% rename from docs/api/savings-records.md rename to docs/api/savings/savings-records.md index 31fcf6c..6e43fdb 100644 --- a/docs/api/savings-records.md +++ b/docs/api/savings/savings-records.md @@ -1,6 +1,6 @@ # GET /api/v1/savings/records — 省钱明细(游标分页) -> 所属:Savings 组(前缀 `/api/v1/savings`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Savings 组(前缀 `/api/v1/savings`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参(query) diff --git a/docs/api/savings-summary.md b/docs/api/savings/savings-summary.md similarity index 92% rename from docs/api/savings-summary.md rename to docs/api/savings/savings-summary.md index 3082a28..6d8f21a 100644 --- a/docs/api/savings-summary.md +++ b/docs/api/savings/savings-summary.md @@ -1,6 +1,6 @@ # GET /api/v1/savings/summary — 累计帮你省了 -> 所属:Savings 组(前缀 `/api/v1/savings`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Savings 组(前缀 `/api/v1/savings`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参 无(用户由 token 确定)。 diff --git a/docs/api/signin-boost.md b/docs/api/signin/signin-boost.md similarity index 100% rename from docs/api/signin-boost.md rename to docs/api/signin/signin-boost.md diff --git a/docs/api/signin-do.md b/docs/api/signin/signin-do.md similarity index 90% rename from docs/api/signin-do.md rename to docs/api/signin/signin-do.md index 3bb6504..b484082 100644 --- a/docs/api/signin-do.md +++ b/docs/api/signin/signin-do.md @@ -1,6 +1,6 @@ # POST /api/v1/signin — 执行今日签到 -> 所属:Signin 组(前缀 `/api/v1/signin`,本接口 POST 到前缀本身) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Signin 组(前缀 `/api/v1/signin`,本接口 POST 到前缀本身) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参 无(用户由 token 确定)。 diff --git a/docs/api/signin-status.md b/docs/api/signin/signin-status.md similarity index 95% rename from docs/api/signin-status.md rename to docs/api/signin/signin-status.md index 541d6c8..23e5498 100644 --- a/docs/api/signin-status.md +++ b/docs/api/signin/signin-status.md @@ -1,6 +1,6 @@ # GET /api/v1/signin/status — 今日签到状态 + 14 天档位 -> 所属:Signin 组(前缀 `/api/v1/signin`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Signin 组(前缀 `/api/v1/signin`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参 无(用户由 token 确定)。 diff --git a/docs/api/tasks-claim.md b/docs/api/tasks/tasks-claim.md similarity index 96% rename from docs/api/tasks-claim.md rename to docs/api/tasks/tasks-claim.md index 5ef1f2c..00f489b 100644 --- a/docs/api/tasks-claim.md +++ b/docs/api/tasks/tasks-claim.md @@ -1,6 +1,6 @@ # POST /api/v1/tasks/{task_key}/claim — 领取任务奖励 -> 所属:Tasks 组(前缀 `/api/v1/tasks`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Tasks 组(前缀 `/api/v1/tasks`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参(path) diff --git a/docs/api/tasks-list.md b/docs/api/tasks/tasks-list.md similarity index 94% rename from docs/api/tasks-list.md rename to docs/api/tasks/tasks-list.md index d826f1d..e921d9d 100644 --- a/docs/api/tasks-list.md +++ b/docs/api/tasks/tasks-list.md @@ -1,6 +1,6 @@ # GET /api/v1/tasks — 任务及领取状态 -> 所属:Tasks 组(前缀 `/api/v1/tasks`,本接口 GET 前缀本身) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Tasks 组(前缀 `/api/v1/tasks`,本接口 GET 前缀本身) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参 无(用户由 token 确定)。 diff --git a/docs/api/user-avatar.md b/docs/api/user/user-avatar.md similarity index 96% rename from docs/api/user-avatar.md rename to docs/api/user/user-avatar.md index 21d0cc9..9fc6b2b 100644 --- a/docs/api/user-avatar.md +++ b/docs/api/user/user-avatar.md @@ -1,6 +1,6 @@ # POST /api/v1/user/avatar — 上传头像 -> 所属:User 组(前缀 `/api/v1/user`) | 鉴权:Bearer access_token | [← 返回 API 索引](./README.md) +> 所属:User 组(前缀 `/api/v1/user`) | 鉴权:Bearer access_token | [← 返回 API 索引](../README.md) ## 入参 **multipart/form-data**: diff --git a/docs/api/user-delete.md b/docs/api/user/user-delete.md similarity index 95% rename from docs/api/user-delete.md rename to docs/api/user/user-delete.md index 611918b..c6162d7 100644 --- a/docs/api/user-delete.md +++ b/docs/api/user/user-delete.md @@ -1,6 +1,6 @@ # DELETE /api/v1/user — 注销账号 -> 所属:User 组(前缀 `/api/v1/user`) | 鉴权:Bearer access_token | [← 返回 API 索引](./README.md) +> 所属:User 组(前缀 `/api/v1/user`) | 鉴权:Bearer access_token | [← 返回 API 索引](../README.md) ## 入参 无(身份取自 Header token) diff --git a/docs/api/user/user-onboarding.md b/docs/api/user/user-onboarding.md new file mode 100644 index 0000000..3985f29 --- /dev/null +++ b/docs/api/user/user-onboarding.md @@ -0,0 +1,67 @@ +# 新手引导状态(onboarding 族) + +> 所属:User 组(前缀 `/api/v1/user`) | 鉴权:全部 Bearer | [← 返回 API 索引](../README.md) + +按(账号, 设备)维度跟踪新手引导完成状态,跨卸载重装持久。运营可在 admin 删记录触发重走。 + +--- + +## GET /onboarding/status — 查新手引导是否已完成 + +已登录用户启动时查:该(账号, 设备)走过引导没。`false` → 客户端应重走。 + +### 入参(query) + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `device_id` | string | ❌(≤64 位) | 硬件级 ANDROID_ID;空 = 按未完成处理(不误跳过) | + +Mock 请求: +``` +GET /api/v1/user/onboarding/status?device_id=android_abc123def456 +``` + +### 出参 + +响应 `200`:`OnboardingStatusResponse` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `completed` | bool | 该(设备+账号)是否已走过新手引导 | + +Mock 出参: +```json +{"completed": true} +``` + +--- + +## POST /onboarding/complete — 标记新手引导完成 + +走完新手引导时调一次。按(当前账号, device_id)落一条完成标记,幂等(重复调用不报错)。 + +### 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `device_id` | string | ✅ | 硬件级 ANDROID_ID(与登录请求一致) | + +Mock 入参: +```json +{ + "device_id": "android_abc123def456" +} +``` + +### 出参 + +```json +{"ok": true} +``` + +--- + +## 说明 +- 替代原 `force_onboarding`(按用户)→ 改设备维度后,运营删记录即触发重走 +- 幂等:重复标记不报错 +- `device_id` 为空时 `status` 一律返回未完成 diff --git a/docs/api/user-profile.md b/docs/api/user/user-profile.md similarity index 94% rename from docs/api/user-profile.md rename to docs/api/user/user-profile.md index b170db8..28fb10f 100644 --- a/docs/api/user-profile.md +++ b/docs/api/user/user-profile.md @@ -1,6 +1,6 @@ # PATCH /api/v1/user/profile — 修改昵称 -> 所属:User 组(前缀 `/api/v1/user`) | 鉴权:Bearer access_token | [← 返回 API 索引](./README.md) +> 所属:User 组(前缀 `/api/v1/user`) | 鉴权:Bearer access_token | [← 返回 API 索引](../README.md) ## 入参 请求体 JSON: diff --git a/docs/api/wallet-account.md b/docs/api/wallet/wallet-account.md similarity index 92% rename from docs/api/wallet-account.md rename to docs/api/wallet/wallet-account.md index c059f4c..fc8c12f 100644 --- a/docs/api/wallet-account.md +++ b/docs/api/wallet/wallet-account.md @@ -1,6 +1,6 @@ # GET /api/v1/wallet/account — 金币 + 现金余额(我的资产) -> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参 无(用户由 token 确定)。 diff --git a/docs/api/wallet-bind-wechat.md b/docs/api/wallet/wallet-bind-wechat.md similarity index 93% rename from docs/api/wallet-bind-wechat.md rename to docs/api/wallet/wallet-bind-wechat.md index 34d3022..ad07d7d 100644 --- a/docs/api/wallet-bind-wechat.md +++ b/docs/api/wallet/wallet-bind-wechat.md @@ -1,6 +1,6 @@ # POST /api/v1/wallet/bind-wechat — 微信授权 code 换 openid 并绑定 -> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | 限流:同 IP ≤10 次/分 | [← 返回 API 索引](./README.md) +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | 限流:同 IP ≤10 次/分 | [← 返回 API 索引](../README.md) > > 集成实现:见 [integrations/wxpay](../integrations/wxpay.md)(code 换 openid / userinfo)。 diff --git a/docs/api/wallet-cash-transactions.md b/docs/api/wallet/wallet-cash-transactions.md similarity index 96% rename from docs/api/wallet-cash-transactions.md rename to docs/api/wallet/wallet-cash-transactions.md index 80463e9..d8b7379 100644 --- a/docs/api/wallet-cash-transactions.md +++ b/docs/api/wallet/wallet-cash-transactions.md @@ -1,6 +1,6 @@ # GET /api/v1/wallet/cash-transactions — 现金流水(游标分页) -> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参(query) diff --git a/docs/api/wallet-coin-transactions.md b/docs/api/wallet/wallet-coin-transactions.md similarity index 95% rename from docs/api/wallet-coin-transactions.md rename to docs/api/wallet/wallet-coin-transactions.md index 70e0b69..c73142c 100644 --- a/docs/api/wallet-coin-transactions.md +++ b/docs/api/wallet/wallet-coin-transactions.md @@ -1,6 +1,6 @@ # GET /api/v1/wallet/coin-transactions — 金币流水(游标分页) -> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参(query) diff --git a/docs/api/wallet-exchange-info.md b/docs/api/wallet/wallet-exchange-info.md similarity index 97% rename from docs/api/wallet-exchange-info.md rename to docs/api/wallet/wallet-exchange-info.md index 23c4b04..49e9f31 100644 --- a/docs/api/wallet-exchange-info.md +++ b/docs/api/wallet/wallet-exchange-info.md @@ -1,6 +1,6 @@ # GET /api/v1/wallet/exchange-info — 金币兑现金 汇率/规则 -> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:**无**(返回静态配置,不读用户) | [← 返回 API 索引](./README.md) +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:**无**(返回静态配置,不读用户) | [← 返回 API 索引](../README.md) ## 入参 无。 diff --git a/docs/api/wallet-exchange.md b/docs/api/wallet/wallet-exchange.md similarity index 95% rename from docs/api/wallet-exchange.md rename to docs/api/wallet/wallet-exchange.md index 9eaefae..73bd46c 100644 --- a/docs/api/wallet-exchange.md +++ b/docs/api/wallet/wallet-exchange.md @@ -1,6 +1,6 @@ # POST /api/v1/wallet/exchange — 金币兑现金 -> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参 diff --git a/docs/api/wallet/wallet-transfer-auth.md b/docs/api/wallet/wallet-transfer-auth.md new file mode 100644 index 0000000..b900255 --- /dev/null +++ b/docs/api/wallet/wallet-transfer-auth.md @@ -0,0 +1,108 @@ +# 免确认收款授权(transfer-auth 族) + +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:全部 Bearer | [← 返回 API 索引](../README.md) +> +> 集成实现:见 [integrations/wxpay](../../integrations/wxpay.md)(微信 V3 免确认收款授权)。 + +开启一次后,后续提现走免确认转账直接到账,不再跳微信确认。绑定 openid 是前提。 + +--- + +## POST /transfer-auth — 开启免确认到账 + +申请免确认授权,返回拉起微信授权页的 `package_info`。 + +### 入参 + +无(`user_id` 从 JWT 取)。 + +### 出参 + +响应 `200`:`TransferAuthResultOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `already_active` | bool | 是否已是开启状态(无需再授权) | +| `package_info` | string \| null | 拉起微信授权页的 package;已开启时为空 | +| `mch_id` | string \| null | 商户号 | +| `app_id` | string \| null | 微信 AppID | + +Mock 出参(首次申请): +```json +{ + "already_active": false, + "package_info": "affirmTransferAuth|{\"mchId\":\"1234567890\",\"appId\":\"wxabc123\",\"package\":\"affirm_biz_123\"}", + "mch_id": "1234567890", + "app_id": "wxabc123" +} +``` + +Mock 出参(已开启,无需重复授权): +```json +{ + "already_active": true, + "package_info": null, + "mch_id": "1234567890", + "app_id": "wxabc123" +} +``` + +### 错误码 +- `400` 未绑定微信 +- `502` 微信授权接口调用失败 +- `503` 微信支付授权未配置 + +--- + +## GET /transfer-auth/status — 查免确认授权状态 + +从微信授权页返回后轮询。 + +### 入参 + +无(`user_id` 从 JWT 取)。 + +### 出参 + +响应 `200`:`TransferAuthStatusOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `state` | string | `none`(未开启)/ `pending`(待确认)/ `active`(已开启)/ `closed`(已关闭) | +| `enabled` | bool | 是否已开启免确认到账 | + +Mock 出参: +```json +{ + "state": "active", + "enabled": true +} +``` + +--- + +## POST /transfer-auth/close — 关闭免确认到账 + +解除授权,恢复每次提现跳微信确认。 + +### 入参 + +无(`user_id` 从 JWT 取)。 + +### 出参 + +响应 `200`:`TransferAuthStatusOut`(同上) + +Mock 出参: +```json +{ + "state": "closed", + "enabled": false +} +``` + +--- + +## 错误码(通用) +- `401` 未鉴权 / token 失效 +- `503` 微信支付授权未配置 diff --git a/docs/api/wallet-unbind-wechat.md b/docs/api/wallet/wallet-unbind-wechat.md similarity index 97% rename from docs/api/wallet-unbind-wechat.md rename to docs/api/wallet/wallet-unbind-wechat.md index 01211dc..a94baf2 100644 --- a/docs/api/wallet-unbind-wechat.md +++ b/docs/api/wallet/wallet-unbind-wechat.md @@ -1,6 +1,6 @@ # POST /api/v1/wallet/unbind-wechat — 解绑微信(清空 openid) -> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) > > 集成实现:见 [integrations/wxpay](../integrations/wxpay.md)。 diff --git a/docs/api/wallet-withdraw-info.md b/docs/api/wallet/wallet-withdraw-info.md similarity index 95% rename from docs/api/wallet-withdraw-info.md rename to docs/api/wallet/wallet-withdraw-info.md index 7a6f1ce..1d45d69 100644 --- a/docs/api/wallet-withdraw-info.md +++ b/docs/api/wallet/wallet-withdraw-info.md @@ -1,6 +1,6 @@ # GET /api/v1/wallet/withdraw-info — 提现额度 / 绑定状态 -> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) > > 集成实现:见 [integrations/wxpay](../integrations/wxpay.md)(提现链路)。 diff --git a/docs/api/wallet-withdraw-orders.md b/docs/api/wallet/wallet-withdraw-orders.md similarity index 95% rename from docs/api/wallet-withdraw-orders.md rename to docs/api/wallet/wallet-withdraw-orders.md index 80b5105..3c86f7c 100644 --- a/docs/api/wallet-withdraw-orders.md +++ b/docs/api/wallet/wallet-withdraw-orders.md @@ -1,6 +1,6 @@ # GET /api/v1/wallet/withdraw-orders — 提现单列表(游标分页) -> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) ## 入参(query) diff --git a/docs/api/wallet-withdraw-status.md b/docs/api/wallet/wallet-withdraw-status.md similarity index 96% rename from docs/api/wallet-withdraw-status.md rename to docs/api/wallet/wallet-withdraw-status.md index 31a851f..636c50e 100644 --- a/docs/api/wallet-withdraw-status.md +++ b/docs/api/wallet/wallet-withdraw-status.md @@ -1,6 +1,6 @@ # GET /api/v1/wallet/withdraw/status — 查提现单状态(轮询) -> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](../README.md) > > 集成实现:见 [integrations/wxpay](../integrations/wxpay.md)(查单 / 撤销转账)。 diff --git a/docs/api/wallet-withdraw.md b/docs/api/wallet/wallet-withdraw.md similarity index 95% rename from docs/api/wallet-withdraw.md rename to docs/api/wallet/wallet-withdraw.md index ce740f0..6c0d55f 100644 --- a/docs/api/wallet-withdraw.md +++ b/docs/api/wallet/wallet-withdraw.md @@ -1,6 +1,6 @@ # POST /api/v1/wallet/withdraw — 发起提现到微信零钱 -> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | 限流:同 IP ≤20 次/分 | [← 返回 API 索引](./README.md) +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | 限流:同 IP ≤20 次/分 | [← 返回 API 索引](../README.md) > > 集成实现:见 [integrations/wxpay](../integrations/wxpay.md)(微信 V3 商家转账、签名、实名加密)。 diff --git a/docs/superpowers/plans/2026-06-30-h5-home-feed-h2b.md b/docs/superpowers/plans/2026-06-30-h5-home-feed-h2b.md new file mode 100644 index 0000000..3f563fc --- /dev/null +++ b/docs/superpowers/plans/2026-06-30-h5-home-feed-h2b.md @@ -0,0 +1,624 @@ +# H2b 首页 feed 真接入 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 把 `h5/home/index.html` 写死的 mock feed 换成真实 `/api/v1/meituan/*` 数据,三排序 tab 接通、「抢」拉起美团,对齐原生 `HomeViewModel`/`HomeScreen` 全功能。 + +**Architecture:** 纯前端 feed 模块(裸 `fetch` 同源相对 `/api/v1`)按 `CouponCard` 渲染进现有 `.feed-card` 标记;`rec`/`distance`→`/meituan/feed`,`sales`→`/meituan/top-sales`,「抢」→`/meituan/referral-link`→`SGBridge.openMeituan`。坐标由新增 `SGBridge.getLocation()` 提供(安卓取 FusedLocation,拿不到落北京默认)。后端零改动。 + +**Tech Stack:** 原生 ES5 风格 JS(对齐 home 现有写法)+ FastAPI StaticFiles + pytest(TestClient 内容冒烟)+ Kotlin(安卓 SGBridge / WebViewScreen)。 + +**测试说明(本仓约定):** 本仓**无 JS 单测框架**;H5 的既有测试是 `tests/test_h5_hosting.py` 的**服务端内容冒烟**(断言 StaticFiles 下发的 HTML/JS 含关键标记 + 响应头)。故本计划:① 用 pytest 内容冒烟做可自动化的回归护栏(test-first);② feed 的**运行时行为**(渲染/翻页/三态/抢)用**浏览器手动验证**步骤(无桥→mock 北京)逐项核对;③ 安卓改动用**编译 + 真机**验证。这是与本仓现状一致的取舍,非偷懒。 + +**参考源(原生权威实现):** +- `shaguabijia-app-android` `ui/home/HomeViewModel.kt`(tab 映射 / 翻页 / 缓存 / 竞态 / getReferralLink) +- `ui/home/HomeScreen.kt:1694`(`FeedCard` 渲染)、`:615`(抢点击)、`:421`(FusedLocation lastLocation) +- 后端 `app/api/v1/meituan.py` + `app/schemas/meituan.py`(`CouponCard` 字段) + +--- + +## File Structure + +| 文件 | 责任 | 改动 | +|---|---|---| +| `h5/shared/bridge.js` | SGBridge JS 桥 | 新增 `getLocation()`(查询类同步) | +| `h5/home/index.html` | 首页 H5 | 删 mock 静态卡;新增 feed 模块 ``(行 10179)**之后**新增一个 ` +``` + +- [ ] **Step 4: 浏览器手动验证(无桥→mock 北京)** + +Run:`uvicorn app.main:app --port 8770`(或 `./run.sh`),浏览器开 `http://localhost:8770/h5/home/` +临时在 console 执行 `HomeFeed.loadFirst()`(Task 4 接 applyLocationState 后会自动触发)。 +Expected:`.home-feed` 出现真实券卡(头图/店名/价格/标签);滚到底自动追加下一页;点「智能推荐/距离最近/销量最高」切换列表;再切回 1s 内命中缓存不转圈。 + +- [ ] **Step 5: 提交** + +```bash +git add h5/home/index.html +git commit -m "feat(h5): 首页 feed 接 /meituan/feed+top-sales(渲染/翻页/缓存/切 tab/防竞态;删 mock 静态卡)" +``` + +--- + +## Task 4: 接定位状态(applyLocationState 联动)+ 三态 UI + 抢按钮 + +**Files:** +- Modify: `h5/home/index.html`(`applyLocationState` @9135;新增 `HomeFeedStates` + 抢委托) + +- [ ] **Step 1: 新增 `HomeFeedStates`(loading/empty/degraded → 复用 H2a 容器)** + +在 Task 3 的 feed 模块 ` +``` + +- [ ] **Step 2: 加 loading / degraded 两个空态容器** + +在 `#homeFeedNoDealEmpty`(行 5041 那个 div)**之后**插入两个容器(复用其样式骨架): + +```html + + + + +``` + +- [ ] **Step 3: applyLocationState 接 feed 加载** + +在 `applyLocationState`(行 9135)函数体**末尾 `}` 之前**加:已授权则首次拉/刷新坐标,未授权则清 feed 三态由 H2a 空态接管: + +```javascript + // H2b: 已授权 → 拉当前 tab(首次)或按坐标变化刷新;未授权 → 关 feed 三态(H2a 去授权空态接管) + if (locationGranted) { + if (window.HomeFeed) { + if (!HomeFeed.state.loadedOnce) HomeFeed.loadFirst(); + else HomeFeed.refreshIfCoordsChanged(); + } + } else { + ['homeFeedLoading', 'homeFeedDegraded', 'homeFeedNoDealEmpty'].forEach(function (id) { + var n = document.getElementById(id); if (n) n.style.display = 'none'; + }); + } +``` + +- [ ] **Step 4: 抢按钮 → referral-link → openMeituan(事件委托)** + +在 `HomeFeedStates` 的 `