jenkinsClient->isConfigured()) { Log::warning('Jenkins client is not configured, skipping monitor'); return ['skipped' => true, 'reason' => 'Jenkins not configured']; } $projects = Project::getJenkinsNotifyEnabled(); $results = []; foreach ($projects as $project) { $results[$project->slug] = $this->checkProject($project); } return $results; } public function checkProject(Project $project): array { if (empty($project->jenkins_job_name)) { return ['skipped' => true, 'reason' => 'No Jenkins job configured']; } $jobName = $project->jenkins_job_name; $lastNotifiedBuild = $project->jenkins_last_notified_build ?? 0; $allowedTriggers = $this->getAllowedTriggers(); $builds = $this->jenkinsClient->getBuilds($jobName, 5); $newBuilds = []; foreach ($builds as $build) { // 只处理已完成的构建 if ($build['building'] ?? true) { continue; } $buildNumber = $build['number']; // 跳过已通知的构建 if ($buildNumber <= $lastNotifiedBuild) { continue; } // 检查是否已存在记录 $exists = JenkinsDeployment::where('job_name', $jobName) ->where('build_number', $buildNumber) ->exists(); if ($exists) { continue; } // 解析构建信息 $triggeredBy = $this->extractTriggeredBy($build); $branch = $this->extractBranch($build); $commitSha = $this->extractCommitSha($build); $buildParams = $this->extractBuildParams($build); // 过滤触发者(只通知指定用户触发的构建) // 如果没有配置允许的触发者列表,则跳过所有通知 if (empty($allowedTriggers)) { Log::info('Skipping build - no allowed triggers configured', [ 'job' => $jobName, 'build' => $buildNumber, 'triggered_by' => $triggeredBy, ]); continue; } // 检查触发者是否在允许列表中 if (!$this->isAllowedTrigger($triggeredBy, $allowedTriggers)) { Log::info('Skipping build due to trigger filter', [ 'job' => $jobName, 'build' => $buildNumber, 'triggered_by' => $triggeredBy, ]); continue; } // 保存发布记录 $deployment = JenkinsDeployment::create([ 'project_id' => $project->id, 'build_number' => $buildNumber, 'job_name' => $jobName, 'status' => $build['result'] ?? 'UNKNOWN', 'branch' => $branch, 'commit_sha' => $commitSha, 'triggered_by' => $triggeredBy, 'duration' => $build['duration'] ?? null, 'build_url' => $build['url'] ?? null, 'raw_data' => $build, 'build_params' => $buildParams, 'notified' => false, ]); // 发送通知 $this->sendNotification($project, $deployment); $deployment->update(['notified' => true]); $newBuilds[] = $buildNumber; } // 更新最后通知的构建号 if (!empty($newBuilds)) { $project->update([ 'jenkins_last_notified_build' => max($newBuilds), ]); } return [ 'job' => $jobName, 'new_builds' => $newBuilds, ]; } private function sendNotification(Project $project, JenkinsDeployment $deployment): void { $lines = []; $lines[] = sprintf( "%s 【Jenkins 发布通知】", $deployment->getStatusEmoji() ); $lines[] = sprintf("项目: %s", $project->name); $lines[] = sprintf("状态: %s", $deployment->getStatusLabel()); $lines[] = sprintf("构建号: #%d", $deployment->build_number); $lines[] = sprintf("触发者: %s", $deployment->triggered_by ?? '-'); $lines[] = sprintf("耗时: %s", $deployment->getFormattedDuration()); // 添加构建参数 if (!empty($deployment->build_params)) { $lines[] = "\n构建参数:"; foreach ($deployment->build_params as $key => $value) { // 格式化参数值 if (is_bool($value)) { $value = $value ? 'true' : 'false'; } elseif (is_array($value)) { $value = json_encode($value, JSON_UNESCAPED_UNICODE); } elseif ($value === null || $value === '') { continue; // 跳过空值 } $lines[] = sprintf(" %s: %s", $key, $value); } } $lines[] = sprintf("\n详情: %s", $deployment->build_url ?? '-'); $message = implode("\n", $lines); $this->dingTalkService->sendText($message); } private function extractTriggeredBy(array $build): ?string { $actions = $build['actions'] ?? []; foreach ($actions as $action) { // UserIdCause - 用户手动触发 if (isset($action['causes'])) { foreach ($action['causes'] as $cause) { if (isset($cause['userId'])) { return $cause['userId']; } if (isset($cause['userName'])) { return $cause['userName']; } } } } return null; } private function extractBranch(array $build): ?string { $actions = $build['actions'] ?? []; // 优先从参数中获取 branchName foreach ($actions as $action) { if (isset($action['parameters'])) { foreach ($action['parameters'] as $param) { if (in_array($param['name'] ?? '', ['branchName', 'BRANCH', 'branch', 'GIT_BRANCH', 'BRANCH_NAME'])) { $value = $param['value'] ?? null; if (!empty($value)) { return $value; } } } } } // 如果参数中没有,再从 Git 分支信息中获取 foreach ($actions as $action) { if (isset($action['lastBuiltRevision']['branch'])) { foreach ($action['lastBuiltRevision']['branch'] as $branch) { $name = $branch['name'] ?? ''; // 移除 origin/ 和 refs/remotes/origin/ 前缀 return preg_replace('/^(refs\/remotes\/origin\/|origin\/)/', '', $name); } } } return null; } private function extractCommitSha(array $build): ?string { $actions = $build['actions'] ?? []; foreach ($actions as $action) { if (isset($action['lastBuiltRevision']['SHA1'])) { return substr($action['lastBuiltRevision']['SHA1'], 0, 8); } } return null; } private function getAllowedTriggers(): array { $config = $this->configService->get('jenkins_allowed_triggers', []); // 如果配置为空,返回空数组 if (empty($config)) { return []; } // 如果配置是数组,直接返回(过滤空值) if (is_array($config)) { return array_filter(array_map('trim', $config)); } // 如果配置是字符串(兼容旧格式),按逗号分隔 if (is_string($config)) { return array_filter(array_map('trim', explode(',', $config))); } return []; } private function isAllowedTrigger(?string $triggeredBy, array $allowedTriggers): bool { if (empty($triggeredBy)) { return false; } foreach ($allowedTriggers as $allowed) { if (strcasecmp($triggeredBy, $allowed) === 0) { return true; } } return false; } private function extractBuildParams(array $build): array { $actions = $build['actions'] ?? []; foreach ($actions as $action) { // 查找 ParametersAction if (isset($action['_class']) && $action['_class'] === 'hudson.model.ParametersAction') { if (isset($action['parameters']) && is_array($action['parameters'])) { $params = []; foreach ($action['parameters'] as $param) { $name = $param['name'] ?? null; $value = $param['value'] ?? null; if ($name !== null) { $params[$name] = $value; } } return $params; } } } return []; } }