打通Gitlab與釘釘之間的通訊

[TOC]

公司使用了Gitlab,Jira等工具來管理,溝通方面主要是釘釘,但郁悶的是各系統(tǒng)相互獨(dú)立,而我已經(jīng)習(xí)慣了前公司那種方式:

有bug的時(shí)候會自動發(fā)送消息到聊天框中,而不是目前這樣,需要開發(fā)人員手動定時(shí)去刷新jira頁面才能知道,效率低下;

gitlab也是一樣,有merge請求的時(shí)候,我希望不需要別人提醒我去審核代碼,而是gitlab直接發(fā)送merge消息到我釘釘即可;

可能其他同事習(xí)慣郵件通知吧,公司并無打通各系統(tǒng)與釘釘聯(lián)系的計(jì)劃,所以我只能自己擼一套了,我不是專職后端,輕噴,功能夠我用就好;

已遷移到 掘金, 歡迎關(guān)注;

更新記錄:

1. 2017.04.17 發(fā)現(xiàn)釘釘直接支持幾個(gè)平臺的webhook推送

  1. 打開釘釘群聊天框右上角的聊天機(jī)器人


    聊天機(jī)器人
  2. 選擇其中需要的平臺


    hook列表
  3. 添加完后再對應(yīng)平臺的設(shè)置中添加webhook地址即可;
    但感覺這個(gè)比較粗糙,以jira為例,消息過于精簡,而且通知到群里的話,會讓用戶操心了本不需要操心的內(nèi)容,個(gè)人覺得,這個(gè)比較適合gitlab的merge代碼被通過時(shí)的通知,通知成員用戶更新本地代碼:


    jira通知示例

2. 2017.8.30 重構(gòu)項(xiàng)目

使用gradle/kotlin/rxjava/retrofit等改造了之前的項(xiàng)目,支持快速新增gitlab項(xiàng)目部門,手動刷新accessToken及部門信息等功能,部分示例如下,具體請看 項(xiàng)目 :
P.S.重構(gòu)后,war包大小由原來的30+M減小到5M左右 ==!

gitlab有新merge代碼審核請求時(shí)會通知審核人

gitlab merge 請求被通過時(shí),會通知相關(guān)項(xiàng)目部門所有成員更新代碼

Github項(xiàng)目地址

相關(guān)文檔

Gitlab webhook document
Jira webhook document
釘釘開放文檔-服務(wù)器端

步驟

  1. gitlab 上啟用 Webhooks 通知(可指定要 Webhooks 的操作,這里僅hook merge 操作,注意:需要項(xiàng)目管理權(quán)限才能設(shè)定, jira 也是類似)
    gitlab添加webhook
  2. 在服務(wù)端,根據(jù)post請求的head信息來區(qū)分不同系統(tǒng)發(fā)來的hook消息:
  3. gitlab的merge請求包含: X-Gitlab-Event:Merge Request Hook
  4. jira的hook請求包含: user-agent:Atlassian HttpClient0.17.3 / JIRA-6.3.15 (6346) / Default
  5. 在服務(wù)器端打開獲取釘釘?shù)娜藛T信息,并調(diào)用其 企業(yè)會話消息接口 發(fā)送指定信息;
    由于該會話接口需要 員工id和企業(yè)應(yīng)用id以及access_token,而 獲取access_token 需要 CorpIdCorpSecret (二者是企業(yè)的唯一標(biāo)識), 因此可知:
  6. 雖然公司的釘釘后臺上有 CorpId 等信息,但不一定會開放,而等公司組織人員開發(fā)又可能遙遙無期,因此還是自己注冊一下企業(yè),創(chuàng)建部門并添加你想通知的人員作為部門員工即可,這樣也能獲取員工 通訊錄詳情 , 得到用userId,從而發(fā)送釘釘消息;
  7. 需要創(chuàng)建一個(gè)微應(yīng)用,以該應(yīng)用為會話發(fā)起人來發(fā)送消息;


    釘釘管理后臺

