本文主要介紹如何使用gitlab的webhook來打通企業微信消息提醒。
前提准備
企業微信消息發送接口
根據企業微信開發者文檔得到一個消息發送的接口url,參照:企業微信群機器人配置說明;
gitlab(賬號,用戶組,項目)
-
生成gitlab賬號token


-
獲取項目的project_id
參考gitlab如何查詢項目ID -
獲取用戶組的group_id
方法類似於上面project_id的獲取
gitlab開放API文檔
webhook配置和開發
配置webhook

Secret Token和Enable SSL verification配置項可以先不配置。
在這里配置wenhook,我這里先配置兩個觸發事件,Tag push events(tag新增/刪除事件)和Merge request events(MR新增/刪除事件)。
gitlab的webhook原理
上面的配置中有一個URL配置項還沒有配置。
想知道這里應該配什么,首先應該了解gitlab的webhook工作原理。
這里還是以發送通知到企業微信為例。

- 項目代碼變動往gitlab上推送相應的事件,例如代碼push,新建tag,創建merge request等等;
- gitlab收到相應事件,觸發對應的webhook,設置HTTP請求的header以及request body,然后發送HTTP請求到配置的webhook的URL;
- HTTP請求到達對應的處理服務器以后,對request body和header進行解析,包裝通知內容;
- 將通知的內容通過企業微信的消息發送接口發送到企業微信;
具體參考webhook使用指南

