from fastapi import FastAPI, Request from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse from utils.config import settings from utils.auth_middleware import auth_middleware from utils.logger import logger from routers import api_router import uvicorn import time from pathlib import Path # 创建FastAPI应用 app = FastAPI( title=settings.app.name, debug=settings.app.debug ) # 配置CORS(必须先配置) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_headers=["Origin", "Authorization", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type", "token"], expose_headers=["Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"] ) # 添加请求日志和认证中间件 @app.middleware("http") async def combined_middleware(request: Request, call_next): """组合中间件:日志 + 认证""" from fastapi.responses import JSONResponse from utils.token import verify_token start_time = time.time() path = request.url.path # 先打印,确认中间件被执行 print(f"[DEBUG] 中间件执行 - 路径: {path}") logger.info(f"[中间件] 开始处理请求: {path}") # 白名单路径(不需要认证) whitelist_paths = ["/health", "/docs", "/redoc", "/openapi.json", "/static/", "/assets/", "/apiv1/auth/local_login", "/apiv1/auth/register"] # 检查是否在白名单中(精确匹配或以/结尾的前缀匹配) is_whitelist = path == "/" or any(path.startswith(wp) for wp in whitelist_paths) print(f"[DEBUG] 是否白名单: {is_whitelist}") if is_whitelist: print(f"[DEBUG] 白名单路径,跳过认证") request.state.user = None response = await call_next(request) else: # 获取Token auth_header = (request.headers.get("Authorization") or "").strip() token = request.headers.get("token") or request.headers.get("Token") or auth_header if auth_header.lower().startswith("bearer "): token = auth_header[7:].strip() print(f"[DEBUG] Token: {token[:20] if token else 'None'}...") logger.info(f"认证中间件 - 路径: {path}") logger.info(f"认证中间件 - Token (前20字符): {token[:20] if token else 'None'}...") if not token: print(f"[DEBUG] 未提供Token") logger.warning("认证中间件 - 未提供Token") response = JSONResponse( status_code=401, content={"statusCode": 401, "msg": "未提供认证Token"} ) else: # 验证Token print(f"[DEBUG] 开始验证Token") logger.info("认证中间件 - 开始验证Token") try: user_info = await verify_token(token) except Exception as e: logger.error(f"Token验证异常: {e}") user_info = None print(f"[DEBUG] 验证结果: {user_info}") if not user_info: print(f"[DEBUG] Token验证失败") logger.error("认证中间件 - Token验证失败,返回401") response = JSONResponse( status_code=401, content={"statusCode": 401, "msg": "Token验证失败"} ) else: print(f"[DEBUG] Token验证成功: {user_info.username}") logger.info(f"认证中间件 - Token验证成功,用户: {user_info.username} ({user_info.account})") request.state.user = user_info response = await call_next(request) # 记录日志 process_time = time.time() - start_time print(f"[DEBUG] 请求完成 - 状态码: {response.status_code}") logger.info(f"请求完成: {request.method} {path} - 状态码: {response.status_code} - 耗时: {process_time:.3f}s") return response # 注册路由 app.include_router(api_router) # 单独注册报告兼容路由(避免双重前缀) from routers.report_compat import router as report_compat_router app.include_router(report_compat_router) # 创建静态文件目录 Path("static").mkdir(exist_ok=True) Path("assets").mkdir(exist_ok=True) # 挂载静态文件 app.mount("/static", StaticFiles(directory="static"), name="static") app.mount("/assets", StaticFiles(directory="assets"), name="assets") @app.get("/", response_class=HTMLResponse) async def root(): """根路径 - 欢迎页面""" html_content = """
基于 FastAPI 的现代化 AI 聊天服务
v1.0.0
FastAPI
MySQL + SQLAlchemy
Token Based