whereNotNull('health_check_url') ->get(); } foreach ($services as $service) { $result = static::checkSingleService($service); $results[] = $result; // 记录监控结果 static::recordMonitorResult($service, 'health', $result); // 更新服务健康状态 $service->updateHealthStatus($result['status']); } return $results; } /** * 检查单个服务 * * @param ThirdPartyService $service * @return array */ protected static function checkSingleService(ThirdPartyService $service): array { $result = [ 'service_id' => $service->id, 'service_code' => $service->code, 'service_name' => $service->name, 'status' => ThirdPartyMonitor::STATUS_UNKNOWN, 'response_time' => null, 'status_code' => null, 'error_message' => null, 'details' => [], ]; try { // 检查服务基本状态 $healthCheck = ServiceLogic::checkServiceHealth($service); if ($healthCheck['status'] !== 'healthy') { $result['status'] = ThirdPartyMonitor::STATUS_ERROR; $result['error_message'] = $healthCheck['message']; $result['details'] = $healthCheck['details']; return $result; } // 如果有健康检查URL,执行HTTP检查 if ($service->health_check_url) { $httpResult = static::performHttpCheck($service); $result = array_merge($result, $httpResult); } else { // 没有健康检查URL,基于服务状态判断 $result['status'] = ThirdPartyMonitor::STATUS_SUCCESS; $result['details']['message'] = '服务状态正常,无HTTP健康检查'; } } catch (\Exception $e) { $result['status'] = ThirdPartyMonitor::STATUS_ERROR; $result['error_message'] = $e->getMessage(); } return $result; } /** * 执行HTTP健康检查 * * @param ThirdPartyService $service * @return array */ protected static function performHttpCheck(ThirdPartyService $service): array { $result = [ 'status' => ThirdPartyMonitor::STATUS_UNKNOWN, 'response_time' => null, 'status_code' => null, 'error_message' => null, 'details' => [], ]; $startTime = microtime(true); try { $timeout = config('thirdparty.monitoring.health_check.timeout', 10); $response = Http::timeout($timeout) ->get($service->health_check_url); $responseTime = (int)((microtime(true) - $startTime) * 1000); $result['response_time'] = $responseTime; $result['status_code'] = $response->status(); // 判断响应状态 if ($response->successful()) { $result['status'] = ThirdPartyMonitor::STATUS_SUCCESS; // 检查响应时间 $slowThreshold = config('thirdparty.monitoring.performance.slow_threshold', 2000); if ($responseTime > $slowThreshold) { $result['status'] = ThirdPartyMonitor::STATUS_WARNING; $result['details']['warning'] = "响应时间 {$responseTime}ms 超过阈值 {$slowThreshold}ms"; } } else { $result['status'] = ThirdPartyMonitor::STATUS_ERROR; $result['error_message'] = "HTTP状态码: {$response->status()}"; } $result['details']['response_body'] = $response->body(); } catch (\Illuminate\Http\Client\ConnectionException $e) { $responseTime = (int)((microtime(true) - $startTime) * 1000); $result['response_time'] = $responseTime; $result['status'] = ThirdPartyMonitor::STATUS_TIMEOUT; $result['error_message'] = '连接超时: ' . $e->getMessage(); } catch (\Exception $e) { $responseTime = (int)((microtime(true) - $startTime) * 1000); $result['response_time'] = $responseTime; $result['status'] = ThirdPartyMonitor::STATUS_ERROR; $result['error_message'] = $e->getMessage(); } return $result; } /** * 记录监控结果 * * @param ThirdPartyService $service * @param string $checkType * @param array $result * @return ThirdPartyMonitor */ public static function recordMonitorResult(ThirdPartyService $service, string $checkType, array $result): ThirdPartyMonitor { return ThirdPartyMonitor::createMonitor([ 'service_id' => $service->id, 'check_type' => $checkType, 'status' => $result['status'], 'response_time' => $result['response_time'], 'status_code' => $result['status_code'], 'error_message' => $result['error_message'], 'details' => $result['details'] ?? [], ]); } /** * 获取服务监控历史 * * @param int $serviceId * @param array $options * @return Collection */ public static function getServiceMonitorHistory(int $serviceId, array $options = []): Collection { $query = ThirdPartyMonitor::where('service_id', $serviceId); // 时间范围过滤 if (isset($options['start_date'])) { $query->where('checked_at', '>=', $options['start_date']); } if (isset($options['end_date'])) { $query->where('checked_at', '<=', $options['end_date']); } // 检查类型过滤 if (isset($options['check_type'])) { $query->where('check_type', $options['check_type']); } // 状态过滤 if (isset($options['status'])) { $query->where('status', $options['status']); } // 排序和限制 $limit = $options['limit'] ?? 100; $query->orderBy('checked_at', 'desc')->limit($limit); return $query->get(); } /** * 获取监控统计信息 * * @param int|null $serviceId * @param array $options * @return array */ public static function getMonitorStats(?int $serviceId = null, array $options = []): array { $query = ThirdPartyMonitor::query(); if ($serviceId) { $query->where('service_id', $serviceId); } // 时间范围 $startDate = $options['start_date'] ?? now()->subDays(7); $endDate = $options['end_date'] ?? now(); $query->whereBetween('checked_at', [$startDate, $endDate]); $total = $query->count(); $successful = $query->where('status', ThirdPartyMonitor::STATUS_SUCCESS)->count(); $warnings = $query->where('status', ThirdPartyMonitor::STATUS_WARNING)->count(); $errors = $query->whereIn('status', [ ThirdPartyMonitor::STATUS_ERROR, ThirdPartyMonitor::STATUS_TIMEOUT ])->count(); // 平均响应时间 $avgResponseTime = $query->whereNotNull('response_time') ->avg('response_time'); // 可用性计算 $availability = $total > 0 ? round(($successful / $total) * 100, 2) : 0; // 按状态统计 $statusStats = $query->selectRaw('status, COUNT(*) as count') ->groupBy('status') ->pluck('count', 'status') ->toArray(); // 按检查类型统计 $typeStats = $query->selectRaw('check_type, COUNT(*) as count') ->groupBy('check_type') ->pluck('count', 'check_type') ->toArray(); return [ 'total_checks' => $total, 'successful' => $successful, 'warnings' => $warnings, 'errors' => $errors, 'availability' => $availability, 'avg_response_time' => $avgResponseTime ? round($avgResponseTime, 2) : null, 'by_status' => $statusStats, 'by_type' => $typeStats, 'period' => [ 'start' => $startDate->toDateTimeString(), 'end' => $endDate->toDateTimeString(), ], ]; } /** * 获取服务可用性报告 * * @param int $serviceId * @param int $days * @return array */ public static function getAvailabilityReport(int $serviceId, int $days = 30): array { $service = ThirdPartyService::findOrFail($serviceId); $startDate = now()->subDays($days); $monitors = ThirdPartyMonitor::where('service_id', $serviceId) ->where('checked_at', '>=', $startDate) ->orderBy('checked_at') ->get(); $totalChecks = $monitors->count(); $successfulChecks = $monitors->where('status', ThirdPartyMonitor::STATUS_SUCCESS)->count(); $availability = $totalChecks > 0 ? round(($successfulChecks / $totalChecks) * 100, 2) : 0; // 按天统计 $dailyStats = $monitors->groupBy(function ($monitor) { return $monitor->checked_at->format('Y-m-d'); })->map(function ($dayMonitors) { $total = $dayMonitors->count(); $successful = $dayMonitors->where('status', ThirdPartyMonitor::STATUS_SUCCESS)->count(); $avgResponseTime = $dayMonitors->whereNotNull('response_time')->avg('response_time'); return [ 'total_checks' => $total, 'successful_checks' => $successful, 'availability' => $total > 0 ? round(($successful / $total) * 100, 2) : 0, 'avg_response_time' => $avgResponseTime ? round($avgResponseTime, 2) : null, ]; }); // 故障时间段 $downtimes = []; $currentDowntime = null; foreach ($monitors as $monitor) { if ($monitor->status !== ThirdPartyMonitor::STATUS_SUCCESS) { if (!$currentDowntime) { $currentDowntime = [ 'start' => $monitor->checked_at, 'end' => $monitor->checked_at, 'duration' => 0, 'reason' => $monitor->error_message, ]; } else { $currentDowntime['end'] = $monitor->checked_at; } } else { if ($currentDowntime) { $currentDowntime['duration'] = $currentDowntime['end']->diffInMinutes($currentDowntime['start']); $downtimes[] = $currentDowntime; $currentDowntime = null; } } } // 如果最后还有未结束的故障 if ($currentDowntime) { $currentDowntime['end'] = now(); $currentDowntime['duration'] = $currentDowntime['end']->diffInMinutes($currentDowntime['start']); $downtimes[] = $currentDowntime; } return [ 'service' => [ 'id' => $service->id, 'name' => $service->name, 'code' => $service->code, ], 'period' => [ 'days' => $days, 'start' => $startDate->toDateTimeString(), 'end' => now()->toDateTimeString(), ], 'summary' => [ 'total_checks' => $totalChecks, 'successful_checks' => $successfulChecks, 'availability' => $availability, 'downtime_count' => count($downtimes), 'total_downtime_minutes' => array_sum(array_column($downtimes, 'duration')), ], 'daily_stats' => $dailyStats, 'downtimes' => $downtimes, ]; } /** * 清理旧的监控记录 * * @param int $days * @return int */ public static function cleanupOldMonitorRecords(int $days = 90): int { return ThirdPartyMonitor::where('checked_at', '<', now()->subDays($days))->delete(); } /** * 获取需要关注的服务 * * @return array */ public static function getServicesNeedingAttention(): array { $services = ThirdPartyService::where('status', 'ACTIVE')->get(); $needingAttention = []; foreach ($services as $service) { $issues = []; // 检查最近的监控记录 $recentMonitor = ThirdPartyMonitor::where('service_id', $service->id) ->orderBy('checked_at', 'desc') ->first(); if (!$recentMonitor) { $issues[] = '缺少监控数据'; } elseif ($recentMonitor->status !== ThirdPartyMonitor::STATUS_SUCCESS) { $issues[] = '最近检查失败: ' . $recentMonitor->error_message; } // 检查健康检查间隔 if ($service->needsHealthCheck()) { $issues[] = '需要健康检查'; } // 检查凭证状态 $credential = $service->getActiveCredential(); if (!$credential) { $issues[] = '缺少活跃凭证'; } elseif ($credential->isExpiringSoon()) { $issues[] = '凭证即将过期'; } if (!empty($issues)) { $needingAttention[] = [ 'service' => $service, 'issues' => $issues, ]; } } return $needingAttention; } }