app.py 24 KB

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