接下來所有的重點就是這個URL是什么?他應該是一個接口,用來處理gitlab的事件。
項目實戰
前提准備做好之后,就可以開發處理事件的服務端了(基於SpringBoot項目)。
以下是一些核心代碼。
GitLabApiUtils.java
/**
* 獲取所以項目master成員
* @param projectId
* @return
*/
public static List<String> getAllProjectMembers(Integer projectId) {
getProjectMembersUrl = getProjectMembersUrl.replace("$",""+projectId);
getGroupMembersUrl = getGroupMembersUrl.replace("$","800");
List<String> projectMasterMembers = getGitLabMasterMembers(getProjectMembersUrl);
List<String> groupMasterMembers = getGitLabMasterMembers(getGroupMembersUrl);
return Stream.of(projectMasterMembers,groupMasterMembers).flatMap(Collection::stream).distinct().collect(Collectors.toList());
}
/**
* 獲取master成員
* @param url
* @return
*/
private static List<String> getGitLabMasterMembers(String url) {
List<String> result = new ArrayList<>();
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(url).header("PRIVATE-TOKEN",token).build();
Response response = null;
try {
response = okHttpClient.newCall(request).execute();
String body = response.body().string();
JSONArray jsonArray = JSONArray.parseArray(body);
for (int i = 0;i < jsonArray.size();i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
if (jsonObject.getInteger("access_level") == 40) {
result.add(jsonObject.getString("name"));
}
}
} catch (IOException e) {
log.error("調用GitLab API失敗!",e);
} finally {
if (response != null) {
response.close();
}
}
return result;
}
這個例子中只做了Tag Push Event和Merge Request Event的處理,主要是根據不同的事件構建不同的企業微信消息內容。其他的可以自己擴展。
MessageStrategy.java
/**
* 構建消息內容
*/
public interface MessageStrategy {
String produceMsg(JSONObject jsonObject);
}
TagMessageStrategy.java
@Override
public String produceMsg(JSONObject jsonObject) {
String operateType = OPERATE_TYPE_ADD;
if ("0000000000000000000000000000000000000000".equals(jsonObject.getString("after"))) {
operateType = OPERATE_TYPE_DELETE;
}
Integer projectId = jsonObject.getInteger("project_id");
JSONObject prObject = jsonObject.getJSONObject("project");
String repo = prObject.getString("name");
String operator = jsonObject.getString("user_name");
String tag = jsonObject.getString("ref");
String[] tagArr = tag.split("/");
tag = tagArr[tagArr.length-1];
String detailUrl = prObject.getString("web_url")+"/tags/"+tag;
String commitInfo = "";
if (OPERATE_TYPE_ADD.equals(operateType)) {
String newTagMsg = jsonObject.getString("message");
JSONObject latestCommit = jsonObject.getJSONArray("commits").getJSONObject(0);
String latestCommitMsg = latestCommit.getString("message").replaceAll("\r\n"," ");
String latestCommitUser = latestCommit.getJSONObject("author").getString("name");
commitInfo = "\n>Tag描述:"+newTagMsg
+"\n>最近一次提交信息:"+latestCommitMsg
+"\n>最近一次提交人:"+latestCommitUser;
}
List<String> members = GitLabApiUtils.getAllProjectMembers(projectId);
String alertUsers = members.stream().map(s -> "@"+s+" ").collect(Collectors.joining());
String alertContent = alertUsers+"<font color=\\\"info\\\">【"+repo+"】</font>"+operator+"<font color=\\\"info\\\">"+operateType+"</font>了一個Tag!"
+"\n>Tag名稱:"+tag
+commitInfo
+"\n>[查看詳情]("+detailUrl+")";
return alertContent;
}
MRMessageStrategy.java
@Override
public String produceMsg(JSONObject jsonObject) {
String operator = jsonObject.getJSONObject("user").getString("name");
JSONObject objectAttributes = jsonObject.getJSONObject("object_attributes");
String operateType = "變更";
String state = objectAttributes.getString("state");
if ("closed".equals(state)) {
operateType = "關閉";
} else if ("opened".equals(state)) {
operateType = "新增";
} else if ("merged".equals(state)) {
operateType = "審核通過";
}
String source = objectAttributes.getString("source_branch");
String target = objectAttributes.getString("target_branch");
Integer projectId = objectAttributes.getInteger("target_project_id");
String title = objectAttributes.getString("title");
String description = objectAttributes.getString("description");
JSONObject lastCommit = objectAttributes.getJSONObject("last_commit");
String lastCommitMsg = lastCommit.getString("message").replaceAll("\n","");
String lastCommitUser = lastCommit.getJSONObject("author").getString("name");
String repo = objectAttributes.getJSONObject("target").getString("name");
String url = objectAttributes.getString("url");
List<String> members = GitLabApiUtils.getAllProjectMembers(projectId);
String alertUsers = members.stream().map(s -> "@"+s+" ").collect(Collectors.joining());
String alertContent = alertUsers+"<font color=\\\"info\\\">【"+repo+"】</font>"+operator+"<font color=\\\"info\\\">"+operateType+"</font>了一個Merge Request!"
+"\n>標題:"+title
+"\n>描述:"+description
+"\n>Source Branch:"+source
+"\n>Target Branch:"+target
+"\n>最近一次提交信息:"+lastCommitMsg
+"\n>最近一次提交人:"+lastCommitUser
+"\n>[查看詳情]("+url+")";
return alertContent;
}
AlertController.java
@PostMapping("/alert")
public String alert(@RequestBody JSONObject jsonObject, HttpServletRequest request) {
String bodyContext = "發送成功";
String objectKind = jsonObject.getString("object_kind");
MessageStrategy messageStrategy = null;
if("tag_push".equals(objectKind)) {
messageStrategy = new TagMessageStrategy();
} else if("merge_request".equals(objectKind)) {
messageStrategy = new MRMessageStrategy();
}
MessageStrategyContext messageStrategyContext = new MessageStrategyContext(messageStrategy);
String alertContent = messageStrategyContext.buildMessage(jsonObject);
log.info("消息內容:"+alertContent);
String[] cmds={"curl",weChatSendUrl,"-H"
,"Content-Type: application/json","-d","{\"msgtype\": \"markdown\",\"markdown\": {\"content\": \""+alertContent+"\"}}"};
ProcessBuilder process = new ProcessBuilder(cmds);
try {
process.start();
} catch (Exception e) {
bodyContext = "發送失敗";
}
return bodyContext;
}
完整代碼請查看gitlab-to-企業微信
經過開發之后webhook配置項里的URL自然也就有了,那就是http://{服務ip}:{服務端口}/alert
總結
其實hook這種設計在很多地方都有,且不說一些開源中間件,JDK本身就提供了ShutdownHook,最重要的還是了解hook的工作原理,才能更好的使用hook,感受它帶來的擴展和便捷。

