# -*- coding = utf-8 -*- # @Time : 2025/2/13 下午4:51 # @File : routes.py # @Software : PyCharm # @Author : xingc # @Desc : import logging from typing import Literal, List import ujson from fastapi import APIRouter, Depends, Query, HTTPException, status import redis.asyncio as aioredis from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession from . import schema from .scraper import card_scraper from config import settings from core.base import schema as base_schema from core.db.crud import CRUD from core.db.engine import get_async_session, get_cache from core.db.models import Card, SearchCardCache, UserCollection logger = logging.getLogger(__name__) card_router = APIRouter(prefix='/card', tags=['卡册模块']) @card_router.get('/search', response_model=base_schema.BaseResponse[schema.CardOut]) async def get_card_search( cert_number: str = Query(pattern='[a-zA-Z0-9]+'), platform: base_schema.SupportPlatformType = Query(...), db_session: AsyncSession = Depends(get_async_session), redis_conn: aioredis.Redis = Depends(get_cache) ): logger.info(f'Get card search request: {cert_number}') logger.info(platform) result = None # 缓存 cache_keyname = f'card_book:card_search:{platform.value}:{cert_number}' result_str = await redis_conn.get(cache_keyname) if result_str is not None: result = ujson.decode(result_str) return {'data': result} db_crud = CRUD(db_session, SearchCardCache) # 缓存库 if result is None: card = await db_crud.get(filters={'cert_number': cert_number, 'source': platform.value}) if card is not None: result = card.data # 采集 if result is None: scraper_result = await card_scraper.choice(platform.value, cert_number) if scraper_result is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail='相关卡片信息未找到' ) result = scraper_result['data'] await db_crud.create( data={ **scraper_result, 'source': result.get('source') or platform.value, }, filters={'cert_number': cert_number, 'source': platform.value} ) await redis_conn.set(cache_keyname, ujson.encode(result), ex=settings.REDIS_CACHE_EXPIRATION_SECONDS) return {'data': result} @card_router.get( '/me/all', response_model=base_schema.BaseResponse[ base_schema.Paginated[ List[schema.CardOut] ] ], ) async def get_me_all_card( page: int = Query(default=1, gt=0), page_size: int = Query(default=15, gt=0, le=200), category: Literal['all', 'anime', 'sports', 'other', 'displayed'] = Query(default='all'), db_session: AsyncSession = Depends(get_async_session) ): user_id = None conditions = [ UserCollection.user_id == user_id, ] if category in ['anime', 'sports', 'other']: conditions.append(Card.category == category) if category == 'displayed': conditions.append(UserCollection.displayed is True) total_stmt = ( select(func.count()).select_from(UserCollection).filter( *conditions ) ) total = await db_session.execute(total_stmt) stmt = ( select(UserCollection, Card) .join(Card, UserCollection.card_id == Card.id) .where(*conditions) .order_by(UserCollection.created_at.desc()).limit(page_size).offset((page - 1) * page_size) ) records = await db_session.execute(stmt) return { 'data': { 'list': [record.dict() for record in records.scalars().all()], 'total': total.scalar(), 'page': page, 'page_size': page_size, }, } @card_router.get( '/visit/{user_id}/all', response_model=base_schema.BaseResponse[ base_schema.Paginated[ List[schema.CardOut] ] ], ) async def get_visit_user_all_card( user_id: str, page: int = Query(default=1, gt=0), page_size: int = Query(default=15, gt=0, le=200), # category: Literal['displayed'] = Query(default='displayed'), db_session: AsyncSession = Depends(get_async_session) ): conditions = [ UserCollection.user_id == user_id, UserCollection.displayed is True ] total_stmt = ( select(func.count()).select_from(UserCollection).filter(*conditions) ) total = await db_session.execute(total_stmt) stmt = ( select(UserCollection, Card) .join(Card, UserCollection.card_id == Card.id) .where(*conditions) .order_by(UserCollection.created_at.desc()).limit(page_size).offset((page - 1) * page_size) ) records = await db_session.execute(stmt) return { 'data': { 'list': [record.dict() for record in records.scalars().all()], 'total': total.scalar(), 'page': page, 'page_size': page_size, }, } @card_router.post('/create', response_model=base_schema.BaseResponse[schema.CardOut]) async def created_card( payload: schema.Card, db_session: AsyncSession = Depends(get_async_session) ): db_crud = CRUD(db_session, Card) card: Card = await db_crud.create( data={ **payload.dict(exclude_none=True), 'create_user_id': None, 'source': payload.source.value, }, filters={'cert_number': payload.cert_number, 'source': payload.source.value} ) return {'data': card} @card_router.post('/{card_id}/update/') async def update_card_price_info( card_id: int, payload: schema.UpdateCard, db_session: AsyncSession = Depends(get_async_session), ): db_crud = CRUD(db_session, UserCollection) result = await db_crud.update( data=payload.dict(exclude_none=True), filters={'card_id': card_id, 'user_id': None} ) return {'message': 'success' if result is not None else 'fail'} @card_router.post('/{card_id}/display/') async def display_card_status( card_id: int, status: bool, db_session: AsyncSession = Depends(get_async_session) ): db_crud = CRUD(db_session, UserCollection) result = await db_crud.update( data={ 'displayed': status }, filters={'card_id': card_id, 'user_id': None} ) return {'message': 'success' if result is not None else 'fail'} @card_router.delete('/delete/', response_model=base_schema.BaseResponse) async def delete_card( payload: schema.DeleteCardIn, db_session: AsyncSession = Depends(get_async_session) ): db_crud = CRUD(db_session, Card) state = await db_crud.delete(filters={'card_id_in': payload.card_ids}) if state is False: db_crud = CRUD(db_session, UserCollection) state = await db_crud.delete(filters={'card_id_in': payload.card_ids, 'user_id': None}) return {'message': 'success' if state else 'fail'}