feat: global exception handler, concurrent VLM, and docker security
- Add global FastAPI exception handler for unhandled errors - Add get_current_user_obj() dependency for cleaner auth patterns - Switch VLM image description from serial to asyncio.gather concurrency - Extract score conversion to shared score_utils module - Docker: use env vars for passwords, remove hardcoded API key default - Add .gitattributes and update .gitignore for tar.gz and tsbuildinfo Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+35
-34
@@ -4,14 +4,17 @@ FastAPI应用入口
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
import uvicorn
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 设置系统时区为北京时间
|
||||
os.environ['TZ'] = 'Asia/Shanghai'
|
||||
|
||||
@@ -52,9 +55,9 @@ def startup_knowledge_base():
|
||||
try:
|
||||
from src.services.file_watcher_service import start_file_watcher
|
||||
start_file_watcher()
|
||||
print("知识库文件监控服务启动成功")
|
||||
logger.info("知识库文件监控服务启动成功")
|
||||
except Exception as e:
|
||||
print(f"启动知识库文件监控服务失败: {str(e)}")
|
||||
logger.error(f"启动知识库文件监控服务失败: {str(e)}")
|
||||
|
||||
|
||||
def seed_forum_categories():
|
||||
@@ -72,17 +75,17 @@ def seed_forum_categories():
|
||||
try:
|
||||
existing_count = db.query(ForumCategory).count()
|
||||
if existing_count > 0:
|
||||
print(f"论坛分类已存在({existing_count} 个),跳过初始化")
|
||||
logger.info(f"论坛分类已存在({existing_count} 个),跳过初始化")
|
||||
return
|
||||
|
||||
for cat_data in DEFAULT_CATEGORIES:
|
||||
category = ForumCategory(**cat_data)
|
||||
db.add(category)
|
||||
db.commit()
|
||||
print(f"已创建 {len(DEFAULT_CATEGORIES)} 个论坛分类")
|
||||
logger.info(f"已创建 {len(DEFAULT_CATEGORIES)} 个论坛分类")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(f"初始化论坛分类失败: {e}")
|
||||
logger.error(f"初始化论坛分类失败: {e}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@@ -94,10 +97,9 @@ async def startup_event():
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
print("=" * 50, file=sys.stderr)
|
||||
print("应用启动事件开始", file=sys.stderr)
|
||||
print("=" * 50, file=sys.stderr)
|
||||
sys.stderr.flush()
|
||||
logger.info("=" * 50)
|
||||
logger.info("应用启动事件开始")
|
||||
logger.info("=" * 50)
|
||||
|
||||
# 等待数据库可用(重试机制,处理 DNS 解析延迟)
|
||||
max_retries = 10
|
||||
@@ -105,28 +107,23 @@ async def startup_event():
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
print(f"尝试连接数据库(尝试 {attempt + 1}/{max_retries})...", file=sys.stderr)
|
||||
sys.stderr.flush()
|
||||
logger.info(f"尝试连接数据库(尝试 {attempt + 1}/{max_retries})...")
|
||||
# 尝试创建数据库表
|
||||
create_tables()
|
||||
print("数据库表创建成功", file=sys.stderr)
|
||||
sys.stderr.flush()
|
||||
logger.info("数据库表创建成功")
|
||||
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()
|
||||
logger.warning(f"数据库连接失败(尝试 {attempt + 1}/{max_retries}): {e}")
|
||||
await asyncio.sleep(retry_delay)
|
||||
else:
|
||||
print(f"数据库连接失败,已达到最大重试次数: {e}", file=sys.stderr)
|
||||
sys.stderr.flush()
|
||||
logger.error(f"数据库连接失败,已达到最大重试次数: {e}")
|
||||
# 不抛出异常,让应用继续启动,但数据库操作会失败
|
||||
|
||||
try:
|
||||
startup_knowledge_base()
|
||||
except Exception as e:
|
||||
print(f"启动知识库服务失败: {e}", file=sys.stderr)
|
||||
logger.error(f"启动知识库服务失败: {e}")
|
||||
|
||||
# 确保系统知识库与目录同步
|
||||
try:
|
||||
@@ -137,17 +134,15 @@ async def startup_event():
|
||||
kb_service.ensure_system_knowledge_bases()
|
||||
db.close()
|
||||
except Exception as e:
|
||||
print(f"同步系统知识库失败: {e}", file=sys.stderr)
|
||||
logger.error(f"同步系统知识库失败: {e}")
|
||||
|
||||
# 初始化论坛分类
|
||||
try:
|
||||
seed_forum_categories()
|
||||
except Exception as e:
|
||||
print(f"初始化论坛分类失败: {e}", file=sys.stderr)
|
||||
sys.stderr.flush()
|
||||
|
||||
print("应用启动事件完成", file=sys.stderr)
|
||||
sys.stderr.flush()
|
||||
logger.error(f"初始化论坛分类失败: {e}")
|
||||
|
||||
logger.info("应用启动事件完成")
|
||||
|
||||
# 应用关闭事件
|
||||
@app.on_event("shutdown")
|
||||
@@ -156,9 +151,18 @@ async def shutdown_event():
|
||||
try:
|
||||
from src.services.file_watcher_service import stop_file_watcher
|
||||
stop_file_watcher()
|
||||
print("知识库文件监控服务已停止")
|
||||
logger.info("知识库文件监控服务已停止")
|
||||
except Exception as e:
|
||||
print(f"停止知识库文件监控服务失败: {str(e)}")
|
||||
logger.error(f"停止知识库文件监控服务失败: {str(e)}")
|
||||
|
||||
# 全局异常处理器
|
||||
@app.exception_handler(Exception)
|
||||
async def generic_exception_handler(request: Request, exc: Exception):
|
||||
logger.error(f"未处理的异常: {exc}", exc_info=True)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"detail": "服务器内部错误,请稍后重试"}
|
||||
)
|
||||
|
||||
# 注册路由
|
||||
app.include_router(auth.router)
|
||||
@@ -315,11 +319,8 @@ async def list_services():
|
||||
|
||||
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()
|
||||
logger.info("启动 Uvicorn 服务器...")
|
||||
logger.info(f"Host: {settings.host}, Port: {settings.port}")
|
||||
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
|
||||
Reference in New Issue
Block a user