""" 国土空间规划课程智能体后端服务 FastAPI应用入口 """ import os import sys from pathlib import Path from datetime import datetime from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse import uvicorn # 设置系统时区为北京时间 os.environ['TZ'] = 'Asia/Shanghai' # 添加src目录到Python路径 sys.path.append(str(Path(__file__).parent / "src")) from src.core.config import get_settings, ensure_directories, is_postgresql_database from src.core.database import create_tables, check_database_connection, get_database_info from src.api import auth, chat # 获取配置 settings = get_settings() # 确保必要目录存在 ensure_directories() # 创建FastAPI应用 app = FastAPI( title=settings.app_name, version=settings.app_version, description="基于大模型的国土空间规划课程智能体系统", docs_url="/docs", redoc_url="/redoc" ) # 配置CORS app.add_middleware( CORSMiddleware, allow_origins=settings.allowed_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 启动知识库文件监控服务 def startup_knowledge_base(): """启动知识库服务""" try: from src.services.file_watcher_service import start_file_watcher start_file_watcher() print("知识库文件监控服务启动成功") except Exception as e: print(f"启动知识库文件监控服务失败: {str(e)}") def seed_forum_categories(): """初始化论坛默认分类""" from src.core.database import get_db from src.models.forum import ForumCategory DEFAULT_CATEGORIES = [ {"slug": "announcements", "name": "课程公告", "description": "课程通知、作业安排与重要信息"}, {"slug": "course-learning", "name": "学习讨论", "description": "交流学习心得,讨论课程内容与难点"}, {"slug": "system-feedback", "name": "使用反馈", "description": "分享系统使用经验,提出改进建议"}, ] db = next(get_db()) try: existing_count = db.query(ForumCategory).count() if existing_count > 0: print(f"论坛分类已存在({existing_count} 个),跳过初始化") return for cat_data in DEFAULT_CATEGORIES: category = ForumCategory(**cat_data) db.add(category) db.commit() print(f"已创建 {len(DEFAULT_CATEGORIES)} 个论坛分类") except Exception as e: db.rollback() print(f"初始化论坛分类失败: {e}") finally: db.close() # 应用启动事件 @app.on_event("startup") async def startup_event(): """应用启动时执行""" import time import asyncio import sys print("=" * 50, file=sys.stderr) print("应用启动事件开始", file=sys.stderr) print("=" * 50, file=sys.stderr) sys.stderr.flush() # 等待数据库可用(重试机制,处理 DNS 解析延迟) max_retries = 10 retry_delay = 2 for attempt in range(max_retries): try: print(f"尝试连接数据库(尝试 {attempt + 1}/{max_retries})...", file=sys.stderr) sys.stderr.flush() # 尝试创建数据库表 create_tables() print("数据库表创建成功", file=sys.stderr) sys.stderr.flush() break except Exception as e: if attempt < max_retries - 1: print(f"数据库连接失败(尝试 {attempt + 1}/{max_retries}): {e}", file=sys.stderr) print(f"等待 {retry_delay} 秒后重试...", file=sys.stderr) sys.stderr.flush() await asyncio.sleep(retry_delay) else: print(f"数据库连接失败,已达到最大重试次数: {e}", file=sys.stderr) sys.stderr.flush() # 不抛出异常,让应用继续启动,但数据库操作会失败 try: startup_knowledge_base() except Exception as e: print(f"启动知识库服务失败: {e}", file=sys.stderr) # 确保系统知识库与目录同步 try: from src.core.database import get_db from src.services.knowledge_base_service import KnowledgeBaseService db = next(get_db()) kb_service = KnowledgeBaseService(db) kb_service.ensure_system_knowledge_bases() db.close() except Exception as e: print(f"同步系统知识库失败: {e}", file=sys.stderr) # 初始化论坛分类 try: seed_forum_categories() except Exception as e: print(f"初始化论坛分类失败: {e}", file=sys.stderr) sys.stderr.flush() print("应用启动事件完成", file=sys.stderr) sys.stderr.flush() # 应用关闭事件 @app.on_event("shutdown") async def shutdown_event(): """应用关闭时执行""" try: from src.services.file_watcher_service import stop_file_watcher stop_file_watcher() print("知识库文件监控服务已停止") except Exception as e: print(f"停止知识库文件监控服务失败: {str(e)}") # 注册路由 app.include_router(auth.router) app.include_router(chat.router) # 导入并注册文档API from src.api import document app.include_router(document.router) # 导入并注册图像API from src.api import image app.include_router(image.router) # 导入并注册分析API from src.api import analytics app.include_router(analytics.router) # 导入并注册知识库API(已合并knowledge.py的功能) from src.api import knowledge_base app.include_router(knowledge_base.router) # 导入并注册课程内容API from src.api import course_content, forum app.include_router(course_content.router) app.include_router(forum.router) # 导入并注册后台管理API from src.api import admin app.include_router(admin.router) # 静态文件服务 if os.path.exists(settings.upload_dir): app.mount("/uploads", StaticFiles(directory=settings.upload_dir), name="uploads") # 挂载生成图像目录 if os.path.exists(settings.generated_images_dir): app.mount("/generated_images", StaticFiles(directory=settings.generated_images_dir), name="generated_images") # 根路径 @app.get("/") async def root(): """根路径""" return { "message": f"欢迎使用{settings.app_name}", "version": settings.app_version, "docs": "/docs" } # 健康检查端点 @app.get("/health") async def health_check(): """健康检查端点""" # 检查数据库连接 db_healthy = check_database_connection() status = "healthy" if db_healthy else "unhealthy" return { "status": status, "timestamp": datetime.now().isoformat(), "version": settings.app_version, "database": "connected" if db_healthy else "disconnected", "services": { "api": "running", "database": "connected" if db_healthy else "disconnected" } } # 系统状态端点 @app.get("/system/status") async def system_status(): """系统状态端点""" db_info = get_database_info() # 获取系统信息 import psutil import platform system_info = { "platform": platform.platform(), "python_version": platform.python_version(), "cpu_count": psutil.cpu_count(), "memory_total": psutil.virtual_memory().total, "memory_available": psutil.virtual_memory().available, } # 检查磁盘使用情况 try: disk_usage = psutil.disk_usage("/")._asdict() system_info["disk_usage"] = disk_usage except: system_info["disk_usage"] = None # 获取服务状态 services_status = { "api": "running", "database": db_info.get("type", "unknown"), "vector_store": "available" if os.path.exists(settings.vector_store_path) else "unavailable" } return { "status": "operational", "timestamp": datetime.now().isoformat(), "system": system_info, "services": services_status, "database": db_info, "config": { "app_name": settings.app_name, "app_version": settings.app_version, "debug": settings.debug, "database_type": "postgresql" if is_postgresql_database() else "sqlite" } } # 服务发现端点 @app.get("/services") async def list_services(): """列出所有服务""" services = { "backend": { "name": "FastAPI后端服务", "endpoint": f"http://localhost:{settings.port}", "health": "/health", "docs": "/docs" }, "database": { "name": "数据库服务", "type": "postgresql" if is_postgresql_database() else "sqlite", "status": "running" }, "vectorizer": { "name": "向量化服务", "description": "独立文档向量化处理服务", "status": "available" }, "frontend": { "name": "Next.js前端服务", "endpoint": "http://localhost:8001", "status": "external" } } return { "services": services, "timestamp": datetime.now().isoformat() } if __name__ == "__main__": import sys print("=" * 50, file=sys.stderr) print("启动 Uvicorn 服务器...", file=sys.stderr) print(f"Host: {settings.host}, Port: {settings.port}", file=sys.stderr) print("=" * 50, file=sys.stderr) sys.stderr.flush() uvicorn.run( "main:app", host=settings.host, port=settings.port, reload=settings.debug, log_level=settings.log_level.lower(), log_config=None # 使用默认日志配置 )