Initial commit: 国土空间规划课程智能体 v1.0
单容器 Docker 架构的国土空间规划课程智能问答系统,集成 FastAPI 后端与 Next.js 前端。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+275
@@ -0,0 +1,275 @@
|
||||
"""
|
||||
国土空间规划课程智能体后端服务
|
||||
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("uploads"):
|
||||
app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
|
||||
|
||||
# 挂载生成图像目录
|
||||
if os.path.exists("generated_images"):
|
||||
app.mount("/generated_images", StaticFiles(directory="generated_images"), 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 # 使用默认日志配置
|
||||
)
|
||||
Reference in New Issue
Block a user