Files
banban/talkingq-url/services/card_service.py
2026-06-04 15:18:31 +08:00

214 lines
7.4 KiB
Python

import asyncio
from typing import Dict, Optional
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import Card as DBCard
from services.database_service_base import DatabaseServiceBase
from utils.logger import session_logger
class Card(BaseModel):
card_id: Optional[int] = None
card_uuid: str
device_id: Optional[str] = None
card_name: Optional[str] = None
status: int = 0
total_swaps: int = 0
created_at: Optional[float] = None
updated_at: Optional[float] = None
@classmethod
def from_db_model(cls, db_model: DBCard):
return cls(
card_id=db_model.card_id,
card_uuid=db_model.card_uuid,
device_id=db_model.device_id,
card_name=db_model.card_name,
status=db_model.status,
total_swaps=db_model.total_swaps,
created_at=db_model.created_at.timestamp() if db_model.created_at else None,
updated_at=db_model.updated_at.timestamp() if db_model.updated_at else None,
)
class CardService(DatabaseServiceBase):
def __init__(self):
super().__init__(service_name="card")
self.cards: Dict[str, Card] = {}
self.lock = asyncio.Lock()
async def _cache_card(self, card: Card) -> None:
async with self.lock:
self.cards[card.card_uuid] = card
async def _load_card_from_db(self, card_uuid: str, async_session: AsyncSession) -> Optional[Card]:
try:
query = select(DBCard).where(DBCard.card_uuid == card_uuid)
result = await async_session.execute(query)
db_card = result.scalar_one_or_none()
if not db_card:
return None
card = Card.from_db_model(db_card)
await self._cache_card(card)
return card
except Exception as exc:
session_logger.error("card", "service", f"load card failed: {exc}")
return None
async def _save_card_to_db(self, card: Card, async_session: AsyncSession, commit: bool = True) -> None:
try:
query = select(DBCard).where(DBCard.card_uuid == card.card_uuid)
result = await async_session.execute(query)
existing_card = result.scalar_one_or_none()
if existing_card:
existing_card.device_id = card.device_id
existing_card.card_name = card.card_name
existing_card.status = card.status
existing_card.total_swaps = card.total_swaps
db_card = existing_card
else:
db_card = DBCard(
card_uuid=card.card_uuid,
device_id=card.device_id,
card_name=card.card_name,
status=card.status,
total_swaps=card.total_swaps,
)
async_session.add(db_card)
await async_session.flush()
if commit:
await async_session.commit()
card.card_id = db_card.card_id
await self._cache_card(card)
session_logger.info("card", "service", f"card saved: {card.card_uuid}")
except Exception as exc:
if commit:
await async_session.rollback()
session_logger.error("card", "service", f"save card failed: {exc}")
raise
async def get_card_by_uuid(
self,
card_uuid: str,
force_refresh: bool = False,
db_session: Optional[AsyncSession] = None,
) -> Optional[Card]:
await self._init_database()
card = None
if not force_refresh:
async with self.lock:
card = self.cards.get(card_uuid)
if card:
return card
if db_session is not None:
return await self._load_card_from_db(card_uuid, db_session)
session = await self.db_manager.get_session()
try:
return await self._load_card_from_db(card_uuid, session)
finally:
await session.close()
async def get_card_by_device_id(self, device_id: str) -> list[Card]:
await self._init_database()
db_session = await self.db_manager.get_session()
try:
query = select(DBCard).where(DBCard.device_id == device_id)
result = await db_session.execute(query)
db_cards = result.scalars().all()
cards = []
for db_card in db_cards:
card = Card.from_db_model(db_card)
await self._cache_card(card)
cards.append(card)
return cards
except Exception as exc:
session_logger.error(device_id, "card", f"load device cards failed: {exc}")
return []
finally:
await db_session.close()
async def activate_card(
self,
card_uuid: str,
device_id: str,
card_name: Optional[str] = None,
db_session: Optional[AsyncSession] = None,
) -> Card:
await self._init_database()
owns_session = db_session is None
if db_session is None:
db_session = await self.db_manager.get_session()
try:
existing_card = await self.get_card_by_uuid(card_uuid, db_session=db_session)
if existing_card:
if existing_card.device_id and existing_card.device_id != device_id:
raise ValueError("card is already bound to another device")
existing_card.device_id = device_id
existing_card.card_name = card_name
existing_card.status = 1
await self._save_card_to_db(existing_card, db_session, commit=owns_session)
session_logger.info(device_id, "card", f"card activated: {card_uuid}")
return existing_card
new_card = Card(
card_uuid=card_uuid,
device_id=device_id,
card_name=card_name,
status=1,
total_swaps=0,
)
await self._save_card_to_db(new_card, db_session, commit=owns_session)
session_logger.info(device_id, "card", f"new card activated: {card_uuid}")
return new_card
except Exception:
if owns_session:
await db_session.rollback()
raise
finally:
if owns_session:
await db_session.close()
async def increment_swap_count(self, card_uuid: str) -> Optional[Card]:
await self._init_database()
card = await self.get_card_by_uuid(card_uuid)
if not card:
return None
card.total_swaps += 1
db_session = await self.db_manager.get_session()
try:
await self._save_card_to_db(card, db_session)
session_logger.info("card", "service", f"swap count incremented: {card_uuid}, total={card.total_swaps}")
return card
finally:
await db_session.close()
async def check_card_ownership(self, card_uuid: str, device_id: str) -> bool:
card = await self.get_card_by_uuid(card_uuid)
return bool(card and card.device_id == device_id)
async def is_active_for_device(self, card_uuid: str, device_id: str) -> bool:
card = await self.get_card_by_uuid(card_uuid)
return bool(card and card.device_id == device_id and int(card.status) == 1)
card_service = CardService()