說明:該篇部落格是部落客一字一碼編寫的,實屬不易,請尊重原創,謝謝大家!
在上一篇部落格中已經驗證了伺服器有效性: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)