release: Elysia ToDo v1.0.0
鍏ㄦ爤涓汉淇℃伅绠$悊搴旂敤锛岄泦鎴愬緟鍔炰换鍔°€佷範鎯墦鍗°€佺邯蹇垫棩鎻愰啋銆佽祫浜ф€昏鍔熻兘銆 Made-with: Cursor
This commit is contained in:
144
api/app/main.py
Normal file
144
api/app/main.py
Normal file
@@ -0,0 +1,144 @@
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse, FileResponse
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
|
||||
from app.config import CORS_ORIGINS, WEBUI_PATH, HOST, PORT
|
||||
from app.database import init_db
|
||||
from app.routers import api_router
|
||||
from app.utils.logger import logger
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""应用生命周期管理"""
|
||||
# 启动时
|
||||
logger.info("应用启动中...")
|
||||
init_db()
|
||||
logger.info("数据库初始化完成")
|
||||
yield
|
||||
# 关闭时
|
||||
logger.info("应用关闭中...")
|
||||
|
||||
|
||||
# 创建 FastAPI 应用
|
||||
app = FastAPI(
|
||||
title="爱莉希雅待办事项 API",
|
||||
description="Elysia ToDo - 个人信息管理应用",
|
||||
version="1.0.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
# 配置 CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=CORS_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
# 请求日志中间件
|
||||
@app.middleware("http")
|
||||
async def log_requests(request: Request, call_next):
|
||||
start_time = time.time()
|
||||
|
||||
# 记录请求信息
|
||||
request_method = request.method
|
||||
request_path = request.url.path
|
||||
query_params = dict(request.query_params) if request.query_params else None
|
||||
|
||||
# 构建日志信息
|
||||
log_parts = [f"请求开始 -> {request_method} {request_path}"]
|
||||
if query_params:
|
||||
log_parts.append(f"Query参数: {json.dumps(query_params, ensure_ascii=False)}")
|
||||
|
||||
# 尝试读取请求体(仅对有 body 的方法)
|
||||
body_info = None
|
||||
if request.method in ["POST", "PUT", "PATCH"]:
|
||||
try:
|
||||
body_bytes = await request.body()
|
||||
if body_bytes:
|
||||
try:
|
||||
body_json = json.loads(body_bytes)
|
||||
body_info = json.dumps(body_json, ensure_ascii=False)
|
||||
except json.JSONDecodeError:
|
||||
body_info = body_bytes.decode('utf-8', errors='replace')[:200]
|
||||
log_parts.append(f"Body: {body_info}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.info(" | ".join(log_parts))
|
||||
|
||||
# 执行请求
|
||||
response = await call_next(request)
|
||||
|
||||
# 计算耗时
|
||||
process_time = (time.time() - start_time) * 1000
|
||||
|
||||
# 记录响应信息
|
||||
logger.info(
|
||||
f"请求完成 <- {request_method} {request_path} | "
|
||||
f"状态码: {response.status_code} | 耗时: {process_time:.2f}ms"
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
# 全局异常处理器
|
||||
@app.exception_handler(Exception)
|
||||
async def global_exception_handler(request: Request, exc: Exception):
|
||||
# 使用 exc_info=True 记录完整堆栈信息
|
||||
logger.error(
|
||||
f"全局异常: {request.method} {request.url.path} | 错误: {str(exc)}",
|
||||
exc_info=True
|
||||
)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={
|
||||
"success": False,
|
||||
"message": "服务器内部错误",
|
||||
"error_code": "INTERNAL_ERROR"
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# 注册路由
|
||||
app.include_router(api_router)
|
||||
|
||||
|
||||
# 健康检查(必须在 static mount 之前注册,否则会被静态文件拦截)
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
"""健康检查"""
|
||||
return {"status": "ok", "message": "服务运行正常"}
|
||||
|
||||
|
||||
# SPA 静态文件回退路由(支持前端 History 模式路由)
|
||||
if os.path.exists(WEBUI_PATH):
|
||||
|
||||
@app.get("/")
|
||||
async def serve_index():
|
||||
return FileResponse(os.path.join(WEBUI_PATH, "index.html"))
|
||||
|
||||
@app.get("/{full_path:path}")
|
||||
async def spa_fallback(request: Request, full_path: str):
|
||||
"""SPA 回退:先尝试提供真实文件,找不到则返回 index.html"""
|
||||
file_path = os.path.join(WEBUI_PATH, full_path)
|
||||
if os.path.isfile(file_path):
|
||||
return FileResponse(file_path)
|
||||
return FileResponse(os.path.join(WEBUI_PATH, "index.html"))
|
||||
|
||||
logger.info(f"SPA 静态文件服务已配置: {WEBUI_PATH}")
|
||||
else:
|
||||
logger.warning(f"WebUI 目录不存在: {WEBUI_PATH}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
logger.info(f"启动服务: {HOST}:{PORT}")
|
||||
uvicorn.run(app, host=HOST, port=PORT)
|
||||
Reference in New Issue
Block a user