建立釘釘微應(yīng)用

  1. 釘釘開放平臺 中搜索 微應(yīng)用 就可以找到 Step 1 -- 注冊釘釘企業(yè)鏈接;
  2. 根據(jù)上面的 step 引導(dǎo)操作注冊企業(yè)并添加部門和員工,然后進(jìn)入 釘釘管理后臺;
  3. 企業(yè)應(yīng)用 標(biāo)簽頁左側(cè)導(dǎo)航條中選擇 微應(yīng)用設(shè)置 即可在右側(cè)看到 CorpIDCorpSecret;
  4. 企業(yè)應(yīng)用 標(biāo)簽下 新建應(yīng)用 即可;
  5. 完成后點(diǎn)擊新建的微應(yīng)用圖標(biāo),選擇 設(shè)置 接口查看到微應(yīng)用的 AgentID;

通訊錄規(guī)則

在通訊錄root部門中添加所有人,以便發(fā)送消息到特定用戶時(shí)可以從root部門中通過查詢用戶姓名得到用戶id;
根據(jù)gitlab項(xiàng)目路徑配置各項(xiàng)目部門,比如:

  • 假設(shè)gitlab項(xiàng)目地址為: https://gitlab.lynxz.org/demo-android/detail-android
    則表示項(xiàng)目名稱(name) 為: detail-android ,項(xiàng)目所在空間(namespace)為: demo-android
  • 在釘釘后臺通訊錄中需要先創(chuàng)建部門: demo_android ,然后創(chuàng)建其子部門 detail_android
    注意: 由于釘釘部門名稱不允許使用 -,因此創(chuàng)建時(shí)改為 _ 替代
  • 目前只支持兩級部門結(jié)構(gòu),若有多個(gè)部門符合上述規(guī)則gitlab merge通過時(shí)會通知所有匹配的部門成員;
    備注: 更新釘釘通訊錄后,記得及時(shí)通知server刷新本地?cái)?shù)據(jù),本版支持通過url出發(fā)刷新命令,直接訪問如下網(wǎng)址即可(其中 yourServerHost 是war包運(yùn)行后的訪問地址):
    {yourServerHost}/action/updateDepartmentInfo
    釘釘通訊錄

釘釘發(fā)送消息流程

1. retrofit請求

interface ApiService {
    /**
     * [獲取釘釘AccessToken](https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.dfrJ5p&treeId=172&articleId=104980&docType=1)
     * @param id        corpid 企業(yè)id
     * @param secret    corpsecret 企業(yè)應(yīng)用的憑證密鑰
     * */
    @GET("gettoken")
    fun getAccessToken(@Query("corpid") id: String, @Query("corpsecret") secret: String): Observable<AccessTokenBean>

    /**
     * [獲取部門列表信息](https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.xIVqtB&treeId=172&articleId=104979&docType=1#s0)
     */
    @GET("department/list")
    fun getDepartmentList(): Observable<DepartmentListBean>

    /**
     * [獲取指定部門的成員信息,默認(rèn)獲取全部成員](https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.xIVqtB&treeId=172&articleId=104979&docType=1#s12)
     * */
    @GET("user/simplelist")
    fun getDepartmentMemberList(@Query("department_id") id: Int = 1): Observable<DepartmentMemberListBean>

    /**
     * [向指定用戶發(fā)送普通文本消息](https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.oavHEu&treeId=172&articleId=104973&docType=1#s2)
     */
    @POST("message/send")
    fun sendTextMessage(@Body bean: MessageTextBean): Observable<MessageResponseBean>
}

2. 添加必要的request信息

// 給請求添加統(tǒng)一的query參數(shù):access_token
// 這里的ConstantsPara.accessToken是全局變量,存儲獲取到的accessToken 
val queryInterceptor = Interceptor { chain ->
    val original = chain.request()
    val url = original.url().newBuilder()
            .addQueryParameter("access_token", ConstantsPara.accessToken)
            .build()

    val requestBuilder = original.newBuilder().url(url)
    chain.proceed(requestBuilder.build())
}

// 給請求添加統(tǒng)一的header參數(shù):Content-Type
val headerInterceptor = Interceptor { chain ->
    val request = chain.request().newBuilder()
            .addHeader("Content-Type", "application/json")
            .build()
    chain.proceed(request)
}

