import db from "../../db" import service from "../../service" import netTool from "../../service/netTool" import { EggMessage } from "../../types/eggMessage" import { Gitlab } from "../../types/gitlab" import { sec2minStr } from "../../utils/timeTools" /** * 判断是否是合并请求 * @param {Gitlab.PipelineEvent} pipeline - GitLab 流水线事件对象 * @returns {boolean} - 如果提交信息符合合并请求的格式,返回 true;否则返回 false */ const checkIsMergeCommit = (pipeline: Gitlab.PipelineEvent): boolean => { const regex = /^Merge branch '.*' into '.*'$/ return regex.test(pipeline.commit.title) } enum NEXT_ACTION { SKIP, NOTIFY, ADD_MONITOR, REMOVE_MONITOR, NOTIFY_AND_REMOVE_MONITOR, } /** * 获取下一步操作 * @param {Gitlab.PipelineEvent} pipeline - GitLab 流水线事件对象 * @param {string | null} [targetStage] - 目标阶段,可选 * @returns {Promise} - 返回下一步操作的枚举值 */ const getNextAction = async ( pipeline: Gitlab.PipelineEvent, targetStage?: string | null ): Promise => { // 没有指定Stage则整个流水线成功即为成功 if (!targetStage) { if (pipeline.object_attributes.status === "success") { return NEXT_ACTION.NOTIFY } return NEXT_ACTION.SKIP } // 指定了stage,但是流水线非成功状态,删除监控 if ( ["failed", "canceled", "skipped"].includes( pipeline.object_attributes.status ) ) { return NEXT_ACTION.REMOVE_MONITOR } // 指定了Stage,且流水线成功了,删除监控并发送通知 if (pipeline.object_attributes.status === "success") { return NEXT_ACTION.NOTIFY_AND_REMOVE_MONITOR } // 在流水线为`running`时,且该stage全部的job有非结束状态时即`created`、`pending`、`running`、`manual`、`scheduled`时,添加监控 if (pipeline.object_attributes.status === "running") { const jobs = await service.gitlab.pipeline.getJobs( pipeline.project.id, pipeline.object_attributes.id ) if ( jobs.some( (job) => job.stage === targetStage && !["success", "failed", "canceled", "skipped"].includes(job.status) ) ) { return NEXT_ACTION.ADD_MONITOR } } // 其他情况都跳过 return NEXT_ACTION.SKIP } /** * 获取合并请求 * @param {Gitlab.PipelineEvent} pipeline - GitLab 流水线事件对象 * @returns {Promise} - 返回合并请求对象或 null */ const getMergeRequest = async ( pipeline: Gitlab.PipelineEvent ): Promise => { if (!checkIsMergeCommit(pipeline)) return null const res = await service.gitlab.commit.getMr( pipeline.project.id, pipeline.object_attributes.sha ) if (res.length === 0) return null return res.find((mr) => mr.merge_commit_sha === pipeline.commit.id) || null } /** * 获取用户信息 * @param {Gitlab.PipelineEvent} pipeline - GitLab 流水线事件对象 * @param {Gitlab.MergeRequest | null} mergeRequest - 合并请求对象或 null * @returns {{ participant: string, receiver: string[] }} - 返回包含参与者和接收者信息的对象 */ const getUserInfo = ( pipeline: Gitlab.PipelineEvent, mergeRequest: Gitlab.MergeRequest | null ): { participant: string; receiver: string[] } => { let participant = pipeline.user.name const receiver = [pipeline.user.username] // 有MR且用户不同 if (mergeRequest && mergeRequest.author.username !== pipeline.user.username) { participant += `、${mergeRequest.author.name}` receiver.push(mergeRequest.author.username) } return { participant, receiver } } /** * 生成消息模板变量 * @param {Gitlab.PipelineEvent} pipeline - GitLab 流水线事件对象 * @returns {Promise<{ receiver: string[], variable: EggMessage.CardVariable }>} - 返回包含接收者和模板变量的对象 */ const genCardVariable = async ( pipeline: Gitlab.PipelineEvent ): Promise<{ receiver: string[] variable: EggMessage.CardVariable }> => { // 获取对应的合并请求 const mergeRequest = await getMergeRequest(pipeline) // 获取用户信息 const { participant, receiver } = getUserInfo(pipeline, mergeRequest) // sonar的ID const sonarParams = new URLSearchParams({ id: pipeline.project.path_with_namespace.replaceAll("/", ":"), branch: pipeline.object_attributes.ref, }) // 获取模板变量 const variable: EggMessage.CardVariable = { project: pipeline.project.path_with_namespace, project_link: pipeline.project.web_url, pipeline: pipeline.object_attributes.id.toString(), pipeline_link: `${pipeline.project.web_url}/-/pipelines`, ref: pipeline.object_attributes.ref, ref_link: `${pipeline.project.web_url}/-/commits/${pipeline.object_attributes.ref}`, commit_user: pipeline.user.name, duration: sec2minStr(pipeline.object_attributes.duration), participant, commit_message: pipeline.commit.title, mr: mergeRequest ? mergeRequest.references.full : "无关联的MR", mr_link: mergeRequest ? mergeRequest.web_url : "", sonar_link: `https://sonarqube.mioffice.cn/dashboard?${sonarParams}`, } return { receiver, variable, } } /** * 生成机器人消息 * @param {EggMessage.CardVariable} variable - 模板变量 * @returns {string} - 返回生成的机器人消息内容 */ const genLarkRobotMsgContent = (variable: EggMessage.CardVariable): string => JSON.stringify({ type: "template", data: { config: { update_multi: true, }, template_id: "ctp_AA36QafWyob2", template_variable: variable, }, }) /** * 发送提醒消息 * @param {Gitlab.PipelineEvent} pipeline - 流水线信息 * @param {string} apiKey - API 密钥 * @returns {Promise} - 无返回值 */ const sendNotify = async ( pipeline: Gitlab.PipelineEvent, apiKey: string ): Promise => { // 获取消息信息 const { receiver, variable } = await genCardVariable(pipeline) // 获取机器人消息 const robotMsg = genLarkRobotMsgContent(variable) // 发送消息 service.message.byUserIdList(receiver, robotMsg, apiKey) // 记录日志 await db.notify.create({ ...variable }) } /** * 添加监控 * @param {Gitlab.PipelineEvent} pipeline - 流水线信息 * @param {string} apiKey - API 密钥 * @param {string} stage - 阶段名称 * @returns {Promise} - 无返回值 */ const addMonitor = async ( pipeline: Gitlab.PipelineEvent, apiKey: string, stage: string ): Promise => { const monitor = await db.monitor.getOne( pipeline.project.id.toString(), pipeline.object_attributes.id.toString(), stage, apiKey ) if (monitor) return // 获取消息信息 const { receiver, variable } = await genCardVariable(pipeline) await db.monitor.create({ project_id: pipeline.project.id.toString(), pipeline_id: pipeline.object_attributes.id.toString(), stage, api_key: apiKey, receiver, variable, }) } /** * 移除监控 * @param {Gitlab.PipelineEvent} pipeline - 流水线信息 * @param {string} apiKey - API 密钥 * @param {string} stage - 阶段名称 * @returns {Promise} - 无返回值 */ const removeMonitor = async ( pipeline: Gitlab.PipelineEvent, apiKey: string, stage: string ): Promise => { const monitor = await db.monitor.getOne( pipeline.project.id.toString(), pipeline.object_attributes.id.toString(), stage, apiKey ) if (!monitor) return await db.monitor.del(monitor.id) } /** * 移除监控并发送提醒消息 * @param {Gitlab.PipelineEvent} pipeline - 流水线信息 * @param {string} apiKey - API 密钥 * @param {string} stage - 阶段名称 * @returns {Promise} - 无返回值 */ const removeMonitorAndNotify = async ( pipeline: Gitlab.PipelineEvent, apiKey: string, stage: string ): Promise => { const monitor = await db.monitor.getOne( pipeline.project.id.toString(), pipeline.object_attributes.id.toString(), stage, apiKey ) if (!monitor) return db.monitor.del(monitor.id) sendNotify(pipeline, apiKey) } /** * 发送通知消息 * @param {Gitlab.PipelineEvent} pipeline - GitLab 流水线事件对象 * @param {string} apiKey - API 密钥 * @param {URLSearchParams} params - URL 查询参数 * @returns {Promise} - 返回操作结果 */ const manageRawEvent = async ( pipeline: Gitlab.PipelineEvent, apiKey: string, params: URLSearchParams ): Promise => { // 获取Stage参数 const stage = params.get("stage") // 获取下一步操作 const action = await getNextAction(pipeline, stage) // 发送通知 if (action === NEXT_ACTION.NOTIFY) { sendNotify(pipeline, apiKey) } // 添加监控 if (action === NEXT_ACTION.ADD_MONITOR) { addMonitor(pipeline, apiKey, stage!) } // 删除监控 if (action === NEXT_ACTION.REMOVE_MONITOR) { removeMonitor(pipeline, apiKey, stage!) } // 删除监控并发送通知 if (action === NEXT_ACTION.NOTIFY_AND_REMOVE_MONITOR) { removeMonitorAndNotify(pipeline, apiKey, stage!) } // 返回成功 return netTool.ok() } /** * 管理流水线事件 */ const managePipelineEvent = { manageRawEvent, genLarkRobotMsgContent, } export default managePipelineEvent