153 lines
4.0 KiB
Python
153 lines
4.0 KiB
Python
|
|
from fastapi import FastAPI
|
|||
|
|
from settings import TORTOISE_ORM
|
|||
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|||
|
|
from tortoise.contrib.fastapi import register_tortoise
|
|||
|
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|||
|
|
from apscheduler.triggers.interval import IntervalTrigger
|
|||
|
|
from tortoise import Tortoise
|
|||
|
|
from contextlib import asynccontextmanager
|
|||
|
|
from apis import app as main_router
|
|||
|
|
import asyncio
|
|||
|
|
import signal
|
|||
|
|
import sys
|
|||
|
|
|
|||
|
|
@asynccontextmanager
|
|||
|
|
async def lifespan(app: FastAPI):
|
|||
|
|
"""
|
|||
|
|
应用生命周期管理函数
|
|||
|
|
|
|||
|
|
- 启动:注册定时任务并启动调度器
|
|||
|
|
- 关闭:优雅关闭调度器与数据库连接
|
|||
|
|
"""
|
|||
|
|
print('项目启动...')
|
|||
|
|
|
|||
|
|
# 初始化数据库连接(使用 Tortoise 直接初始化,确保路由与定时任务可用)
|
|||
|
|
try:
|
|||
|
|
await Tortoise.init(config=TORTOISE_ORM)
|
|||
|
|
print('数据库初始化完成')
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f'数据库初始化失败: {e}')
|
|||
|
|
|
|||
|
|
# 每30分钟保持一次数据库连接活跃
|
|||
|
|
scheduler.add_job(
|
|||
|
|
keep_db_connection_alive,
|
|||
|
|
IntervalTrigger(minutes=30),
|
|||
|
|
id='keep_db_alive',
|
|||
|
|
name='保持数据库连接',
|
|||
|
|
coalesce=True,
|
|||
|
|
misfire_grace_time=30,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
scheduler.start()
|
|||
|
|
try:
|
|||
|
|
yield
|
|||
|
|
finally:
|
|||
|
|
print('项目结束...')
|
|||
|
|
|
|||
|
|
# 关闭数据库连接
|
|||
|
|
print('关闭数据库连接...')
|
|||
|
|
try:
|
|||
|
|
await asyncio.wait_for(Tortoise.close_connections(), timeout=2)
|
|||
|
|
except asyncio.TimeoutError:
|
|||
|
|
print('关闭数据库连接超时')
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f'关闭数据库连接出错: {e}')
|
|||
|
|
|
|||
|
|
# 关闭调度器
|
|||
|
|
print('关闭调度器...')
|
|||
|
|
try:
|
|||
|
|
if scheduler is not None and hasattr(scheduler, 'shutdown'):
|
|||
|
|
scheduler.shutdown(wait=False)
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f'关闭调度器出错: {e}')
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 创建 FastAPI 应用实例
|
|||
|
|
app = FastAPI(lifespan=lifespan)
|
|||
|
|
|
|||
|
|
# 配置 CORS 中间件
|
|||
|
|
app.add_middleware(
|
|||
|
|
CORSMiddleware,
|
|||
|
|
allow_origins=["*"],
|
|||
|
|
allow_credentials=True,
|
|||
|
|
allow_methods=["*"],
|
|||
|
|
allow_headers=["*"],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 创建调度器实例
|
|||
|
|
scheduler = AsyncIOScheduler()
|
|||
|
|
|
|||
|
|
# 包含主路由
|
|||
|
|
app.include_router(main_router)
|
|||
|
|
|
|||
|
|
# 注意:使用自定义 lifespan 已在启动时手动初始化数据库。
|
|||
|
|
# 若改回默认事件机制,可重新启用 register_tortoise。
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def keep_db_connection_alive():
|
|||
|
|
"""
|
|||
|
|
保持数据库连接活跃的函数
|
|||
|
|
定期执行简单查询以防止连接超时
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
conn = Tortoise.get_connection("default")
|
|||
|
|
await conn.execute_query("SELECT 1")
|
|||
|
|
print("数据库连接检查成功")
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"数据库连接检查失败: {e}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def signal_handler():
|
|||
|
|
"""
|
|||
|
|
处理终止信号,确保资源正确释放
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
async def shutdown():
|
|||
|
|
print("收到终止信号,开始优雅关闭...")
|
|||
|
|
|
|||
|
|
# 关闭数据库连接
|
|||
|
|
print("关闭数据库连接...")
|
|||
|
|
try:
|
|||
|
|
await Tortoise.close_connections()
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"关闭数据库连接时出错: {e}")
|
|||
|
|
|
|||
|
|
# 关闭调度器
|
|||
|
|
print("关闭调度器...")
|
|||
|
|
try:
|
|||
|
|
scheduler.shutdown()
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"关闭调度器时出错: {e}")
|
|||
|
|
|
|||
|
|
print("所有资源已关闭,程序退出")
|
|||
|
|
sys.exit(0)
|
|||
|
|
|
|||
|
|
loop = asyncio.get_event_loop()
|
|||
|
|
loop.create_task(shutdown())
|
|||
|
|
# 给异步任务一些时间完成
|
|||
|
|
loop.run_until_complete(asyncio.sleep(2))
|
|||
|
|
sys.exit(0)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == '__main__':
|
|||
|
|
from uvicorn import run
|
|||
|
|
|
|||
|
|
# 注册信号处理
|
|||
|
|
for sig in (signal.SIGINT, signal.SIGTERM):
|
|||
|
|
signal.signal(sig, lambda sig, frame: signal_handler())
|
|||
|
|
|
|||
|
|
run(
|
|||
|
|
'main:app',
|
|||
|
|
host='0.0.0.0',
|
|||
|
|
port=6060,
|
|||
|
|
reload=False,
|
|||
|
|
workers=1,
|
|||
|
|
# loop='uvloop',
|
|||
|
|
http='httptools',
|
|||
|
|
limit_concurrency=10000,
|
|||
|
|
backlog=4096,
|
|||
|
|
timeout_keep_alive=5
|
|||
|
|
)
|