Files
course-agent-od/backend/main.py
T
pengxiao 90ab913ef9 fix: use config paths for static file mounts, redesign knowledge graph
- Fix generated images 404: use settings.generated_images_dir and
  settings.upload_dir instead of hardcoded relative paths in main.py
- Redesign knowledge graph with p5-style particle background, glow
  nodes, and curved gradient links
- Update course-content page border styling

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 14:20:28 +08:00

276 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
国土空间规划课程智能体后端服务
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)}")
# 应用启动事件
@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)
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)
# 静态文件服务
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 # 使用默认日志配置
)