Java微信公众号开发
本文從本人博客搬運,原文格式更加美觀,可以移步原文閱讀:Java微信公眾號開發
微信公眾號介紹
1.公眾號的分類
我們平常在微信應用上會看到有很多的公眾號,但是各自并不一樣,公眾號也分很多種類型,不過最常見的就是服務號和訂閱號了。下面我們來看一下他們的區別:
- 訂閱號:為媒體和個人提供一種信息傳播方式,主要偏于為用戶傳達資訊(類似報紙雜志),主要的定位是閱讀,每天可以群發1條消息
- 服務號:為企業,政府或組織提供對用戶進行服務,主要偏于服務交互(類似銀行提供服務查詢),每個月只可群發4條消息
- 企業微信:為企業,政府,事業單位,實現生產管理和協作運營的移動化,主要用于公司內部通訊使用,旨在為用戶提供移動辦公,需要先有成員的通訊信息驗證才可以關注成功企業微信
訂閱號和服務號有一個比較明顯的區別就是:訂閱號都是存放在一個名叫訂閱號的文件夾中,點開才能看到所有關注過的訂閱號,但是服務號卻和好友一樣直接就顯示在聊天列表中。這個大家打開微信客戶端便能看到
2.公眾號注冊流程
以注冊訂閱號為例,訪問注冊地址:https://mp.weixin.qq.com/cgi-bin/registermidpage?action=index&lang=zh_CN&token=
填寫基本信息,驗證郵箱
選擇賬號類型
賬號主體類型選擇個人,填寫信息
填寫公眾號信息
創建成功后,可以進入公眾號的管理界面
3.公眾號的管理模式
3.1 編輯模式
主要針對非編程人員及信息發布類公眾帳號使用。開啟該模式后,可以方便地通過界面配置“自定義菜單”和“自動回復的消息”。好處是可視化界面配置,操作簡單,快捷,但是功能有限
我們可以給自己的公眾號設置關鍵詞回復,收到已關注用戶發送的消息,匹配到你好時回復一個你好~~
編輯模式只能預先自定義一些固定的規則和數據,這些數據會保存在微信服務器,只能完成一些簡單的功能。如果要完成更復雜的功能,比如根據用戶輸入信息動態獲取數據返回,則需要使用開發模式
3.2 開發模式
主要針對具備開發能力的人使用。開啟該模式后,能夠使用微信公眾平臺開放的接口,但是編輯模式的設置會失效,比如“自定義菜單”和“自動回復的消息”功能。通過編程方式可以實現更多復雜的功能,提供個性化服務。
總的來說,編輯模式就是為所有人提供的,如果你的需求僅僅只是最常見的菜單,自動回復等,使用編輯模式已經滿足,但是如果你需求的功能比較復雜,比如需要從自己的業務系統數據庫中查詢數據返回給微信用戶,就需要使用到開發模式。
接下來我們介紹開發模式的各種功能使用步驟。出于安全考慮,普通的訂閱號權限非常有限,可以調用的接口非常有限,不便于測試
所以,為了幫助開發者快速了解和上手微信公眾號開發,熟悉各個接口的調用,微信推出了公眾帳號測試號,無需公眾帳號、快速申請接口測試號,通過手機微信掃描二維碼即可獲得,利用測試號我們可以體驗和測試更多高級功能。測試號申請地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
但測試號也不是萬能的,部分高級功能,如微信支付,卡券功能等也是不開放的。如果要實現支付功能還是得去注冊個正式的公眾號。本文后續所有測試都基于測試公眾號
接入開發模式
我們首先了解下微信與我們的服務器交互的過程:
當我們在微信app上,給公眾號發送一條內容的時候,實際會發送到微信的服務器上,此時微信的服務器就會對內容進行封裝成某種格式的數據比如xml格式,再轉發到我們配置好的某個URL上,所以該URL實際就是我們處理數據的一個請求路徑。所以該URL必須是能暴露給外界訪問的一個公網地址,不能使用內網地址,生產環境可以申請騰訊云,阿里云服務器等,但是在開發環境中可以暫時利用一些軟件來完成內網穿透
在進行和微信公眾號服務器交互之前,需要進行接入驗證。接入驗證涉及的2個關鍵參數如下:
- URL:就是指我們自己的服務器地址。該URL是開發者用來接收和響應微信消息和事件的接口URL(必須以http://或https://開頭,分別支持80端口和443端口)
- Token:可任意填寫,用作生成簽名(必須為英文或數字,長度為3-32字符)
接入驗證的大致流程如下:
| signature | 微信加密簽名,signature結合了開發者填寫的token參數和請求中的timestamp參數、nonce參數。 |
| timestamp | 時間戳 |
| nonce | 隨機數 |
| echostr | 隨機字符串 |
開發者通過檢驗signature對請求進行校驗(下面有校驗方式)。若確認此次GET請求來自微信服務器,請原樣返回echostr參數內容,則接入生效,成為開發者成功,否則接入失敗。加密/校驗流程如下:
注意到微信發送的GET請求中并沒有攜帶我們填寫的token,而簽名signature的生成過程中token是一個入參,所以我們填寫的url處理程序中,校驗signature時也需要token,這就要求我們在url對應的后端程序中定義token,要和通過微信頁面填寫的token一致。這樣做是為了安全性,只有知道token才可以接入成功,避免了他人盜用公眾號做操作
根據上述驗證流程,我們創建一個Springboot應用,編寫驗證接口
@RestController @RequestMapping("wechat/publicAccount") public class WechatPublicAccountController {// 微信頁面填寫的token,必須保密private static final String TOKEN = "";@GetMapping("validate")public String validate(String signature,String timestamp,String nonce,String echostr){// 1. 將token、timestamp、nonce三個參數進行字典序排序String[] arr = {timestamp, nonce, TOKEN};Arrays.sort(arr);// 2. 將三個參數字符串拼接成一個字符串進行sha1加密StringBuilder sb = new StringBuilder();for (String temp : arr) {sb.append(temp);}// 這里利用了hutool的加密工具類String sha1 = SecureUtil.sha1(sb.toString());// 3. 加密后的字符串與signature對比,如果相同則該請求來源于微信,原樣返回echostrif (sha1.equals(signature)){return echostr;}// 接入失敗return null;} }利用內網穿透工具,將我們的本地應用地址映射到公網域名
然后在測試號頁面填寫回調url和正確的token,即可接入成功。如果token填錯,將接入失敗
特別注意:
- 在接入成功后,后續所有微信發送過來的消息都會攜帶signature、timestamp、nonce這3個參數,我們每次接收微信消息時都要跟初始接入一樣去校驗signature,以確保接收到的消息是微信發過來的,而不是其他人發過來的
- 僅接入消息會攜帶echostr,后續所有微信發過來的消息不會攜帶echostr,所以可以根據請求是否攜帶了echostr來判斷是否是接入消息。接入消息除了校驗signature外要原樣返回echostr,其他后續消息只需校驗signature即可
消息接收與響應
1.接收消息
接入成功以后,我們就可以利用微信提供的接口實現各種功能。首先來看一下基本的消息接收和回復,官方文檔位置如下
當關注了公眾號的用戶向公眾號發送消息時,微信服務器將POST消息的XML數據包到開發者填寫的URL上。所以我們要在Controller中新建一個處理方法
微信會將用戶發送的消息信息封裝到請求體的xml中,根據消息類型的不同,xml的格式也有所不同:
- 文本消息
| ToUserName | 開發者微信號 |
| FromUserName | 發送方帳號(一個OpenID) |
| CreateTime | 消息創建時間 (整型) |
| MsgType | 消息類型,文本為text |
| Content | 文本消息內容 |
| MsgId | 消息id,64位整型 |
- 圖片消息
| ToUserName | 開發者微信號 |
| FromUserName | 發送方帳號(一個OpenID) |
| CreateTime | 消息創建時間 (整型) |
| MsgType | 消息類型,圖片為image |
| PicUrl | 圖片鏈接(由系統生成) |
| MediaId | 圖片消息媒體id,可以調用獲取臨時素材接口拉取數據。 |
| MsgId | 消息id,64位整型 |
- 語音消息
| ToUserName | 開發者微信號 |
| FromUserName | 發送方帳號(一個OpenID) |
| CreateTime | 消息創建時間 (整型) |
| MsgType | 語音為voice |
| MediaId | 語音消息媒體id,可以調用獲取臨時素材接口拉取數據。 |
| Format | 語音格式,如amr,speex等 |
| MsgId | 消息id,64位整型 |
- 視頻消息
| ToUserName | 開發者微信號 |
| FromUserName | 發送方帳號(一個OpenID) |
| CreateTime | 消息創建時間 (整型) |
| MsgType | 視頻為video |
| MediaId | 視頻消息媒體id,可以調用獲取臨時素材接口拉取數據。 |
| ThumbMediaId | 視頻消息縮略圖的媒體id,可以調用多媒體文件下載接口拉取數據。 |
| MsgId | 消息id,64位整型 |
- 小視頻消息
| ToUserName | 開發者微信號 |
| FromUserName | 發送方帳號(一個OpenID) |
| CreateTime | 消息創建時間 (整型) |
| MsgType | 小視頻為shortvideo |
| MediaId | 視頻消息媒體id,可以調用獲取臨時素材接口拉取數據。 |
| ThumbMediaId | 視頻消息縮略圖的媒體id,可以調用獲取臨時素材接口拉取數據。 |
| MsgId | 消息id,64位整型 |
- 地理位置消息
| ToUserName | 開發者微信號 |
| FromUserName | 發送方帳號(一個OpenID) |
| CreateTime | 消息創建時間 (整型) |
| MsgType | 消息類型,地理位置為location |
| Location_X | 地理位置緯度 |
| Location_Y | 地理位置經度 |
| Scale | 地圖縮放大小 |
| Label | 地理位置信息 |
| MsgId | 消息id,64位整型 |
- 鏈接消息
| ToUserName | 接收方微信號 |
| FromUserName | 發送方微信號,若為普通用戶,則是一個OpenID |
| CreateTime | 消息創建時間 |
| MsgType | 消息類型,鏈接為link |
| Title | 消息標題 |
| Description | 消息描述 |
| Url | 消息鏈接 |
| MsgId | 消息id,64位整型 |
我們對比一下不同類型的xml數據包中的參數,ToUserName,FromUserName,CreateTime,MsgType,MsgId這五個是公共的,所有類型都會帶上這些參數。接下來,我們需要來了解這5個參數的具體意義:
- ToUserName:文檔上描述的是開發者微信號,實際上,直接把它當做你的公眾號的微信號即可,表示的是發到那個公眾號的意思。
- FromUserName:與ToUserName相反,這是代表是由哪個用戶發過來的,同一個用戶發多條信息過來,FromUserName都是不變的。但這并不是用戶的微信號,而是一個OpenID。那什么是OpenID呢:當用戶和公眾號發生了交互,微信服務器會為每個用戶針對每個公眾號產生一個OpenID(也就是指該OpenID是利用兩個因素:用戶和公眾號來產生的,也就意味著如果該用戶跟另外一個公眾號交互,產生的OpenID也是不同的,這樣安全性會比較高),如果一個公司有多個公眾號,并且需要在多公眾號、移動應用之間做用戶共通,則需要使用UnionID,前往微信開放平臺,將這些公眾號和應用綁定到一個開放平臺賬號下,綁定后,一個用戶雖然對多個公眾號和應用有多個不同的OpenID,但他對所有這些同一開放平臺賬號下的公眾號和應用,只有一個UnionID,可以在用戶管理-獲取用戶基本信息(UnionID機制)文檔了解詳情。
- CreateTime:消息創建時間
- MsgType:用戶發送的消息的類型,如text代表文本消息,image代表圖片消息等。
- MsgId:用戶發送的每個消息都有自己的id,可以用于消息排重,比如微信服務器把xml消息包發送到URL了,但是五秒內微信服務器沒有收到我們的響應,則會重新發起請求,總共重試三次。如果不做消息排重,那么用戶可能就收到多條相同的響應消息了。
微信服務器在五秒內收不到響應會斷掉連接,并且重新發起請求,總共重試三次。假如服務器無法保證在五秒內處理并回復,可以直接回復空串,微信服務器不會對此作任何處理,并且不會發起重試
接下來,我們可以創建一個封裝消息的實體類,把所有可接收到的參數都放進入,其他類型的暫時不演示,所以只在最后加入了文本和圖片的參數。
@Data @AllArgsConstructor @NoArgsConstructor public class InMessage {// 開發者微信號protected String FromUserName;// 發送方帳號(一個OpenID)protected String ToUserName;// 消息創建時間protected Long CreateTime;/*** 消息類型* text 文本消息* image 圖片消息* voice 語音消息* video 視頻消息* music 音樂消息*/protected String MsgType;// 消息idprotected Long MsgId;// 文本內容private String Content;// 圖片鏈接(由系統生成)private String PicUrl;// 圖片消息媒體id,可以調用多媒體文件下載接口拉取數據private String MediaId; }這時候大家可能會有個疑問,為什么字段名稱都是大寫開頭呢?因為微信服務器傳過來的xml數據包中的xml元素都是大寫開頭的,如下所示:
<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>因為xml解析是大小寫敏感的,所以為了方便封裝,我直接把字段名設置為大寫開頭。當然,如果還是想要小寫開頭的字段,也是可以的,我們待會再說處理方式。
實體已經建好之后,我們就可以開始接收微信傳過來的xml數據了。
第1步:在handleMessage方法的形參上添加消息實體類型的參數:
第2步:需要配合JAXB的注解來解析xml。在消息實體類上添加以下兩個注解:
這2個注解的含義如下:
- @XmlRootElement:是一個類級別注解,主要屬性為name,意為指定根節點的名字。往上面看前面舉了個微信傳過來的xml數據的例子里,里面的根節點就是"xml",所以這里就直接設置name=“xml”
- @XmlAccessorType:用于定義這個類中的何種類型需要映射到XML中
- XmlAccessType.PROPERTY:代表映射這個類中的屬性(get/set方法)到XML
- XmlAccessType.FIELD:代表映射這個類中的所有字段到XML
之前我們為了方便解析,將消息實體類的屬性命名成和xml中的節點一樣都是大寫開頭,現在我們優化一下,按照java規范命名屬性小寫開頭,然后利用@XmlElement給每個屬性標注對應的xml節點名稱
@Data @AllArgsConstructor @NoArgsConstructor @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) public class InMessage {/*** 開發者微信號*/@XmlElement(name="FromUserName")protected String fromUserName;/*** 發送方帳號(一個OpenID)*/@XmlElement(name="ToUserName")protected String toUserName;/*** 消息創建時間*/@XmlElement(name="CreateTime")protected Long createTime;/*** 消息類型* text 文本消息* image 圖片消息* voice 語音消息* video 視頻消息* music 音樂消息*/@XmlElement(name="MsgType")protected String msgType;/*** 消息id*/@XmlElement(name="MsgId")protected Long msgId;/*** 文本內容*/@XmlElement(name="Content")private String content;/*** 圖片鏈接(由系統生成)*/@XmlElement(name="PicUrl")private String picUrl;/*** 圖片消息媒體id,可以調用多媒體文件下載接口拉取數據*/@XmlElement(name="MediaId")private String mediaId; }然后我們在Controller接收消息的方法中設置斷點,關注測試公眾號,給它發送一個文本消息"你好",就可以接收到了
2.響應消息
官方文檔:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html
當用戶發送消息給公眾號時(或某些特定的用戶操作引發的事件推送時),會產生一個POST請求,開發者可以在響應包(Get)中返回特定XML結構,來對該消息進行響應(現支持回復文本、圖片、圖文、語音、視頻、音樂)。嚴格來說,發送被動響應消息其實并不是一種接口,而是對微信服務器發過來消息的一次回復。
微信服務器在將用戶的消息發給公眾號的開發者服務器地址(開發者中心處配置)后,微信服務器在五秒內收不到響應會斷掉連接,并且重新發起請求,總共重試三次,如果在調試中,發現用戶無法收到響應的消息,可以檢查是否消息處理超時。關于重試的消息排重,有msgid的消息推薦使用msgid排重。事件類型消息推薦使用FromUserName + CreateTime 排重。
如果開發者希望增強安全性,可以在開發者中心處開啟消息加密,這樣,用戶發給公眾號的消息以及公眾號被動回復用戶消息都會繼續加密。
假如服務器無法保證在五秒內處理并回復,必須做出下述回復,這樣微信服務器才不會對此作任何處理,并且不會發起重試(這種情況下,可以使用客服消息接口進行異步回復),否則,將出現嚴重的錯誤提示:
一旦遇到以下情況,微信都會在公眾號會話中,向用戶下發系統提示“該公眾號暫時無法提供服務,請稍后再試”:
另外,請注意,回復圖片(不支持gif動圖)等多媒體消息時需要預先通過素材管理接口上傳臨時素材到微信服務器,可以使用素材管理中的臨時素材,也可以使用永久素材。
各種類型的響應消息格式如下:
- 回復文本消息
| ToUserName | 是 | 接收方帳號(收到的OpenID) |
| FromUserName | 是 | 開發者微信號 |
| CreateTime | 是 | 消息創建時間 (整型) |
| MsgType | 是 | 消息類型,文本為text |
| Content | 是 | 回復的消息內容(換行:在content中能夠換行,微信客戶端就支持換行顯示) |
- 回復圖片消息
| ToUserName | 是 | 接收方帳號(收到的OpenID) |
| FromUserName | 是 | 開發者微信號 |
| CreateTime | 是 | 消息創建時間 (整型) |
| MsgType | 是 | 消息類型,圖片為image |
| MediaId | 是 | 通過素材管理中的接口上傳多媒體文件,得到的id。 |
- 回復語音消息
| ToUserName | 是 | 接收方帳號(收到的OpenID) |
| FromUserName | 是 | 開發者微信號 |
| CreateTime | 是 | 消息創建時間戳 (整型) |
| MsgType | 是 | 消息類型,語音為voice |
| MediaId | 是 | 通過素材管理中的接口上傳多媒體文件,得到的id |
- 回復視頻消息
| ToUserName | 是 | 接收方帳號(收到的OpenID) |
| FromUserName | 是 | 開發者微信號 |
| CreateTime | 是 | 消息創建時間 (整型) |
| MsgType | 是 | 消息類型,視頻為video |
| MediaId | 是 | 通過素材管理中的接口上傳多媒體文件,得到的id |
| Title | 否 | 視頻消息的標題 |
| Description | 否 | 視頻消息的描述 |
- 回復音樂消息
| ToUserName | 是 | 接收方帳號(收到的OpenID) |
| FromUserName | 是 | 開發者微信號 |
| CreateTime | 是 | 消息創建時間 (整型) |
| MsgType | 是 | 消息類型,音樂為music |
| Title | 否 | 音樂標題 |
| Description | 否 | 音樂描述 |
| MusicURL | 否 | 音樂鏈接 |
| HQMusicUrl | 否 | 高質量音樂鏈接,WIFI環境優先使用該鏈接播放音樂 |
| ThumbMediaId | 是 | 縮略圖的媒體id,通過素材管理中的接口上傳多媒體文件,得到的id |
- 回復圖文消息
| ToUserName | 是 | 接收方帳號(收到的OpenID) |
| FromUserName | 是 | 開發者微信號 |
| CreateTime | 是 | 消息創建時間 (整型) |
| MsgType | 是 | 消息類型,圖文為news |
| ArticleCount | 是 | 圖文消息個數;當用戶發送文本、圖片、語音、視頻、圖文、地理位置這六種消息時,開發者只能回復1條圖文消息;其余場景最多可回復8條圖文消息 |
| Articles | 是 | 圖文消息信息,注意,如果圖文數超過限制,則將只發限制內的條數 |
| Title | 是 | 圖文消息標題 |
| Description | 是 | 圖文消息描述 |
| PicUrl | 是 | 圖片鏈接,支持JPG、PNG格式,較好的效果為大圖360*200,小圖200*200 |
| Url | 是 | 點擊圖文消息跳轉鏈接 |
我們可以看到回復的格式中除了都沒有MsgId,只有普通文本和接收的格式基本是一樣的,圖片消息或其他類型的消息與接收到的格式相比,多了xml元素的嵌套,所以原先定義的接收消息實體類無法復用,我們再封裝一個響應消息的實體類,這里暫時只考慮文本和圖片類型
@Data @AllArgsConstructor @NoArgsConstructor @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) public class OutMessage {/*** 開發者微信號*/@XmlElement(name="FromUserName")protected String fromUserName;/*** 發送方帳號(一個OpenID)*/@XmlElement(name="ToUserName")protected String toUserName;/*** 消息創建時間*/@XmlElement(name="CreateTime")protected Long createTime;/*** 消息類型* text 文本消息* image 圖片消息* voice 語音消息* video 視頻消息* music 音樂消息*/@XmlElement(name="MsgType")protected String msgType;/*** 文本內容*/@XmlElement(name="Content")private String content;/*** 圖片的媒體id列表*/@XmlElementWrapper(name="Image") // 表示MediaId屬性內嵌于<Image>標簽@XmlElement(name = "MediaId")private String[] mediaId; }@XmlElementWrapper注解可以在原xml結點上再包裝一層xml,但僅允許出現在數組或集合屬性上
現在我們來實現一下簡單的回復消息:用戶給我們發什么,我們就回復什么,只需要把接收到InMessage的內容設置到OutMessage上,并且把ToUserName與FromUserName的值設置為相反即可
@PostMapping(value = "validate", produces = "application/xml;charset=UTF-8") public Object handleMessage(@RequestBody InMessage inMessage){// 創建響應消息實體對象OutMessage outMessage = new OutMessage();// 把原來的接收方設置為發送方outMessage.setFromUserName(inMessage.getToUserName());// 把原來的發送方設置為接收方outMessage.setToUserName(inMessage.getFromUserName());// 設置消息類型outMessage.setMsgType(inMessage.getMsgType());// 設置消息時間outMessage.setCreateTime(System.currentTimeMillis());// 根據接收到消息類型,響應對應的消息內容if ("text".equals(inMessage.getMsgType())){// 文本outMessage.setContent(inMessage.getContent());}else if ("image".equals(inMessage.getMsgType())){// 圖片outMessage.setMediaId(new String[]{inMessage.getMediaId()});}return outMessage; }注意:這里要在接口的@PostMapping中指定produces = "application/xml;charset=UTF-8",表示返回的數據格式是xml,否則將默認以json格式返回
測試效果如下
關鍵字回復
在開發模式下要實現關鍵字回復非常簡單,只要判斷發送過來的文本消息中是否包含關鍵字,然后給予相應的回復即可
@PostMapping(value = "validate", produces = "application/xml;charset=UTF-8") public Object handleMessage(@RequestBody InMessage inMessage){// 創建響應消息實體對象OutMessage outMessage = new OutMessage();// 把原來的接收方設置為發送方outMessage.setFromUserName(inMessage.getToUserName());// 把原來的發送方設置為接收方outMessage.setToUserName(inMessage.getFromUserName());// 設置消息類型outMessage.setMsgType(inMessage.getMsgType());// 設置消息時間outMessage.setCreateTime(System.currentTimeMillis() / 1000);// 根據接收到消息類型,響應對應的消息內容if ("text".equals(inMessage.getMsgType())){// 根據不同的關鍵字回復消息String inContent = inMessage.getContent();if (inContent.contains("游戲")){outMessage.setContent("仙劍");}else if (inContent.contains("動漫")){outMessage.setContent("進擊的巨人");}else {outMessage.setContent(inContent);}}else if ("image".equals(inMessage.getMsgType())){// 圖片outMessage.setMediaId(new String[]{inMessage.getMediaId()});}return outMessage; }事件推送
官方文檔:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
我們之前的案例都是用戶發送信息過來,我們才回復的。但是,如果是關注的時候需要馬上回復,就要使用到事件消息,實際上,微信已經提供給我們很多的事件
在微信用戶和公眾號產生交互的過程中,用戶的某些操作會使得微信服務器通過事件推送的形式通知到開發者在開發者中心處設置的服務器地址,從而開發者可以獲取到該信息。其中,某些事件推送在發生后,是允許開發者回復用戶的,某些則不允許
1.關注/取消關注事件
用戶在關注與取消關注公眾號時,微信會把這個事件推送到開發者填寫的URL。方便開發者給用戶下發歡迎消息或者做帳號的解綁。為保護用戶數據隱私,開發者收到用戶取消關注事件時需要刪除該用戶的所有信息。
微信服務器在五秒內收不到響應會斷掉連接,并且重新發起請求,總共重試三次。
關于重試的消息排重,推薦使用FromUserName + CreateTime 排重。
假如服務器無法保證在五秒內處理并回復,可以直接回復空串,微信服務器不會對此作任何處理,并且不會發起重試。
推送XML數據包示例:
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[FromUser]]></FromUserName><CreateTime>123456789</CreateTime><MsgType><![CDATA[event]]></MsgType><Event><![CDATA[subscribe]]></Event> </xml>| ToUserName | 開發者微信號 |
| FromUserName | 發送方帳號(一個OpenID) |
| CreateTime | 消息創建時間 (整型) |
| MsgType | 消息類型,event |
| Event | 事件類型,subscribe(訂閱)、unsubscribe(取消訂閱) |
2.掃描帶參數二維碼事件
用戶掃描帶場景值二維碼時,可能推送以下兩種事件:
| ToUserName | 開發者微信號 |
| FromUserName | 發送方帳號(一個OpenID) |
| CreateTime | 消息創建時間 (整型) |
| MsgType | 消息類型,event |
| Event | 事件類型,subscribe |
| EventKey | 事件KEY值,qrscene_為前綴,后面為二維碼的參數值 |
| Ticket | 二維碼的ticket,可用來換取二維碼圖片 |
| ToUserName | 開發者微信號 |
| FromUserName | 發送方帳號(一個OpenID) |
| CreateTime | 消息創建時間 (整型) |
| MsgType | 消息類型,event |
| Event | 事件類型,SCAN |
| EventKey | 事件KEY值,是一個32位無符號整數,即創建二維碼時的二維碼scene_id |
| Ticket | 二維碼的ticket,可用來換取二維碼圖片 |
3.上報地理位置事件
這個事件僅用于服務號,訂閱號不行。用戶同意上報地理位置后,每次進入公眾號會話時,都會在進入時上報地理位置,或在進入會話后每5秒上報一次地理位置,公眾號可以在公眾平臺網站中修改以上設置。上報地理位置時,微信會將上報地理位置事件推送到開發者填寫的URL。
推送XML數據包示例:
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[fromUser]]></FromUserName><CreateTime>123456789</CreateTime><MsgType><![CDATA[event]]></MsgType><Event><![CDATA[LOCATION]]></Event><Latitude>23.137466</Latitude><Longitude>113.352425</Longitude><Precision>119.385040</Precision> </xml>| ToUserName | 開發者微信號 |
| FromUserName | 發送方帳號(一個OpenID) |
| CreateTime | 消息創建時間 (整型) |
| MsgType | 消息類型,event |
| Event | 事件類型,LOCATION |
| Latitude | 地理位置緯度 |
| Longitude | 地理位置經度 |
| Precision | 地理位置精度 |
4.自定義菜單事件
用戶點擊自定義菜單后,微信會把點擊事件推送給開發者,請注意,點擊菜單彈出子菜單,不會產生上報。
點擊菜單拉取消息時的事件推送
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[FromUser]]></FromUserName><CreateTime>123456789</CreateTime><MsgType><![CDATA[event]]></MsgType><Event><![CDATA[CLICK]]></Event><EventKey><![CDATA[EVENTKEY]]></EventKey> </xml>| ToUserName | 開發者微信號 |
| FromUserName | 發送方帳號(一個OpenID) |
| CreateTime | 消息創建時間 (整型) |
| MsgType | 消息類型,event |
| Event | 事件類型,CLICK |
| EventKey | 事件KEY值,與自定義菜單接口中KEY值對應 |
點擊菜單跳轉鏈接時的事件推送
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[FromUser]]></FromUserName><CreateTime>123456789</CreateTime><MsgType><![CDATA[event]]></MsgType><Event><![CDATA[VIEW]]></Event><EventKey><![CDATA[www.qq.com]]></EventKey> </xml>| ToUserName | 開發者微信號 |
| FromUserName | 發送方帳號(一個OpenID) |
| CreateTime | 消息創建時間 (整型) |
| MsgType | 消息類型,event |
| Event | 事件類型,VIEW |
| EventKey | 事件KEY值,設置的跳轉URL |
事件推送案例演示
我們來實現一下用戶關注公眾號時接收推送消息并自動回復的功能
事件和消息都是推送到我們的URL上,怎么區分他們也很簡單,通過MsgType這個屬性,那么進一步再區分是關注還是取消關注,根據Event屬性即可。所以,我們在原來的InMessage類,再添加一個Event屬性
然后在Controller處理方法中添加對應邏輯
然后取消關注后再關注,即可展現效果
自定義菜單
1.自定義菜單簡介
官方文檔:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
自定義菜單能夠幫助公眾號豐富界面,讓用戶更好更快地理解公眾號的功能。開啟自定義菜單后,公眾號界面如圖所示:
請注意:
- 自定義菜單最多包括3個一級菜單,每個一級菜單最多包含5個二級菜單。
- 一級菜單最多4個漢字,二級菜單最多7個漢字,多出來的部分將會以“…”代替。
- 創建自定義菜單后,菜單的刷新策略是,在用戶進入公眾號會話頁或公眾號profile頁時,如果發現上一次拉取菜單的請求在5分鐘以前,就會拉取一下菜單,如果菜單有更新,就會刷新客戶端的菜單。測試時可以嘗試取消關注公眾賬號后再次關注,則可以看到創建后的效果。
自定義菜單接口可實現多種類型按鈕,如下:
請注意,3到8的所有事件,僅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用戶,舊版本微信用戶點擊后將沒有回應,開發者也不能正常接收到事件推送。9和10,是專門給第三方平臺旗下未微信認證(具體而言,是資質認證未通過)的訂閱號準備的事件類型,它們是沒有事件推送的,能力相對受限,其他類型的公眾號不必使用。
如果我們要給公眾號創建自定義菜單,需要發送下列請求
http請求方式:POST(請使用https協議) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
請求體示例如下:
{"button":[{ "type":"click","name":"今日歌曲","key":"V1001_TODAY_MUSIC"},{"name":"菜單","sub_button":[{ "type":"view","name":"搜索","url":"http://www.soso.com/"},{"type":"miniprogram","name":"wxa","url":"http://mp.weixin.qq.com","appid":"wx286b93c14bbf93aa","pagepath":"pages/lunar/index"},{"type":"click","name":"贊一下我們","key":"V1001_GOOD"}]}]}| button | 是 | 一級菜單數組,個數應為1~3個 |
| sub_button | 否 | 二級菜單數組,個數應為1~5個 |
| type | 是 | 菜單的響應動作類型,view表示網頁類型,click表示點擊類型,miniprogram表示小程序類型 |
| name | 是 | 菜單標題,不超過16個字節,子菜單不超過60個字節 |
| key | click等點擊類型必須 | 菜單KEY值,用于消息接口推送,不超過128字節 |
| url | view、miniprogram類型必須 | 網頁 鏈接,用戶點擊菜單可打開鏈接,不超過1024字節。 type為miniprogram時,不支持小程序的老版本客戶端將打開本url。 |
| media_id | media_id類型和view_limited類型必須 | 調用新增永久素材接口返回的合法media_id |
| appid | miniprogram類型必須 | 小程序的appid(僅認證公眾號可配置) |
| pagepath | miniprogram類型必須 | 小程序的頁面路徑 |
正確時的返回JSON數據包如下:
{"errcode":0,"errmsg":"ok"}錯誤時的返回JSON數據包如下(示例為無效菜單名長度):
{"errcode":40018,"errmsg":"invalid button name size"}其他類型的菜單示例如下
{"button": [{"name": "掃碼", "sub_button": [{"type": "scancode_waitmsg", "name": "掃碼帶提示", "key": "rselfmenu_0_0", "sub_button": [ ]}, {"type": "scancode_push", "name": "掃碼推事件", "key": "rselfmenu_0_1", "sub_button": [ ]}]}, {"name": "發圖", "sub_button": [{"type": "pic_sysphoto", "name": "系統拍照發圖", "key": "rselfmenu_1_0", "sub_button": [ ]}, {"type": "pic_photo_or_album", "name": "拍照或者相冊發圖", "key": "rselfmenu_1_1", "sub_button": [ ]}, {"type": "pic_weixin", "name": "微信相冊發圖", "key": "rselfmenu_1_2", "sub_button": [ ]}]}, {"name": "發送位置", "type": "location_select", "key": "rselfmenu_2_0"},{"type": "media_id", "name": "圖片", "media_id": "MEDIA_ID1"}, {"type": "view_limited", "name": "圖文消息", "media_id": "MEDIA_ID2"}] }2.獲取access_token
在我們調用創建菜單接口之前,必須先獲取調用微信接口的access_token。access_token是公眾號的全局唯一接口調用憑據,公眾號調用各接口時都需使用access_token。開發者需要進行妥善保存。access_token的存儲至少要保留512個字符空間。access_token的有效期目前為2個小時,需定時刷新,重復獲取將導致上次獲取的access_token失效。
公眾平臺的API調用所需的access_token的使用及生成方式說明:
- 建議公眾號開發者使用中控服務器統一獲取和刷新access_token,其他業務邏輯服務器所使用的access_token均來自于該中控服務器,不應該各自去刷新,否則容易造成沖突,導致access_token覆蓋而影響業務;
- 目前access_token的有效期通過返回的expire_in來傳達,目前是7200秒之內的值。中控服務器需要根據這個有效時間提前去刷新新access_token。在刷新過程中,中控服務器可對外繼續輸出的老access_token,此時公眾平臺后臺會保證在5分鐘內,新老access_token都可用,這保證了第三方業務的平滑過渡;
- access_token的有效時間可能會在未來有調整,所以中控服務器不僅需要內部定時主動刷新,還需要提供被動刷新access_token的接口,這樣便于業務服務器在API調用獲知access_token已超時的情況下,可以觸發access_token的刷新流程。
- 對于可能存在風險的調用,在開發者進行獲取 access_token調用時進入風險調用確認流程,需要用戶管理員確認后才可以成功獲取。具體流程為:開發者通過某IP發起調用->平臺返回錯誤碼[89503]并同時下發模板消息給公眾號管理員->公眾號管理員確認該IP可以調用->開發者使用該IP再次發起調用->調用成功。如公眾號管理員第一次拒絕該IP調用,用戶在1個小時內將無法使用該IP再次發起調用,如公眾號管理員多次拒絕該IP調用,該IP將可能長期無法發起調用。平臺建議開發者在發起調用前主動與管理員溝通確認調用需求,或請求管理員開啟IP白名單功能并將該IP加入IP白名單列表。
公眾號和小程序均可以使用AppID和AppSecret調用本接口來獲取access_token。AppID和AppSecret可在“微信公眾平臺-開發-基本配置”頁中獲得(需要已經成為開發者,且帳號沒有異常狀態)。**調用接口時,請登錄“微信公眾平臺-開發-基本配置”提前將服務器IP地址添加到IP白名單中,否則將無法調用成功。**小程序無需配置IP白名單。
獲取access_token需要調用的接口如下
https請求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
| grant_type | 是 | 獲取access_token填寫client_credential |
| appid | 是 | 公眾號唯一憑證,注冊成功后由微信提供 |
| secret | 是 | 公眾號唯一憑證密鑰,注冊成功后由微信提供 |
正常情況下,微信會返回下述JSON數據包給公眾號:
{"access_token":"ACCESS_TOKEN","expires_in":7200}| access_token | 獲取到的憑證 |
| expires_in | 憑證有效時間,單位:秒 |
錯誤時微信會返回錯誤碼等信息,JSON數據包示例如下(該示例為AppID無效錯誤):
{"errcode":40013,"errmsg":"invalid appid"}| -1 | 系統繁忙,此時請開發者稍候再試 |
| 0 | 請求成功 |
| 40001 | AppSecret錯誤或者AppSecret不屬于這個公眾號,請開發者確認AppSecret的正確性 |
| 40002 | 請確保grant_type字段值為client_credential |
| 40164 | 調用接口的IP地址不在白名單中,請在接口IP白名單中進行設置。(小程序及小游戲調用不要求IP地址在白名單內。) |
| 89503 | 此IP調用需要管理員確認,請聯系管理員 |
| 89501 | 此IP正在等待管理員確認,請聯系管理員 |
| 89506 | 24小時內該IP被管理員拒絕調用兩次,24小時內不可再使用該IP調用 |
| 89507 | 1小時內該IP被管理員拒絕調用一次,1小時內不可再使用該IP調用 |
在Controller中創建獲取access_token的方法,獲取成功后將其保存到redis中
@GetMapping("getAccessToken") public String getAccessToken(){String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID +"&secret=" + APPSECRET;// 利用hutool的http工具類請求獲取access_tokenString result = HttpUtil.get(url);// 將結果解析為jsonJSONObject jsonObject = JSONUtil.parseObj(result);// 獲取access_tokenString accessToken = jsonObject.getStr("access_token");if (!StringUtils.isEmpty(accessToken)){// 將access_token存入redisstringRedisTemplate.opsForValue().set("access_token", accessToken);}return accessToken; }3.創建自定義菜單
在Controller中新增方法,發送創建菜單的請求
@GetMapping("createMenu") public String createMenu(){// 從redis中取出access_tokenString accessToken = stringRedisTemplate.opsForValue().get("access_token");String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + accessToken;// 創建菜單的請求體String body = "{\n" +" \"button\":[\n" +" {\t\n" +" \"type\":\"click\",\n" +" \"name\":\"位置\",\n" +" \"key\":\"button_location\"\n" +" }]}";return HttpUtil.post(url, body); }發送請求后返回結果
{"errcode":0,"errmsg":"ok"}創建菜單后效果如下
發送模板消息
官方文檔:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html
在我們的生活中,無論是微商城消費,還是日常生活消費,都可能收到這種提示,比如訂單通知,快遞狀態通知,銀行卡支付通知,都屬于業務通知,很多公眾號也都實現了這種功能,當觸發了某種行為或狀態改變,就會發送這么一個消息給你,因為這種消息都是按照一定的的格式來編輯,所以也叫模板消息
認證后的服務號才可以申請模板消息的使用權限并獲得該權限,否則就只能使用測試號
我們要發送模板消息,第一步是需要創建一個模板,有了模板之后,我們才能填充內容來進行發送。創建模板不需要調用接口,在公眾號后臺即可設置
現在我們來按照下面案例來新建一個模板。但是模板的內容是有一定的規則的,不能隨便添加:
- 測試模板的模板ID僅用于測試,不能用來給正式帳號發送模板消息
- 為方便測試,測試模板可任意指定內容,但實際上正式帳號的模板消息,只能從模板庫中獲得
- 需為正式帳號申請新增符合要求的模板,需使用正式號登錄公眾平臺,按指引申請
- 模板內容可設置參數(模板標題不可),供接口調用時使用,參數需以{{開頭,以.DATA}}結尾
保存之后,微信會給該模板分配一個ID,待我們要發送模板消息的時候就需要用到這個ID了
發送模板消息需要如下請求:
http請求方式: POST https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN
請求體示例如下
{"touser":"OPENID","template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY","url":"http://weixin.qq.com/download", "miniprogram":{"appid":"xiaochengxuappid12345","pagepath":"index?foo=bar"}, "data":{"goodsName":{"value":"巧克力","color":"#173177"},"price": {"value":"39.8元","color":"#173177"},"time": {"value":"2014年9月22日","color":"#173177"},"remark":{"value":"歡迎再次購買!","color":"#173177"}} }| touser | 是 | 接收者openid |
| template_id | 是 | 模板ID |
| url | 否 | 模板跳轉鏈接(海外帳號沒有跳轉能力) |
| miniprogram | 否 | 跳小程序所需數據,不需跳小程序可不用傳該數據 |
| appid | 是 | 所需跳轉到的小程序appid(該小程序appid必須與發模板消息的公眾號是綁定關聯關系,暫不支持小游戲) |
| pagepath | 否 | 所需跳轉到小程序的具體頁面路徑,支持帶參數,(示例index?foo=bar),要求該小程序已發布,暫不支持小游戲 |
| data | 是 | 模板數據 |
| color | 否 | 模板內容字體顏色,不填默認為黑色 |
在調用模板消息接口后,會返回JSON數據包。正常時的返回JSON數據包示例:
{"errcode":0,"errmsg":"ok","msgid":200228332 }總結
以上是生活随笔為你收集整理的Java微信公众号开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Sublime Text 插件之常用20
- 下一篇: Java网络编程二:Socket详解