1、国内主流企业即时通讯

  • 企业微信:腾讯出品,和微信互通;聊天、审批、打卡、会议、客户联系(客户朋友圈)、应用接入;支持私有化部署。适合绝大多数国内大中小微企业,客户运营场景优势明显。
  • 钉钉:阿里,强 OA 工作流:审批、考勤、钉盘、钉会议、宜搭低代码;功能重管理。制造业、传统企业、行政流程多的企业使用广泛。
  • 飞书: 字节跳动,文档深度集成,会议、多维表格、知识库,适合知识型团队、互联网、研发团队。

        本文主要讲解用户可以通过 REST API 或钉钉机器人消息触发 MR 的批准、拒绝、关闭以及创建 Tag 等操作。

2、流程图

  1. 研发人员提出合并请求url,将合并请求发给审核者。
  2. 审核者通过钉钉发送"同意合并url",即可合并程功。

3、实现代码

依赖

<dependency>
    <groupId>org.gitlab4j</groupId>
    <artifactId>gitlab4j-api</artifactId>
    <version>${gitlab4j-api.version}</version>
</dependency>
<dependency>
    <groupId>com.dingtalk.open</groupId>
    <artifactId>dingtalk-stream</artifactId>
    <version>${dingtalk-stream.version}</version>
</dependency>

应用配置(application.yaml

audit:
  gitlab:
    base-url: http://127.0.0.1:8090  # GitLab 服务器地址
    token: glpat-xxxxxxxxxxxx               # GitLab Personal Access Token
ding:
  talk:
    robot:
      client-id: dingxxxxxxxxxxxx       # 钉钉机器人 ClientID
      client-secret: xxxxxxxxxxxxxxxx    # 钉钉机器人 ClientSecret

钉钉监听核心代码

/**
 * 启动 Stream 客户端,监听机器人回调消息
 */
@PostConstruct
public void start() {
    if (!properties.isEnabled()) {
        log.info("钉钉机器人 Stream 监听已禁用 (dingtalk.robot.enabled=false)");
        return;
    }
    streamThread = new Thread(this::doStartStreamClient, "dingtalk-robot-stream");
    // 设置为守护线程,避免应用退出时 Stream 客户端未正常关闭
    streamThread.setDaemon(true);
    streamThread.start();
    log.info("钉钉机器人 Stream 客户端已启动, topic={}", properties.getTopic());
}

/**
 * 启动 Stream 客户端,监听机器人回调消息
 */
private void doStartStreamClient() {
    try {
        streamClient = OpenDingTalkStreamClientBuilder
                .custom()
                .credential(new AuthClientCredential(properties.getClientId(), properties.getClientSecret()))
                .registerCallbackListener(properties.getTopic(), (ChatbotMessage message) -> {
                    //1、 处理消息,开发者根据自身业务需求,处理机器人回调
                    String result = handleMessage(message);
                    //2、回复消息
                    reply(message, result);
                    return new JSONObject();
                })
                .build();
        // start() 为阻塞方法,需在独立线程中执行
        streamClient.start();
    } catch (Exception e) {
        log.error("钉钉机器人 Stream 客户端异常", e);
    }
}

/**
 * 处理消息
 *
 * @param message 收到消息
 * @methodName: handleMessage
 * @return: java.lang.String
 * @author: ybw
 * @date: 2026/7/28
 **/
private String handleMessage(ChatbotMessage message) {
    //1、 获取钉钉消息内容
    String content = message.getText().getContent();
    //2、判断是否有"同意合并"关键字
    if (!content.contains("同意合并")) {
        //2.1 不包含
        return "没有同意合并关键字";
    }
    //2.2 包含
    //2.2.1 获取合并链接地址
    String mergeRequestUrl = MyStrUtil.getMergeRequestUrl(content);
    log.info("合并请求地址:{}", mergeRequestUrl);
    if (StrUtil.isBlank(mergeRequestUrl)) {
        return "无合并请求链接地址";
    }
    //2.2.2 发送合并请求
    ApproveByUrlRequest request = new ApproveByUrlRequest(mergeRequestUrl);
    AuditResult auditResult = auditService.approveMrByUrl(request);
    return auditResult.getMessage();
}

/**
 * 回复消息
 *
 * @param message 收到消息
 * @param result  处理结果
 * @methodName: reply
 * @return: void
 * @author: ybw
 * @date: 2026/7/28
 **/
private void reply(ChatbotMessage message, String result) {
    try {
        //1、 使用 sessionWebhook 回复消息
        String sessionWebhook = message.getSessionWebhook();
        if (StrUtil.isBlank(sessionWebhook)) {
            return;
        }
        //2、回复消息,使用 sessionWebhook 回复消息
        BotReplier replier = new BotReplier(sessionWebhook);
        replier.replyText("处理结果:" + result);
        log.info("已通过 sessionWebhook 回复消息");
    } catch (Exception e) {
        log.error("reply error", e);
    }
}

/**
 * 停止 Stream 客户端
 */
@PreDestroy
public void stop() {
    if (streamClient != null) {
        try {
            streamClient.stop();
            log.info("钉钉机器人 Stream 客户端已关闭");
        } catch (Exception e) {
            log.error("关闭 Stream 客户端失败", e);
        }
    }
    if (streamThread != null && streamThread.isAlive()) {
        streamThread.interrupt();
    }
}

审核核心代码

/**
 * 通过 MR 链接地址批准 MR
 */
public AuditResult approveMrByUrl(ApproveByUrlRequest request) {
    // 1. 解析 URL,提取项目路径和 MR IID
    GitLabApiClient.MrUrlInfo urlInfo = gitLabApiClient.parseMrUrl(request.getMrUrl());
    log.info("解析 MR URL: projectPath={}, mrIid={}", urlInfo.projectPath(), urlInfo.mrIid());

    // 2. 将项目路径解析为项目 ID
    Long projectId = gitLabApiClient.resolveProjectId(urlInfo.projectPath());

    // 3. 委托给现有的 approveMr 逻辑
    ApproveRequest approveRequest = new ApproveRequest();
    approveRequest.setProjectId(projectId);
    approveRequest.setMrIid(urlInfo.mrIid());
    return approveMr(approveRequest);
}

4、使用

  • 钉钉用户发送:同意合并:http://172.168.0.2/test/git-audit/-/merge_requests/1
  • 钉钉返回消息:处理结果:MR 已批准并合并。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