# 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)` 得当日总观看时长。当前产品只保留每日 500 次上限,`rewards.DAILY_AD_WATCH_SECONDS_LIMIT=0` 表示不启用时长闸;本表保留用于旧客户端兼容和排查。

> 防刷设计:前端上报时长不可信,不能作为唯一发奖依据;当前发奖闸口以 `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` 读取;当 `DAILY_AD_WATCH_SECONDS_LIMIT=0` 时不触发 capped。

## 字段
| 列 | 类型 | 约束 / 默认 | 说明(取值 / join) |
|---|---|---|---|
| `id` | Integer | PK, autoincrement | |
| `user_id` | Integer | FK→user.id, index, NOT NULL | 归属用户 |
| `watch_seconds` | Integer | NOT NULL, default 0 | 本次实际观看秒数(服务端已夹 `[0,120]`) |
| `watch_date` | String(10) | index, NOT NULL | 北京时间日期串 `YYYY-MM-DD`,按它等值做"当日总时长"聚合 |
| `created_at` | DateTime(tz) | server_default now(), index | 时间 |

## 关系 / Join Key
- `user_id` → `user.id`(多对一)。
- 与 `ad_reward_record` / `ad_ecpm_record` **无公共键**:不做逐条关联,只按 `(user_id, watch_date)` 聚合。

## 索引与约束
- PK `id`;index `user_id`、`watch_date`、`created_at`。

## 注意
- 当前 `DAILY_AD_WATCH_SECONDS_LIMIT=0` 表示时长闸停用;`MAX_SINGLE_WATCH_SECONDS=120` 仍用于夹单次上报秒数。
- 按日期串等值查,不在 SQL 跨时区比 date(SQLite 不可靠)。
