From 32f300b5a204a3f9aa715f2f67941de9897b16e0 Mon Sep 17 00:00:00 2001 From: zhuzihao Date: Tue, 30 Jun 2026 12:02:56 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E7=8E=B0=E9=87=91=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=9B=AE=E6=A0=87=E8=B4=A6=E6=88=B7(?= =?UTF-8?q?=E9=87=91=E5=B8=81=E7=8E=B0=E9=87=91=20/=20=E9=82=80=E8=AF=B7?= =?UTF-8?q?=E5=A5=96=E5=8A=B1=E9=87=91)+=20overview=20=E5=B8=A6=E9=82=80?= =?UTF-8?q?=E8=AF=B7=E4=BD=99=E9=A2=9D=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GrantCashRequest 加 account(coin_cash / invite_cash,默认 coin_cash 兼容旧调用) - 调现金接口按 account 分支:邀请账户走 grant_invite_cash,余额读取/扣负保护/审计 detail 都按对应账户走;两本账物理隔离不可串 - 用户 360 overview 返回 invite_cash_balance_cents(admin 调现金弹窗展示邀请余额) Co-Authored-By: Claude Opus 4.8 --------- Co-authored-by: zzhyyyyy <2685922758@qq.com> Reviewed-on: https://gitea.shaguabijia.com/WonderableAI/shaguabijia-app-server/pulls/95 Co-authored-by: zhuzihao Co-committed-by: zhuzihao --- app/admin/repositories/queries.py | 1 + app/admin/routers/users.py | 30 +++++++++++++++++++----------- app/admin/schemas/user.py | 6 ++++++ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/app/admin/repositories/queries.py b/app/admin/repositories/queries.py index f041210..488bcf7 100644 --- a/app/admin/repositories/queries.py +++ b/app/admin/repositories/queries.py @@ -788,6 +788,7 @@ def get_user_overview(db: Session, user_id: int) -> dict | None: "user": user, "coin_balance": acc.coin_balance if acc else 0, "cash_balance_cents": acc.cash_balance_cents if acc else 0, + "invite_cash_balance_cents": acc.invite_cash_balance_cents if acc else 0, "total_coin_earned": acc.total_coin_earned if acc else 0, "comparison_total": _count(ComparisonRecord, ComparisonRecord.user_id == user_id), "comparison_success": _count( diff --git a/app/admin/routers/users.py b/app/admin/routers/users.py index 4b98bea..7e4a81b 100644 --- a/app/admin/routers/users.py +++ b/app/admin/routers/users.py @@ -210,22 +210,28 @@ def grant_user_cash( db: AdminDb, ) -> OkResponse: """给指定用户增/减或设值现金(分)。delta:正=发放、负=扣减;set:直接设为目标值。 + account=coin_cash(金币兑现金)/ invite_cash(邀请奖励金):两本账物理隔离、各调各的。 主要用于让无现金用户直接测试提现。""" user = user_repo.get_user_by_id(db, user_id) if user is None: raise HTTPException(status_code=404, detail="用户不存在") - # set=设为目标值:读当前余额算差值,仍复用 grant_cash 写一笔流水(沿用原子/审计/扣负保护) + # 按目标账户选「余额字段 + 变动入口」(grant_invite_cash 与 grant_cash 同构) + is_invite = body.account == "invite_cash" + balance_attr = "invite_cash_balance_cents" if is_invite else "cash_balance_cents" + grant_fn = wallet_repo.grant_invite_cash if is_invite else wallet_repo.grant_cash + acct_label = "邀请奖励金" if is_invite else "现金" + before: int | None = None + # set=设为目标值:读当前余额算差值,仍复用 grant 写一笔流水(沿用原子/审计/扣负保护) if body.mode == "set": if body.amount_cents < 0: - raise HTTPException(status_code=400, detail="目标现金值不能为负") + raise HTTPException(status_code=400, detail=f"目标{acct_label}值不能为负") # lock=True:锁账户行,防连点/并发各读同一 before 算同一 delta 双写,余额错位 - before = wallet_repo.get_or_create_account( - db, user_id, commit=False, lock=True - ).cash_balance_cents + acc_locked = wallet_repo.get_or_create_account(db, user_id, commit=False, lock=True) + before = getattr(acc_locked, balance_attr) delta = body.amount_cents - before if delta == 0: raise HTTPException( - status_code=400, detail=f"当前现金已为 {body.amount_cents} 分,无需调整" + status_code=400, detail=f"当前{acct_label}已为 {body.amount_cents} 分,无需调整" ) else: if body.amount_cents == 0: @@ -234,18 +240,20 @@ def grant_user_cash( # 负数扣减时不允许扣成负余额(运营误操作保护);lock=True 防并发扣穿 if delta < 0: acc_now = wallet_repo.get_or_create_account(db, user_id, commit=False, lock=True) - if acc_now.cash_balance_cents + delta < 0: + if getattr(acc_now, balance_attr) + delta < 0: raise HTTPException( - status_code=400, detail=f"扣减后现金为负(当前余额 {acc_now.cash_balance_cents} 分)" + status_code=400, + detail=f"扣减后{acct_label}为负(当前余额 {getattr(acc_now, balance_attr)} 分)", ) biz_type = "admin_grant" if delta > 0 else "admin_deduct" - # grant_cash 只 flush 不 commit;审计同 commit=False;最后一起 commit → 原子(改钱+留痕) - acc, _ = wallet_repo.grant_cash( + # grant 只 flush 不 commit;审计同 commit=False;最后一起 commit → 原子(改钱+留痕) + acc, _ = grant_fn( db, user_id, delta, biz_type=biz_type, remark=f"admin:{body.reason}"[:128], ) detail = { + "account": body.account, "amount_cents": delta, - "balance_after_cents": acc.cash_balance_cents, + "balance_after_cents": getattr(acc, balance_attr), "reason": body.reason, } if body.mode == "set": diff --git a/app/admin/schemas/user.py b/app/admin/schemas/user.py index fad0a62..8e7020e 100644 --- a/app/admin/schemas/user.py +++ b/app/admin/schemas/user.py @@ -29,6 +29,7 @@ class AdminUserOverview(BaseModel): user: AdminUserListItem coin_balance: int cash_balance_cents: int + invite_cash_balance_cents: int # 邀请奖励金余额(与 cash_balance_cents 物理隔离) total_coin_earned: int comparison_total: int comparison_success: int @@ -88,6 +89,11 @@ class GrantCoinsRequest(BaseModel): class GrantCashRequest(BaseModel): + # 目标账户:金币兑换的现金(cash_balance_cents)与邀请奖励金(invite_cash_balance_cents)物理隔离, + # 各调各的、不可串。默认 coin_cash 兼容旧调用。 + account: Literal["coin_cash", "invite_cash"] = Field( + "coin_cash", description="目标账户:coin_cash=金币兑现金账户 / invite_cash=邀请奖励金账户" + ) mode: Literal["delta", "set"] = Field( "delta", description="delta=增减(amount_cents 为变动量) / set=设为(amount_cents 为目标值,须≥0)" )