Java 使用 GitLab4J 实现 WebHook
2026/9/16 11:32:16 网站建设 项目流程

1、SpringBoot 接收 GitLab WebHook(核心)

GitLab 会 POST 请求你的服务接口,把事件 JSON 推过来;gitlab4j 提供GitLabWebHook工具类,直接解析各种事件对象,不用手写 JSON 解析。

GitLab WebHook 请求头:X-GitLab-Token,用于校验请求来源合法性。

2、代码实现

引入依赖

github地址:https://github.com/gitlab4j/gitlab4j-api

<dependency> <groupId>org.gitlab4j</groupId> <artifactId>gitlab4j-api</artifactId> <version>${gitlab4j-api.version}</version> </dependency>

配置信息

application.yaml配置

audit: gitlab: base-url: http://127.0.0.1:21031 # gitlab 服务器地址 token: 'aaa-bbb-ccc-' # gitlab 访问 token webhook-secret: 'fffffffffffffffffffwa1901111' # webhook 密钥

配置类

package com.ybw.config; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "audit") public record AuditProperties( GitLab gitlab ) { public record GitLab(String baseUrl, String token, String webhookSecret) { } }
package com.ybw.config; import org.gitlab4j.api.GitLabApi; import org.gitlab4j.api.WebHookManager; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; /** * GitLabApi Bean 配置 * 使用 gitlab4j-api 框架替代原有的 RestClient 手工 HTTP 调用 */ @Configuration @EnableConfigurationProperties(AuditProperties.class) public class GitLabApiConfig { /** * 连接超时(毫秒) */ private static final int CONNECT_TIMEOUT_MS = 10_000; /** * 读取超时(毫秒),对于返回大 diff 的 MR changes 接口,需要较长超时 */ private static final int READ_TIMEOUT_MS = 120_000; @Bean public GitLabApi gitLabApi(AuditProperties auditProperties) { GitLabApi gitLabApi = new GitLabApi(auditProperties.gitlab().baseUrl(), auditProperties.gitlab().token()); gitLabApi.withRequestTimeout(CONNECT_TIMEOUT_MS, READ_TIMEOUT_MS); return gitLabApi; } /** * WebHookManager 用于解析 GitLab webhook 请求 * 自动根据 X-Gitlab-Event 头将 JSON 反序列化为对应的事件对象 * * @param auditProperties 审计配置(含 webhook secret token) */ @Bean public WebHookManager webHookManager(AuditProperties auditProperties) { String secret = auditProperties.gitlab().webhookSecret(); if (secret != null && !secret.isEmpty()) { return new WebHookManager(secret); } return new WebHookManager(); } }

Controller 接收接口

