feat(wallet): 金币记录按 trace_id 聚合比价/领券看广告金币 #225
Reference in New Issue
Block a user
Delete Branch "feat/coin-ledger-aggregate-ad-rewards"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景
「金币变动记录」里看广告金币按每条广告统计,一次比价/领券的等候期会连续看多条信息流广告,于是列表刷出一长串「比价奖励 +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)。ad_feed_reward_record按ref_id == client_event_id补 trace_id)。ad_feed_reward_record.trace_id过时注释(领券自 2026-07-15 也带)。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裁剪,避免会话行跨游标时产生「残组」重复(有专门回归测试锁死)。CoinLedgerRowdataclass(非 ORM,杜绝把聚合后的 amount 误写回底层流水)。🤖 review-pr 深审结论
🔴 一个部署阻断项:alembic 多头(必须先修再合);其余实现优秀(置信度 0.92)。功能本身正确、测试扎实,问题只在迁移链。
阻断项(合并 main 后实跑复现)
alembic/versions/coin_transaction_trace_id.py:19—down_revision='comparison_updated_at'。但 #224 的savings_record_trace_id已先合入main,也挂在comparison_updated_at下 → 合并后两个 alembic head。已实跑复现:alembic upgrade head会直接失败。测试没抓到是因为测试用create_all建表、不跑迁移(故 19 passed 仍绿)。修复(一行):把
down_revision改为'savings_record_trace_id'(#224 已先合入,线性化接在其后),或加一条 alembic merge 迁移。改完alembic heads应回单头。测试/构建:✅ 已实跑(合并 main 后 worktree,venv Py3.12)
pytest tests/test_welfare.py→ 19 passedalembic upgrade head→ FAILED(Multiple heads,见上);alembic heads→ 两个 head正面(rebase 一行后即可合)
list_coin_transactions正确且注释到位:"T:"+trace_id/"I:"+id前缀分组不撞车;CTE 先对全量分组再按rep_id<cursor游标过滤(正确避免跨页「残组」);next_cursor=rep_id自洽。EXISTS守护 +trace_id IS NULL),biz_type 硬编码历史快照符合「迁移不可变」;downgrade对称。CoinLedgerRow用 frozen dataclass 而非 ORM 行,避免把聚合后的 amount 误写回底层流水——好设计。grant_feed_reward透传 / 聚合展示 均有专测。建议:仅需改
coin_transaction_trace_id.py的down_revision → 'savings_record_trace_id',重跑alembic heads确认单头后即可合并。🤖 review-pr 深审结论
🟢 可合 · 置信度 0.9 —— 设计正确、测试严谨、对最新 main 合并后无回归。(完整仓 worktree 深审 + 合并结果实跑)
构建 / 测试(已实跑)
tests/test_welfare.py隔离跑:19/19 通过(含 7 条新增)。origin/main同套件:829 passed / 相同的 12 failed + 5 errors。→ 本 PR 净 +7 通过、零新增失败;那 12+5 是 main 上既有的全量跑测试污染(device_push / guide_video / withdraw_tiers 等,与本改动无关)。heads= 单一coin_transaction_trace_id(作者已把 down_revision 重挂到savings_record_trace_id消双 head ✅)。ruff --fix即可,不影响行为。设计正确性 👍
rep_id < cursor施于外层 → 正确规避会话行跨游标的「残组」重复;test_coin_transactions_pagination_no_phantom_regroup断言游标严格落在会话两成员 id 之间,是货真价实的跨游标回归守护。T:trace/I:id命名空间隔离无碰撞;rep_id=MAX(id)跨组全局唯一 → 游标分页无重/漏。CoinLedgerRow(frozen dataclass、非 ORM)杜绝聚合后 amount 误写回底层流水,考虑周到。EXISTS守护 +trace_id IS NULL幂等;client_event_id有UniqueConstraint→ 标量子查询 PG 安全(不会多行报错);biz_type 按「迁移不可变」硬编码快照,正确。merged_count默认 1、grant_coins新增可选 kwarg → 向后兼容;adminlist_all_coin_transactions未动,blast radius 收敛。grant_feed_reward→grant_coins→CoinTransaction.trace_id,与分组键、常量三处一致。小建议(非阻塞)
list_coin_transactions每页都在用户全量 coin_transaction 上分组(为正确性刻意不做 id<cursor 预裁)。重度用户深翻页为 O(N)/页;当前量级 OK(trace_id/user_id 有索引),表增大后可关注。docstring 已注明该权衡。🤖 由 review-pr 自动深审 · 全程只读、worktree 旁路隔离,未触碰任何分支/工作区