feat(task): 后台任务改为apscheduler执行

This commit is contained in:
xingc
2025-05-12 20:08:50 +08:00
parent eec9d3048c
commit 680c5c2b63
9 changed files with 74 additions and 11 deletions

View File

@@ -13,6 +13,7 @@ from fastapi_limiter import FastAPILimiter
from config import settings from config import settings
from core.base.exceptions import CardBookException from core.base.exceptions import CardBookException
from core.base.scheduler import scheduler
from core.db.engine import get_cache from core.db.engine import get_cache
from core.routers.route import routers from core.routers.route import routers
from core.routers.chat.handlers import socket_manager from core.routers.chat.handlers import socket_manager
@@ -44,6 +45,7 @@ def create_app() -> FastAPI:
@app.on_event('startup') @app.on_event('startup')
async def startup_event(): async def startup_event():
redis_conn = await get_cache() redis_conn = await get_cache()
scheduler.start()
await FastAPILimiter.init(redis_conn) await FastAPILimiter.init(redis_conn)
logger.info(f'Application ``{settings.NAME}`` is started') logger.info(f'Application ``{settings.NAME}`` is started')
@@ -51,6 +53,7 @@ def create_app() -> FastAPI:
async def shutdown_event(): async def shutdown_event():
logger.info(f'Application ``{settings.NAME}`` is closed') logger.info(f'Application ``{settings.NAME}`` is closed')
await socket_manager.pubsub_client.disconnect() await socket_manager.pubsub_client.disconnect()
scheduler.shutdown()
@app.exception_handler(HTTPException) @app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException): async def http_exception_handler(request: Request, exc: HTTPException):

22
core/base/scheduler.py Normal file
View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# @File: scheduler.py
# @Date: 2025/5/12 16:17
# @Software: PyCharm
# @Author: xingc
# @Desc:
from datetime import datetime, timedelta
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.date import DateTrigger
scheduler = AsyncIOScheduler()
def add_task(func, *args, task_id: str = None, **kwargs):
scheduler.add_job(
func,
args=args,
kwargs=kwargs,
trigger=DateTrigger(run_date=(datetime.now() + timedelta(seconds=1))),
id=task_id
)

View File