package com.ybw.controller; import com.alibaba.fastjson2.JSON; import com.ybw.service.AuditService; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.gitlab4j.api.WebHookManager; import org.gitlab4j.api.webhook.Event; import org.gitlab4j.api.webhook.MergeRequestEvent; import org.gitlab4j.api.webhook.PushEvent; import org.gitlab4j.api.webhook.NoteEvent; import org.gitlab4j.api.webhook.IssueEvent; import org.gitlab4j.api.webhook.TagPushEvent; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * GitLab Webhook 事件处理 * <p> * 使用 gitlab4j 的 WebHookManager 自动根据 X-Gitlab-Event 请求头 * 将 JSON 反序列化为对应的事件对象(MergeRequestEvent、PushEvent 等), * 不同事件类型对应不同的实体类。 * * @author ybw * @version V1.0 * @className WebhookController * @date 2026/8/7 **/ @Slf4j @RestController @RequestMapping("/api/webhook") @RequiredArgsConstructor public class WebhookController { private final AuditService auditService; private final WebHookManager webHookManager; /** * 接收 GitLab webhook 请求,自动按事件类型分发 */ @PostMapping("/gitlab") public ResponseEntity<String> handleGitlabWebhook(HttpServletRequest request) { // WebHookManager 自动做两件事: // 1. 校验 X-Gitlab-Token(如果配置了 secretToken) // 2. 根据 X-Gitlab-Event 头将 body 反序列化为对应的事件子类 Event event; try { event = webHookManager.handleRequest(request); } catch (Exception e) { log.error("Webhook 请求处理异常", e); return ResponseEntity.badRequest().body("Invalid webhook request: " + e.getMessage()); } if (event == null) { log.warn("WebHookManager 返回了 null 事件"); return ResponseEntity.badRequest().body("Unable to parse webhook event"); } log.info("接收到 Webhook 事件: eventType={}", event.getClass().getSimpleName()); // 按事件类型分发,不同类型使用不同对象 if (event instanceof MergeRequestEvent mrEvent) { return handleMergeRequest(mrEvent); } else if (event instanceof PushEvent) { log.debug("Push 事件暂不处理"); return ResponseEntity.ok("Push event received (not processed)"); } else if (event instanceof NoteEvent) { log.debug("Note 事件暂不处理"); return ResponseEntity.ok("Note event received (not processed)"); } else if (event instanceof IssueEvent) { log.debug("Issue 事件暂不处理"); return ResponseEntity.ok("Issue event received (not processed)"); } else if (event instanceof TagPushEvent) { log.debug("Tag Push 事件暂不处理"); return ResponseEntity.ok("Tag Push event received (not processed)"); } else { log.debug("忽略不支持的事件类型: {}", event.getClass().getSimpleName()); return ResponseEntity.ok("Ignored: unsupported event type"); } } /** * 处理 Merge Request 事件 */ private ResponseEntity<String> handleMergeRequest(MergeRequestEvent event) { log.info("handleMergeRequest event:{}", JSON.toJSONString(event)); var attrs = event.getObjectAttributes(); log.info("Merge Request 事件: projectId={}, mrIid={}, action={}", event.getProject() != null ? event.getProject().getId() : null, attrs != null ? attrs.getIid() : null, attrs != null ? attrs.getAction() : "unknown"); String action = attrs != null ? attrs.getAction() : "unknown"; if (!"open".equals(action) && !"reopen".equals(action)) { log.debug("忽略非 open/reopen 的 MR 事件: action={}", action); return ResponseEntity.ok("Ignored MR action: " + action); } auditService.processMrWebhook(event); return ResponseEntity.ok("OK"); } }

合并打印日志

{ "object_kind": "merge_request", "changes": {}, "eventType": "merge_request", "labels": [], "objectAttributes": { "action": "update", "assigneeIds": [], "authorId": 4, "blockingDiscussionsResolved": true, "createdAt": "2026-08-08 11:29:37", "description": "", "detailedMergeStatus": "unchecked", "firstContribution": false, "headPipelineId": 200, "id": 4301, "iid": 2, "labels": [], "lastCommit": { "author": { "email": "test@aaa.com", "name": "test" }, "id": "daca0e0c40ab04c62a15d15315389b8a7dc12ef5", "message": "edit\n", "timestamp": "2026-08-08 11:42:16", "title": "edit", "url": "http://gitlab.example.com/test/git-audit/-/commit/daca0e0c40ab04c62a15d15315389b8a7dc12ef5" }, "mergeParams": { "force_remove_source_branch": "1" }, "mergeStatus": "unchecked", "mergeWhenPipelineSucceeds": false, "oldrev": "b1d98ac6079962de9760b009a21d3965827223f0", "reviewerIds": [], "source": { "defaultBranch": "main", "gitHttpUrl": "http://gitlab.example.com/test/git-audit.git", "gitSshUrl": "git@gitlab.example.com:test/git-audit.git", "homepage": "http://gitlab.example.com/test/git-audit", "httpUrl": "http://gitlab.example.com/test/git-audit.git", "id": 505, "name": "git-audit", "namespace": "test", "pathWithNamespace": "test/git-audit", "sshUrl": "git@gitlab.example.com:test/git-audit.git", "url": "git@gitlab.example.com:test/git-audit.git", "visibilityLevel": 0, "webUrl": "http://gitlab.example.com/test/git-audit" }, "sourceBranch": "dev", "sourceProjectId": 505, "state": "opened", "stateId": 1, "target": { "defaultBranch": "main", "gitHttpUrl": "http://gitlab.example.com/test/git-audit.git", "gitSshUrl": "git@gitlab.example.com:test/git-audit.git", "homepage": "http://gitlab.example.com/test/git-audit", "httpUrl": "http://gitlab.example.com/test/git-audit.git", "id": 505, "name": "git-audit", "namespace": "test", "pathWithNamespace": "test/git-audit", "sshUrl": "git@gitlab.example.com:test/git-audit.git", "url": "git@gitlab.example.com:test/git-audit.git", "visibilityLevel": 0, "webUrl": "http://gitlab.example.com/test/git-audit" }, "targetBranch": "main", "targetProjectId": 505, "timeChange": 0, "timeEstimate": 0, "title": "Dev", "totalTimeSpent": 0, "updatedAt": "2026-08-08 11:41:33", "url": "http://gitlab.example.com/test/git-audit/-/merge_requests/2", "workInProgress": false }, "objectKind": "merge_request", "project": { "defaultBranch": "main", "gitHttpUrl": "http://gitlab.example.com/test/git-audit.git", "gitSshUrl": "git@gitlab.example.com:test/git-audit.git", "homepage": "http://gitlab.example.com/test/git-audit", "httpUrl": "http://gitlab.example.com/test/git-audit.git", "id": 505, "name": "git-audit", "namespace": "test", "pathWithNamespace": "test/git-audit", "sshUrl": "git@gitlab.example.com:test/git-audit.git", "url": "git@gitlab.example.com:test/git-audit.git", "visibilityLevel": 0, "webUrl": "http://gitlab.example.com/test/git-audit" }, "repository": { "homepage": "http://gitlab.example.com/test/git-audit", "name": "git-audit", "url": "git@gitlab.example.com:test/git-audit.git" }, "user": { "avatarUrl": "https://www.gravatar.com/avatar/077105529dfd283d0b36419f639754f51bc953a1a6af6f7119a2818edbd046b6?s=80&d=identicon", "email": "[REDACTED]", "id": 4, "name": "audit-test", "username": "audit-test" } }

3、gitlab相关信息

3.1 获取token

编辑个人资料-》Access Token-》添加新令牌

3.2 gitlab webhooks配置

  1. gitlab->设置->Webhooks->添加新的webhook。
  2. Secret令牌和配置信息中的webhook-secret相同。
  3. 勾选需要触发的事件:合并请求事件等
  4. 添加 webhook,点击【测试】发送测试请求验证接口。

4、常见事件类型

事件类说明
PushEvent代码 push 提交
MergeRequestEvent合并请求 MR
TagPushEventTag 推送
IssueEventIssue 事件
NoteEvent评论事件
PipelineEventCI 流水线事件
JobEventCI Job 任务事件

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询