1. 程式人生 > >微信公眾號開發之接收與傳送訊息

微信公眾號開發之接收與傳送訊息

說明:該篇部落格是博主一字一碼編寫的,實屬不易,請尊重原創,謝謝大家!

在上一篇部落格中已經驗證了伺服器有效性: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.普通訊息類別

  1. 文字訊息
  2. 圖片訊息
  3. 語音訊息
  4. 視訊訊息
  5. 小視訊訊息
  6. 地理位置訊息
  7. 連結訊息

文字訊息

 <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. 回覆的訊息型別

  1. 文字訊息
  2. 圖片訊息
  3. 語音訊息
  4. 視訊訊息
  5. 音樂訊息
  6. 圖文訊息

回覆文字訊息

<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)