val okHttpClient: OkHttpClient = OkHttpClient()
        .newBuilder()
        .addInterceptor(headerInterceptor)
        .addInterceptor(queryInterceptor)
        .build()

val ddRetrofit: Retrofit = Retrofit.Builder()
        .client(okHttpClient)
        .baseUrl("https://oapi.dingtalk.com/")
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build()
    
val apiService: ApiService = ddRetrofit.create(ApiService::class.java)

3. 刷新釘釘?shù)腁ccessToken

    apiService.getAccessToken(ConstantsPara.dd_corp_id, ConstantsPara.dd_corp_secret)
            .retry(1)
            .subscribe(object : Observer<AccessTokenBean> {
                override fun onError(e: Throwable) {
                    e.printStackTrace()
                }

                override fun onSubscribe(d: Disposable) {
                    addDisposable(d)
                }

                override fun onComplete() {
                }

                override fun onNext(t: AccessTokenBean) {
                    println("refreshAccessToken $t")
                    ConstantsPara.accessToken = t.access_token ?: ""
                }
            })

4. 獲取部門列表及各部門下的成員信息

部門信息存放在 ConstantsPara.departmentNameMap 中,是一個(gè)hashmap,記錄部門id及名稱,id用于唯一確定部門,以便后續(xù)查找指定部門成員信息;
部門名稱需跟gitlab項(xiàng)目名稱對應(yīng),其中部門id為1的是公司的根部門,主要要將所有人員都添加進(jìn)去,因?yàn)橥ㄖ付ㄈ藛T時(shí),是從根部門中查找用戶姓名,若匹配就發(fā)出消息,而子部門的存在只為適配gitlab項(xiàng)目路徑;

apiService.getDepartmentList()
        .flatMap { list ->
            ConstantsPara.departmentList = list
            list.department.forEach { ConstantsPara.departmentNameMap.put(it.id, it.name) }
            Observable.fromIterable(list.department)
        }
        .map { departmentBean -> departmentBean.id }
        .flatMap { departmentId ->
            Observable.zip(Observable.create({ it.onNext(departmentId) }),
                    apiService.getDepartmentMemberList(departmentId),
                    BiFunction<Int, DepartmentMemberListBean, DepartmentMemberListBean> { t1, t2 ->
                        t2.departmentId = t1
                        t2
                    })
        }
        .retry(1)
        .subscribe(object : Observer<DepartmentMemberListBean> {
            override fun onNext(t: DepartmentMemberListBean) {
                ConstantsPara.departmentMemberMap.put(t.departmentId, t.userlist)
            }

            override fun onSubscribe(d: Disposable) {
                addDisposable(d)
            }

            override fun onError(e: Throwable) {
                e.printStackTrace()
            }

            override fun onComplete() {
                println("getDepartmentInfo onComplete:\n${ConstantsPara.departmentMemberMap.keys.forEach { println("departId: $it") }}")
//                        sendTextMessage(ConstantsPara.defaultNoticeUserName, "test from server")
            }
        })

5. 發(fā)送釘釘消息

/**
* 向指定用戶[targetUserName]發(fā)送文本內(nèi)容[message]
* 若目標(biāo)用戶名[targetUserName]為空,則發(fā)送給指定部門[departmentId]所有人,比如gitlab merge請求通過時(shí),通知所有人
* */
fun sendTextMessage(targetUserName: String? = null, message: String = "", departmentId: Int = 1) {
    ConstantsPara.departmentMemberMap[departmentId]?.apply {
        stream().filter { targetUserName.isNullOrBlank() or it.name.equals(targetUserName, true) }
                .forEach {
                    val textBean = MessageTextBean().apply {
                        touser = it.userid
                        agentid = ConstantsPara.dd_agent_id
                        msgtype = MessageType.TEXT
                        text = MessageTextBean.TextBean().apply {
                            content = message
                        }
                    }
                    apiService.sendTextMessage(textBean)
                            .subscribeOn(Schedulers.io())
                            .subscribe(object : Observer<MessageResponseBean> {
                                override fun onComplete() {
                                }

                                override fun onSubscribe(d: Disposable) {
                                    addDisposable(d)
                                }

                                override fun onNext(t: MessageResponseBean) {
                                    println("${msec2date()} sendTextMessage $t")
                                }

                                override fun onError(e: Throwable) {
                                    e.printStackTrace()
                                }
                            })
                }
    }
}

