# 消息推送
消息推送是開放平臺(tái)推出的一種主動(dòng)推送服務(wù),基于該推送服務(wù),開發(fā)者及時(shí)獲取開放平臺(tái)相關(guān)信息,無(wú)需調(diào)用API。 目前提供三種方式接入:
# 開發(fā)者服務(wù)器接收消息推送
總數(shù)據(jù)鏈路如圖所示:
# 消息推送服務(wù)器配置
消息推送服務(wù)于小程序、公眾號(hào)、小游戲、視頻號(hào)小店、第三方平臺(tái),這里介紹小程序平臺(tái)的配置。
# 填寫相關(guān)信息
登陸小程序管理后臺(tái),在「開發(fā)」-「開發(fā)管理」-「消息推送配置」中,需填寫以下信息:
- URL服務(wù)器地址:開發(fā)者用來(lái)接收微信消息和事件的接口 URL,必須以 http:// 或 https:// 開頭,分別支持 80 端口和 443 端口。
- Token令牌:用于簽名處理,下文會(huì)介紹相關(guān)流程。
- EncodingAESKey:將用作消息體加解密密鑰。
- 消息加解密方式:
- 明文模式:不使用消息加解密,明文發(fā)送,安全系數(shù)較低,不建議使用。
- 兼容模式:明文、密文共存,不建議使用。
- 安全模式:使用消息加解密,純密文,安全系數(shù)高,強(qiáng)烈推薦使用。
- 數(shù)據(jù)格式:消息體的格式,可選XML或JSON。
# 發(fā)起驗(yàn)證
點(diǎn)擊“提交”后,微信服務(wù)器會(huì)對(duì)開發(fā)者服務(wù)器發(fā)起驗(yàn)證,請(qǐng)?jiān)谔峤磺鞍匆韵路绞介_發(fā): 微信服務(wù)器將發(fā)送GET請(qǐng)求到填寫的服務(wù)器地址URL上, GET請(qǐng)求攜帶參數(shù)如下表所示:
| 參數(shù) | 描述 |
|---|---|
| signature | 簽名 |
| timestamp | 時(shí)間戳 |
| nonce | 隨機(jī)數(shù) |
| echostr | 隨機(jī)字符串 |
其中,signature簽名的生成方式是:
- 將Token、timestamp、nonce三個(gè)參數(shù)進(jìn)行字典序排序。
- 將三個(gè)參數(shù)字符串拼接成一個(gè)字符串進(jìn)行sha1計(jì)算簽名,即可獲得signature。 開發(fā)者需要校驗(yàn)signature是否正確,以判斷請(qǐng)求是否來(lái)自微信服務(wù)器,驗(yàn)簽通過(guò)后,請(qǐng)?jiān)瓨臃祷豦chostr字符串。
舉例:假設(shè)填寫的URL="https://www.qq.com/revice", Token="AAAAA"。
- 推送的URL鏈接:https://www.qq.com/revice?signature=f464b24fc39322e44b38aa78f5edd27bd1441696&echostr=4375120948345356249×tamp=1714036504&nonce=1514711492
- 將token、timestamp、nonce三個(gè)參數(shù)進(jìn)行字典序排序,排序后結(jié)果為:["1514711492","1714036504","AAAAA"]。
- 將三個(gè)參數(shù)字符串拼接成一個(gè)字符串:"15147114921714036504AAAAA"
- 進(jìn)行sha1計(jì)算簽名:f464b24fc39322e44b38aa78f5edd27bd1441696
- 與URL鏈接中的signature參數(shù)進(jìn)行對(duì)比,相等說(shuō)明請(qǐng)求來(lái)自微信服務(wù)器,合法。
- 構(gòu)造回包返回微信,回包消息體內(nèi)容為URL鏈接中的echostr參數(shù)4375120948345356249。
為了便于開發(fā)者調(diào)試,我們提供了URL驗(yàn)證工具供開發(fā)者使用。
開發(fā)者需填寫AccessToken、URL地址、Token,點(diǎn)擊“檢查參數(shù)并發(fā)起驗(yàn)證”后,調(diào)試工具會(huì)發(fā)送GET請(qǐng)求到URL所指的服務(wù)器,并返回相關(guān)調(diào)試信息。
# 接收消息推送
當(dāng)特定消息或事件觸發(fā)時(shí),微信服務(wù)器會(huì)將消息(或事件)的數(shù)據(jù)包以 POST 請(qǐng)求發(fā)送到開發(fā)者配置的 URL,下面以“debug_demo”事件為例,詳細(xì)介紹整個(gè)過(guò)程:
# 消息解密方式為明文模式
- 假設(shè)URL配置為https://www.qq.com/revice, 數(shù)據(jù)格式為JSON,Token="AAAAA"。
- 推送的URL鏈接:https://www.qq.com/recive?signature=899cf89e464efb63f54ddac96b0a0a235f53aa78×tamp=1714037059&nonce=486452656
- 推送的包體:
{
"ToUserName": "gh_97417a04a28d",
"FromUserName": "o9AgO5Kd5ggOC-bXrbNODIiE3bGY",
"CreateTime": 1714037059,
"MsgType": "event",
"Event": "debug_demo",
"debug_str": "hello world"
}
- 校驗(yàn)signature簽名是否正確,以判斷請(qǐng)求是否來(lái)自微信服務(wù)器。
- 將token、timestamp(URL參數(shù)中的)、nonce(URL參數(shù)中的)三個(gè)參數(shù)進(jìn)行字典序排序,排序后結(jié)果為:["1714037059","486452656","AAAAA"]
- 將三個(gè)參數(shù)字符串拼接成一個(gè)字符串:"1714037059486452656AAAAA"
- 進(jìn)行sha1計(jì)算簽名:899cf89e464efb63f54ddac96b0a0a235f53aa78
- 與URL鏈接中的signature參數(shù)進(jìn)行對(duì)比,相等說(shuō)明請(qǐng)求來(lái)自微信服務(wù)器,合法。
- 回包給微信,具體回包內(nèi)容取決于特定接口文檔要求,如無(wú)特定要求,回復(fù)空串或者success即可。
# 消息解密方式為安全模式
- 假設(shè)URL配置為https://www.qq.com/revice, 數(shù)據(jù)格式為JSON,Token="AAAAA",EncodingAESKey="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",小程序Appid="wxba5fad812f8e6fb9"。
- 推送的URL鏈接::https://www.qq.com/recive?signature=6c5c811b55cc85e0e1b54100749188c20beb3f5d×tamp=1714112445&nonce=415670741&openid=o9AgO5Kd5ggOC-bXrbNODIiE3bGY&encrypt_type=aes&msg_signature=046e02f8204d34f8ba5fa3b1db94908f3df2e9b3
- 推送的包體:
{
"ToUserName": "gh_97417a04a28d",
"Encrypt": "+qdx1OKCy+5JPCBFWw70tm0fJGb2Jmeia4FCB7kao+/Q5c/ohsOzQHi8khUOb05JCpj0JB4RvQMkUyus8TPxLKJGQqcvZqzDpVzazhZv6JsXUnnR8XGT740XgXZUXQ7vJVnAG+tE8NUd4yFyjPy7GgiaviNrlCTj+l5kdfMuFUPpRSrfMZuMcp3Fn2Pede2IuQrKEYwKSqFIZoNqJ4M8EajAsjLY2km32IIjdf8YL/P50F7mStwntrA2cPDrM1kb6mOcfBgRtWygb3VIYnSeOBrebufAlr7F9mFUPAJGj04="
}
- 校驗(yàn)msg_signature簽名是否正確,以判斷請(qǐng)求是否來(lái)自微信服務(wù)器。注意:不要使用signature驗(yàn)證!
- 將token、timestamp(URL參數(shù)中的)、nonce(URL參數(shù)中的)、Encrypt(包體內(nèi)的字段)四個(gè)參數(shù)進(jìn)行字典序排序,排序后結(jié)果為: ["+qdx1OKCy+5JPCBFWw70tm0fJGb2Jmeia4FCB7kao+/Q5c/ohsOzQHi8khUOb05JCpj0JB4RvQMkUyus8TPxLKJGQqcvZqzDpVzazhZv6JsXUnnR8XGT740XgXZUXQ7vJVnAG+tE8NUd4yFyjPy7GgiaviNrlCTj+l5kdfMuFUPpRSrfMZuMcp3Fn2Pede2IuQrKEYwKSqFIZoNqJ4M8EajAsjLY2km32IIjdf8YL/P50F7mStwntrA2cPDrM1kb6mOcfBgRtWygb3VIYnSeOBrebufAlr7F9mFUPAJGj04=", "1714112445", "415670741", "AAAAA"]。
- 將四個(gè)參數(shù)字符串拼接成一個(gè)字符串,然后進(jìn)行sha1計(jì)算簽名:046e02f8204d34f8ba5fa3b1db94908f3df2e9b3
- 與URL參數(shù)中的msg_signature參數(shù)進(jìn)行對(duì)比,相等說(shuō)明請(qǐng)求來(lái)自微信服務(wù)器,合法。
- 解密消息體"Encrypt"密文。
- AESKey = Base64_Decode( EncodingAESKey + "=" ),EncodingAESKey 尾部填充一個(gè)字符的 "=", 用 Base64_Decode 生成 32 個(gè)字節(jié)的 AESKey;
- 將Encrypt密文進(jìn)行Base64解碼,得到TmpMsg, 字節(jié)長(zhǎng)度為224
- 將TmpMsg使用AESKey進(jìn)行AES解密,得到FullStr,字節(jié)長(zhǎng)度為205。AES 采用 CBC 模式,秘鑰長(zhǎng)度為 32 個(gè)字節(jié)(256 位),數(shù)據(jù)采用 PKCS#7 填充; PKCS#7:K 為秘鑰字節(jié)數(shù)(采用 32),Buf 為待加密的內(nèi)容,N 為其字節(jié)數(shù)。Buf 需要被填充為 K 的整數(shù)倍。在 Buf 的尾部填充(K - N%K)個(gè)字節(jié),每個(gè)字節(jié)的內(nèi)容 是(K - N%K)。微信團(tuán)隊(duì)提供了多種語(yǔ)言的示例代碼(包括 PHP、Java、C++、Python、C#),請(qǐng)開發(fā)者盡量使用示例代碼,仔細(xì)閱讀技術(shù)文檔、示例代碼及其注釋后,再進(jìn)行編碼調(diào)試。示例下載
- FullStr=random(16B) + msg_len(4B) + msg + appid,其中:
- random(16B)為 16 字節(jié)的隨機(jī)字符串;
- msg_len 為 msg 長(zhǎng)度,占 4 個(gè)字節(jié)(網(wǎng)絡(luò)字節(jié)序);
- msg為解密后的明文;
- appid為小程序Appid,開發(fā)者需驗(yàn)證此Appid是否與自身小程序相符。
- 在此示例中:
- random(16B)="a8eedb185eb2fecf"
- msg_len=167(注意:需按網(wǎng)絡(luò)字節(jié)序,占4個(gè)字節(jié))
- msg="{"ToUserName":"gh_97417a04a28d","FromUserName":"o9AgO5Kd5ggOC-bXrbNODIiE3bGY","CreateTime":1714112445,"MsgType":"event","Event":"debug_demo","debug_str":"hello world"}"
- appid="wxba5fad812f8e6fb9"
- 回包給微信服務(wù)器,首先需確定回包包體的明文內(nèi)容,具體取決于特定接口文檔要求,如無(wú)特定要求,回復(fù)空串或者success(無(wú)需加密)即可,其他回包內(nèi)容需加密處理。這里假設(shè)回包包體的明文內(nèi)容為"{"demo_resp":"good luck"}",數(shù)據(jù)格式為JSON,下面介紹如何對(duì)回包進(jìn)行加密:
- 回包格式如下,具體取決于你配置的數(shù)據(jù)格式(JSON或XML),其中:
- Encrypt:加密后的內(nèi)容;
- MsgSignature:簽名,微信服務(wù)器會(huì)驗(yàn)證簽名;
- TimeStamp:時(shí)間戳;
- Nonce:隨機(jī)數(shù)
{ "Encrypt": "${msg_encrypt}$", "MsgSignature": "${msg_signature}$", "TimeStamp": ${timestamp}$, "Nonce": ${nonce}$ }<xml> <Encrypt><![CDATA[${msg_encrypt}$]]></Encrypt> <MsgSignature><![CDATA[${msg_signature}$]]></MsgSignature> <TimeStamp>${timestamp}$</TimeStamp> <Nonce><![CDATA[${nonce}$]]></Nonce> </xml> - Encrypt的生成方法:
- AESKey = Base64_Decode( EncodingAESKey + "=" ),EncodingAESKey 尾部填充一個(gè)字符的 "=", 用 Base64_Decode 生成 32 個(gè)字節(jié)的 AESKey;
- 構(gòu)造FullStr=random(16B) + msg_len(4B) + msg + appid,其中
- random(16B)為 16 字節(jié)的隨機(jī)字符串;
- msg_len 為 msg 長(zhǎng)度,占 4 個(gè)字節(jié)(網(wǎng)絡(luò)字節(jié)序);
- msg為明文;
- appid為小程序Appid。
- 在此示例中:
- random(16B)="707722b803182950"
- msg_len=25(注意:需按網(wǎng)絡(luò)字節(jié)序,占4個(gè)字節(jié))
- msg="{"demo_resp":"good luck"}"
- appid="wxba5fad812f8e6fb9"
- FullStr的字節(jié)大小為63
- 將FullStr用AESKey進(jìn)行加密,得到TmpMsg,字節(jié)大小為64。AES 采用 CBC 模式,秘鑰長(zhǎng)度為 32 個(gè)字節(jié)(256 位),數(shù)據(jù)采用 PKCS#7 填充; PKCS#7:K 為秘鑰字節(jié)數(shù)(采用 32),Buf 為待加密的內(nèi)容,N 為其字節(jié)數(shù)。Buf 需要被填充為 K 的整數(shù)倍。在 Buf 的尾部填充(K - N%K)個(gè)字節(jié),每個(gè)字節(jié)的內(nèi)容 是(K - N%K)。微信團(tuán)隊(duì)提供了多種語(yǔ)言的示例代碼(包括 PHP、Java、C++、Python、C#),請(qǐng)開發(fā)者盡量使用示例代碼,仔細(xì)閱讀技術(shù)文檔、示例代碼及其注釋后,再進(jìn)行編碼調(diào)試。示例下載
- 對(duì)TmpMsg進(jìn)行Base64編碼,得到Encrypt="ELGduP2YcVatjqIS+eZbp80MNLoAUWvzzyJxgGzxZO/5sAvd070Bs6qrLARC9nVHm48Y4hyRbtzve1L32tmxSQ=="。
- TimeStamp由開發(fā)者生成,使用當(dāng)前時(shí)間戳即可,示例使用1713424427。
- Nonce回填URL參數(shù)中的nonce參數(shù)即可,示例使用415670741。
- MsgSignature的生成方法:
- 將token、TimeStamp(回包中的)、Nonce(回包中的)、Encrypt(回包中的)四個(gè)參數(shù)進(jìn)行字典序排序,排序后結(jié)果為: ["1713424427", "415670741", "AAAAA", "ELGduP2YcVatjqIS+eZbp80MNLoAUWvzzyJxgGzxZO/5sAvd070Bs6qrLARC9nVHm48Y4hyRbtzve1L32tmxSQ=="]
- 將四個(gè)參數(shù)字符串拼接成一個(gè)字符串,并進(jìn)行sha1計(jì)算簽名:1b9339964ed2e271e7c7b6ff2b0ef902fc94dea1
- 最終回包為:
{
"Encrypt": "ELGduP2YcVatjqIS+eZbp80MNLoAUWvzzyJxgGzxZO/5sAvd070Bs6qrLARC9nVHm48Y4hyRbtzve1L32tmxSQ==",
"MsgSignature": "1b9339964ed2e271e7c7b6ff2b0ef902fc94dea1",
"TimeStamp": 1713424427,
"Nonce": "415670741"
}
為了便于開發(fā)者調(diào)試,我們提供了相關(guān)的調(diào)試工具(請(qǐng)求構(gòu)造、調(diào)試工具)供開發(fā)者使用。
- “請(qǐng)求構(gòu)造”允許開發(fā)者填寫相關(guān)參數(shù)后,生成debug_demo事件發(fā)包或回包的相關(guān)調(diào)試信息,供開發(fā)者使用。
- “調(diào)試工具”允許開發(fā)者填寫AccessToken、Body后,微信服務(wù)器會(huì)拉取你在小程序后臺(tái)配置的消息推送配置,實(shí)際推送一條debug_demo事件供開發(fā)者調(diào)試。
# 云函數(shù)接收消息推送
需開發(fā)者工具版本至少
1.02.1906252
開通了云開發(fā)的小程序可以使用云函數(shù)接收消息推送,目前僅支持客服消息推送。
接入步驟如下:
- 云開發(fā)控制臺(tái)中填寫配置并上傳
- 云函數(shù)中處理消息
# 第一步:開發(fā)者工具云開發(fā)控制臺(tái)中增加配置
前往路徑“「云開發(fā)」-「設(shè)置」-「其他設(shè)置」-「消息推送」”,選擇推送模式為云函數(shù);
添加消息推送配置。消息類型對(duì)應(yīng)收包的
MsgType,事件類型對(duì)應(yīng)收包的 Event,同一個(gè) <消息類型, 事件類型> 二元組只能推到一個(gè)環(huán)境的一個(gè)云函數(shù)。例如客服消息文本消息對(duì)應(yīng)的就是消息類型為 text,事件類型為空。具體值請(qǐng)查看各個(gè)消息的消息格式。
多個(gè)消息類型、事件類型多次添加消息推送配置即可。
注意:如在云函數(shù)中配置了某個(gè)類型的消息,該類型消息將不再推送至“微信公眾平臺(tái)-開發(fā)設(shè)置-消息推送”中配置的域名中。
# 第二步:云函數(shù)中處理消息
云函數(shù)被觸發(fā)時(shí),其 event 參數(shù)即是接口所定義的 JSON 結(jié)構(gòu)的對(duì)象(統(tǒng)一 JSON 格式,不支持 XML 格式)。
以客服消息為例,接收到客服消息推送時(shí),event 結(jié)構(gòu)如下:
{
"FromUserName": "ohl4L0Rnhq7vmmbT_DaNQa4ePaz0",
"ToUserName": "wx3d289323f5900f8e",
"Content": "測(cè)試",
"CreateTime": 1555684067,
"MsgId": "49d72d67b16d115e7935ac386f2f0fa41535298877_1555684067",
"MsgType": "text"
}
此時(shí)可調(diào)用客服消息發(fā)送接口回復(fù)消息,一個(gè)簡(jiǎn)單的接收到消息后統(tǒng)一回復(fù) “收到” 的示例如下:
// 云函數(shù)入口文件
const cloud = require('wx-server-sdk')
cloud.init()
// 云函數(shù)入口函數(shù)
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
await cloud.openapi.customerServiceMessage.send({
touser: wxContext.OPENID,
msgtype: 'text',
text: {
content: '收到',
},
})
return 'success'
}
# 微信云托管接收消息推送
使用微信云托管的小程序/公眾號(hào)可以使用云托管服務(wù)接收消息推送,只需配置一個(gè)云托管服務(wù)即可支持所有類型的消息推送。
接入步驟如下:
- 微信云托管控制臺(tái)中填寫配置
- 云托管服務(wù)中處理消息
# 第一步 云托管控制臺(tái)填寫配置
前往路徑“「微信云托管」-「設(shè)置」-「其他設(shè)置」-「消息推送」”中配置;
點(diǎn)擊配置,選擇目標(biāo)云開發(fā)環(huán)境、填寫對(duì)應(yīng)的云托管服務(wù)路徑(路徑可前往“云托管”-“服務(wù)列表”-“路徑字段”中復(fù)制)、選擇推送類型;
- 環(huán)境ID:選擇接收消息推送
- 服務(wù)名稱:接收消息推送的服務(wù),只需配置1個(gè)服務(wù)即可接收所有類型消息;
- path:服務(wù)下哪個(gè)接口接收即寫該接口在服務(wù)內(nèi)的路徑即可;
- 推送模式:支持JSON、XML兩種模式;
配置完成后,該云托管服務(wù)即可接收當(dāng)前小程序/公眾號(hào)下所有類型消息推送。
# 配置測(cè)試
配置消息推送時(shí),微信后臺(tái)會(huì)向配置的服務(wù)發(fā)起一個(gè)檢測(cè)請(qǐng)求。
當(dāng)配置格式為 JSON 時(shí),請(qǐng)求體為:
{ "action": "CheckContainerPath"}
當(dāng)配置格式為 XML 時(shí),請(qǐng)求體為:
<xml><action>CheckContainerPath</action></xml>
開發(fā)者回復(fù) success 或回復(fù)空即可。
# 確認(rèn)消息來(lái)源
若云托管未開啟公網(wǎng)訪問(wèn),則可以信任所有消息推送。若云托管開啟了公網(wǎng)訪問(wèn),需要驗(yàn)證消息推送的請(qǐng)求頭,帶 x-wx-sources 的請(qǐng)求才是微信側(cè)發(fā)起的推送。
# 第二步 云托管服務(wù)中處理消息
下面的例子展示如何使用云托管結(jié)合消息推送,實(shí)現(xiàn)客服消息回復(fù)。 注意:需要先部署好以下的鏡像,再在設(shè)置-其他設(shè)置-消息推送中,填入對(duì)應(yīng)服務(wù)的路徑和環(huán)境 ID。
const express = require('express')
const bodyParser = require('body-parser')
const axios = require('axios')
const PORT = process.env.PORT || 80
const HOST = '0.0.0.0'
// App
const app = express()
app.use(bodyParser.raw())
app.use(bodyParser.json({}))
app.use(bodyParser.urlencoded({ extended: true }))
const client = axios.default
app.all('/', async (req, res) => {
const headers = req.headers
const weixinAPI = `http://api.weixin.qq.com/cgi-bin/message/custom/send`
const payload = {
touser: headers['x-wx-openid'],
msgtype: 'text',
text: {
content: `云托管接收消息推送成功,內(nèi)容如下:\n${JSON.stringify(req.body, null, 2)}`
}
}
// dispatch to wx server
const result = await client.post(weixinAPI, payload)
console.log('received request', req.body, result.data)
res.send('success')
});
app.listen(PORT, HOST)
console.log(`Running on http://${HOST}:${PORT}`)
配置成功后,使用 <button open-type="contact"> 類型的按鈕喚起客服會(huì)話,發(fā)送任意消息即可看到云托管處理的回復(fù)。