[TOC]html
公司使用了Gitlab,Jira等工具來管理,溝通方面主要是釘釘,但鬱悶的是各系統相互獨立,而我已經習慣了前公司那種方式:
有bug的時候會自動發送消息到聊天框中,而不是目前這樣,須要開發人員手動定時去刷新jira頁面才能知道,效率低下;javagitlab也是同樣,有merge請求的時候,我但願不須要別人提醒我去審覈代碼,而是gitlab直接發送merge消息到我釘釘便可;android
可能其餘同事習慣郵件通知吧,公司並沒有打通各系統與釘釘聯繫的計劃,因此我只能本身擼一套了,我不是專職後端,輕噴,功可以我用就好;git
原理: 利用各系統自帶的
Webhook
功能, 在觸發指定操做時,發送一條hook
信息到咱們的服務器上, 服務器作出處理後轉發消息到對應人員的釘釘上;github
gitlab
上啓用 Webhooks
通知(可指定要 Webhooks
的操做,這裏hook了 merge
操做);jira
也是相似;
post
請求的 head
信息來區分不一樣系統發來的 hook
消息:
gitlab
的 merge
請求包含: X-Gitlab-Event:Merge Request Hook
jira
的 hook
請求包含: user-agent:Atlassian HttpClient0.17.3 / JIRA-6.3.15 (6346) / Default
server
端獲取釘釘的人員信息,並調用其 企業會話消息接口 發送指定消息;員工id
和 企業應用id
以及 access_token
,而 獲取access_token 須要 CorpId
和 CorpSecret
(兩者是企業的惟一標識);
公司固然不可能對我的開放其
CorpId
等信息 ,所以仍是本身註冊一個企業,建立部門並添加你想通知的人員做爲部門員工便可,這樣也能獲取員工的 通信錄詳情 , 獲得其userId
,從而發送消息到其釘釘上;web
微應用
就能夠找到 Step 1 -- 註冊釘釘企業
的 連接;step
引導操做註冊企業並添加部門和員工,而後進入 釘釘管理後臺;工做臺
標籤頁(即上圖中的 企業應用
,現已更名,偷懶就不從新截圖了╮( ̄▽ ̄)╭) , 點擊下方的 自建應用 ,按需填寫信息;設置
便可查看到微應用的 AgentID
;CorpID
和 CorpSecret
在通信錄root部門中添加全部人,以便發送消息到特定用戶時能夠從root部門中經過查詢用戶姓名獲得用戶id;
gitlab會特殊一點,有些操做須要通知項目全部成員,因此還須要根據項目來建立部門:後端
https://gitlab.lynxz.org/demo-android/detail-android
,則表示項目名稱(name
) 爲: detail-android
,項目所在空間(namespace
)爲: demo-android
demo_android
,而後建立其子部門 detail_android
;-
,所以建立時改成 _
替代;gitlab merge
經過時會通知全部匹配的部門成員;備註: 更新釘釘通信錄後,記得及時通知 server
刷新本地數據,本版支持經過url出發刷新命令,直接訪問以下網址便可(其中 yourServerHost
是war包運行後的訪問地址): {yourServerHost}/action/updateDepartmentInfo
api
// kotlin
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 企業id * @param secret corpsecret 企業應用的憑證密鑰 * */
@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>
/** * [獲取指定部門的成員信息,默認獲取所有成員](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>
/** * [向指定用戶發送普通文本消息](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>
}
複製代碼
// kotlin
// 給請求添加統一的query參數: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())
}
// 給請求添加統一的header參數: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)
複製代碼
// kotlin
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 ?: ""
}
})
複製代碼
ConstantsPara.departmentNameMap
中,是一個 HashMap
,記錄部門id及名稱;ConstantsPara.departmentMemberMap
中, 也是一個 HashMap
, 記錄部門id及部門中的全部成員信息;備註:數組
gitlab
項目名稱對應,須要羣發時經過項目名稱查找對應的部門id; (目的: 建立 gitlab 項目 與 釘釘部門之間的映射關係);1
的是公司的根部門(root部門),要將全部人員都添加進去,以便在須要通知指定人員時,能從root部門成員中經過查找用戶姓名獲取其用戶id,而後發出釘釘消息;// kotlin
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")
}
})
複製代碼
/** * kotlin * 向指定用戶[targetUserName]發送文本內容[message] * 若目標用戶名[targetUserName]爲空,則發送給指定部門[departmentId]全部人,好比gitlab merge請求經過時,通知全部人 * */
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()
}
})
}
}
}
複製代碼
forbiddenUserId: 因發送消息過於頻繁或超量而被流控過濾後實際未發送的userid。未被限流的接收者仍會被成功發送。
限流規則包括:
一、給同一用戶發相同內容消息一天僅容許一次;
二、若是是ISV接入方式,給同一用戶發消息一天不得超過50次;若是是企業接入方式,此上限爲500。
changelog
則代表有用戶修改了issue的狀態或者內容,另外, issuse.comment
必定存在, 數組 comments
存儲了用戶提交的全部備註信息,按時間前後順序排列;聊天機器人
, 直接支持幾個平臺的 webhook 消息, 不過只能轉發到 羣
中, 對不須要關注的成員來講, 就是垃圾消息, 並且消息格式固定死板,靈活性不強,具體操做可到 釘釘開放平臺 搜索 機器人
查看;