fix(compute): 个人余额转企业以引擎可用额度为权威,修复账本与引擎不一致导致无法转出
根因:个人可用算力有三套口径——引擎 quota(实际可消费)、平台账本 compute_personal_balance(充值/企业转入记录)、充值订单。三者可能不一致 (赠送/历史调整后引擎额度 > 账本),而 from-personal 转账硬校验账本 >= 金额,导致「算力/我的余额显示有余额,但企业管理个人余额 0、无法转企业」。 修复:转账校验与扣减改为引擎可用额度(quota - used_quota,个人实际可消费 的算力)为权威;账本同步扣减、不足扣到 0 不转负(账本仅记录充值/转入部分); 错误信息中文友好(不再外泄引擎异常细节)。
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user