feat(wallet): coin_transaction 增 trace_id 列 + grant_coins 透传

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
guke
2026-08-07 15:28:41 +08:00
parent f4ef2ae9af
commit e0d403c080
4 changed files with 34 additions and 0 deletions
+10
View File
@@ -26,6 +26,16 @@ SIGNIN_REWARDS: tuple[int, ...] = (
SIGNIN_CYCLE_LEN: int = len(SIGNIN_REWARDS)
# ===== 信息流广告「按会话聚合」的 biz_type =====
# 比价 / 领券等候期看的信息流广告,每条各写一条 coin_transaction;这两类在「金币变动记录」
# 里按 trace_id 聚合成一条展示(见 repositories/wallet.list_coin_transactions)。其余类型
# (reward_video / guide_video / 通用 feed_ad_reward / signin / task_* 等)不聚合。
FEED_AD_SESSION_BIZ_TYPES: tuple[str, str] = (
"feed_ad_reward_comparison",
"feed_ad_reward_coupon",
)
def signin_reward(cycle_day: int) -> int:
"""cycle_day 取值 1..SIGNIN_CYCLE_LEN。"""
return SIGNIN_REWARDS[cycle_day - 1]
+3
View File
@@ -84,6 +84,9 @@ class CoinTransaction(Base):
# 关联业务 id(签到日期、任务 key 等),可空
ref_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
remark: Mapped[str | None] = mapped_column(String(128), nullable=True)
# 会话键:仅比价/领券信息流发奖(feed_ad_reward_comparison/coupon)时写入本场 trace_id,
# 金币记录列表据此把一次比价/领券的多条广告金币聚合成一条。其余类型 = NULL。
trace_id: Mapped[str | None] = mapped_column(String(64), index=True, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), index=True, nullable=False
+2
View File
@@ -170,6 +170,7 @@ def grant_coins(
biz_type: str,
ref_id: str | None = None,
remark: str | None = None,
trace_id: str | None = None,
) -> tuple[CoinAccount, CoinTransaction]:
"""金币变动入口(正数入账 / 负数出账)。更新余额 + 写流水,不 commit。
@@ -189,6 +190,7 @@ def grant_coins(
biz_type=biz_type,
ref_id=ref_id,
remark=remark,
trace_id=trace_id,
created_at=datetime.now(rewards.CN_TZ).replace(tzinfo=None), # 存北京 wall-clock(客户端原样切片显示)
)
db.add(txn)
+19
View File
@@ -384,3 +384,22 @@ def test_endpoints_require_auth(client) -> None:
assert client.get("/api/v1/savings/summary").status_code == 401
assert client.get("/api/v1/savings/battle").status_code == 401
assert client.get("/api/v1/savings/records").status_code == 401
def test_grant_coins_persists_trace_id(client) -> None:
"""grant_coins 传 trace_id 落库;不传则为 None。"""
phone = "13800002001"
_login(client, phone)
with SessionLocal() as db:
user = get_user_by_phone(db, phone)
assert user is not None
_, txn1 = crud_wallet.grant_coins(
db, user.id, 5, biz_type="feed_ad_reward_comparison",
ref_id="evt1", remark="比价奖励", trace_id="trace-A",
)
_, txn2 = crud_wallet.grant_coins(
db, user.id, 30, biz_type="signin", remark="每日签到奖励",
)
db.commit()
assert txn1.trace_id == "trace-A"
assert txn2.trace_id is None