feat(platform): 首页轮播 feed 接入真实数据 + 三统计配置增强

轮播 feed(marquee_seed,全新):
- 新增 marquee_seed 表 + admin CRUD/批量生成/预览;真实比价记录(success 且 0<省额≤300元、按 user 去重)优先,种子兜底混播
- 种子为「生成规则」:用户名可空(空则按脱敏格式随机合成、避开同屏撞名),金额改 [min,max] 区间随机,feed 公平随机抽取(不看 sort_order)
- 新增 GET /api/v1/platform/savings-feed(无鉴权);展示时间统一刷新为相对当前的最近时刻

三统计配置(platform_stat_display)增强:
- 自增长新增「绝对增量」方式(random_kind=add,每周期 +[step_min,step_max])
- 真实值模式加基数偏移(real_offset,展示=真实+偏移)
- 只增不减护栏(allow_decrease 默认关,real/manual 不回退防门面缩水)
- 累计节省 real 口径只计 0<单条≤300元防虚高;倍率上限 5.0→1.5;PATCH 支持 apply_now 立即更新

迁移:marquee_seed_table / marquee_seed_range / platform_stat_growth_offset(均可逆,已验证)
文档:API/DB 对应文档同步更新

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
OuYingJun1024
2026-06-07 12:05:17 +08:00
parent cfeacb4bab
commit c9578f5ba2
23 changed files with 930 additions and 62 deletions
+8 -1
View File
@@ -3,7 +3,7 @@
> Base URL:生产 `https://app-api.shaguabijia.com`;本地联调 `http://<开发机>:8770`
> 协议:HTTP / JSON,请求与响应体均 `application/json`,字段统一 **snake_case**
> 鉴权:需鉴权的接口在请求头带 `Authorization: Bearer <access_token>`
> 最后更新:2026-06-04+ 运营后台 Admin 子应用 A1A18,见下方「运营后台 Admin」组
> 最后更新:2026-06-07运营后台 Admin 子应用 A1A26;首页门面数据 #39#40 + 轮播种子 A21A26
> 架构:`app/api/v1/` 只放很轻的接口层;穿山甲/微信支付/极光/短信/美团等 SDK 集成的重逻辑在 `app/integrations/`,实现细节见 [docs/integrations/](../integrations/README.md)。
---
@@ -68,6 +68,7 @@
| 38 | `POST /api/v1/feedback` | Bearer | [详情](./feedback.md) |
| **首页门面数据**(前缀 `/api/v1/platform`;全平台展示数字,登录前可读) |||
| 39 | `GET /api/v1/platform/stats` | 无 | [详情](./platform-stats.md) |
| 40 | `GET /api/v1/platform/savings-feed` | 无 | [详情](./platform-savings-feed.md) |
| **静态资源**StaticFiles 挂载,见下方 `/media` 静态服务) |||
| - | `GET /media/avatars/<file>` | 无 | 用户头像;返回二进制图片 |
| - | `GET /media/feedback/<file>` | 无 | 反馈截图;返回二进制图片 |
@@ -92,6 +93,12 @@
| A18 | `GET /admin/api/audit-logs` | admin | [详情](./admin-audit-logs.md) |
| A19 | `GET /admin/api/dashboard-display` | admin | [详情](./admin-dashboard-display.md) |
| A20 | `PATCH /admin/api/dashboard-display/{metric}` | operator | [详情](./admin-dashboard-display.md) |
| A21 | `GET /admin/api/marquee-seeds` | admin | [详情](./admin-marquee-seeds.md) |
| A22 | `POST /admin/api/marquee-seeds` | operator | [详情](./admin-marquee-seeds.md) |
| A23 | `PATCH /admin/api/marquee-seeds/{seed_id}` | operator | [详情](./admin-marquee-seeds.md) |
| A24 | `DELETE /admin/api/marquee-seeds/{seed_id}` | operator | [详情](./admin-marquee-seeds.md) |
| A25 | `POST /admin/api/marquee-seeds/bulk` | operator | [详情](./admin-marquee-seeds.md) |
| A26 | `GET /admin/api/marquee-seeds/preview` | admin | [详情](./admin-marquee-seeds.md) |
| - | `GET /admin/api/health` | 无 | admin 健康检查(无单独文档) |
> ⚠️ 美团三个接口当前**无鉴权**,且 `referral-link` 的 `sid` 允许客户端传值覆盖默认渠道——见各接口"备注"。
+21 -9
View File
@@ -4,7 +4,7 @@
配置客户端首页三个门面数字(帮助用户 / 完成比价 / 累计节省)的展示模式。每个指标可独立选 real/manual/random。用户侧读取见 [platform-stats](./platform-stats.md);表见 [platform_stat_display](../database/platform_stat_display.md)。
> **单位约定**:倍率用**千分比整数**(1.000→`1000`、1.100→`1100`)。`total_saved` 的 `manual_value` / `random_current` / `random_initial` 单位是**分**(前端展示时 ÷100 取元)。
> **单位约定**:倍率用**千分比整数**(1.000→`1000`、1.100→`1100`)。`total_saved` 的 `manual_value` / `random_current` / `random_initial` / `random_step_min` / `random_step_max` / `real_offset` 单位是**分**(前端展示时 ÷100 取元);两个计数指标是个数
---
@@ -24,9 +24,14 @@
| `random_mult_max` | int | 倍率上限(千分比) |
| `random_tick_seconds` | int | 更新间隔(秒,默认 86400=1 天) |
| `random_anchor_minutes` | int | 触发时刻对齐偏移(距北京 0 点分钟数:天=每日时刻、小时=每小时第几分、分钟=0) |
| `random_current` | int \| null | random 当前累积值(基础单位) |
| `random_kind` | string | 自增长方式:`mult` ×倍率 / `add` +绝对增量 |
| `random_step_min` | int | add 模式增量下限(基础单位) |
| `random_step_max` | int | add 模式增量上限(基础单位) |
| `real_offset` | int | real 模式基数偏移(基础单位):展示 = 真实值 + 偏移 |
| `allow_decrease` | bool | 是否允许展示值下降(默认 false = 只增不减) |
| `random_current` | int \| null | 当前展示值(所有模式,基础单位) |
| `random_last_tick_at` | string \| null | 上次 tick 时刻(ISO |
| `real_value` | int | 当前真实值(给运营对比参考,基础单位) |
| `real_value` | int | 当前真实值(纯真实、不含偏移;给运营对比参考,基础单位) |
| `updated_at` | string \| null | 上次修改时间 |
---
@@ -40,22 +45,29 @@
|---|---|---|
| `mode` | string | `real` / `manual` / `random` |
| `manual_value` | int | manual 固定值(基础单位,≥0) |
| `random_mult_min` | int | 倍率下限(千分比,1000~5000 |
| `random_mult_max` | int | 倍率上限(千分比,≥下限) |
| `random_mult_min` | int | 倍率下限(千分比,1000~1500 |
| `random_mult_max` | int | 倍率上限(千分比,≥下限,≤1500 |
| `random_tick_seconds` | int | 更新间隔(秒,≥60) |
| `random_anchor_minutes` | int | 触发时刻对齐偏移(分钟,`*60 < 更新间隔` |
| `random_anchor_minutes` | int | 触发时刻对齐偏移(分钟,0~1439 |
| `random_kind` | string | 自增长方式:`mult` / `add` |
| `random_step_min` | int | add 增量下限(基础单位,≥0) |
| `random_step_max` | int | add 增量上限(基础单位,≥下限) |
| `real_offset` | int | real 基数偏移(基础单位,≥0) |
| `allow_decrease` | bool | 允许展示值下降(默认 false=只增不减) |
| `random_initial` | int | 切 random 的初始基数(基础单位);**留空则用当前真实值播种** |
| `apply_now` | bool | 立即更新:不等更新钟点,保存后马上刷新一次(random 走一档,real/manual 取应有值,均经护栏) |
### 出参
响应 `200`:更新后的 `PlatformStatItemOut`(同上)。
### 错误
- `400`:mode 非法 / 倍率越界(<1000 或 >5000/ 上限<下限 / tick<60 / 负值。
- `400`:mode/kind 非法 / 倍率越界(<1000 或 >1500/ 上限<下限 / 增量负值或上限<下限 / 偏移负值 / tick<60 / 更新时间越界 / manual 负值。
- `403`:角色不足(需 operator 或 super_admin)。
- `404`:无效 metric(不在三指标内,作为 400 文案返回)。
### 说明
- **统一定时刷新**:三模式的展示值(`random_current`)只在「更新时间(按更新间隔)」对齐的北京钟点刷新一次——real 快照查库 / manual 取固定值 / random ×倍率`random_tick_seconds`+`random_anchor_minutes` 三模式通用。
- 改 mode/manual/倍率等到**下个更新钟点**才在客户端生效(展示值不立即变);`random_initial` 例外:立即设为该值并重置(自增长设起点)。首次无展示值时按当前模式播种
- **统一定时刷新**:三模式的展示值(`random_current`)只在「更新时间(按更新间隔)」对齐的北京钟点刷新一次——real 快照查库(+偏移)/ manual 取固定值 / random `random_kind` 走一档(`mult` ×倍率 / `add` +增量)`random_tick_seconds`+`random_anchor_minutes` 三模式通用。
- **只增不减护栏**(`allow_decrease=false`,默认):real/manual 刷新时新目标值低于当前展示值则保持不降;要下调须显式传 `allow_decrease=true`
- 改 mode/manual/倍率/增量/偏移等到**下个更新钟点**才在客户端生效(展示值不立即变);`random_initial` 立即设起点并重置;`apply_now=true` 立即刷新一次。首次无展示值时按当前模式播种。
- 每次改动写 `admin_audit_log`(action=`dashboard_display.set`,detail 含改前/改后快照)。
- 客户端在**进首页 / 回前台时**重拉 `/stats` 取最新展示值。
+45
View File
@@ -0,0 +1,45 @@
# Admin 首页轮播种子管理
> 所属: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);表见 [marquee_seed](../database/marquee_seed.md)。金额单位:分(前端 ÷100 显示元)。
## 复用结构 MarqueeSeedOut
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | int | |
| `masked_user` | string \| null | 脱敏用户名;`null` = feed 随机合成 |
| `min_cents` | int | 金额区间下限(分) |
| `max_cents` | int | 金额区间上限(分);固定金额则与下限相等 |
| `enabled` | bool | 停用不参与混播 |
| `sort_order` | int | 仅后台列表排序(小在前) |
| `created_at` | string \| null | 创建时间(ISO) |
## GET /admin/api/marquee-seeds — 种子列表
出参 `200`:`list[MarqueeSeedOut]`(按 `sort_order,id`)。
## GET /admin/api/marquee-seeds/preview — 预览实际混播 feed
预览客户端实际会看到的轮播(真实记录会插队、种子随机抽取 / 金额随机 / 名字合成),供运营对效果。**含随机,每次结果不同**。
- 入参:`limit`(query,1~30,默认 8)
- 出参 `200`:`{"items": [{masked_user, saved_amount_cents, time}]}`(条目同 [platform-savings-feed](./platform-savings-feed.md))
## POST /admin/api/marquee-seeds — 新增(带审计)
入参 `MarqueeSeedCreate`:`masked_user`(可选,空 / 不传 → 随机合成)、`min_cents`(必填,≥0)、`max_cents`(必填,≥min,≤1000 元)、`enabled`(默认 true)、`sort_order`(默认 0)。出参:新建的 `MarqueeSeedOut``400`=金额非法。
## POST /admin/api/marquee-seeds/bulk — 批量生成(带审计)
冷启动铺量:一次生成 `count` 条「自动合成名」(masked_user=null)种子,金额均为给定区间,`sort_order` 续在当前最大值之后。
- 入参 `MarqueeSeedBulkCreate`:`count`(1~200)、`min_cents``max_cents``enabled`(默认 true)
- 出参 `200`:`list[MarqueeSeedOut]`(新建的 count 条)。`400`=数量 / 金额非法。
## PATCH /admin/api/marquee-seeds/{seed_id} — 改(带审计)
入参 `MarqueeSeedUpdate`(均可选):`masked_user` / `min_cents` / `max_cents` / `enabled` / `sort_order`
- `masked_user` 约定:**不传**=不改;**传空串**=清空(改回随机合成);**传值**=固定该名。
- 只改单边 `min_cents` / `max_cents` 时,与现有的另一边一起做「上限≥下限」校验。
出参:更新后的 `MarqueeSeedOut``404`=种子不存在;`400`=金额非法。
## DELETE /admin/api/marquee-seeds/{seed_id} — 删(带审计)
出参 `200`:`{"deleted": true}``404`=种子不存在。
## 说明
- 写操作需 operator(super 恒可),均写 `admin_audit_log`(action=`marquee_seed.create` / `update` / `delete` / `bulk_generate`)。预览为只读,任意已登录 admin 可用。
- 改动客户端**进首页 / 回前台**重拉 feed 时生效。
+27
View File
@@ -0,0 +1,27 @@
# GET /api/v1/platform/savings-feed — 首页轮播 feed(真实+种子混播)
> 所属:Platform 组(前缀 `/api/v1/platform` | 鉴权:**无** | [← 返回 API 索引](./README.md)
客户端首页顶部「用户****xxx 比价后节省 xx 元」滚动条数据源。全平台真实比价记录优先;不足时用运营配的种子([marquee_seed](../database/marquee_seed.md))补齐到 `limit` 条「混播」,保证轮播不空。
## 入参
| 参数 | 类型 | 说明 |
|---|---|---|
| `limit` | int, query | 返回条数,默认 8,范围 1~30 |
## 出参
响应 `200`:`SavingsFeedOut`
| 字段 | 类型 | 说明 |
|---|---|---|
| `items` | list | 轮播条目数组(最多 `limit` 条) |
| `items[].masked_user` | string | 脱敏用户名,如 `用户********a52` |
| `items[].saved_amount_cents` | int | 节省(分),客户端 ÷100 显示「x.xx 元」 |
| `items[].time` | string | 北京时间 `HH:MM:SS` |
## 说明
- 真实条:`comparison_record``status='success'``0 < saved_amount_cents ≤ 300 元` 的近期记录(金额超 300 元视为异常 / bug 值剔除,防「节省 999 元」穿帮),**按 `user_id` 去重**(同一用户只取最新一条,避免单人刷屏);用户名按 `user_id` 哈希脱敏(`用户********`+3 位)。
- 种子条:`marquee_seed`(`enabled=true`),仅在真实去重后不足 `limit` 时补齐;**从启用种子中公平随机抽取**(不再固定取前 N,所有种子都有机会露出)。每条种子:用户名留空则按脱敏格式**随机合成并避开同屏撞名**,金额在 `[min_cents, max_cents]` 区间**随机取值**(固定金额则 min==max)。
- **`time` 为合成的「最近」时间**(从当前北京时间往前递减,首条约 20 秒前,其余每条 2~6 分钟):社会证明轮播保证永远像刚发生,不受旧测试数据 / 低谷期记录影响。真实的用户/金额不变,只换展示时间。
- 因含随机(抽取 / 金额 / 合成名),**每次请求结果都不同**——这是轮播想要的鲜活感。
- 逻辑见 `app/repositories/marquee.py`;运营管理种子见 [admin-marquee-seeds](./admin-marquee-seeds.md)。
+3 -2
View File
@@ -18,9 +18,10 @@
## 说明
三指标各自独立选模式,且**统一「定时刷新」**:客户端看到的展示值只在「更新时间(按更新间隔)」对齐的北京钟点(`anchor + k*间隔`)刷新一次,平时不变。每次刷新按模式算新值:
- **real**:重新快照查库。`help_users`=有过 `status='success'` 比价记录的去重用户数;`total_compares`=成功比价记录数;`total_saved_cents`=成功记录 `saved_amount_cents` 求和。
- **real**:重新快照查库 + 基数偏移`help_users`=有过 `status='success'` 比价记录的去重用户数;`total_compares`=成功比价记录数;`total_saved_cents`=成功记录 `saved_amount_cents` 求和(**只计 `0<saved≤300 元` 的条目**,剔除 bug 异常大值);最终展示 = 真实值 + 运营配的 `real_offset`
- **manual**:取运营当前手填的固定值(改值到下个更新钟点生效)。
- **random**:在上次展示值上 ×一个 [min,max](≥1.0)的随机倍率,只增不减(跨几个钟点几次)。
- **random**:在上次展示值上按方式增长——`mult` ×一个 [1.0,1.5] 随机倍率 / `add` +一个 [下限,上限] 随机绝对增量(跨几个钟点几次)。
- **只增不减**:默认门面数字不回退(real/manual 新值低于当前则保持),除非运营开「允许下降」。
计算逻辑见 `app/repositories/platform_stat.py`,模型见 `app/models/platform_stat.py`(表 [platform_stat_display](../database/platform_stat_display.md))。