fix(compute): 个人余额转企业以引擎可用额度为权威,修复账本与引擎不一致导致无法转出

根因:个人可用算力有三套口径——引擎 quota(实际可消费)、平台账本
compute_personal_balance(充值/企业转入记录)、充值订单。三者可能不一致
(赠送/历史调整后引擎额度 > 账本),而 from-personal 转账硬校验账本
>= 金额,导致「算力/我的余额显示有余额,但企业管理个人余额 0、无法转企业」。

修复:转账校验与扣减改为引擎可用额度(quota - used_quota,个人实际可消费
的算力)为权威;账本同步扣减、不足扣到 0 不转负(账本仅记录充值/转入部分);
错误信息中文友好(不再外泄引擎异常细节)。
This commit is contained in:
Pine
2026-09-13 17:14:44 +08:00
parent 6d4c592e0e
commit 00ee4b0333
+18 -20
View File
@@ -1020,7 +1020,9 @@ async def transfer_from_personal(
raise HTTPException(status_code=500, detail="个人算力账号未就绪")
total_micro = amount_fen * MICRO_PER_FEN
# ★ 服务端严格校验个人可余额(以平台账本为权威,引擎余额为参考)
# ★ 个人可余额以引擎可用额度为权威(quota - used_quota,即个人实际可消费的算力)。
# 平台账本 compute_personal_balance 仅记录「个人充值/企业转入」的累计,可能与引擎额度
# 不一致(赠送/历史调整等),转账时同步扣减、不足扣到 0、不转负。
from ..infrastructure.models import User as _User
from sqlalchemy import update as _sa_update
user_row = await db.session.get(_User, user["id"])
@@ -1030,34 +1032,30 @@ async def transfer_from_personal(
quota_micro = int(user_bal.get("quota", 0) or 0)
used_micro = int(user_bal.get("used_quota", 0) or 0)
available_micro = max(0, quota_micro - used_micro)
except Exception as exc:
raise HTTPException(status_code=502, detail=f"查询个人余额失败: {exc}") from exc
if ledger_balance < amount_fen:
raise HTTPException(
status_code=400,
detail=f"个人可转余额不足:账本可用 ¥{ledger_balance / 100:.2f},尝试转出 ¥{body.amount_yuan:.2f}",
)
except Exception as exc: # noqa: BLE001
raise HTTPException(status_code=502, detail="查询个人余额失败,请稍后重试") from exc
if available_micro < total_micro:
raise HTTPException(
status_code=400,
detail=f"个人可用余额不足:可用 ¥{available_micro / 1000000:.2f}尝试转出 ¥{body.amount_yuan:.2f}",
detail=f"个人可用余额不足:可用 ¥{available_micro / 1000000:.2f}本次需转出 ¥{body.amount_yuan:.2f}",
)
# 先扣账本(幂等条件更新,防止并发超扣)
upd = await db.session.execute(
_sa_update(_User)
.where(_User.id == user["id"], _User.compute_personal_balance >= amount_fen)
.values(compute_personal_balance=_User.compute_personal_balance - amount_fen)
)
if upd.rowcount == 0:
raise HTTPException(status_code=400, detail="个人可转余额不足(账本校验失败)")
# 先扣引擎额度(引擎为权威),成功后再落库
try:
await compute_client.adjust_user_quota(engine_user_id, total_micro, "subtract")
except Exception as exc:
raise HTTPException(status_code=400, detail=f"个人余额扣减失败: {exc}") from exc
except Exception as exc: # noqa: BLE001
raise HTTPException(status_code=400, detail="个人余额扣减失败,请稍后重试") from exc
await compute_client.sync_user_mirror(db, engine_user_id)
# 账本同步扣减(不足扣到 0,不转负)
new_ledger = max(0, ledger_balance - amount_fen)
if new_ledger != ledger_balance:
await db.session.execute(
_sa_update(_User)
.where(_User.id == user["id"])
.values(compute_personal_balance=new_ledger)
)
# 增加企业余额
company.compute_balance = (company.compute_balance or 0) + amount_fen