""" 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 from ..utils.timezone import now_cn_naive 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=now_cn_naive(), ), IntegrationType.LARK: IntegrationHealth( type=IntegrationType.LARK, status=IntegrationStatus.PENDING, last_check=now_cn_naive(), ), } # 统计信息 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 = now_cn_naive() 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 = (now_cn_naive() - 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 = now_cn_naive() 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 = (now_cn_naive() - 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 = now_cn_naive() 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 = now_cn_naive() 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 = now_cn_naive() health.latency_ms = (now_cn_naive() - 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 = now_cn_naive() return health async def check_lark_health(self) -> IntegrationHealth: """检查飞书集成健康""" integration_type = IntegrationType.LARK health = self._health_status[integration_type] start_time = now_cn_naive() 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 = now_cn_naive() 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 = now_cn_naive() health.latency_ms = (now_cn_naive() - 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 = now_cn_naive() 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 = now_cn_naive() 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 = now_cn_naive() # 达到降级阈值 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