feat(coin): 金币数值体系一期 — 签到加成 + 信息流广告结算

签到加成(boost):
- 新增 signin_boost_record 表(user+date 唯一)+ POST /api/v1/signin/boost
- signin 状态/结算逻辑调整

信息流广告结算:
- 新增 ad_feed_reward_record 表(client_event_id 幂等)+ POST /api/v1/ad/feed-reward
- ad_feed_reward 模型/repo、ad schema、按 ecpm/时长结算金币

金币数值:core/rewards.py 数值调整、config_schema/admin config 适配
迁移 coin_reward_phase1(down_revision pstat3growth);测试 + API/DB 文档同步

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
OuYingJun1024
2026-06-07 22:52:06 +08:00
parent 4faf0b8e16
commit 356d6e52b9
36 changed files with 824 additions and 117 deletions
+6 -6
View File
@@ -1,15 +1,15 @@
# ad_watch_log — 看激励视频观看时长(每日防刷主闸)
# ad_watch_log — 看激励视频观看时长(旧版兼容字段)
> 模型 `app/models/ad_watch_log.py` · 仓库 `app/repositories/ad_watch.py` · 接口 `POST /api/v1/ad/watch-report`(上报)、读取面 [ad-reward-status](../api/ad-reward-status.md) · [← 索引](./README.md) · [总览](./OVERVIEW.md)
每条 = 客户端 `onAdClose` 上报的一次激励视频**实际观看秒数**。按 `(user_id, watch_date)` 聚合 `SUM(watch_seconds)` 得当日总观看时长,用于"每天最多看 50 分钟激励视频"硬上限(`rewards.DAILY_AD_WATCH_SECONDS_LIMIT`)。是看广告三条数据流之一(**时长闸**),与 `ad_reward`(发奖)、`ad_ecpm`(收益)并列、**互不关联**
每条 = 客户端 `onAdClose` 上报的一次激励视频**实际观看秒数**。按 `(user_id, watch_date)` 聚合 `SUM(watch_seconds)` 得当日总观看时长。当前产品只保留每日 500 次上限,`rewards.DAILY_AD_WATCH_SECONDS_LIMIT=0` 表示不启用时长闸;本表保留用于旧客户端兼容和排查
> 防刷设计:前端上报时长不可信 → 时长是**主闸**;配合 `ad_reward_record` 的每日**次数兜底**(`DAILY_AD_REWARD_LIMIT`,默认 200),前端少报时长也刷不过次数闸
> 防刷设计:前端上报时长不可信,不能作为唯一发奖依据;当前发奖闸口以 `ad_reward_record` 的每日次数上限(`DAILY_AD_REWARD_LIMIT`,默认 500)为准
## 用在哪 / 增删改查
- **C(插入)**:`POST /ad/watch-report`(`add_watch_seconds`)。每次看完激励视频上报一次 → 写一行,`watch_seconds` 服务端夹 `[0, MAX_SINGLE_WATCH_SECONDS=120]`(防异常大值)。鉴权接口已确保 user 存在,不校验 UnknownUser。
- **U / D**:无。当日多次观看 = 多行,靠 SUM 聚合。
- **R**:`watched_seconds_today` 当日累计 —— 被 `ad_reward.grant_ad_reward`(发奖前的时长闸)`GET /ad/reward-status`(展示"今日已看 X/50 分钟" + 满则不给看)调用
- **R**:`watched_seconds_today` 当日累计 —— 被 `ad_reward.grant_ad_reward` `GET /ad/reward-status` 读取;当 `DAILY_AD_WATCH_SECONDS_LIMIT=0` 时不触发 capped
## 字段
| 列 | 类型 | 约束 / 默认 | 说明(取值 / join) |
@@ -22,11 +22,11 @@
## 关系 / Join Key
- `user_id``user.id`(多对一)。
-`ad_reward_record` / `ad_ecpm_record` **无公共键**:不做逐条关联,只按 `(user_id, watch_date)` 聚合。发奖时 `ad_reward` 读本表当日累计判时长闸。
-`ad_reward_record` / `ad_ecpm_record` **无公共键**:不做逐条关联,只按 `(user_id, watch_date)` 聚合。
## 索引与约束
- PK `id`;index `user_id``watch_date``created_at`
## 注意
- 上限走常量 `DAILY_AD_WATCH_SECONDS_LIMIT=3000`(50min)/ `MAX_SINGLE_WATCH_SECONDS=120`(暂未挂 app_config,改值改 `core/rewards.py`)
- 当前 `DAILY_AD_WATCH_SECONDS_LIMIT=0` 表示时长闸停用;`MAX_SINGLE_WATCH_SECONDS=120` 仍用于夹单次上报秒数
- 按日期串等值查,不在 SQL 跨时区比 date(SQLite 不可靠)。