前提
搭建釘釘應(yīng)答機(jī)器人,需要先準(zhǔn)備或擁有以下權(quán)限:
- 釘釘企業(yè)的管理員或子管理員(如果不是企業(yè)管理員,可以自己創(chuàng)建一個企業(yè),很方便的)
- 有公網(wǎng)通信地址(內(nèi)網(wǎng)穿透也可以);
釘釘群機(jī)器人開發(fā)文檔:https://developers.dingtalk.com/document/app/overview-of-group-robots
創(chuàng)建「機(jī)器人」應(yīng)用
登錄「釘釘開發(fā)者后臺」,選擇「應(yīng)用開發(fā)」——「企業(yè)內(nèi)部開發(fā)」—— 「機(jī)器人」
輸入好機(jī)器人的基本信息之后,就會生成創(chuàng)建一個「釘釘機(jī)器人」
我們的后端應(yīng)用通過其提供的「agentid」、「appkey」、「appsecret」就能夠與釘釘機(jī)器人進(jìn)行通信。
接收消息
在釘釘機(jī)器人的設(shè)定中,當(dāng)用戶@機(jī)器人時,釘釘會通過機(jī)器人開發(fā)者的服務(wù)器地址,用 post 請求方法把消息內(nèi)容發(fā)送出去,其 http header 如下所示:
1
2
3
4
5
|
{ "content-type" : "application/json; charset=utf-8" , "timestamp" : "1577262236757" , "sign" : "xxxxxxxxxx" } |
其中,timestamp
是消息發(fā)送時的時間戳,sign
是簽名值,我們需要對這兩個值進(jìn)行校驗(yàn)。
如果timestamp
與系統(tǒng)當(dāng)前時間相差1小時以上,則為非法請求。
如果sign
簽名值與后臺計(jì)算的值不一樣,也為非法請求。
其中sign
簽名值的計(jì)算方法為:header中的timestamp + “\n” + 機(jī)器人的appsecret當(dāng)做簽名字符串,使用hmacsha256算法計(jì)算簽名,然后進(jìn)行base64 encode,得到最終的簽名值。
其 python 實(shí)現(xiàn)代碼如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
|
import hmac import hashlib import base64 timestamp = '1577262236757' app_secret = 'this is a secret' app_secret_enc = app_secret.encode( 'utf-8' ) string_to_sign = '{}\n{}' . format (timestamp, app_secret) string_to_sign_enc = string_to_sign.encode( 'utf-8' ) hmac_code = hmac.new(app_secret_enc, string_to_sign_enc, digestmod = hashlib.sha256).digest() sign = base64.b64encode(hmac_code).decode( 'utf-8' ) print (sign) |
其發(fā)送的消息格如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
{ "conversationid" : "xxx" , "atusers" : [ { "dingtalkid" : "xxx" , "staffid" : "xxx" } ], "chatbotcorpid" : "dinge8a565xxxx" , "chatbotuserid" : "$:lwcp_v1:$cxxxxx" , "msgid" : "msg0xxxxx" , "sendernick" : "楊xx" , "isadmin" : true, "senderstaffid" : "user123" , "sessionwebhookexpiredtime" : 1613635652738 , "createat" : 1613630252678 , "sendercorpid" : "dinge8a565xxxx" , "conversationtype" : "2" , "senderid" : "$:lwcp_v1:$ff09gixxxxx" , "conversationtitle" : "機(jī)器人測試-test" , "isinatlist" : true, "sessionwebhook" : "https://oapi.dingtalk.com/robot/sendbysession?session=xxxxx" , "text" : { "content" : " 你好" }, "msgtype" : "text" } |
其中,一些參數(shù)的說明如下圖所示:
我們接收到釘釘?shù)南⒑螅梢愿鶕?jù)實(shí)際的業(yè)務(wù)需求解析出相應(yīng)字段的數(shù)據(jù)來進(jìn)行處理。
響應(yīng)消息
釘釘機(jī)器人支持我們通過「text」、「markdown」、「整體跳轉(zhuǎn)actioncard」、「獨(dú)立跳轉(zhuǎn)actioncard」和「feedcard」這5種消息類型發(fā)送消息到群里。
下面我們通過實(shí)際的代碼來展示接收釘釘機(jī)器人的消息,以及發(fā)送 5 種消息類型到釘釘群里。
創(chuàng)建一個后端應(yīng)用
接下來,我們通過創(chuàng)建一個 django 應(yīng)用來接收的處理用戶發(fā)送給釘釘機(jī)器人的消息。
首先,創(chuàng)建一個 django 項(xiàng)目和應(yīng)用:
1
2
|
django - admin startproject ddrobot python manage.py startapp app_robot |
然后打開 “c:\ddrobot\ddrobot\settings.py” 文件,修改 allowed_hosts 變量:
1
|
allowed_hosts = [ '*' ] |
將 app_robot 添加到 installed_apps 變量列表中:
1
2
3
4
5
6
7
8
9
|
installed_apps = [ 'django.contrib.admin' , 'django.contrib.auth' , 'django.contrib.contenttypes' , 'django.contrib.sessions' , 'django.contrib.messages' , 'django.contrib.staticfiles' , 'app_robot' , ] |
創(chuàng)建校驗(yàn)時間戳和簽名函數(shù)
因?yàn)獒斸敊C(jī)器人會在請求頭里面?zhèn)魅?code>timestamp時間戳和sign
簽名供我們對請求的合法性進(jìn)行校驗(yàn),所以為了機(jī)器人的安全,我們需要編寫 2 個函數(shù)對它們進(jìn)行校驗(yàn)(在ddrobot/app_robot/views.py
文件中進(jìn)行)。
首先,是時間戳的校驗(yàn):
1
2
3
4
5
6
|
def check_timestamp(timestamp): now_timestamp = int (time.time() * 1000 ) if now_timestamp - int (timestamp) > 3600000 : return false else : return true |
然后是簽名值的校驗(yàn),簽名值的計(jì)算方法和示例代碼釘釘已經(jīng)提供,我們借用即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
def check_sign(timestamp,sign): import hmac import hashlib import base64 # now_timestamp = str(int(time.time()*1000)) app_secret = 'tetlgs3xzvlp6z99mxvgvpinouyjqfskj3jlb7crfdjrsj3_77e-kxhlibbgbnjx' app_secret_enc = app_secret.encode( 'utf-8' ) string_to_sign = '{}\n{}' . format (timestamp, app_secret) string_to_sign_enc = string_to_sign.encode( 'utf-8' ) hmac_code = hmac.new(app_secret_enc, string_to_sign_enc, digestmod = hashlib.sha256).digest() new_sign = base64.b64encode(hmac_code).decode( 'utf-8' ) # print(sign) # print(new_sign) if sign = = new_sign: return true else : return false |
對于這 2 個值,校驗(yàn)成功我們都返回 true,校驗(yàn)失敗我們都返回 false。
創(chuàng)建視圖函數(shù)
接著,我們創(chuàng)建一個視圖函數(shù),用來接收釘釘傳輸過來的消息,以及響應(yīng)給釘釘。
1
2
3
|
@csrf_exempt def resp_dd(request): pass |
在 resp_dd() 函數(shù)中,首先從請求頭中讀取釘釘傳輸過來的時間戳和簽名值,然后進(jìn)行校驗(yàn):
1
2
3
4
5
6
7
8
9
10
|
@csrf_exempt def resp_dd(request): timestamp = request.headers.get( 'timestamp' ,'') sign = request.headers.get( 'sign' ,'') # 校驗(yàn)時間戳 if check_timestamp(timestamp) is false: return jsonresponse({ 'status' :false, 'data' : '非法請求' }) # 校驗(yàn)簽名 if check_sign(timestamp,sign) is false: return jsonresponse({ 'status' :false, 'data' : '非法請求' }) |
若是時間戳和簽名值校驗(yàn)無誤,我們繼續(xù)從請求 body 里面獲取消息信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@csrf_exempt def resp_dd(request): timestamp = request.headers.get( 'timestamp' ,'') sign = request.headers.get( 'sign' ,'') # 校驗(yàn)時間戳 if check_timestamp(timestamp) is false: return jsonresponse({ 'status' :false, 'data' : '非法請求' }) # 校驗(yàn)簽名 if check_sign(timestamp,sign) is false: return jsonresponse({ 'status' :false, 'data' : '非法請求' }) body = json.loads(request.body) # 獲取用戶id # user_id = body['senderstaffid'] 機(jī)器人上線后才會返回 user_id = body[ 'senderid' ] # 獲取發(fā)送的消息 msg_type = body[ 'msgtype' ] if msg_type = = 'text' : content = body[ 'text' ][ 'content' ] |
目前釘釘機(jī)器人只支持text
文本內(nèi)容的消息接收,所以在此處我們只對消息類型為text
的消息進(jìn)行處理。
獲取到釘釘機(jī)器人發(fā)送過來的信息之后,我們就可以根據(jù)自己的業(yè)務(wù)邏輯進(jìn)行處理,然后返回特定的消息類型了。
在這里,我們只對消息進(jìn)行簡單的處理:
-
當(dāng)發(fā)送來的消息文本為
text
時,機(jī)器人回復(fù)文本消息; -
當(dāng)發(fā)送來的消息文本為
markdown
時,機(jī)器人回復(fù)一個 markdown 的示例消息; -
當(dāng)發(fā)送來的消息文本為
整體跳轉(zhuǎn)
時,機(jī)器人回復(fù)一個「整體跳轉(zhuǎn)卡片」的示例消息; -
當(dāng)發(fā)送來的消息文本為
獨(dú)立跳轉(zhuǎn)
時,機(jī)器人回復(fù)一個「獨(dú)立跳轉(zhuǎn)卡片」的示例消息; -
當(dāng)發(fā)送來的消息文本為
feed
時,機(jī)器人回復(fù)一個「feedcard」的示例消息;
先來定義 5 個不同消息類型的響應(yīng)格式。
文本消息類型
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 響應(yīng)文字 resp_text = { "at" : { "atuserids" : [ user_id ], "isatall" : false }, "text" : { "content" : "你剛剛發(fā)的消息是:[{}]" . format (content) }, "msgtype" : "text" } |
markdown消息類型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 響應(yīng)markdown resp_markdown = { "msgtype" : "markdown" , "markdown" : { "title" : "州的先生機(jī)器人助理" , "text" : "## 這是什么? \n 這是一個釘釘機(jī)器人 \n " }, "at" : { "atuserids" : [ user_id ], "isatall" : false } } |
整體跳轉(zhuǎn)卡片消息類型:
1
2
3
4
5
6
7
8
9
10
|
# 響應(yīng)整體跳轉(zhuǎn)actioncard resp_actioncard = { "msgtype" : "actioncard" , "actioncard" : { "title" : "州的先生 python 實(shí)戰(zhàn)教程合集" , "text" : " \n #### 州的先生 python 實(shí)戰(zhàn)教程合集 \n\n 學(xué)習(xí)python的一個好方法就是用實(shí)際的項(xiàng)目來熟練語言" , "singletitle" : "閱讀全文" , "singleurl" : "http://mrdoc.zmister.com" } } |
獨(dú)立跳轉(zhuǎn)卡片消息類型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
resp_actioncard_2 = { "msgtype" : "actioncard" , "actioncard" : { "title" : "州的先生 python 實(shí)戰(zhàn)教程合集" , "text" : " \n #### 州的先生 python 實(shí)戰(zhàn)教程合集 \n\n 學(xué)習(xí)python的一個好方法就是用實(shí)際的項(xiàng)目來熟練語言" , "hideavatar" : "0" , "btnorientation" : "0" , "btns" : [ { "title" : "去看看" , "actionurl" : "http://mrdoc.zmister.com" }, { "title" : "不感興趣" , "actionurl" : "https://zmister.com/" } ] } } |
feed卡片消息類型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# 響應(yīng)feedcard resp_feedcard = { "msgtype" : "feedcard" , "feedcard" : { "links" : [ { "title" : "時代的火車向前開1" , "messageurl" : "http://mrdoc.zmister.com" , "picurl" : "https://img.alicdn.com/tfs/tb1nwmbel9tbunjy1zbxxxpepxa-2400-1218.png" }, { "title" : "時代的火車向前開2" , "messageurl" : "https://zmister.com/" , "picurl" : "https://img.alicdn.com/tfs/tb1nwmbel9tbunjy1zbxxxpepxa-2400-1218.png" } ] } } |
其他的消息響應(yīng)空:
1
2
3
4
|
# 響應(yīng)空,不回復(fù) resp_empty = { "msgtype" : "empty" } |
定義好幾個消息響應(yīng)類型數(shù)據(jù)后,我們對獲取到的 content 變量進(jìn)行判斷返回響應(yīng)即可:
1
2
3
4
5
6
7
8
9
10
11
12
|
if content[ 1 :] = = 'text' : return jsonresponse(resp_text) elif content[ 1 :] = = 'markdown' : return jsonresponse(resp_markdown) elif content[ 1 :] = = '整體跳轉(zhuǎn)' : return jsonresponse(resp_actioncard) elif content[ 1 :] = = '獨(dú)立跳轉(zhuǎn)' : return jsonresponse(resp_actioncard_2) elif content[ 1 :] = = 'feed' : return jsonresponse(resp_feedcard) else : return jsonresponse(resp_empty) |
這樣,我們這個釘釘機(jī)器人的后端處理函數(shù)就寫好了。
配置路由
寫好視圖函數(shù)之后,我們配置一下這個函數(shù)的 url 路由。
在 “c:\ddrobot\ddrobot\urls.py” 文件中把內(nèi)容修改為如下代碼所示:
1
2
3
4
5
6
7
8
|
from django.contrib import admin from django.urls import path from app_robot import views urlpatterns = [ path( 'admin/' , admin.site.urls), path( 'dd_robot/' ,views.resp_dd, name = "resp_dd" ), ] |
這樣 http://ip地址/dd_robot/ 就是釘釘機(jī)器人的消息接收地址。
配置釘釘機(jī)器人
回到釘釘開發(fā)者平臺的網(wǎng)頁,在釘釘機(jī)器人的「開發(fā)管理」頁面,我們需要把服務(wù)器的出口ip 和釘釘機(jī)器人的消息接收地址填寫好:
調(diào)試釘釘機(jī)器人
在配置好機(jī)器人的「服務(wù)器出口ip」與「消息接收地址」之后,我們點(diǎn)擊網(wǎng)頁菜單的「版本管理與發(fā)布」,點(diǎn)擊「調(diào)試按鈕」,進(jìn)入到釘釘機(jī)器人的調(diào)試群:
這回在「釘釘機(jī)器人名稱-test」的群里面添加創(chuàng)建的釘釘機(jī)器人:
我們可以在這個群里面@創(chuàng)建的群機(jī)器人進(jìn)行測試:
在測試沒問題之后,我們就可以點(diǎn)擊「上線」按鈕。釘釘機(jī)器人上線之后,就可以在釘釘群內(nèi)添加這個機(jī)器人。
這樣,我們就實(shí)現(xiàn)了從 0 到 1 使用 python 開發(fā)釘釘群機(jī)器人。
基本的框架和流程大抵如此,具體的業(yè)務(wù)邏輯則需要根據(jù)不同的需求進(jìn)行額外處理。比如:
查詢天氣,就得解析消息中的城市,然后請求天氣接口獲取天氣數(shù)據(jù),進(jìn)行消息的響應(yīng);
淘寶客,就得解析消息中的文本,進(jìn)行分詞或其他處理,再查詢數(shù)據(jù)庫中的商品優(yōu)惠券數(shù)據(jù)或是直接請求淘客接口獲取商品優(yōu)惠券數(shù)據(jù);
員工績效,就得接入釘釘?shù)膽?yīng)用開發(fā),借助釘釘開發(fā)的用戶接口進(jìn)行數(shù)據(jù)查詢和響應(yīng)。
到此這篇關(guān)于教你如何使用python開發(fā)一個釘釘群應(yīng)答機(jī)器人的文章就介紹到這了,更多相關(guān)python開發(fā)釘釘群應(yīng)答機(jī)器人內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://blog.csdn.net/y747702801/article/details/117919659