Files
X25020501_card_book/core/routers/card/routes.py

354 lines
12 KiB
Python

# -*- coding = utf-8 -*-
# @Time : 2025/2/13 下午4:51
# @File : routes.py
# @Software : PyCharm
# @Author : xingc
# @Desc :
import logging
from typing import List, Literal
import ujson
from asyncer import asyncify
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 config import settings
from core.base import schema as base_schema
from core.base.scheduler import add_task
from core.db.crud import CRUD
from core.db.engine import get_async_session, get_cache
from core.db.models import Card, SearchCardCache, UserCollection, User
from core.routers.auth.schema import LoginUser
from core.routers.auth.services import get_current_user_form_http
from core.utils.helper import generate_keyname, check_picture_for_empty
from . import schema
from .actions import reward_points_after_uploading_pictures
from .scraper import card_scraper
from .services import check_user_card_created_permission
logger = logging.getLogger(__name__)
card_router = APIRouter(prefix='/card', tags=['卡册模块'])
@card_router.get('/search', summary='搜索卡片', response_model=base_schema.BaseResponse[schema.SearchCardOut])
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 = generate_keyname(f'card_search:{platform.value}:{cert_number}', use_today=True)
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': scraper_result['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',
summary='卡片列表(所有者)',
response_model=base_schema.BaseResponse[
base_schema.Paginated[
List[schema.CardOut]
]
],
)
async def get_me_all_card(
login_user: LoginUser = Depends(get_current_user_form_http),
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)
):
# 组装查询条件
conditions = [
UserCollection.user_id == login_user.user_id,
]
if category in ['anime', 'sports', 'other']:
conditions.append(Card.category == category)
if category == 'displayed':
conditions.append(UserCollection.displayed == True)
# 记录总数查询
total_stmt = (
select(func.count()).select_from(UserCollection).filter(
*conditions
)
)
total = await db_session.execute(total_stmt)
# 分页记录查询
stmt = (
select(Card, UserCollection)
.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': [
{
**card.dict(),
**user_collection.dict(exclude={'id'})
} for card, user_collection in records
],
'total': total.scalar(),
'page': page,
'page_size': page_size,
},
}
@card_router.get(
'/visit/{user_id}/all',
summary='卡片列表(访问者)',
response_model=base_schema.BaseResponse[
base_schema.Paginated[
List[schema.CardVisitOut]
]
],
)
async def get_visit_user_all_card(
user_id: int,
login_user: LoginUser = Depends(get_current_user_form_http),
page: int = Query(default=1, gt=0),
page_size: int = Query(default=15, gt=0, le=200),
db_session: AsyncSession = Depends(get_async_session)
):
# 组装查询条件
conditions = [
UserCollection.user_id == user_id, UserCollection.displayed == True
]
# 记录总数查询
total_stmt = (
select(func.count()).select_from(UserCollection).filter(*conditions)
)
total = await db_session.execute(total_stmt)
# 分页记录查询
stmt = (
select(Card, UserCollection)
.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': [
{
**card.dict(),
**user_collection.dict()
} for card, user_collection in records
],
'total': total.scalar(),
'page': page,
'page_size': page_size,
},
}
@card_router.post('/create', summary='新增卡片', response_model=base_schema.BaseResponse[schema.CardOut])
async def created_card(
payload: schema.CardIn,
login_user: LoginUser = Depends(get_current_user_form_http),
db_session: AsyncSession = Depends(get_async_session)
):
db_crud = CRUD(db_session, Card)
# 确认用户存卡权限
if await check_user_card_created_permission(
db_session, login_user=login_user
) is False:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f'非会员仅支持存入{settings.USER_MAX_CREATED_CARD_NUM}张卡片'
)
# 卡片库查询
card: Card = await db_crud.get(filters={'cert_number': payload.cert_number})
if payload.source.value != 'other':
if card is None:
# 卡片缓存库转正至卡片库, 重新查询保障数据准确性
cache_card_crud = CRUD(db_session, SearchCardCache)
cache_card = await cache_card_crud.get(
filters={'cert_number': payload.cert_number, 'source': payload.source.value}
)
card = await db_crud.create(
data={
**cache_card.data,
'category': payload.category,
},
filters={
'cert_number': payload.cert_number,
'source': payload.source.value
}
)
# 校验是否需要更新图片
card_picture_empty = check_picture_for_empty(card.card_picture)
if all([
check_picture_for_empty(payload.card_picture.dict()) is False, # 创建时用户有提交上传图片链接
check_picture_for_empty(card.card_spare_picture), # 采集原始来源无图片
check_picture_for_empty(card.card_picture, or_=True), # 无本地化图片
]):
# 补充有效值
card_picture = {} if card_picture_empty else card.card_picture.copy()
for key, value in payload.card_picture.dict(exclude_none=True).items():
if card_picture.get(key) is None:
card_picture[key] = value
await db_crud.update(
data={
'card_picture': card_picture,
'perfect_user_id': login_user.user_id,
},
filters={'id': card.id}
)
# 上传图片奖励
if payload.reward and settings.UPLOAD_REWARD_POINTS_GIFT_ID > 0:
await asyncify(add_task)(
reward_points_after_uploading_pictures,
gift_product_id=settings.UPLOAD_REWARD_POINTS_GIFT_ID, login_user=login_user,
task_id=f'{login_user.user_id}-{card.card_number}'
)
else:
if card is None:
card: Card = await db_crud.create(
data={
**payload.dict(exclude_none=True, exclude={'reward'}),
'create_user_id': login_user.user_id,
'source': payload.source.value,
},
filters={'cert_number': payload.cert_number, 'source': payload.source.value}
)
record = {
'user_id': login_user.user_id,
'card_id': card.id
}
await db_crud.create(
model=UserCollection,
data=record,
filters=record
)
return {'data': card}
@card_router.post(
'/{card_id}/update/',
summary='卡片更新(会员功能,入手价值、展示价值)',
response_model=base_schema.BaseResponse
)
async def update_card_price_info(
card_id: int,
payload: schema.UpdateCard,
login_user: LoginUser = Depends(get_current_user_form_http),
db_session: AsyncSession = Depends(get_async_session),
):
db_crud = CRUD(db_session, UserCollection)
user = await db_crud.get(model=User, filters={'user_id': login_user.user_id})
if user.is_vip() is False:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='会员未开通或者已过期')
result = await db_crud.update(
data=payload.dict(exclude_none=True),
filters={'card_id': card_id, 'user_id': login_user.user_id}
)
return {'message': 'ok' if result is not None else 'fail'}
@card_router.post('/display/', summary='卡片展示/隐藏', response_model=base_schema.BaseResponse)
async def display_card_status(
action: Literal['displayed', 'undisplayed'],
payload: schema.DisplayCardIn,
login_user: LoginUser = Depends(get_current_user_form_http),
db_session: AsyncSession = Depends(get_async_session)
):
# 确认用户展示卡权限
if await check_user_card_created_permission(
db_session, login_user=login_user, other_conditions={'displayed': True}
) is False and action == 'displayed':
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f'非会员仅支持展示{settings.USER_MAX_CREATED_CARD_NUM}张卡片'
)
db_crud = CRUD(db_session, UserCollection)
result = await db_crud.updates(
data={
'displayed': (action == 'displayed'),
},
filters={'card_id__in': payload.card_ids, 'user_id': login_user.user_id}
)
return {'message': 'ok' if result is not None else 'fail'}
@card_router.delete('/delete/', summary='卡片删除(批量)', response_model=base_schema.BaseResponse)
async def delete_card(
payload: schema.DeleteCardIn,
login_user: LoginUser = Depends(get_current_user_form_http),
db_session: AsyncSession = Depends(get_async_session)
):
# 删除关联关系
db_crud = CRUD(db_session, UserCollection)
state = await db_crud.deletes(
filters={
'card_id__in': payload.card_ids,
'user_id': login_user.user_id
}
)
if state is True:
# 删除原始卡片(自定义)
db_crud = CRUD(db_session, Card)
await db_crud.deletes(
filters={
'id__in': payload.card_ids,
'create_user_id': login_user.user_id,
'source': 'OTHER'
}
)
return {'message': 'ok' if state else 'fail'}