3

gitlab可以设置在完成某些操作(例如:提交pr,合并pr,提出issue...)可以对接钉钉机器人,从而将这些信息实时推送到钉钉群中。

虽然钉钉也对gitlab设置了官方的机器人但是可能并不能很好的满足我们的需求,或者说gitlab所发出的请求并不符合钉钉机器人所要求的格式。

这时候我们就需要一个类似中转站的东西来满足我们的要求——接收gitlab所发出的请求,将其转换为钉钉机器人可以正常接收的格式再根据这些数据构造一个新的请求来调用我们的钉钉机器人从而实现我们所需要的功能。

本文基于传送门编写。

流程图:
未命名文件.png

我们如果直接运行该项目的话我们会发现当我们人为模拟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 = "&timestamp=" + timestamp + "&sign=" + sign;
        return result;
    }

之后我们只需要再从C层获取到serect再传给这个函数使用即可。

关于serect:
我们再gitlab上设置好serect后,gitlab会将其加入到请求的header中供我们获取即下图中的X-Gitlab-Token。

图片.png

之后我们再根据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;
    }

李明
441 声望18 粉丝