feat: 版本号管理机制(VERSION文件+git哈希,部署/启动/UI/health均展示) + 平台账号管理(accounts表CRUD,资产所属账号候选联动,重命名同步引用)
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
"""平台账号业务逻辑
|
||||
|
||||
账号与资产的关系:Asset.account 按名称引用账号(字符串,兼容历史自由文本)。
|
||||
重命名账号时同步更新所有引用资产,保证两边一致。
|
||||
"""
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy import func
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from app.models.asset import Account, Asset
|
||||
from app.schemas.account import AccountCreate, AccountRead, AccountUpdate
|
||||
|
||||
|
||||
def _asset_counts(session: Session) -> Dict[str, int]:
|
||||
"""按 account 名称统计引用资产数"""
|
||||
rows = session.exec(
|
||||
select(Asset.account, func.count(Asset.id))
|
||||
.where(Asset.account.is_not(None)) # type: ignore[union-attr]
|
||||
.group_by(Asset.account)
|
||||
).all()
|
||||
return {name: cnt for name, cnt in rows}
|
||||
|
||||
|
||||
def _to_read(account: Account, counts: Dict[str, int]) -> AccountRead:
|
||||
read = AccountRead.model_validate(account)
|
||||
read.asset_count = counts.get(account.name, 0)
|
||||
return read
|
||||
|
||||
|
||||
def list_accounts(session: Session) -> List[AccountRead]:
|
||||
counts = _asset_counts(session)
|
||||
accounts = session.exec(select(Account).order_by(Account.name.asc())).all()
|
||||
return [_to_read(a, counts) for a in accounts]
|
||||
|
||||
|
||||
def _get_account(session: Session, account_id: int) -> Account:
|
||||
account = session.get(Account, account_id)
|
||||
if not account:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="账号不存在")
|
||||
return account
|
||||
|
||||
|
||||
def _check_name_taken(session: Session, name: str, exclude_id: int | None = None) -> None:
|
||||
stmt = select(Account).where(Account.name == name)
|
||||
if exclude_id is not None:
|
||||
stmt = stmt.where(Account.id != exclude_id)
|
||||
if session.exec(stmt).first():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail=f"账号已存在:{name}"
|
||||
)
|
||||
|
||||
|
||||
def create_account(session: Session, data: AccountCreate) -> AccountRead:
|
||||
name = data.name.strip()
|
||||
if not name:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="账号名称不能为空")
|
||||
_check_name_taken(session, name)
|
||||
account = Account(name=name, platform=data.platform, remark=data.remark)
|
||||
session.add(account)
|
||||
session.commit()
|
||||
session.refresh(account)
|
||||
return _to_read(account, _asset_counts(session))
|
||||
|
||||
|
||||
def update_account(session: Session, account_id: int, data: AccountUpdate) -> AccountRead:
|
||||
account = _get_account(session, account_id)
|
||||
if data.name is not None:
|
||||
new_name = data.name.strip()
|
||||
if not new_name:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="账号名称不能为空")
|
||||
if new_name != account.name:
|
||||
_check_name_taken(session, new_name, exclude_id=account.id)
|
||||
# 同步更新引用该账号的资产,避免重命名后资产端失联
|
||||
assets = session.exec(select(Asset).where(Asset.account == account.name)).all()
|
||||
for a in assets:
|
||||
a.account = new_name
|
||||
session.add(a)
|
||||
account.name = new_name
|
||||
if data.platform is not None:
|
||||
account.platform = data.platform or None
|
||||
if data.remark is not None:
|
||||
account.remark = data.remark or None
|
||||
session.add(account)
|
||||
session.commit()
|
||||
session.refresh(account)
|
||||
return _to_read(account, _asset_counts(session))
|
||||
|
||||
|
||||
def delete_account(session: Session, account_id: int) -> Dict[str, int]:
|
||||
account = _get_account(session, account_id)
|
||||
affected = len(session.exec(select(Asset.id).where(Asset.account == account.name)).all())
|
||||
session.delete(account)
|
||||
session.commit()
|
||||
# 资产端保留原账号名文本(不级联清空),由用户自行处理
|
||||
return {"affected_assets": affected}
|
||||
Reference in New Issue
Block a user