@@ -4,7 +4,9 @@
# @Software : PyCharm # @Software : PyCharm
# @Author : xingc # @Author : xingc
# @Desc : # @Desc :
from typing import AsyncGenerator import functools
import logging
from typing import AsyncGenerator, Callable
import redis.asyncio as aioredis import redis.asyncio as aioredis
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
@@ -12,6 +14,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from config import settings from config import settings
logger = logging.getLogger(__name__)
engine = create_async_engine( engine = create_async_engine(
( (
settings.DB_URL or settings.DB_URL or
@@ -39,6 +42,26 @@ async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
yield session yield session
def async_db_session():
""" 异步获取db_session装饰器。 """
def decorator(func: Callable):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
async with session_maker() as session:
try:
kwargs.setdefault('db_session', session)
return await func(*args, **kwargs)
except Exception as e:
await session.rollback()
logger.exception(e)
return None
return wrapper
return decorator
def create_redis_pool(): def create_redis_pool():
return aioredis.ConnectionPool( return aioredis.ConnectionPool(
host=settings.REDIS_HOST, port=settings.REDIS_PORT, password=settings.REDIS_PASSWORD, db=settings.REDIS_DB, host=settings.REDIS_HOST, port=settings.REDIS_PORT, password=settings.REDIS_PASSWORD, db=settings.REDIS_DB,

View File

@@ -87,7 +87,7 @@ async def get_current_user_form_ws(
"""在ws headers中提取token并验证再获取当前用户""" """在ws headers中提取token并验证再获取当前用户"""
auth_header = websocket.headers.get("authorization") or websocket.headers.get("Authorization") auth_header = websocket.headers.get("authorization") or websocket.headers.get("Authorization")
if auth_header and auth_header.lower().startswith("bearer "): if auth_header and auth_header.lower().startswith("bearer "):
token = auth_header.removeprefix("Bearer ").strip() token = auth_header.removeprefix("bearer ").strip()
else: else:
token = '' token = ''
user = await get_current_user(token, db_session, redis_conn) user = await get_current_user(token, db_session, redis_conn)

View File

@@ -7,8 +7,11 @@
# @Desc : # @Desc :
import logging import logging
from sqlalchemy.ext.asyncio import AsyncSession
from core.base.schema import ProductCategory from core.base.schema import ProductCategory
from core.db.crud import CRUD from core.db.crud import CRUD
from core.db.engine import async_db_session
from core.db.models import User, Product from core.db.models import User, Product
from core.routers.auth.schema import LoginUser from core.routers.auth.schema import LoginUser
from core.routers.pay.actions import actions_handler from core.routers.pay.actions import actions_handler
@@ -16,11 +19,13 @@ from core.routers.pay.actions import actions_handler
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@async_db_session()
async def reward_points_after_uploading_pictures( async def reward_points_after_uploading_pictures(
db_crud: CRUD,
gift_product_id: int, gift_product_id: int,
login_user: LoginUser login_user: LoginUser,
db_session: AsyncSession
): ):
db_crud = CRUD(db_session, Product)
product: Product = await db_crud.get( product: Product = await db_crud.get(
model=Product, filters={ model=Product, filters={
'id': gift_product_id, 'id': gift_product_id,

View File

@@ -8,6 +8,7 @@ import logging
from typing import List, Literal from typing import List, Literal
import ujson import ujson
from asyncer import asyncify
from fastapi import APIRouter, Depends, Query, HTTPException, status from fastapi import APIRouter, Depends, Query, HTTPException, status
import redis.asyncio as aioredis import redis.asyncio as aioredis
from sqlalchemy import select, func from sqlalchemy import select, func
@@ -15,6 +16,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from config import settings from config import settings
from core.base import schema as base_schema from core.base import schema as base_schema
from core.base.scheduler import add_task
from core.db.crud import CRUD from core.db.crud import CRUD
from core.db.engine import get_async_session, get_cache from core.db.engine import get_async_session, get_cache
from core.db.models import Card, SearchCardCache, UserCollection, User from core.db.models import Card, SearchCardCache, UserCollection, User
@@ -243,11 +245,12 @@ async def created_card(
) )
# 上传图片奖励 # 上传图片奖励
if payload.reward and settings.UPLOAD_REWARD_POINTS_GIFT_ID > 0: if payload.reward and settings.UPLOAD_REWARD_POINTS_GIFT_ID > 0:
await reward_points_after_uploading_pictures( await asyncify(add_task)(
db_crud, gift_product_id=settings.UPLOAD_REWARD_POINTS_GIFT_ID, login_user=login_user 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: else:
if card is None: if card is None:
card: Card = await db_crud.create( card: Card = await db_crud.create(

View File

@@ -11,11 +11,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
from core.base.schema import ProductCategory, ProductAction, OrderStatusType from core.base.schema import ProductCategory, ProductAction, OrderStatusType
from core.db.crud import CRUD from core.db.crud import CRUD
from core.db.engine import async_db_session
from core.db.models import User, Order, Product, PointsTransaction from core.db.models import User, Order, Product, PointsTransaction
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@async_db_session()
async def actions_after_payment( async def actions_after_payment(
notify_params: dict, notify_params: dict,
db_session: AsyncSession db_session: AsyncSession

View File

@@ -8,10 +8,12 @@ import logging
from xml.parsers.expat import ExpatError from xml.parsers.expat import ExpatError
import xmltodict import xmltodict
from fastapi import APIRouter, Request, HTTPException, status, Depends, BackgroundTasks from asyncer import asyncify
from fastapi import APIRouter, Request, HTTPException, status, Depends
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from core.base import schema as base_schema from core.base import schema as base_schema
from core.base.scheduler import add_task
from core.db.crud import CRUD from core.db.crud import CRUD
from core.db.engine import get_async_session from core.db.engine import get_async_session
from core.db.models import Order, Product from core.db.models import Order, Product
@@ -94,7 +96,6 @@ async def create_order(
@pay_router.post('/wechat/notify', summary='处理支付结果通知', response_model=base_schema.BaseResponse) @pay_router.post('/wechat/notify', summary='处理支付结果通知', response_model=base_schema.BaseResponse)
async def wechat_notify( async def wechat_notify(
request: Request, request: Request,
background_tasks: BackgroundTasks,
db_session: AsyncSession = Depends(get_async_session) db_session: AsyncSession = Depends(get_async_session)
): ):
body = await request.body() body = await request.body()
@@ -129,7 +130,11 @@ async def wechat_notify(
'status': base_schema.OrderStatusType.PAID 'status': base_schema.OrderStatusType.PAID
}) })
result = {'status': 200, 'message': 'OK'} result = {'status': 200, 'message': 'OK'}
background_tasks.add_task(actions_after_payment, notify_params=params, db_session=db_session) await asyncify(add_task)(
actions_after_payment,
notify_params=params,
task_id=record['order_id'],
)
else: else:
result = {'status': 400, 'message': '支付失败'} result = {'status': 400, 'message': '支付失败'}
record.update({ record.update({

View File

@@ -23,7 +23,7 @@ uvicorn[standard]
pycryptodome pycryptodome
xmltodict xmltodict
fastapi-limiter fastapi-limiter
asyncer