""" Neural Sync 2.0 - Integration Service 集成服务 - 管理 Plane 和飞书集成 - 配置管理 - 状态监控 - 健康检查 - 故障降级 """ import asyncio import logging from typing import Optional, Dict, List, Any from pydantic import BaseModel, Field from datetime import datetime, timedelta from enum import Enum import httpx logger = logging.getLogger(__name__) class IntegrationType(str, Enum): """集成类型""" PLANE = "plane" LARK = "lark" GITHUB = "github" GITLAB = "gitlab" class IntegrationStatus(str, Enum): """集成状态""" ACTIVE = "active" INACTIVE = "inactive" ERROR = "error" DEGRADED = "degraded" PENDING = "pending" class PlaneConfig(BaseModel): """Plane 配置""" api_url: str = "https://api.plane.so/api/v1" api_key: str = "" workspace_slug: str = "" project_id: str = "" enabled: bool = True class LarkConfig(BaseModel): """飞书配置""" app_id: str = "" app_secret: str = "" verification_token: str = "" encrypt_key: str = "" default_chat_id: str = "" enabled: bool = True class IntegrationHealth(BaseModel): """集成健康状态""" type: IntegrationType status: IntegrationStatus last_check: datetime last_success: Optional[datetime] = None last_error: Optional[str] = None error_count: int = 0 consecutive_failures: int = 0 latency_ms: Optional[float] = None message: str = "" class IntegrationConfig(BaseModel): """集成配置""" plane: PlaneConfig = Field(default_factory=PlaneConfig) lark: LarkConfig = Field(default_factory=LarkConfig) class TaskSyncResult(BaseModel): """任务同步结果""" success: bool integration_type: IntegrationType tasks_created: int = 0 tasks_updated: int = 0 errors: List[str] = Field(default_factory=list) details: Dict[str, Any] = Field(default_factory=dict) class NotificationResult(BaseModel): """通知发送结果""" success: bool integration_type: IntegrationType recipients: int = 0 error: Optional[str] = None class IntegrationService: """ 集成服务 - 管理外部集成 特性: - 配置管理(动态加载/保存) - 健康检查和监控 - 自动故障降级 - 重试机制 - 事件通知 """ # 健康检查配置 HEALTH_CHECK_INTERVAL = 60 # 秒 MAX_CONSECUTIVE_FAILURES = 3 DEGRADE_THRESHOLD = 5 # 错误次数后降级 def __init__(self, config: Optional[IntegrationConfig] = None, event_bus=None): self.config = config or IntegrationConfig() self.event_bus = event_bus # 健康状态 self._health_status: Dict[IntegrationType, IntegrationHealth] = { IntegrationType.PLANE: IntegrationHealth( type=IntegrationType.PLANE, status=IntegrationStatus.PENDING, last_check=datetime.utcnow(), ), IntegrationType.LARK: IntegrationHealth( type=IntegrationType.LARK, status=IntegrationStatus.PENDING, last_check=datetime.utcnow(), ), } # 统计信息 self._stats = { IntegrationType.PLANE: {"synced": 0, "failed": 0}, IntegrationType.LARK: {"sent": 0, "failed": 0}, } # 服务实例(延迟加载) self._plane_service = None self._lark_service = None # 健康检查任务 self._health_check_task: Optional[asyncio.Task] = None async def start(self): """启动集成服务""" self._health_check_task = asyncio.create_task(self._health_check_loop()) logger.info("[Integration] Service started") async def stop(self): """停止集成服务""" if self._health_check_task: self._health_check_task.cancel() try: await self._health_check_task except asyncio.CancelledError: pass logger.info("[Integration] Service stopped") async def sync_tasks_to_plane( self, tasks: List[Dict[str, Any]], is_urgent: bool = False ) -> TaskSyncResult: """ 同步任务到 Plane 增强功能: - 批量创建 - 标签管理 - 优先级映射 - 错误重试 """ integration_type = IntegrationType.PLANE health = self._health_status[integration_type] # 检查服务是否可用 if not self._is_integration_available(integration_type): return TaskSyncResult( success=False, integration_type=integration_type, errors=[f"Plane integration is {health.status.value}"], ) start_time = datetime.utcnow() try: # 获取 Plane 服务 plane = await self._get_plane_service() # 批量同步 result = await plane.sync_tasks(tasks, is_urgent) # 更新统计 if result.get("status") == "success": self._stats[integration_type]["synced"] += result.get("created_count", 0) await self._record_success(integration_type) else: self._stats[integration_type]["failed"] += len(result.get("errors", [])) await self._record_failure(integration_type, ", ".join(result.get("errors", []))) # 计算延迟 latency = (datetime.utcnow() - start_time).total_seconds() * 1000 health.latency_ms = latency return TaskSyncResult( success=result.get("status") == "success", integration_type=integration_type, tasks_created=result.get("created_count", 0), errors=result.get("errors", []), details=result, ) except Exception as e: logger.error(f"[Integration] Plane sync failed: {e}") await self._record_failure(integration_type, str(e)) return TaskSyncResult( success=False, integration_type=integration_type, errors=[str(e)], ) async def send_strategic_alert( self, chat_id: str, signal_content: str, decision: Dict[str, Any], tasks: List[Dict[str, Any]] ) -> NotificationResult: """ 发送战略预警到飞书 增强功能: - 富文本卡片 - 可操作按钮 - @提及支持 - 消息去重 """ integration_type = IntegrationType.LARK health = self._health_status[integration_type] # 检查服务是否可用 if not self._is_integration_available(integration_type): return NotificationResult( success=False, integration_type=integration_type, error=f"Lark integration is {health.status.value}", ) start_time = datetime.utcnow() try: # 获取飞书服务 lark = await self._get_lark_service() # 发送卡片消息 await lark.send_strategic_alert(chat_id, signal_content, decision, tasks) # 更新统计 self._stats[integration_type]["sent"] += 1 await self._record_success(integration_type) # 计算延迟 latency = (datetime.utcnow() - start_time).total_seconds() * 1000 health.latency_ms = latency # 发送事件 if self.event_bus: from ..events import EventType, Event await self.event_bus.publish( Event( type=EventType.LARK_NOTIFY_SUCCESS, data={ "type": "strategic_alert", "chat_id": chat_id, }, ) ) return NotificationResult( success=True, integration_type=integration_type, recipients=1, ) except Exception as e: logger.error(f"[Integration] Lark notification failed: {e}") await self._record_failure(integration_type, str(e)) # 发送失败事件 if self.event_bus: from ..events import EventType, Event await self.event_bus.publish( Event( type=EventType.LARK_NOTIFY_FAILED, data={ "type": "strategic_alert", "error": str(e), }, ) ) return NotificationResult( success=False, integration_type=integration_type, error=str(e), ) async def send_card_message( self, chat_id: str, content: str, tasks: List[Dict[str, Any]] ) -> NotificationResult: """ 发送卡片消息到飞书 """ integration_type = IntegrationType.LARK health = self._health_status[integration_type] if not self._is_integration_available(integration_type): return NotificationResult( success=False, integration_type=integration_type, error=f"Lark integration is {health.status.value}", ) try: lark = await self._get_lark_service() await lark.send_card_message(chat_id, content, tasks) self._stats[integration_type]["sent"] += 1 await self._record_success(integration_type) return NotificationResult( success=True, integration_type=integration_type, recipients=1, ) except Exception as e: logger.error(f"[Integration] Lark card message failed: {e}") await self._record_failure(integration_type, str(e)) return NotificationResult( success=False, integration_type=integration_type, error=str(e), ) async def update_plane_issue_status( self, issue_id: str, status: str = "Done" ) -> bool: """更新 Plane 工单状态""" integration_type = IntegrationType.PLANE if not self._is_integration_available(integration_type): return False try: plane = await self._get_plane_service() result = await plane.update_issue_status(issue_id, status) if "error" not in result: await self._record_success(integration_type) return True await self._record_failure(integration_type, result.get("error", "Unknown error")) return False except Exception as e: logger.error(f"[Integration] Plane status update failed: {e}") await self._record_failure(integration_type, str(e)) return False def get_health_status(self) -> Dict[str, IntegrationHealth]: """获取所有集成健康状态""" return {k.value: v for k, v in self._health_status.items()} def get_stats(self) -> Dict[str, Dict[str, int]]: """获取集成统计信息""" return {k.value: v for k, v in self._stats.items()} async def check_plane_health(self) -> IntegrationHealth: """检查 Plane 集成健康""" integration_type = IntegrationType.PLANE health = self._health_status[integration_type] start_time = datetime.utcnow() if not self.config.plane.enabled or not self.config.plane.api_key: health.status = IntegrationStatus.INACTIVE health.message = "Plane integration is disabled" health.last_check = datetime.utcnow() return health try: # 简单的健康检查:尝试获取项目标签 plane = await self._get_plane_service() label_id = await plane.get_label_id("__health_check__") health.status = IntegrationStatus.ACTIVE health.message = "Connected" health.last_check = datetime.utcnow() health.latency_ms = (datetime.utcnow() - start_time).total_seconds() * 1000 except Exception as e: logger.warning(f"[Integration] Plane health check failed: {e}") health.status = IntegrationStatus.ERROR health.message = f"Health check failed: {str(e)}" health.last_check = datetime.utcnow() return health async def check_lark_health(self) -> IntegrationHealth: """检查飞书集成健康""" integration_type = IntegrationType.LARK health = self._health_status[integration_type] start_time = datetime.utcnow() if not self.config.lark.enabled or not self.config.lark.app_id: health.status = IntegrationStatus.INACTIVE health.message = "Lark integration is disabled" health.last_check = datetime.utcnow() return health try: # 简单的健康检查:尝试获取访问令牌 lark = await self._get_lark_service() token = await lark.get_tenant_access_token() if token and token != "mock_token": health.status = IntegrationStatus.ACTIVE health.message = "Connected" else: health.status = IntegrationStatus.DEGRADED health.message = "Running in simulation mode" health.last_check = datetime.utcnow() health.latency_ms = (datetime.utcnow() - start_time).total_seconds() * 1000 except Exception as e: logger.warning(f"[Integration] Lark health check failed: {e}") health.status = IntegrationStatus.ERROR health.message = f"Health check failed: {str(e)}" health.last_check = datetime.utcnow() return health async def update_config(self, config: IntegrationConfig) -> None: """更新集成配置""" self.config = config # 重新评估健康状态 await self.check_plane_health() await self.check_lark_health() logger.info("[Integration] Configuration updated") async def _get_plane_service(self): """获取 Plane 服务实例""" if self._plane_service is None: from .plane import PlaneService self._plane_service = PlaneService() # 应用配置 if self.config.plane.api_url: self._plane_service.api_url = self.config.plane.api_url if self.config.plane.api_key: self._plane_service.api_key = self.config.plane.api_key if self.config.plane.workspace_slug: self._plane_service.workspace_slug = self.config.plane.workspace_slug if self.config.plane.project_id: self._plane_service.project_id = self.config.plane.project_id self._plane_service.is_simulation = not self.config.plane.api_key return self._plane_service async def _get_lark_service(self): """获取飞书服务实例""" if self._lark_service is None: from .lark import LarkService self._lark_service = LarkService() # 应用配置 if self.config.lark.app_id: self._lark_service.app_id = self.config.lark.app_id if self.config.lark.app_secret: self._lark_service.app_secret = self.config.lark.app_secret if self.config.lark.verification_token: self._lark_service.verification_token = self.config.lark.verification_token return self._lark_service def _is_integration_available(self, integration_type: IntegrationType) -> bool: """检查集成是否可用""" health = self._health_status[integration_type] return health.status in [IntegrationStatus.ACTIVE, IntegrationStatus.DEGRADED] async def _record_success(self, integration_type: IntegrationType) -> None: """记录成功操作""" health = self._health_status[integration_type] health.last_success = datetime.utcnow() health.consecutive_failures = 0 if health.status == IntegrationStatus.ERROR: health.status = IntegrationStatus.ACTIVE async def _record_failure(self, integration_type: IntegrationType, error: str) -> None: """记录失败操作""" health = self._health_status[integration_type] health.last_error = error health.error_count += 1 health.consecutive_failures += 1 health.last_check = datetime.utcnow() # 达到降级阈值 if health.consecutive_failures >= self.MAX_CONSECUTIVE_FAILURES: health.status = IntegrationStatus.ERROR health.message = f"Service degraded after {health.consecutive_failures} consecutive failures" logger.warning(f"[Integration] {integration_type.value} degraded after {health.consecutive_failures} failures") async def _health_check_loop(self) -> None: """健康检查循环""" while True: try: await asyncio.sleep(self.HEALTH_CHECK_INTERVAL) await self.check_plane_health() await self.check_lark_health() except asyncio.CancelledError: break except Exception as e: logger.error(f"[Integration] Health check loop error: {e}") # 全局服务实例 _integration_service = None def get_integration_service(config: Optional[IntegrationConfig] = None, event_bus=None) -> IntegrationService: """获取集成服务实例""" global _integration_service if _integration_service is None: _integration_service = IntegrationService(config, event_bus) return _integration_service