fix(account): 支持跨平台同名账号——(platform,name)联合唯一+Asset.account_id外键重构
This commit is contained in:
@@ -12,6 +12,7 @@ from sqlmodel import Session, select
|
||||
|
||||
from app.core import crypto
|
||||
from app.models.asset import (
|
||||
Account,
|
||||
AIAccount,
|
||||
Asset,
|
||||
AssetStatus,
|
||||
@@ -93,11 +94,29 @@ def _provider_name(session: Session, asset: Asset) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
def _account_name(session: Session, asset: Asset) -> Optional[str]:
|
||||
"""取关联账号标识(展示用)"""
|
||||
if asset.account_id:
|
||||
account = session.get(Account, asset.account_id)
|
||||
if account:
|
||||
return account.name
|
||||
return None
|
||||
|
||||
|
||||
def _validate_account(session: Session, account_id: Optional[int]) -> None:
|
||||
"""account_id 非空时确认账号存在,防外键悬空"""
|
||||
if account_id is not None and not session.get(Account, account_id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail=f"账号不存在:id={account_id}"
|
||||
)
|
||||
|
||||
|
||||
def _to_read(session: Session, asset: Asset, detail) -> AssetRead:
|
||||
"""组装 AssetRead 输出(主表 + 详情 + 计算字段)"""
|
||||
read = AssetRead.model_validate(asset)
|
||||
read.days_to_expiry = _days_to_expiry(asset.expiry_date)
|
||||
read.provider_name = _provider_name(session, asset)
|
||||
read.account_name = _account_name(session, asset)
|
||||
if isinstance(detail, VPSDetail):
|
||||
read.vps_detail = _detail_to_read(detail)
|
||||
elif isinstance(detail, DomainDetail):
|
||||
@@ -156,6 +175,12 @@ def _get_details_batch(session: Session, assets: list) -> dict:
|
||||
if provider_ids:
|
||||
for p in session.exec(select(Provider).where(Provider.id.in_(provider_ids))).all():
|
||||
provider_map[p.id] = p.name
|
||||
# 批量查 Account 名
|
||||
account_ids = {a.account_id for a in assets if a.account_id}
|
||||
account_map = {}
|
||||
if account_ids:
|
||||
for acc in session.exec(select(Account).where(Account.id.in_(account_ids))).all():
|
||||
account_map[acc.id] = acc.name
|
||||
# 批量查各类型详情
|
||||
detail_map = {}
|
||||
for asset_type, (_, model) in DETAIL_MAP.items():
|
||||
@@ -163,7 +188,7 @@ def _get_details_batch(session: Session, assets: list) -> dict:
|
||||
if typed_ids:
|
||||
for d in session.exec(select(model).where(model.asset_id.in_(typed_ids))).all():
|
||||
detail_map[d.asset_id] = d
|
||||
return {"providers": provider_map, "details": detail_map}
|
||||
return {"providers": provider_map, "accounts": account_map, "details": detail_map}
|
||||
|
||||
|
||||
def _to_read_batch(session: Session, asset: Asset, batch: dict) -> AssetRead:
|
||||
@@ -171,6 +196,7 @@ def _to_read_batch(session: Session, asset: Asset, batch: dict) -> AssetRead:
|
||||
read = AssetRead.model_validate(asset)
|
||||
read.days_to_expiry = _days_to_expiry(asset.expiry_date)
|
||||
read.provider_name = batch["providers"].get(asset.provider_id)
|
||||
read.account_name = batch.get("accounts", {}).get(asset.account_id)
|
||||
detail = batch["details"].get(asset.id)
|
||||
if isinstance(detail, VPSDetail):
|
||||
read.vps_detail = _detail_to_read(detail)
|
||||
@@ -203,6 +229,7 @@ def _create_asset_no_commit(session: Session, data: AssetCreate) -> Asset:
|
||||
asset_data = data.model_dump(
|
||||
exclude={"vps_detail", "domain_detail", "ai_detail", "cloudflare_detail"}
|
||||
)
|
||||
_validate_account(session, asset_data.get("account_id"))
|
||||
asset = Asset(**asset_data)
|
||||
session.add(asset)
|
||||
session.flush() # 获取 asset.id,但不提交
|
||||
@@ -239,6 +266,8 @@ def _update_asset_no_commit(session: Session, asset_id: int, data: AssetUpdate)
|
||||
main_fields = data.model_dump(
|
||||
exclude_unset=True, exclude={"vps_detail", "domain_detail", "ai_detail", "cloudflare_detail"}
|
||||
)
|
||||
if "account_id" in main_fields:
|
||||
_validate_account(session, main_fields["account_id"])
|
||||
for key, value in main_fields.items():
|
||||
setattr(asset, key, value)
|
||||
session.add(asset)
|
||||
|
||||
Reference in New Issue
Block a user