gitlab 能够设置在实现某些操作(例如:提交 pr, 合并 pr, 提出 issue…)能够对接钉钉机器人,从而将这些信息实时推送到钉钉群中。
尽管钉钉也对 gitlab 设置了官网的机器人然而可能并不能很好的满足咱们的需要,或者说 gitlab 所收回的申请并不合乎钉钉机器人所要求的格局。
这时候咱们就须要一个相似中转站的货色来满足咱们的要求——接管 gitlab 所收回的申请,将其转换为钉钉机器人能够失常接管的格局再依据这些数据结构一个新的申请来调用咱们的钉钉机器人从而实现咱们所须要的性能。
本文基于传送门编写。
流程图:
咱们如果间接运行该我的项目的话咱们会发现当咱们人为模仿 gitlab 发出请求时会收到以下响应:
{
"errcode":310000,
"errmsg":"sign not match"
}
报错信息提醒:签名不匹配,在查阅钉钉官网文档后就会发现如果咱们采纳“加签”办法对机器人进行平安设置时如果没有对申请的 url 依据设置的 serect 进行解决就会产生这样的报错。
钉钉机器人平安设置
依据下面给出的钉钉官网文档中的内容咱们能够写出上面这个函数对 url 进行解决:
public String encode(String secret) throws Exception {
// 获取工夫戳
Long timestamp = System.currentTimeMillis();
// 把工夫戳和密钥拼接成字符串,两头退出一个换行符
String stringToSign = timestamp + "\n" + secret;
// 申明一个 Mac 对象,用来操作字符串
Mac mac = Mac.getInstance("HmacSHA256");
// 初始化,设置 Mac 对象操作的字符串是 UTF- 8 类型,加密形式是 SHA256
mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
// 把字符串转化成字节模式
byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
// 新建一个 Base64 编码对象
Base64.Encoder encoder = Base64.getEncoder();
// 把下面的字符串进行 Base64 加密后再进行 URL 编码
String sign = URLEncoder.encode(new String(encoder.encodeToString(signData)),"UTF-8");
String result = "×tamp=" + timestamp + "&sign=" + sign;
return result;
}
之后咱们只须要再从 C 层获取到 serect 再传给这个函数应用即可。
对于 serect:
咱们再 gitlab 上设置好 serect 后,gitlab 会将其退出到申请的 header 中供咱们获取即下图中的 X -Gitlab-Token。
之后咱们再依据 gitlab 发送的申请批改一下 C 层对接的 URL 就能够失常地应用这个我的项目。
在 C 层匹配到相应 url 后获取申请中的 json,X-Gitlab-Event,X-Gitlab-Token 并调用对应 M 层函数。
@RestController
@RequestMapping("/oapi.dingtalk.com/robot/send")
@Slf4j
public class GitLabController {
@Autowired
private GitLabNotifyService gitLabNotifyService;
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseVo pushHook(@RequestBody String json, @RequestHeader(name = "X-Gitlab-Event") String event, @RequestHeader(name = "X-Gitlab-Token") String secret) throws IOException{System.out.println("触发推送");
gitLabNotifyService.handleEventData(json,event,secret);
return ResponseUtil.ok();}
String secret;
@Override
public void handleEventData(String json, String eventName, String secret) throws IOException {System.out.println("json:" + json);
System.out.println("eventName:" + eventName);
// 缓存 secret
this.secret = secret;
// 依据哈希表返回 X -Gitlab-Event 对应的 beanName
String handleBeanName = EventMapper.getHandleBeanName(eventName);
// 依据 beanName 获取相应的 service。EventService eventService = (EventService) applicationContextProvider.getBean(handleBeanName);
eventService.handleEvent(json);
}
其中的 applicationContextProvider 实现了 ApplicationContextAware 接口用于设置上下文实例,之后咱们能够依据上下文实例的 getBean 办法来获取对应的 sevice。
@Component
public class ApplicationContextProvider
implements ApplicationContextAware {
/**
* 上下文对象实例
*/
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
/**
* 获取 applicationContext
*/
public ApplicationContext getApplicationContext() {return applicationContext;}
/**
* 通过 name 获取 Bean.
*/
public Object getBean(String name) {return getApplicationContext().getBean(name);
}
}
之后咱们以 pushService 为例阐明:
@Override
public void handleEvent(String json) throws IOException {
// 依据 json 数据设置 gitLabPushRequest
GitLabPushRequest gitLabPushRequest = covertJson(json);
String text = "- event :" + gitLabPushRequest.getObjectKind() + "\n" +
"- project :" + gitLabPushRequest.getProject().getName() + "\n" +
"- author :" + gitLabPushRequest.getUserName() + "\n" +
"- branch:" + gitLabPushRequest.getRef() + "\n" +
"- total_commits:" + gitLabPushRequest.getTotalCommitsCount() + "\n";
// 依据 gitLabPushRequest 设置要推送给钉钉的 MarkDownMessage
dingPushService.pushMarkDownMessage(new MarkDownMessage(MessageTypeConstant.MARKDOWN_TYPE, new MarkDownMessage.MarkDown(gitLabPushRequest.getObjectKind(), text)));
}
其中的 MarkDownMessage 是钉钉官网给出的几种可供钉钉机器人辨认的数据格式之一,上面给出对应的钉钉官网文档。
自定义机器人的接入
dingPushService:
public void pushMarkDownMessage(MarkDownMessage markDownMessage) {DingResponse<Void> response = dingTalkApi.pushMarkDownMessage(markDownMessage);
if (!SUCCESSS_CODE.equals(response.getErrcode())) {throw new UnknownException("error");
}
}
之后咱们须要再调用 dingTalkApi 下的 pushMarkDownMessage 函数
dingTalkApi:
public DingResponse<Void> pushMarkDownMessage(MarkDownMessage markDownMessage) {
HttpClientResponse httpClientResponse;
...
// 依据 sercret 批改 dingTalkUrl
dingTalkUrl = dingTalkUrl + this.encode(gitLabNotifyService.getDingSecret());
httpClientResponse = httpClientWrapper.postReturnHttpResponse(Collections.singletonMap("Content-Type","application/json"),dingTalkUrl, JsonUtil.serializeToJson(markDownMessage,true));
return CommonHttpUtils.handleHttpResponse(httpClientResponse, new TypeReference<DingResponse<Void>>() {...}
这外面调用了 httpClientWrapper.postReturnHttpResponse 进行从新结构申请,并向钉钉机器人发动申请,随后钉钉机器人接管到申请后便可依据申请进行信息的推送。
public HttpClientResponse postReturnHttpResponse(Map<String, String> headerMap, String url, String bodyStr) throws IOException {HttpPost httpPost = getHttpPost(headerMap, url, bodyStr);
return convert2HttpClientResponse(httpClient.execute(httpPost));
}
private HttpPost getHttpPost(Map<String, String> headerMap, String url, String bodyStr) {HttpPost httpPost = new HttpPost(url);
if (headerMap != null) {headerMap.forEach(httpPost::addHeader);
}
httpPost.setEntity(new StringEntity(bodyStr, "UTF-8"));
return httpPost;
}