Files
chongming_lite/src/chongming/app/__init__.py
T

185 lines
5.6 KiB
Python
Raw Normal View History

2026-04-26 10:20:32 +08:00
import time
2026-04-20 17:11:31 +08:00
from pathlib import Path
2026-03-06 17:57:44 +08:00
from contextlib import asynccontextmanager
2026-04-15 16:05:31 +08:00
from fastapi import FastAPI, Request
2026-04-13 15:55:12 +08:00
from fastapi.responses import JSONResponse
2026-03-06 17:57:44 +08:00
from fastapi.middleware.cors import CORSMiddleware
2026-04-20 17:11:31 +08:00
from fastapi.staticfiles import StaticFiles
2026-03-06 17:57:44 +08:00
from sqlmodel import SQLModel, text
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.exc import OperationalError
from .core.config import get_config
2026-04-13 15:55:12 +08:00
from .core.cache import get_cache
2026-03-06 17:57:44 +08:00
from .core.logger import get_logger
2026-04-15 16:05:31 +08:00
from .core.static_files import SVFSStaticFiles
2026-03-06 17:57:44 +08:00
from .api import api_router
2026-04-13 15:55:12 +08:00
from .task import init_tasks_callback
2026-04-26 10:20:32 +08:00
from plugins.scheduler.scheduler import get_task_service_instance
2026-04-13 15:55:12 +08:00
2026-03-06 17:57:44 +08:00
config = get_config()
2026-04-26 10:20:32 +08:00
cache = get_cache()
2026-03-06 17:57:44 +08:00
logger = get_logger("app")
2026-04-20 17:11:31 +08:00
2026-03-06 17:57:44 +08:00
try:
env = config["default"]["env"]
except KeyError: # pragma: no cover
raise ValueError("配置不存在")
# 生命周期管理
@asynccontextmanager
async def lifespan(app: FastAPI):
from plugins import hello
2026-04-26 10:20:32 +08:00
from plugins.jwt.jwt_cache import get_jwt_cache
get_jwt_cache(config, cache)
2026-03-06 17:57:44 +08:00
hello()
database = config["database"]
database_type = database["type"]
database_config = database.get(database_type, None)
if database_config is None:
raise ValueError("配置不存在")
# 启动时:创建引擎和会话工厂,初始化数据库表
engine = create_async_engine(database["url"], **database_config)
async_session_maker = async_sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
# 将资源存储到 app.state
app.state.engine = engine
app.state.async_session_maker = async_session_maker
# 创建表(异步方式)
if env == "development":
async with engine.begin() as conn:
if database_type == "sqlite":
await conn.execute(text("PRAGMA journal_mode=WAL"))
await conn.execute(text("PRAGMA synchronous=NORMAL"))
try:
await conn.run_sync(
lambda conn: SQLModel.metadata.create_all(
bind=conn, checkfirst=True
)
)
except OperationalError as e:
if "already exists" in str(e):
raise
2026-04-20 17:11:31 +08:00
# 启动时:初始化任务服务
2026-04-26 10:20:32 +08:00
task_service = get_task_service_instance(
config, get_logger("scheduler"), async_session_maker
)
2026-04-15 16:05:31 +08:00
app.state.task_service = task_service
await task_service.start(init_tasks_callback)
2026-03-06 17:57:44 +08:00
yield # 应用运行期间
# 关闭时:释放引擎
2026-04-13 15:55:12 +08:00
await task_service.shutdown(wait=True)
2026-03-06 17:57:44 +08:00
await engine.dispose()
2026-04-26 10:20:32 +08:00
cache.close()
2026-03-06 17:57:44 +08:00
# 创建 FastAPI 应用
2026-04-20 17:11:31 +08:00
cors = config[env].get("cors", {})
2026-03-06 17:57:44 +08:00
app = FastAPI(
title=config["default"]["app"]["name"],
version=config["default"]["app"]["version"],
description=config["default"]["app"]["description"],
docs_url="/docs" if config[env]["debug"] else None,
redoc_url="/redoc" if config[env]["debug"] else None,
openapi_url="/openapi.json" if config[env]["debug"] else None,
lifespan=lifespan,
)
2026-04-20 17:11:31 +08:00
2026-03-06 17:57:44 +08:00
# 添加 CORS 中间件
app.add_middleware(CORSMiddleware, **cors)
2026-04-20 17:11:31 +08:00
# 添加路由
app.include_router(api_router, prefix=config["default"]["prefix"])
2026-03-06 17:57:44 +08:00
2026-04-26 10:20:32 +08:00
@app.middleware("http")
async def access_log_middleware(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
duration = (time.perf_counter() - start) * 1000
client = request.client.host if request.client else "-"
logger.info(
f"{request.method} {request.url.path} status={response.status_code} "
f"duration={duration:.2f}ms client={client}",
extra={"status_code": response.status_code}, # ← 关键
)
return response
2026-03-06 17:57:44 +08:00
# 根路由
@app.get("/", summary="根路径")
async def root():
"""根路径,返回应用信息"""
return {
2026-04-13 15:55:12 +08:00
"message": f"欢迎使用 {config['default']['app']['name']}",
2026-03-06 17:57:44 +08:00
"version": config["default"]["app"]["version"],
"docs": "/docs" if config[env]["debug"] else None,
2026-04-20 17:11:31 +08:00
"health": f"{config['default']['prefix']}/health",
2026-03-06 17:57:44 +08:00
}
2026-04-26 10:20:32 +08:00
# from pydantic import BaseModel
# class TaskRequest(BaseModel):
# task_name: str
# interval: int
2026-04-15 16:05:31 +08:00
# @app.post("/schedule")
# async def add_task(
# request: TaskRequest, task_service: TaskService = Depends(get_task_service)
# ):
# """添加定时任务接口"""
# job = await task_service.add_interval_job(
# execute_background_task,
# seconds=request.interval,
# args=[request.task_name],
# job_id=request.task_name,
# )
# return {"status": "success", "job_id": job.id}
2026-04-13 15:55:12 +08:00
2026-04-20 17:11:31 +08:00
# 挂载静态文件
config = get_config()
IMAGE_DIR = Path(f"./{config['default']['upload_path']}/images")
app.mount(
f"{config['default']['prefix']}/images",
StaticFiles(directory=f"./{config['default']['upload_path']}/images"),
name="images",
)
2026-04-13 15:55:12 +08:00
2026-04-20 17:11:31 +08:00
# 挂载前端静态文件
if vfs_db_path := config[env].get("file_system", {}).get("path", None):
2026-04-15 16:05:31 +08:00
logger.info("静态文件已存在,将使用静态文件服务")
2026-04-20 17:11:31 +08:00
static_files_handler = SVFSStaticFiles(directory="/", vfs_db_path=vfs_db_path)
2026-04-14 19:44:25 +08:00
app.mount("/static", static_files_handler, name="static")
2026-03-06 17:57:44 +08:00
# 全局异常处理器
@app.exception_handler(Exception)
2026-04-13 15:55:12 +08:00
async def global_exception_handler(_: Request, exc: Exception):
2026-03-06 17:57:44 +08:00
"""全局异常处理器"""
logger.error(f"全局异常: {exc}")
2026-04-13 15:55:12 +08:00
return JSONResponse(
status_code=500,
content={
"error": "内部服务器错误",
"detail": str(exc) if config[env]["debug"] else "请查看服务器日志",
},
)