Files
shaguabijia-app-server/app
guke e8c2ebda13 feat(wallet): 金币记录按 trace_id 聚合比价/领券看广告金币 (#225)
## 背景

「金币变动记录」里看广告金币按**每条广告**统计,一次比价/领券的等候期会连续看多条信息流广告,于是列表刷出一长串「比价奖励 +x」「领券奖励 +x」,淹没其它记录、观感差。

## 改动

把**一次比价 / 一次领券**连续看广告获得的多条金币,按 `trace_id` **聚合成一条**展示(金额取该次会话合计)。

**纯后端**:比价、领券的结算上报都已把本场 `trace_id` 传到 `/feed-reward`(领券自 2026-07-15、客户端 `a98cab8` 起),无需 Android 改动即可生效。

### 具体
- `coin_transaction` 新增 `trace_id` 列 + 索引;`grant_coins` 增 `trace_id` 参数;`grant_feed_reward` 发奖时写入本场 trace_id。
- `GET /api/v1/wallet/coin-transactions` 改为**按 trace_id 分组的游标分页**(CTE 聚合),响应新增 `merged_count`(合并条数,未合并=1)。
- Alembic 迁移:加列 + 索引 + **回填历史**(从 `ad_feed_reward_record` 按 `ref_id == client_event_id` 补 trace_id)。
- 更正 `ad_feed_reward_record.trace_id` 过时注释(领券自 2026-07-15 也带)。
- **只改 App 用户接口**;admin 审计接口(`list_all_coin_transactions`)保持每条一行不动。

## 关键设计

- **分组键**:`biz_type ∈ (feed_ad_reward_comparison, feed_ad_reward_coupon)` 且 `trace_id` 非空 → 按 `trace_id` 合并;其余(激励视频/签到/通用信息流/空 trace 等)每条一行。
- **代表行**取组内 `MAX(id)`(供余额/时间/标题);`amount = SUM`;游标 `rep_id < cursor` 施于**外层**查询——CTE 在用户全量行上分组,**不**按 `id<cursor` 裁剪,避免会话行跨游标时产生「残组」重复(有专门回归测试锁死)。
- 返回 `CoinLedgerRow` dataclass(非 ORM,杜绝把聚合后的 amount 误写回底层流水)。
- SQL 全程 **SQLite/PG 双方言通用**(dev/测试 SQLite,prod PG)。

---------

Co-authored-by: guke <guke@autohome.com.cn>
Reviewed-on: #225
2026-08-07 19:12:58 +08:00
..
2026-05-31 17:10:16 +08:00