本文主要介绍如何使用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,感受它带来的扩展和便捷。