拉仇恨!webhook + 企業微信給同事做了個代碼提交監聽工具

本文案例收錄在 //github.com/chengxy-nds/Springboot-Notebook

大家好,我是小富~

最近接個任務,用webhook做了個代碼提交監聽功能,就是有人向遠程倉庫提交代碼後,會在企業微信群內發送一條消息,類似 @XXX 在XXX時間,向XXX項目提交 XXXX 代碼 這樣的文案。

至於為啥要做這麼個工具,沒辦法官大一級壓死人,其實我內心是拒絕的,總像是被監視一樣感覺怪怪的。難不成是發現了我平時偷偷提代碼,悄無聲息的修Bug?

webhook

webhook也就是我們經常說的鉤子,如果對鉤子不熟悉,沒關係那我們換一個概念,回調URL應該聽說過吧,例如:微信支付這類的三方平台都支持配置回調URL,通知支付狀態。

當一些事件觸發,例如:”push代碼到遠程倉庫”,或者”提一個issue“等,源網站可以發起一個HTTP請求到webhook配置的URL。

下圖是這個工具的工作流程,開發者向GitHub項目提交代碼,會觸發GitHub的pull event,緊接着向GitHub webhook中配置的三方URL發送一個POST請求,這個三方平台可以是釘釘、飛書、企業微信這類平台。

下面我們以 GitHub + 企業微信 來實現代碼提交監聽,自動向企業微信群組推送消息。

配置GitHub webhook

首先進入GitHub對應項目的 Settings,做webhook的基礎配置。

主要配置四部分:

Payload URL 回調服務的地址;

Content type 回調請求頭,建議JSON格式;

Secret 為了做安全校驗,設置後會在請求 header 中增加如下兩個屬性,用來區分請求的來源,避免暴露的請求被惡意訪問;

X-Hub-Signature: sha1=2478e400758f6114aa18abc4380ef8fad0b16fb9
X-Hub-Signature-256: sha256=68bde5bee18bc36fd95c9b71b4a89f238cb01ab3bf92fd67de3a1de12b4f5c72

最後我們選擇由哪些事件來觸發webhook回調,push event(代碼推送事件)、everything(所有事件)、某些特定事件三種。

我們可以在 Recent Deliveries 查看webhook回調記錄,以及完整的請求和參數數據,還可以redelivery模擬發送請求。

配置企業微信

企業微信的配置其實更簡單,我們先創建一個群組,在群組右鍵有個添加機械人選項,添加成功後會生成webhook地址。我們只要向這個地址發送POST請求,群組內就會收到推送消息。

消息內容支持文本(text)、markdown(markdown)、圖片(image)、圖文(news)四種消息類型,而且還支持在群內@群成員,下邊以文本格式做示範。

   curl '//qyapi.weixin.qq.com/cgi-bin/webhook/send?key=145a516a-dd15-421f-97a3-ba3bf1479369' \
   -H 'Content-Type: application/json' \
   -d '
   {
        "msgtype": "text",
        "text": {
            "content": "你好,我是程序員內點事"
        }
   }'

直接請求 url 發現消息推送成功,說明配置的沒問題。

但是到這大家發現一個問題沒,GitHub企業微信一個只管往出發請求,一個只管接受固定數據格式的請求,兩個接口的數據根本無法兼容啊?

請求轉發

既然他們之間不兼容,沒辦法,那就只能我們自己在中間做一層適配,誰讓兩邊都惹不起呢!

轉發的邏輯也比較簡單,只需接受GitHub回調過來的請求數據,稍加修改組裝成企業微信要求的數據格式,直接發送就可以了。

GitHub推送過來的數據包括,倉庫、作者、提交者、提交內容等信息,基本上夠用。

代碼實現比較粗糙,將就看下吧

@Slf4j
@RestController
public class WebhookController {

    private static String WECHAT_URL = "//qyapi.weixin.qq.com/cgi-bin/webhook/send?key=145a516a-dd15-421f-97a3-ba3bf1479369";

    private static String GITHUB_API = "//api.github.com/users/";

    /**
     * @param webhook webhook
     * @author 程序員內點事
     * @Description: github 回調
     * @date 2021/05/19
     */
    @PostMapping("/webhook")
    public String webhookGithub(@RequestBody GithubWebhookPullVo webhook) {

        log.info("webhook 入參接收 weChatWebhook {}", JSON.toJSONString(webhook));
        // 倉庫名
        String name = webhook.getRepository().getName();
        SimpleDateFormat simpleFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String now = simpleFormatter.format(new Date());
        String content = null;
        if (webhook.getCommits().size() > 0) {
            GithubWebhookPullVo.CommitsDTO commitsDTO = webhook.getCommits().get(0);

            content = "[" + commitsDTO.getCommitter().getName() + "]" +
                    "於:" + now + "," +
                    "向作者:[" + commitsDTO.getAuthor().getName() + "]的,遠程倉庫" + name + "推送代碼" +
                    "詳情:";

            List<String> addeds = commitsDTO.getAdded();
            if (addeds.size() > 0) {
                content += "添加文件:";
                for (int i = 0; i < addeds.size(); i++) {
                    content = (i + 1) + content + addeds.get(i);
                }
            }
            List<String> modifieds = commitsDTO.getModified();
            if (modifieds.size() > 0) {
                content += "修改文件:";
                for (int i = 0; i < modifieds.size(); i++) {
                    content = (i + 1) + content + modifieds.get(i);
                }
            }
            List<String> removeds = commitsDTO.getRemoved();
            if (removeds.size() > 0) {
                content += "刪除文件:";
                for (int i = 0; i < removeds.size(); i++) {
                    content = (i + 1) + content + removeds.get(i);
                }
            }
        }
        log.info(content);

        WeChatWebhook weChatWebhook = new WeChatWebhook();
        weChatWebhook.setMsgtype("text");
        WeChatWebhook.TextDTO textDTO = new WeChatWebhook.TextDTO();
        textDTO.setContent(content);
        textDTO.setMentionedList(Arrays.asList("@all"));
        textDTO.setMentionedMobileList(Arrays.asList("@all"));
        weChatWebhook.setText(textDTO);

        /**
         * 組裝參數後向企業微信發送webhook請求
         */
        log.info("企業微信發送參數 {}", JSON.toJSONString(weChatWebhook));
        String post = HttpUtil.sendPostJsonBody(WECHAT_URL, JSON.toJSONString(weChatWebhook));
        log.info("企業微信發送結果 post {}", post);
        return JSON.toJSONString(post);
    }
}

這裡要提醒一下,GitHub webhook 回調過來的數據有些並不能直接拿來用,某些場景還是要調用GitHub API來換取一些數據的。

文檔地址://docs.github.com/en/rest/reference

上邊的配置工作完成,再將轉發的代碼部署到服務器,測試下整個鏈路看看效果,故意修改pom.xml文件提交,發現提交代碼後成功向企業微信發送了消息,和我們預期的效果一致。

源碼地址://github.com/chengxy-nds/Springboot-Notebook/

這個工程包含我過往文章里所有的案例,比如:抖音去水印工具源碼人臉識別項目源碼、以及redisSeataMQ等中間件的各種問題解決案例,感興趣的同學可以Star個,實際開發一定會用得到。