微信公眾號開發之接收與傳送訊息
說明:該篇部落格是博主一字一碼編寫的,實屬不易,請尊重原創,謝謝大家!
在上一篇部落格中已經驗證了伺服器有效性:https://blog.csdn.net/qq_41782425/article/details/85321424
一丶概論
-
公眾號接收與傳送訊息
驗證URL有效性成功後即接入生效,成為開發者。如果公眾號型別為服務號(訂閱號只能使用普通訊息介面),可以在公眾平臺網站中申請認證,認證成功的服務號將獲得眾多介面許可權,以滿足開發者需求。
此後使用者每次向公眾號傳送訊息、或者產生自定義選單點選事件時,開發者填寫的伺服器配置URL將得到微信伺服器推送過來的訊息和事件,然後開發者可以依據自身業務邏輯進行響應,例如回覆訊息等。
使用者向公眾號傳送訊息時,公眾號方收到的訊息傳送者是一個OpenID,是使用使用者微訊號加密後的結果,每個使用者對每個公眾號有一個唯一的OpenID。
1.接收普通訊息
當普通微信使用者向公眾賬號發訊息時,微信伺服器將POST訊息的XML資料包到開發者填寫的URL上。
微信伺服器在五秒內收不到響應會斷掉連線,並且重新發起請求,總共重試三次。假如伺服器無法保證在五秒內處理並回復,可以直接回復空串,微信伺服器不會對此作任何處理,並且不會發起重試。
各訊息型別的推送使用XML資料包結構,如:
<xml> <ToUserName><![CDATA[gh_866835093fea]]></ToUserName> <FromUserName><![CDATA[ogdotwSc_MmEEsJs9-ABZ1QL_4r4]]></FromUserName> <CreateTime>1478317060</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[你好]]></Content> <MsgId>6349323426230210995</MsgId> </xml>
注意:<![CDATA
與 ]]>
括起來的資料不會被xml解析器解析
2.普通訊息類別
- 文字訊息
- 圖片訊息
- 語音訊息
- 視訊訊息
- 小視訊訊息
- 地理位置訊息
- 連結訊息
文字訊息
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId> </xml>
3. 回覆的訊息型別
- 文字訊息
- 圖片訊息
- 語音訊息
- 視訊訊息
- 音樂訊息
- 圖文訊息
回覆文字訊息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
</xml>
注:開發文件可以到 https://mp.weixin.qq.com/wiki/home/index.html 進行閱讀檢視
二丶程式碼實現
需求:我們現在來實現一個針對文字訊息的收發程式。實現的業務邏輯,關注者發什麼內容,我們就傳回給什麼內容。
說明:微信伺服器推送訊息還是往/wechat/8007,所以在之前程式碼上進行修改即可
1.開發步驟
- step1 如何區分微信伺服器發過來的是第一次的驗證操作還是訊息操作
- 驗證操作為GET請求,訊息操作為POST請求
@app.route("/wechat8007", methods=["GET", "POST"])
- step2 引數變更,echostr引數只是在第一次驗證的時候需要,無論是POST請求還是GET請求這三種引數都必要要,因為需要驗證是不是微信伺服器傳送過來的資料
signature = request.args.get("signature")
timestamp = request.args.get("timestamp")
nonce = request.args.get("nonce")
- step3 對微信伺服器傳送的請求進行驗證判斷,如果是GET請求,那麼代表是第一次的驗證操作,那麼就需要獲取echostr欄位的內容,如果內容為空則丟擲404,存在則返回echostr
if request.method == "GET":
# 表示是第一次接入微信伺服器的驗證
echostr = request.args.get("echostr")
if not echostr:
abort(404)
return echostr
- step4 如果為POST請求,那麼代表為微信伺服器轉發訊息過來,獲取請求中的data xml資料 ,資料為空丟擲400
elif request.method == "POST":
# 表示微信伺服器轉發訊息過來
xml_str = request.data
if not xml_str:
abort(400)
- step5 將對獲取的資料進行解析,通過xmltodict模組中的parse方法將字串型別的xml資料,轉換成字典型別的xml格式資料,因為xml資料最外層有一個<xml></xml>標籤,通過get方式獲取標籤裡的內容,再通過get獲取內容中的MsgType訊息型別欄位的值
# 對xml字串進行解析
xml_dict = xmltodict.parse(xml_str)
xml_dict = xml_dict.get("xml")
# 提取訊息型別
msg_type = xml_dict.get("MsgType")
- step6 對訊息型別進行判斷,如果為text文字訊息,則返回文字訊息,不是文字訊息還是返回文字訊息,這裡可以拓展為(image,voice,video等等可以檢視開發文件),這裡為了演示,就簡單寫寫
if msg_type == "text":
# 表示傳送的是文字訊息
# 構造返回值,經由微信伺服器回覆給使用者的訊息內容
resp_dict = {
"xml": {
"ToUserName": xml_dict.get("FromUserName"),
"FromUserName": xml_dict.get("ToUserName"),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": "taogang say:" + xml_dict.get("Content")
}
}
else:
resp_dict = {
"xml": {
"ToUserName": xml_dict.get("FromUserName"),
"FromUserName": xml_dict.get("ToUserName"),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": "Dear I Love you so much"
}
}
- step7 最後將我們構造的響應返回值通過unparse方法轉換成xml格式的字串,返回給微信伺服器
# 將字典轉換為xml字串
resp_xml_str = xmltodict.unparse(resp_dict)
# 返回訊息資料給微信伺服器
return resp_xml_str
2.部署測試
- step1 將程式碼推送到伺服器上
- step2 在伺服器上進入虛擬環境,執行此程式
- step3 進入微信公眾平臺,用手機掃描測試號二維碼,進行關注測試
掃碼後進行關注
關注後進入此公眾號,公眾號則傳送我們在開發步驟step 6,Dear I Love you so much 訊息內容
回到伺服器程式執行日誌上,顯示為POST請求,說明程式邏輯完全沒問題
公眾號測試平臺使用者列表將我的微信新增上去了
- step4 測試,在關注的此公眾中,進行訊息(文字,表情,語言,圖片,視訊)傳送,當訊息型別為文字時,即返回此訊息內容,如果不是都是返回文字型別,內容為Dear I Love you so much
此時的伺服器程式碼執行日誌
3.完整程式碼
# coding:utf-8
from flask import Flask, request, abort, render_template
import hashlib
import xmltodict, time
# 常量
# 微信的token令牌
WECHAT_TOKEN = "cdtaogang"
app = Flask(__name__)
@app.route("/wechat8007", methods=["GET", "POST"])
def wechat():
"""對接微信公眾號伺服器"""
# 接收微信伺服器傳送的引數
signature = request.args.get("signature")
timestamp = request.args.get("timestamp")
nonce = request.args.get("nonce")
if not all([signature, timestamp, nonce]):
abort(400)
# 按照微信的流程進行計算簽名
li = [WECHAT_TOKEN, timestamp, nonce]
# 排序
li.sort()
# 拼接字串
tmp_str = ''.join(li)
# 進行sha1加密, 得到正確的簽名值
sign = hashlib.sha1(tmp_str).hexdigest()
# 將自己計算的簽名值與請求的簽名引數進行對比,如果相同,則證明請求來自微信伺服器
if sign != signature:
# 表示請求不是微信發的
abort(403)
else:
# 表示是微信傳送的請求
if request.method == "GET":
# 表示是第一次接入微信伺服器的驗證
echostr = request.args.get("echostr")
if not echostr:
abort(404)
return echostr
elif request.method == "POST":
# 表示微信伺服器轉發訊息過來
xml_str = request.data
if not xml_str:
abort(400)
# 對xml字串進行解析
xml_dict = xmltodict.parse(xml_str)
xml_dict = xml_dict.get("xml")
# 提取訊息型別
msg_type = xml_dict.get("MsgType")
if msg_type == "text":
# 表示傳送的是文字訊息
# 構造返回值,經由微信伺服器回覆給使用者的訊息內容
resp_dict = {
"xml": {
"ToUserName": xml_dict.get("FromUserName"),
"FromUserName": xml_dict.get("ToUserName"),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": "taogang say:" + xml_dict.get("Content")
}
}
else:
resp_dict = {
"xml": {
"ToUserName": xml_dict.get("FromUserName"),
"FromUserName": xml_dict.get("ToUserName"),
"CreateTime": int(time.time()),
"MsgType": "text",
"Content": "Dear I Love you so much"
}
}
# 將字典轉換為xml字串
resp_xml_str = xmltodict.unparse(resp_dict)
# 返回訊息資料給微信伺服器
return resp_xml_str
if __name__ == '__main__':
app.run(port=8007, debug=True)