一、说在前面的话
接着前文,本文将介绍gerrit的消息通知机制与对接详细。
你需要确保gerrit安装了webhook插件,截止目前的最新版本已默认安装了该插件,如果你的没有,则需要升级gerrit。
其次,在webhooks.config文件中,配置事件及回调地址。
最后,在devops服务中,接收回调报文,并发送im消息给对应的人。
二、gerrit的webhook配置
插件-plugins_webhooks的源码地址:https://github.com/GerritCodeReview/plugins_webhooks。
详细文档,参考https://github.com/GerritCodeReview/plugins_webhooks/blob/master/src/main/resources/Documentation/config.md
[remote "devops"]
url = http://192.168.5.17/api/v1/gerrit/notify
event = project-created
event = patchset-created
event = reviewer-added
event = reviewer-deleted
event = comment-added
另外,你如果想要让所有的事件都触发一个回调地址,如下:
[remote "devops"]
url = http://192.168.5.17/api/v1/gerrit/notify
三、实现
3.1、controller层定义api接口,接收gerrit的webhook回调
@Autowired
private GerritMessageService gerritMessageService;
@ApiOperation(value = "gerrit回调")
@PostMapping(value = "/api//v1/gerrit/notify")
public ResponseEntity<?> gerritNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
String requestJson = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
if (log.isInfoEnabled()) {
log.info("gerrit回调的完整信息是{}", requestJson);
}
JSONObject jsonObject = JSON.parseObject(requestJson);
String eventType = jsonObject.getString("type");
if (StringUtils.isNotEmpty(eventType)) {
gerritMessageService.notice(eventType, requestJson);
}
return ResponseEntity.ok("SUCCESS");
}
3.2、GerritMessageService.java
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.xxx.gerrit.event.CommentAddedEvent;
import com.xxx.gerrit.event.PatchsetCreatedEvent;
import com.xxx.gerrit.event.ProjectCreatedEvent;
import com.xxx.gerrit.event.ReviewerAddedEvent;
import com.xxx.oa.ProjectInfoService;
import com.xxx.util.MessageFormatUtil;
import com.xxx.wechat.WechatService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import static com.xxx.util.WxchatRobotUtil.*;
/**
* @author xxx
*/
@Slf4j
@Service
public class GerritMessageService {
@Autowired
private WechatService wechatService;
public void notice(String eventType, String content) {
Map<String, String> paramMap = Maps.newHashMap();
if (GerritEventEnum.PATCHSET_CREATED.getCode().equalsIgnoreCase(eventType)) {
// 提交后,等待jenkins的Verified+1,再提醒需要code review
// this.noticeReview(content, paramMap);
} else if (GerritEventEnum.REVIEWER_ADDED.getCode().equalsIgnoreCase(eventType)) {
// 新增reviewer
this.addReviewer(content, paramMap);
} else if (GerritEventEnum.COMMENT_ADDED.getCode().equalsIgnoreCase(eventType)) {
this.noticeReviewResult(content, paramMap);
} else if (GerritEventEnum.PROJECT_CREATED.getCode().equalsIgnoreCase(eventType)) {
this.noticeCreateProject(content, paramMap);
}
}
private void noticeCreateProject(String content, Map<String, String> paramMap) {
log.info("创建gerrit工程成功,收到回调通知,发送IM消息给相关人员, content={}", content);
ProjectCreatedEvent projectCreatedEvent = JSON.parseObject(content, ProjectCreatedEvent.class);
String projectName = projectCreatedEvent.getProject().getName();
String realProjectName = projectName.substring(projectName.lastIndexOf("/") + 1);
// 根据projectName查询工程所在的业务组
String group = ProjectInfoService.getTag(realProjectName, ProjectInfoService.GERRIT);
paramMap.put("projectName", realProjectName);
wechatService.sendByTagName(group, MessageFormatUtil.parseTemplate(GERRIT_PROJECT_CREATE_NOTICE, paramMap));
}
private void noticeReview(String content, Map<String, String> paramMap) {
log.info("gerrit代码需要评审,[content={}]", content);
// 提醒
PatchsetCreatedEvent patchsetCreatedEvent = JSON.parseObject(content, PatchsetCreatedEvent.class);
String project = patchsetCreatedEvent.getChange().getProject();
String projectName = project.substring(project.lastIndexOf("/") + 1);
paramMap.put("projectName", projectName);
paramMap.put("branch", patchsetCreatedEvent.getChange().getBranch());
// 秒转日期
paramMap.put("createdOn", DateUtil.formatDateTime(DateUtil.date(
1000 * patchsetCreatedEvent.getChange().getCreatedOn())));
paramMap.put("commitMessage", patchsetCreatedEvent.getChange().getCommitMessage());
paramMap.put("url", patchsetCreatedEvent.getChange().getUrl());
paramMap.put("name", patchsetCreatedEvent.getUploader().getName());
paramMap.put("username", patchsetCreatedEvent.getUploader().getUsername());
// 根据projectName查询工程所在的业务组
String group = ProjectInfoService.getTag(projectName, ProjectInfoService.GERRIT);
wechatService.sendByTagName(group, MessageFormatUtil.parseTemplate(GERRIT_REVIEW_NOTICE, paramMap));
}
private void addReviewer(String content, Map<String, String> paramMap) {
log.info("gerrit代码需要评审,[content={}]", content);
// 提醒
ReviewerAddedEvent reviewerAddedEvent = JSON.parseObject(content, ReviewerAddedEvent.class);
String project = reviewerAddedEvent.getChange().getProject();
String projectName = project.substring(project.lastIndexOf("/") + 1);
paramMap.put("projectName", projectName);
paramMap.put("branch", reviewerAddedEvent.getChange().getBranch());
// 秒转日期
paramMap.put("createdOn", DateUtil.formatDateTime(DateUtil.date(
1000 * reviewerAddedEvent.getChange().getCreatedOn())));
paramMap.put("commitMessage", reviewerAddedEvent.getChange().getCommitMessage());
paramMap.put("url", reviewerAddedEvent.getChange().getUrl());
paramMap.put("name", reviewerAddedEvent.getPatchSet().getUploader().getName());
paramMap.put("username", reviewerAddedEvent.getPatchSet().getUploader().getUsername());
paramMap.put("adder", reviewerAddedEvent.getAdder().getName());
// 审核人员
String phone = reviewerAddedEvent.getReviewer().getUsername();
wechatService.sendByAccount(phone, MessageFormatUtil.parseTemplate(GERRIT_REVIEW_NOTICE, paramMap));
}
private void noticeReviewResult(String content, Map<String, String> paramMap) {
log.info("gerrit代码评审结果通知,[content={}]", content);
// 审核结果
CommentAddedEvent commentAddedEvent = JSON.parseObject(content, CommentAddedEvent.class);
// 判断
int unPassedItems = 0;
int ignoredItems = 0;
List<CommentAddedEvent.Approval> approvals = commentAddedEvent.getApprovals();
StringBuilder resultBuilder = new StringBuilder();
for (CommentAddedEvent.Approval approval : approvals) {
if ("Code-Review".equals(approval.getType())) {
resultBuilder.append("Code-Review : ").append(approval.getValue()).append(",\t");
if ("0".equals(approval.getValue())) {
ignoredItems++;
}
} else if ("Verified".equals(approval.getType())) {
resultBuilder.append("Verified : ").append(approval.getValue()).append(",\t");
if ("0".equals(approval.getValue())) {
ignoredItems++;
}
}
if ("-1".equals(approval.getValue()) || "-2".equals(approval.getValue())) {
unPassedItems++;
}
}
if (ignoredItems == 2) {
return;
}
paramMap.put("reviewResult", resultBuilder.toString());
String project = commentAddedEvent.getChange().getProject();
String projectName = project.substring(project.lastIndexOf("/") + 1);
paramMap.put("projectName", projectName);
paramMap.put("branch", commentAddedEvent.getChange().getBranch());
// 秒转日期
paramMap.put("createdOn", DateUtil.formatDateTime(DateUtil.date(
1000 * commentAddedEvent.getChange().getCreatedOn())));
paramMap.put("commitMessage", commentAddedEvent.getChange().getCommitMessage());
paramMap.put("url", commentAddedEvent.getChange().getUrl());
paramMap.put("comment", commentAddedEvent.getComment());
paramMap.put("eventCreatedOn", DateUtil.formatDateTime(DateUtil.date(
1000 * commentAddedEvent.getEventCreatedOn())));
paramMap.put("adder", commentAddedEvent.getPatchSet().getUploader().getName());
// jenkins构建通过, 提醒同组的开发同事去评审
if (unPassedItems == 0 && "jenkins".equalsIgnoreCase(commentAddedEvent.getAuthor().getUsername())) {
log.info("gerrit代码需要评审(jenkins验证已通过!),[content={}]", content);
paramMap.put("name", StringUtils.isEmpty(commentAddedEvent.getPatchSet().getUploader().getName()) ?
commentAddedEvent.getPatchSet().getUploader().getUsername() :
commentAddedEvent.getPatchSet().getUploader().getName());
paramMap.put("username", commentAddedEvent.getPatchSet().getUploader().getUsername());
// 根据projectName查询工程所在的业务组
String group = ProjectInfoService.getTag(projectName, ProjectInfoService.GERRIT);
wechatService.sendByTagName(group, MessageFormatUtil.parseTemplate(GERRIT_REVIEW_NOTICE,
paramMap));
} else {
paramMap.put("name", StringUtils.isEmpty(commentAddedEvent.getAuthor().getName()) ?
commentAddedEvent.getAuthor().getUsername() : commentAddedEvent.getAuthor().getName());
paramMap.put("username", commentAddedEvent.getAuthor().getUsername());
String phone = commentAddedEvent.getPatchSet().getUploader().getUsername();
wechatService.sendByAccount(phone, MessageFormatUtil.parseTemplate(
unPassedItems == 0 ? GERRIT_REVIEW_PASS : GERRIT_REVIEW_UNPASS,
paramMap));
}
}
}
四、回调报文示例
- change-merged事件
{
"submitter":{
"name":"xxx",
"email":"xxx@xx.cn",
"username":"158xxxx9229"
},
"newRev":"b9b071813e739e7d63bfa6efaed9ad4057834578",
"patchSet":{
"number":1,
"revision":"b9b071813e739e7d63bfa6efaed9ad4057834578",
"parents":[
"7babe2f1ec9f87c32f5910f87fc7c43e6cd4448c"
],
"ref":"refs/changes/70/40770/1",
"uploader":{
"name":"xxx",
"email":"xxx@xx.cn",
"username":"158xxxx9229"
},
"createdOn":1684137095,
"author":{
"name":"xxx",
"email":"xxx@xx.com",
"username":""
},
"kind":"REWORK",
"sizeInsertions":51,
"sizeDeletions":30
},
"change":{
"project":"service/user-service",
"branch":"master",
"id":"I56c4ec9321aef6e198a626d305bd78dc78788379",
"number":40770,
"subject":"# 2.54.3 - 获取所有区域列表增加省市区筛选",
"owner":{
"name":"xxx",
"email":"xxx@xx.cn",
"username":"158xxxx9229"
},
"url":"http://review.xxx.net/c/service/user-service/+/40770",
"commitMessage":"# 2.54.3\n- 获取所有区域列表增加省市区筛选\n\nChange-Id: I56c4ec9321aef6e198a626d305bd78dc78788379\n",
"createdOn":1684137095,
"status":"MERGED"
},
"project":{
"name":"service/user-service"
},
"refName":"refs/heads/master",
"changeKey":{
"key":"I56c4ec9321aef6e198a626d305bd78dc78788379"
},
"type":"change-merged",
"eventCreatedOn":1684198055
}
- comment-added事件(代码评审人员)
{
"author":{
"name":"xxx",
"email":"xxx@xx.cn",
"username":"158xxxx9229"
},
"approvals":[
{
"type":"Verified",
"description":"Verified",
"value":"0"
},
{
"type":"Code-Review",
"description":"Code-Review",
"value":"2",
"oldValue":"0"
}
],
"comment":"Patch Set 1: Code-Review+2",
"patchSet":{
"number":1,
"revision":"b9b071813e739e7d63bfa6efaed9ad4057834578",
"parents":[
"7babe2f1ec9f87c32f5910f87fc7c43e6cd4448c"
],
"ref":"refs/changes/70/40770/1",
"uploader":{
"name":"xxx",
"email":"xxx@xx.cn",
"username":"158xxxx9229"
},
"createdOn":1684137095,
"author":{
"name":"xxx",
"email":"xxx@xx.com",
"username":""
},
"kind":"REWORK",
"sizeInsertions":51,
"sizeDeletions":30
},
"change":{
"project":"service/user-service",
"branch":"master",
"id":"I56c4ec9321aef6e198a626d305bd78dc78788379",
"number":40770,
"subject":"# 2.54.3 - 获取所有区域列表增加省市区筛选",
"owner":{
"name":"xxx",
"email":"xxx@xx.cn",
"username":"158xxxx9229"
},
"url":"http://review.xx.net/c/service/user-service/+/40770",
"commitMessage":"# 2.54.3\n- 获取所有区域列表增加省市区筛选\n\nChange-Id: I56c4ec9321aef6e198a626d305bd78dc78788379\n",
"createdOn":1684137095,
"status":"NEW"
},
"project":{
"name":"service/user-service"
},
"refName":"refs/heads/master",
"changeKey":{
"key":"I56c4ec9321aef6e198a626d305bd78dc78788379"
},
"type":"comment-added",
"eventCreatedOn":1684198053
}
- comment-added事件(jenkins工具,Verified+1)
{
"author":{
"username":"jenkins"
},
"approvals":[
{
"type":"Verified",
"description":"Verified",
"value":"1",
"oldValue":"0"
},
{
"type":"Code-Review",
"description":"Code-Review",
"value":"0"
}
],
"comment":"Patch Set 1: Verified+1\n\nBuild Successful \n\nhttp://cd.xxx.net/job/V_resource_java_user-service/167/ : SUCCESS",
"patchSet":{
"number":1,
"revision":"e660287f84476680bad5636d4a6ed8d9d654297b",
"parents":[
"5bc9463d90172a9e7b650d46a198f0513c446acc"
],
"ref":"refs/changes/76/40776/1",
"uploader":{
"name":"xxx",
"email":"xxx@xx.cn",
"username":"134xxxx0708"
},
"createdOn":1684222769,
"author":{
"name":"xxx",
"email":"xxx@xx.com",
"username":""
},
"kind":"REWORK",
"sizeInsertions":76,
"sizeDeletions":5
},
"change":{
"project":"resource/user-service",
"branch":"master",
"id":"I850b63f3b03d3df7905ad6b027482ec9a385df48",
"number":40776,
"subject":"字符统计增加详解字符统计",
"owner":{
"name":"xxx",
"email":"xxx@xx.cn",
"username":"134xxxx0708"
},
"url":"http://review.xxx.net/c/resource/user-service/+/40776",
"commitMessage":"字符统计增加详解字符统计\n\nChange-Id: I850b63f3b03d3df7905ad6b027482ec9a385df48\n",
"createdOn":1684222769,
"status":"NEW"
},
"project":{
"name":"resource/user-service"
},
"refName":"refs/heads/master",
"changeKey":{
"key":"I850b63f3b03d3df7905ad6b027482ec9a385df48"
},
"type":"comment-added",
"eventCreatedOn":1684222949
}
- patchset-created事件
{
"uploader":{
"name":"xxx",
"email":"xxx@xx.cn",
"username":"182xxxx0316"
},
"patchSet":{
"number":1,
"revision":"78de0ddced5c4a29265773a9c3df234ed81e991f",
"parents":[
"3da50a3658865f3ff930a2bca0581a202e14a807"
],
"ref":"refs/changes/77/40777/1",
"uploader":{
"name":"xxx",
"email":"xxx@xx.cn",
"username":"182xxxx0316"
},
"createdOn":1684223816,
"author":{
"name":"182xxxx0316",
"email":"xxx@xx.com",
"username":""
},
"kind":"REWORK",
"sizeInsertions":41,
"sizeDeletions":43
},
"change":{
"project":"root/user-service",
"branch":"master",
"id":"I11baec7db30d8bbb71f5155e55e51bd4d6cc5ef9",
"number":40777,
"subject":"1.修改开关相关字段命名",
"owner":{
"name":"xxx",
"email":"xxx@xx.cn",
"username":"182xxxx0316"
},
"url":"http://review.xxx.net/c/root/user-service/+/40777",
"commitMessage":"1.修改开关相关字段命名\n\nChange-Id: I11baec7db30d8bbb71f5155e55e51bd4d6cc5ef9\n",
"createdOn":1684223816,
"status":"NEW"
},
"project":{
"name":"root/user-service"
},
"refName":"refs/heads/master",
"changeKey":{
"key":"I11baec7db30d8bbb71f5155e55e51bd4d6cc5ef9"
},
"type":"patchset-created",
"eventCreatedOn":1684223817
}
- reviewer-added事件(略)
- project_created事件(略)