Files
shaguabijia-app-server/docs/database/coin_transaction.md
T
左辰勇 de1fd58749 feat(ad): 膨胀弹窗改用服务端权威金额 + 本轮累计口径,下线 signin_boost
要守住的不变量:弹窗数字 == 本轮实际到账之和 == 用户看到的余额涨幅。三者对不上,
用户就会认为少发了钱(走查现象:弹窗 240、余额只涨 40)。

- reward-result:按 ad_session_id 查本次实发金币,替代余额差 / coin_per_ad 估算。
  S2S 异步未到账返 200+pending 而非 404(404 只表示路由不存在,混在一起客户端没法
  区分「后端没部署」和「再等等」);同 session 多条时显式优先 granted——客户端先报
  closed_early、S2S 后到时,granted 反而是后写的。

- boost_round_id:客户端经 mediaExtra 透传「这条广告属于哪一轮膨胀」,穿山甲 S2S 原样
  带回后随发奖记录落库。**纯标签,不参与发奖判定**。reward-result 新增 round_coin,按
  (user_id, boost_round_id) 对 granted 记录求和。之所以由服务端求和而非客户端自己累加
  ——客户端进程被杀/重建后本地累计会丢,发奖记录不会。
  · 求和恒带 user_id:轮 id 是客户端生成的,不带就等于让任何人拿别人的轮 id 查别人发了多少。
  · 本条非 granted(capped 等)时仍返本轮累计、该条按 0 计,让限额 toast 有数可显。
  · test-grant 加可选 boost_round_id:它不经 S2S 拿不到 extra,不补则 debug 包验不了累计。
  · 客户端复用同一轮 id 只会把展示数字滚大,求和的是已发生的记录,不产生新入账,无资损。

- 下线 signin_boost(签到膨胀):它按固定 3000 金币发、与广告实际收益脱钩,产品确认从来
  不是设计内的口径——奖励只有「签到」和「看视频」两种。签到弹窗的「看广告膨胀」改与福利页
  看视频同走 reward_video(按 eCPM 公式)。摘除回调分支、POST /signin/boost、
  SigninBoostRecord、signin_boost_coin 配置,并 drop signin_boost_record 表。
  **coin_transaction.biz_type='signin_boost' 的历史流水保留不动**——钱是真发过的,账必须
  留得住;admin 大盘那两项改从金币流水统计(一次膨胀 = 一笔,与原口径等价),继续能查回历史。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 16:55:25 +08:00

47 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# coin_transaction — 金币流水账本
> 模型 `app/models/wallet.py` · 仓库 `app/repositories/wallet.py`(写入口 `grant_coins`) · 接口 [wallet-coin-transactions](../api/wallet-coin-transactions.md) · [← 索引](./README.md) · [总览](./OVERVIEW.md)
金币每变动一笔就记一行(记变动后余额 `balance_after`),用于对账与 App「金币明细」展示。**只增不改不删**——账本不可篡改。
## 用在哪 / 增删改查
- **C(插入)**:唯一来源是 `wallet.grant_coins`(发金币入口),被以下动作调用,每次写一笔:
| 动作 / endpoint | `biz_type` | `amount` | `ref_id` 指向 |
|---|---|---|---|
| 签到 `POST /signin/do` | `signin` | + | 当天日期串(= `signin_record.signin_date` ISO) |
| ~~签到后看广告膨胀~~(**2026-07 已下线**) | `signin_boost` | + | 历史行:当时的广告 `trans_id`,无则当天日期 ISO 串。不再产生新行;签到弹窗的看广告改走 `reward_video` |
| 领任务 `POST /tasks/claim` | `task_<key>`(如 `task_enable_notification`) | + | 一次性任务=`user_task.task_key`;可重复任务(`enable_notification`)=带序号 `task_key:N` |
| 普通激励视频 S2S 回调 `POST /ad/pangle-callback` | `reward_video`(历史兼容:`ad_reward`) | + | `ad_reward_record.trans_id` |
| 信息流广告结算 `POST /ad/feed-reward` | `feed_ad_reward` | + | `ad_feed_reward_record.client_event_id` |
| 金币兑现金 `POST /wallet/exchange` | `exchange_out` | | null(配套 `cash_transaction.exchange_in`) |
| admin 手动加金币 | `admin_grant` | + | null(`remark`=`admin:<reason>`) |
| admin 手动扣金币 | `admin_deduct` | | null |
> ⚠️ 比价里程碑 `comparison_milestone_claim` **当前不发币、不写本表**(产品定暂不真发,详见该表文档)。
- **U / D**:无。账本只追加。
- **R**:`GET /wallet/coin-transactions`(金币明细,`id` 倒序游标分页);admin 跨用户金币流水(可按 `biz_type` 筛)。
## 字段
| 列 | 类型 | 约束 / 默认 | 说明(取值 / join) |
|---|---|---|---|
| `id` | Integer | PK, autoincrement | |
| `user_id` | Integer | FK→user.id, index, NOT NULL | 归属用户 |
| `amount` | Integer | NOT NULL | 本笔变动金币;**正=入账(赚),负=出账(花/兑换)** |
| `balance_after` | Integer | NOT NULL | 本笔后金币余额(= 当时 `coin_account.coin_balance`,对账用) |
| `biz_type` | String(32) | NOT NULL | 取值见上表:`signin` / `signin_boost` / `task_<key>` / `reward_video` / `ad_reward`(历史) / `feed_ad_reward` / `exchange_out` / `admin_grant` / `admin_deduct`。无 DB 枚举约束,靠写入方约定 |
| `ref_id` | String(64) | nullable | **关联业务键,指向随 `biz_type` 变(见上表)**;无关联时为 null |
| `remark` | String(128) | nullable | 备注(如签到「每日签到 第N天」、admin「admin:<reason>」) |
| `created_at` | DateTime(tz) | server_default now(), index | 时间 |
## 关系 / Join Key
- `user_id``user.id`(多对一)。
- `ref_id` 是**软关联**(无 FK),目标随 `biz_type`:`signin`→签到日(`signin_record.signin_date` ISO) / `signin_boost`(历史)→当时的广告 `trans_id`(无则当天日期) / `task_<key>`→一次性任务=`user_task.task_key`、可重复任务=`task_key:N` / `reward_video`/`ad_reward``ad_reward_record.trans_id` / `feed_ad_reward``ad_feed_reward_record.client_event_id` / 其余 null。
## 索引与约束
- PK `id`;index `user_id``created_at`
- 部分唯一索引 `ux_coin_transaction_task_ref`(`user_id`, `biz_type`, `ref_id`),条件 `biz_type LIKE 'task%' AND ref_id IS NOT NULL`:可重复任务(如打开消息提醒)按 `ref_id` 序号(`task_key:N`)去重,挡并发/连点重复发放;仅 `task_*``ref_id` 非空生效,不影响 signin/exchange/withdraw 等其它 `biz_type`
## 注意
- 流水与余额快照(`coin_account`)、与对应业务记录(signin/task/ad_reward/feed_ad_reward 等)在**同一事务**写,不会只发币不留痕。