32d04d7008
代码 review(本分支未提交 WIP)发现并修复: - 高: enable_notification 可重复领取的发放是无锁读-算-写,并发/连点会算出同一序号 双倍发钱。给 coin_transaction 加 (user_id,biz_type,ref_id) 部分唯一索引(仅 task_*), claim 撞唯一约束→IntegrityError 回滚兜底成 AlreadyClaimedError(409),不双发。 配套 alembic 迁移 coin_txn_task_ref_uq(挂 head=drop_force_onboarding)。 - 中: 加领取封顶 rewards.notification_max_claims(减半到底=1 后不再可领),挡通知一直关着 时无限刷 1 金币;列表领满后 claimed 置 True 供客户端隐藏。 - 中: _notification_grant_times 改只数 amount>0 流水,避免日后冲正/负向流水把次数算大。 - 低: 合并 ops_marquee._nickname 两个一字不差的重复分支。 - 文档: tasks-list / tasks-claim 补可重复领取/逐次减半/409 语义。 - 测试: 新增「领满到底→409 + claimed=True」用例;test_welfare 全绿(3 个 proxy 失败 为 pre-existing httpx mock 问题,与本次无关)。 注: review 标的「do_signin 未归一化老 cycle_day」经验算为非 bug(x%LEN+1 对越界值 本就周期正确,且与 get_status 预览公式恒等),未改。 含本分支签到7天改制/任务文案/膨胀金币等 WIP。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
41 lines
1.5 KiB
Python
41 lines
1.5 KiB
Python
"""add coin_transaction task ref unique index
|
|
|
|
可重复任务(task_enable_notification「打开消息提醒」逐次减半领取)的发放路径是无锁的
|
|
读-算-写:并发/连点的两个 claim 会都读到同一已领次数、算出同一 ref_id(task_key:N)、
|
|
各发一笔,导致双倍发钱。给 coin_transaction 加 (user_id, biz_type, ref_id) 部分唯一索引
|
|
(仅 biz_type LIKE 'task%' 且 ref_id 非空),第二笔撞唯一约束 → IntegrityError,被
|
|
task.claim_task 兜底成 AlreadyClaimedError(409)。与 cash_transaction 提现退款、
|
|
withdraw_order 在途单的「唯一约束 + IntegrityError 兜底」同模式。
|
|
|
|
Revision ID: coin_txn_task_ref_uq
|
|
Revises: drop_force_onboarding
|
|
Create Date: 2026-06-12 00:00:00.000000
|
|
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision: str = "coin_txn_task_ref_uq"
|
|
down_revision: Union[str, Sequence[str], None] = "drop_force_onboarding"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_index(
|
|
"ux_coin_transaction_task_ref",
|
|
"coin_transaction",
|
|
["user_id", "biz_type", "ref_id"],
|
|
unique=True,
|
|
sqlite_where=sa.text("biz_type LIKE 'task%' AND ref_id IS NOT NULL"),
|
|
postgresql_where=sa.text("biz_type LIKE 'task%' AND ref_id IS NOT NULL"),
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index("ux_coin_transaction_task_ref", table_name="coin_transaction")
|