50faf7ad4e
- Seed default forum categories (announcements, learning, feedback) on startup - Update forum pages with improved category/post/reply UI - Refine navbar, mobile nav, and home page content layout - Improve analytics, course-content, knowledge, profile, settings, spatial pages Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
312 lines
9.2 KiB
Python
312 lines
9.2 KiB
Python
"""
|
||
国土空间规划课程智能体后端服务
|
||
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:
|
||
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)
|
||
|
||
# 静态文件服务
|
||
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 # 使用默认日志配置
|
||
)
|