app.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. import os
  2. import sys
  3. import time
  4. import redis
  5. import signal
  6. import uvicorn
  7. import datetime
  8. import traceback
  9. import threading
  10. BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  11. sys.path.insert(0, BASE_DIR)
  12. from views import lifespan
  13. from fastapi import FastAPI, HTTPException
  14. from fastapi.responses import JSONResponse
  15. from pydantic import BaseModel
  16. from typing import Optional, Dict, Any
  17. from fastapi.middleware.cors import CORSMiddleware
  18. from foundation.infrastructure.config.config import config_handler
  19. from foundation.infrastructure.cache import RedisConnectionFactory
  20. from foundation.observability.logger.loggering import server_logger
  21. from foundation.infrastructure.messaging.celery_app import app as celery_app
  22. # 导入所有路由
  23. from views.test_views import test_router
  24. from views.construction_review.file_upload import file_upload_router
  25. from views.construction_review.review_results import review_results_router
  26. from views.construction_review.launch_review import launch_review_router
  27. from views.construction_review.task_control import task_control_router
  28. class ServerUtils:
  29. """服务器工具函数类 - 集中管理工具方法"""
  30. @staticmethod
  31. def get_redis_connection():
  32. """获取Redis连接的统一工具函数
  33. Returns:
  34. redis.Redis: Redis连接对象
  35. """
  36. # 从配置文件获取Redis连接参数
  37. redis_host = config_handler.get('redis', 'REDIS_HOST', 'localhost')
  38. redis_port = config_handler.get('redis', 'REDIS_PORT', '6379')
  39. redis_password = config_handler.get('redis', 'REDIS_PASSWORD', '')
  40. redis_db = config_handler.get('redis', 'REDIS_DB', '0')
  41. # 构建Redis URL
  42. if redis_password:
  43. redis_url = f'redis://:{redis_password}@{redis_host}:{redis_port}/{redis_db}'
  44. else:
  45. redis_url = f'redis://{redis_host}:{redis_port}/{redis_db}'
  46. return redis.from_url(redis_url, decode_responses=True)
  47. class RouteManager:
  48. """路由管理类 - 负责路由配置和中间件设置"""
  49. def __init__(self, app: FastAPI):
  50. """初始化路由管理器
  51. Args:
  52. app: FastAPI应用实例
  53. """
  54. self.app = app
  55. self._setup_cors()
  56. self._setup_routes()
  57. self._setup_exception_handlers()
  58. self._setup_health_checks()
  59. self._setup_api_docs()
  60. def _setup_cors(self):
  61. """配置CORS中间件"""
  62. self.app.add_middleware(
  63. CORSMiddleware,
  64. allow_origins=["*"], # 允许所有的来源
  65. allow_credentials=True,
  66. allow_methods=["*"], # 允许的HTTP方法
  67. allow_headers=["*"], # 允许的请求头
  68. )
  69. def _setup_routes(self):
  70. """配置所有路由"""
  71. self.app.include_router(test_router)
  72. self.app.include_router(file_upload_router)
  73. self.app.include_router(review_results_router)
  74. self.app.include_router(launch_review_router)
  75. self.app.include_router(task_control_router) # 新增:任务控制路由
  76. def _setup_exception_handlers(self):
  77. """配置全局异常处理"""
  78. @self.app.exception_handler(HTTPException)
  79. async def http_exception_handler(request, exc):
  80. return JSONResponse(
  81. status_code=exc.status_code,
  82. content=exc.detail
  83. )
  84. def _setup_health_checks(self):
  85. """配置健康检查接口"""
  86. @self.app.get("/health" ,tags=["系统状态"])
  87. async def health_check():
  88. timestamp = datetime.datetime.now().isoformat()
  89. return {"status": "healthy", "timestamp": timestamp}
  90. @self.app.get("/celery/status", tags=["系统状态"])
  91. async def get_celery_status():
  92. """获取Celery Worker状态"""
  93. # 延迟导入避免循环引用
  94. from server.app import celery_manager
  95. status = celery_manager.get_status()
  96. return {
  97. "celery_worker": status,
  98. "timestamp": datetime.datetime.now().isoformat()
  99. }
  100. def _setup_api_docs(self):
  101. """配置Swagger API文档"""
  102. # 添加API文档信息接口
  103. @self.app.get("/api/docs/info", tags=["系统状态"])
  104. async def api_info():
  105. """获取API文档信息"""
  106. return {
  107. "title": "Agent API - 施工方案审查系统",
  108. "description": "集成施工方案审查功能的API接口文档",
  109. "version": "0.3",
  110. "docs_urls": {
  111. "swagger_ui": "/docs",
  112. "redoc": "/redoc",
  113. "openapi_json": "/openapi.json"
  114. },
  115. "features": [
  116. "自动生成API文档",
  117. "交互式API测试",
  118. "OpenAPI 3.0规范",
  119. "支持多种认证方式"
  120. ]
  121. }
  122. @self.app.get("/api/docs/health", tags=["系统状态"])
  123. async def docs_health_check():
  124. """API文档健康检查"""
  125. return {
  126. "status": "healthy",
  127. "service": "API Documentation",
  128. "version": "0.3",
  129. "timestamp": datetime.datetime.now().isoformat()
  130. }
  131. class CeleryWorkerManager:
  132. """Celery Worker程序化管理器 - 独立的Celery管理模块"""
  133. def __init__(self, server_utils: ServerUtils = None):
  134. """初始化Celery Worker管理器
  135. Args:
  136. server_utils: 服务器工具类实例
  137. """
  138. self.worker = None
  139. self.is_running = False
  140. self.worker_thread = None
  141. self.shutdown_event = threading.Event()
  142. self.server_utils = server_utils or ServerUtils()
  143. def start_worker(self, **kwargs) -> bool:
  144. """启动Celery Worker
  145. Returns:
  146. bool: 启动是否成功
  147. """
  148. if self.is_running:
  149. server_logger.warning("Celery Worker已在运行")
  150. return True
  151. try:
  152. # 启动前清理残留任务
  153. self._cleanup_redis_tasks("启动前")
  154. # 创建Worker函数
  155. def run_celery_worker():
  156. try:
  157. server_logger.info("Celery Worker开始运行...")
  158. celery_app.worker_main(['worker'])
  159. except KeyboardInterrupt:
  160. server_logger.info("收到停止信号,Celery Worker退出")
  161. except Exception as e:
  162. server_logger.error(f"Celery Worker运行时出错: {e}")
  163. server_logger.exception("详细错误信息:")
  164. finally:
  165. self.is_running = False
  166. server_logger.info("Celery Worker已停止")
  167. # 在单独线程中启动Worker
  168. self.worker_thread = threading.Thread(target=run_celery_worker, daemon=True)
  169. self.worker_thread.start()
  170. self.is_running = True
  171. # 等待启动
  172. time.sleep(2)
  173. success = self.is_running and self.worker_thread.is_alive()
  174. if success:
  175. server_logger.info("Celery Worker启动成功")
  176. else:
  177. server_logger.error("Celery Worker启动失败")
  178. self.is_running = False
  179. return success
  180. except ImportError as e:
  181. server_logger.error(f"导入Celery失败: {e}")
  182. server_logger.info("请先安装Celery: pip install celery redis")
  183. return False
  184. except Exception as e:
  185. server_logger.error(f"启动Celery Worker失败: {e}")
  186. server_logger.exception("详细错误信息:")
  187. return False
  188. def stop_worker(self, timeout: int = 5) -> bool:
  189. """优雅停止Celery Worker
  190. Args:
  191. timeout: 停止超时时间(秒)
  192. Returns:
  193. bool: 停止是否成功
  194. """
  195. if not self.is_running:
  196. server_logger.info("Celery Worker未运行")
  197. return True
  198. try:
  199. server_logger.info("停止Celery Worker...")
  200. self.shutdown_event.set()
  201. if self.worker_thread and self.worker_thread.is_alive():
  202. # 尝试优雅停止
  203. start_time = time.time()
  204. while self.is_running and (time.time() - start_time) < timeout:
  205. time.sleep(0.1)
  206. if self.is_running:
  207. server_logger.warning("Celery Worker优雅停止超时")
  208. else:
  209. server_logger.info("Celery Worker已优雅停止")
  210. # 停止后清理Redis任务
  211. self._cleanup_redis_tasks("停止时")
  212. self.is_running = False
  213. self.shutdown_event.clear()
  214. return True
  215. except Exception as e:
  216. server_logger.error(f"停止Celery Worker失败: {e}")
  217. return False
  218. def stop_worker_immediately(self) -> bool:
  219. """立即停止Celery Worker,不等待
  220. Returns:
  221. bool: 停止是否成功
  222. """
  223. if not self.is_running:
  224. server_logger.info("Celery Worker未运行")
  225. return True
  226. try:
  227. server_logger.info("立即停止Celery Worker...")
  228. self.shutdown_event.set()
  229. # 发送中断信号
  230. if hasattr(os, 'kill'):
  231. try:
  232. os.kill(os.getpid(), signal.SIGINT)
  233. server_logger.info("已发送中断信号")
  234. except:
  235. pass
  236. # 停止后清理Redis任务
  237. self._cleanup_redis_tasks("立即停止时")
  238. self.is_running = False
  239. self.shutdown_event.clear()
  240. server_logger.info("Celery Worker已立即停止")
  241. return True
  242. except Exception as e:
  243. server_logger.error(f"立即停止Celery Worker失败: {e}")
  244. self.is_running = False
  245. return False
  246. def get_status(self) -> dict:
  247. """获取Worker状态
  248. Returns:
  249. dict: 包含worker状态的字典
  250. """
  251. return {
  252. "is_running": self.is_running,
  253. "thread_alive": self.worker_thread.is_alive() if self.worker_thread else False,
  254. }
  255. def _cleanup_redis_tasks(self, phase: str):
  256. """清理Redis中的Celery任务
  257. Args:
  258. phase: 清理阶段(启动前/停止时/立即停止时)
  259. """
  260. try:
  261. r = self.server_utils.get_redis_connection()
  262. server_logger.info(f"{phase}清理Redis中的Celery任务...")
  263. # 清理任务相关键
  264. task_keys = r.keys('task:*')
  265. celery_meta_keys = r.keys('celery-task-meta-*')
  266. current_keys = r.keys('current:*')
  267. kombu_keys = r.keys('_kombu.binding.*')
  268. all_keys = task_keys + celery_meta_keys + current_keys + kombu_keys
  269. for key in all_keys:
  270. try:
  271. r.delete(key)
  272. server_logger.debug(f"{phase}清理: {key}")
  273. except Exception as e:
  274. server_logger.warning(f"{phase}清理 {key} 失败: {e}")
  275. # 清理队列
  276. queues = ['celery', 'celery.pidbox', 'celeryev']
  277. for queue in queues:
  278. try:
  279. r.delete(queue)
  280. server_logger.debug(f"{phase}清理队列: {queue}")
  281. except Exception as e:
  282. server_logger.warning(f"{phase}清理队列 {queue} 失败: {e}")
  283. if all_keys:
  284. server_logger.info(f"{phase}已清理 {len(all_keys)} 个Redis键")
  285. except Exception as e:
  286. server_logger.error(f"{phase}清理Redis任务失败: {e}")
  287. def __enter__(self):
  288. return self
  289. def __exit__(self, exc_type, exc_val, exc_tb):
  290. self.stop_worker()
  291. class ApplicationFactory:
  292. """应用工厂类 - 负责创建和配置FastAPI应用"""
  293. def __init__(self):
  294. """初始化应用工厂"""
  295. self.server_utils = ServerUtils()
  296. self.celery_manager = CeleryWorkerManager(self.server_utils)
  297. def create_app(self) -> FastAPI:
  298. """创建FastAPI应用实例
  299. Returns:
  300. FastAPI: 配置完成的应用实例
  301. """
  302. app = FastAPI(
  303. title="Agent API - 施工方案审查系统",
  304. version="0.3",
  305. description="Agent API - 集成施工方案审查功能",
  306. lifespan=lifespan
  307. )
  308. # 使用路由管理器配置应用
  309. route_manager = RouteManager(app)
  310. return app
  311. def create_server_config(self) -> dict:
  312. """创建服务器配置
  313. Returns:
  314. dict: 服务器配置字典
  315. """
  316. # 确保端口号是整数类型
  317. port = config_handler.get('launch', 'LAUNCH_PORT', 8002)
  318. try:
  319. port = int(port)
  320. except (ValueError, TypeError):
  321. port = 8002
  322. return {
  323. 'host': config_handler.get('launch', 'HOST', '0.0.0.0'),
  324. 'port': port,
  325. 'reload': False,
  326. 'with_celery': True
  327. }
  328. # 全局实例
  329. app_factory = ApplicationFactory()
  330. celery_manager = app_factory.celery_manager
  331. # 创建 FastAPI 应用
  332. def create_app() -> FastAPI:
  333. """创建主应用服务"""
  334. app = FastAPI(
  335. title="Agent API - 施工方案审查系统",
  336. version="0.3",
  337. description="Agent API - 集成施工方案审查功能",
  338. lifespan=lifespan
  339. )
  340. # 添加 CORS 中间件
  341. app.add_middleware(
  342. CORSMiddleware,
  343. allow_origins=["*"], # 允许所有的来源
  344. allow_credentials=True,
  345. allow_methods=["*"], # 允许的HTTP方法
  346. allow_headers=["*"], # 允许的请求头
  347. )
  348. # 添加所有路由
  349. app.include_router(test_router)
  350. app.include_router(file_upload_router)
  351. app.include_router(review_results_router)
  352. app.include_router(launch_review_router)
  353. # 全局异常处理
  354. @app.exception_handler(HTTPException)
  355. async def http_exception_handler(request, exc):
  356. return JSONResponse(
  357. status_code=exc.status_code,
  358. content=exc.detail
  359. )
  360. # 健康检查
  361. @app.get("/health")
  362. async def health_check():
  363. timestamp = datetime.datetime.now().isoformat()
  364. return {"status": "healthy", "timestamp": timestamp}
  365. # Celery状态检查
  366. @app.get("/celery/status")
  367. async def get_celery_status():
  368. """获取Celery Worker状态"""
  369. global celery_manager
  370. status = celery_manager.get_status()
  371. return {
  372. "celery_worker": status,
  373. "timestamp": datetime.datetime.now().isoformat()
  374. }
  375. return app
  376. def cleanup_redis_before_start():
  377. """启动前清理Redis中的残留Celery任务"""
  378. try:
  379. # 使用统一的Redis连接工具函数
  380. r = server_utils.get_redis_connection()
  381. server_logger.info("清理Redis中的残留Celery任务...")
  382. # 清理所有Celery相关的键,包括更多模式
  383. keys_to_delete = []
  384. for key in r.keys():
  385. key_lower = key.lower()
  386. # 扩展匹配模式,包括你遇到的实际键格式
  387. if any(keyword in key_lower for keyword in [
  388. 'celery', 'task:', 'celery-task', 'kombu', 'current:'
  389. ]):
  390. keys_to_delete.append(key)
  391. # 匹配特定模式
  392. elif key.startswith('celery-task-meta-') or key.startswith('current:'):
  393. keys_to_delete.append(key)
  394. # 临时键
  395. elif key == 't_key':
  396. keys_to_delete.append(key)
  397. # 清理消息队列
  398. try:
  399. # 清理所有Celery队列
  400. queues = ['celery', 'celery.pidbox', 'celeryev']
  401. for queue in queues:
  402. # 删除队列
  403. r.delete(queue)
  404. server_logger.debug(f"已清理队列: {queue}")
  405. # 清理Kombu绑定
  406. kombu_keys = r.keys('_kombu.binding.*')
  407. for key in kombu_keys:
  408. r.delete(key)
  409. server_logger.debug(f"已清理Kombu绑定: {key}")
  410. except Exception as e:
  411. server_logger.warning(f"清理队列失败: {e}")
  412. # 清理识别到的键
  413. if keys_to_delete:
  414. for key in keys_to_delete:
  415. try:
  416. r.delete(key)
  417. server_logger.debug(f"已清理: {key}")
  418. except Exception as e:
  419. server_logger.warning(f"清理 {key} 失败: {e}")
  420. server_logger.info(f"成功清理 {len(keys_to_delete)} 个Redis键")
  421. else:
  422. server_logger.info("没有发现需要清理的残留任务")
  423. # 额外检查:确保关键队列被清空
  424. try:
  425. # 使用FLUSHDB只清空Celery相关的数据,而不是整个数据库
  426. # 这里我们检查是否还有残留,如果有则进行更彻底的清理
  427. remaining_keys = []
  428. for key in r.keys():
  429. if any(pattern in key.lower() for pattern in ['celery', 'kombu']):
  430. remaining_keys.append(key)
  431. if remaining_keys:
  432. server_logger.warning(f"发现 {len(remaining_keys)} 个残留键,进行彻底清理")
  433. for key in remaining_keys:
  434. try:
  435. r.delete(key)
  436. server_logger.debug(f"彻底清理: {key}")
  437. except Exception as e:
  438. server_logger.warning(f"彻底清理 {key} 失败: {e}")
  439. except Exception as e:
  440. server_logger.warning(f"彻底清理检查失败: {e}")
  441. return True
  442. except Exception as e:
  443. server_logger.error(f"清理Redis残留任务失败: {e}")
  444. return False
  445. def start_celery_worker_background():
  446. """在后台启动Celery Worker(异步方式)"""
  447. # 启动前清理残留任务
  448. cleanup_redis_before_start()
  449. # 添加调用栈调试
  450. server_logger.info("=== Celery Worker启动调用栈 ===")
  451. for line in traceback.format_stack():
  452. server_logger.debug(f" {line.strip()}")
  453. server_logger.info("=== 调用栈结束 ===")
  454. return celery_manager.start_worker()
  455. def stop_celery_worker():
  456. """停止Celery Worker"""
  457. global celery_manager
  458. # 立即取消所有任务注册(使用DB0,与启动时保持一致)
  459. try:
  460. # 使用统一的Redis连接工具函数
  461. r = server_utils.get_redis_connection()
  462. server_logger.info("停止时清理Redis中的Celery任务...")
  463. # 清理任务相关键
  464. task_keys = r.keys('task:*') # 重复任务检查器的数据
  465. celery_meta_keys = r.keys('celery-task-meta-*')
  466. current_keys = r.keys('current:*')
  467. kombu_keys = r.keys('_kombu.binding.*')
  468. all_keys = task_keys + celery_meta_keys + current_keys + kombu_keys
  469. for key in all_keys:
  470. try:
  471. r.delete(key)
  472. server_logger.debug(f"停止时清理: {key}")
  473. except Exception as e:
  474. server_logger.warning(f"停止时清理 {key} 失败: {e}")
  475. # 清理队列
  476. queues = ['celery', 'celery.pidbox', 'celeryev']
  477. for queue in queues:
  478. try:
  479. r.delete(queue)
  480. server_logger.debug(f"停止时清理队列: {queue}")
  481. except Exception as e:
  482. server_logger.warning(f"停止时清理队列 {queue} 失败: {e}")
  483. server_logger.info(f"停止时已清理 {len(all_keys)} 个Redis键")
  484. except Exception as e:
  485. server_logger.error(f"停止时清理Redis任务失败: {e}")
  486. # 立即停止Worker,不等待
  487. return celery_manager.stop_worker_immediately()
  488. def run_server(host: str = None, port: int = None, reload: bool = False,
  489. with_celery: bool = True):
  490. """运行服务器"""
  491. # 从配置文件获取默认值
  492. if host is None:
  493. host = config_handler.get('launch', 'HOST', '0.0.0.0')
  494. if port is None:
  495. port = config_handler.get('launch', 'LAUNCH_PORT')
  496. if with_celery:
  497. # 启动Celery Worker
  498. start_celery_worker_background()
  499. # 注册退出时的清理函数
  500. import atexit
  501. atexit.register(stop_celery_worker)
  502. # 设置信号处理
  503. def signal_handler(signum, frame):
  504. server_logger.info(f"收到信号 {signum},正在停止服务...")
  505. stop_celery_worker()
  506. sys.exit(0)
  507. # Windows和Unix系统的信号处理
  508. try:
  509. signal.signal(signal.SIGINT, signal_handler) # Ctrl+C
  510. signal.signal(signal.SIGTERM, signal_handler) # 终止信号
  511. except AttributeError:
  512. # Windows可能不支持某些信号
  513. pass
  514. # Windows特有的控制台事件处理
  515. if sys.platform == 'win32':
  516. try:
  517. import win32api
  518. def win32_handler(dwCtrlType):
  519. # 正确的控制台事件常量
  520. CTRL_C_EVENT = 0
  521. CTRL_BREAK_EVENT = 1
  522. CTRL_CLOSE_EVENT = 2
  523. CTRL_SHUTDOWN_EVENT = 6
  524. if dwCtrlType in (CTRL_C_EVENT, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_SHUTDOWN_EVENT):
  525. server_logger.info(f"收到Windows控制台事件 {dwCtrlType},正在停止服务...")
  526. stop_celery_worker()
  527. sys.exit(0)
  528. return False
  529. win32api.SetConsoleCtrlHandler(win32_handler, True)
  530. except (ImportError, AttributeError) as e:
  531. # 如果win32api不可用,跳过Windows控制台处理
  532. server_logger.debug(f"Windows控制台事件处理不可用: {e}")
  533. pass
  534. try:
  535. if reload:
  536. # 重载模式需要正确的模块路径
  537. app_import_path = "server.app:app"
  538. uvicorn.run(app_import_path, host=host, port=port, reload=reload)
  539. else:
  540. # 直接运行模式,直接使用app对象
  541. uvicorn.run(app, host=host, port=port)
  542. finally:
  543. if with_celery:
  544. stop_celery_worker()
  545. app = create_app()
  546. server_logger.info(msg="APP init successfully - 集成施工方案审查系统")
  547. class ServerRunner:
  548. """服务器运行器 - 简化的主启动入口"""
  549. def __init__(self, app_factory: ApplicationFactory):
  550. """初始化服务器运行器
  551. Args:
  552. app_factory: 应用工厂实例
  553. """
  554. self.app_factory = app_factory
  555. self.celery_manager = app_factory.celery_manager
  556. def run_server(self, **kwargs):
  557. """运行服务器
  558. Args:
  559. **kwargs: 服务器配置参数
  560. """
  561. # 获取配置
  562. config = self.app_factory.create_server_config()
  563. config.update(kwargs)
  564. host = config.get('host', '0.0.0.0')
  565. port = config.get('port', 8002)
  566. # 确保端口号是整数类型
  567. try:
  568. port = int(port)
  569. except (ValueError, TypeError):
  570. port = 8002
  571. reload = config.get('reload', False)
  572. with_celery = config.get('with_celery', True)
  573. if with_celery:
  574. self._setup_celery_integration()
  575. # 创建应用实例
  576. app = self.app_factory.create_app()
  577. try:
  578. if reload:
  579. app_import_path = "server.app:app"
  580. uvicorn.run(app_import_path, host=host, port=port, reload=reload)
  581. else:
  582. uvicorn.run(app, host=host, port=port)
  583. finally:
  584. if with_celery:
  585. self.celery_manager.stop_worker()
  586. def _setup_celery_integration(self):
  587. """设置Celery集成"""
  588. # 启动Celery Worker
  589. self.celery_manager.start_worker()
  590. # 注册退出处理
  591. import atexit
  592. atexit.register(self.celery_manager.stop_worker_immediately)
  593. # 设置信号处理
  594. self._setup_signal_handlers()
  595. def _setup_signal_handlers(self):
  596. """设置信号处理器"""
  597. def signal_handler(signum, frame):
  598. server_logger.info(f"收到信号 {signum},正在停止服务...")
  599. self.celery_manager.stop_worker_immediately()
  600. sys.exit(0)
  601. # 通用信号处理
  602. try:
  603. signal.signal(signal.SIGINT, signal_handler) # Ctrl+C
  604. signal.signal(signal.SIGTERM, signal_handler) # 终止信号
  605. except AttributeError:
  606. # Windows可能不支持某些信号
  607. pass
  608. # Windows特有处理
  609. if sys.platform == 'win32':
  610. self._setup_windows_signal_handler()
  611. def _setup_windows_signal_handler(self):
  612. """设置Windows信号处理器"""
  613. try:
  614. import win32api
  615. def win32_handler(dwCtrlType):
  616. CTRL_C_EVENT = 0
  617. CTRL_BREAK_EVENT = 1
  618. CTRL_CLOSE_EVENT = 2
  619. CTRL_SHUTDOWN_EVENT = 6
  620. if dwCtrlType in (CTRL_C_EVENT, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_SHUTDOWN_EVENT):
  621. server_logger.info(f"收到Windows控制台事件 {dwCtrlType},正在停止服务...")
  622. self.celery_manager.stop_worker_immediately()
  623. sys.exit(0)
  624. return False
  625. win32api.SetConsoleCtrlHandler(win32_handler, True)
  626. except (ImportError, AttributeError) as e:
  627. server_logger.debug(f"Windows控制台事件处理不可用: {e}")
  628. # 创建应用实例和运行器
  629. app = app_factory.create_app()
  630. server_runner = ServerRunner(app_factory)
  631. server_logger.info(msg="APP init successfully - 集成施工方案审查系统")
  632. # 运行Uvicorn服务器
  633. if __name__ == "__main__":
  634. # 使用新的服务器运行器启动
  635. config = app_factory.create_server_config()
  636. server_logger.info(f"Agent API服务启动中...运行在{config['host']}:{config['port']}")
  637. if config['with_celery']:
  638. server_logger.info("Celery Worker: 已集成启动")
  639. else:
  640. server_logger.warning("Celery Worker: 已禁用")
  641. server_runner.run_server(**config)