其他說明

  1. 釘釘消息有個(gè) 限制, 因此我在所有消息文本中添加服務(wù)器當(dāng)前時(shí)間,盡量確保每條消息都不同:

forbiddenUserId: 因發(fā)送消息過于頻繁或超量而被流控過濾后實(shí)際未發(fā)送的userid。未被限流的接收者仍會被成功發(fā)送。限流規(guī)則包括:1这敬、給同一用戶發(fā)相同內(nèi)容消息一天僅允許一次;2奥邮、如果是ISV接入方式,給同一用戶發(fā)消息一天不得超過50次纵竖;如果是企業(yè)接入方式漠烧,此上限為500。

  1. jira的hook信息若是存在 changelog 則表明有用戶修改了issue的狀態(tài)或者內(nèi)容,另外, issuse.comment 一定存在, 數(shù)組 comments 存儲了用戶提交的所有備注信息,按時(shí)間先后順序排列;
  2. accessToken的有效期為7200秒,因此項(xiàng)目中需要定時(shí)刷新token;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末靡砌,一起剝皮案震驚了整個(gè)濱河市已脓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌通殃,老刑警劉巖度液,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異画舌,居然都是意外死亡堕担,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門曲聂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霹购,“玉大人,你說我怎么就攤上這事朋腋∑敫恚” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵旭咽,是天一觀的道長贞奋。 經(jīng)常有香客問我,道長穷绵,這世上最難降的妖魔是什么轿塔? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮仲墨,結(jié)果婚禮上勾缭,老公的妹妹穿的比我還像新娘。我一直安慰自己目养,他們只是感情好漫拭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著混稽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上匈勋,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天礼旅,我揣著相機(jī)與錄音,去河邊找鬼洽洁。 笑死痘系,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的饿自。 我是一名探鬼主播汰翠,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼昭雌!你這毒婦竟也來了复唤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤烛卧,失蹤者是張志新(化名)和其女友劉穎佛纫,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體总放,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呈宇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了局雄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甥啄。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖炬搭,靈堂內(nèi)的尸體忽然破棺而出蜈漓,到底是詐尸還是另有隱情,我是刑警寧澤尚蝌,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布迎变,位于F島的核電站,受9級特大地震影響飘言,放射性物質(zhì)發(fā)生泄漏衣形。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一姿鸿、第九天 我趴在偏房一處隱蔽的房頂上張望谆吴。 院中可真熱鬧,春花似錦苛预、人聲如沸句狼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腻菇。三九已至胳螟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間筹吐,已是汗流浹背糖耸。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丘薛,地道東北人嘉竟。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像洋侨,于是被迫代替她去往敵國和親舍扰。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理希坚,服務(wù)發(fā)現(xiàn)边苹,斷路器,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,163評論 25 707
  • 第一杯酒吏够,陽光明媚勾给,窗外的青藤爬進(jìn)了我的眼。 第二杯酒锅知,春風(fēng)輕漾播急,葉梢輕拂著我的眉。 第三杯酒售睹,鳥兒鳴叫桩警,輕啄著我...
    瑤幺兒閱讀 1,489評論 0 0
  • 最近一個(gè)周,每天早上步行四十分鐘到上班地點(diǎn)昌妹。最初的目的是減肥捶枢。走著走著發(fā)現(xiàn)生活狀態(tài)都開始改變,我會惦記著好好吃早餐...
    龜苓膏味的菜菜閱讀 159評論 0 0
  • 有這么一位偶像,每一次和她的相遇都教會了我許許多多固歪。 即使在后臺累到睡著蒜鸡,但是為了大家,為了她的粉絲們 不管再怎么...
    曾艷芬微光站閱讀 367評論 0 1