作者:陳惠,叩丁狼教育高級講師摇零。原創(chuàng)文章推掸,轉(zhuǎn)載請注明出處。
上一篇文章我們已經(jīng)實(shí)現(xiàn)了URL接入驻仅,接下來就可以利用微信官方的開發(fā)文檔來實(shí)現(xiàn)不同的案例终佛。(注意:若還沒有進(jìn)行接入操作,請參考開發(fā)教程(一))
實(shí)現(xiàn)思路
接下來我們先打開微信的開發(fā)文檔雾家,選擇“消息管理”模塊中的”接收普通消息“铃彰。
文檔中已經(jīng)告訴我們,當(dāng)普通微信用戶向公眾賬號發(fā)送消息時芯咧,微信服務(wù)器會把該消息封裝成XML數(shù)據(jù)包通過POST的方式發(fā)送到開發(fā)者填寫的URL上牙捉。我們設(shè)置的URL僅僅只有一個,上篇文章中是用來做接入驗(yàn)證的敬飒,當(dāng)時是微信服務(wù)器發(fā)送GET請求過來邪铲,而現(xiàn)在是用來做消息處理的,此時微信服務(wù)器發(fā)送的是POST請求无拗,因此想要區(qū)分開來應(yīng)該做什么事情带到,只需要根據(jù)請求方式來判斷即可。
因此英染,我們需要再創(chuàng)建一個handleMessage方法來做消息處理揽惹。
觀察圖中的兩個方法,其實(shí)就是請求路徑相同四康,但請求方式不同搪搏,一個是GET方式一個是POST方式。
參數(shù)理解
現(xiàn)在我們再來看下開發(fā)文檔闪金,當(dāng)用戶發(fā)送普通消息到公眾號疯溺,微信服務(wù)器發(fā)送的XML數(shù)據(jù)中會包含下面的參數(shù)。
如果是圖片消息會包含下面的參數(shù)
實(shí)際上哎垦,用戶可發(fā)送的類型還有很多囱嫩,比如語音,視頻漏设,地理位置等等墨闲。
我們對比一下不同類型的xml數(shù)據(jù)包中的參數(shù),ToUserName愿题,F(xiàn)romUserName损俭,CreateTime蛙奖,MsgType,MsgId這五個是公共的杆兵,所有類型都會帶上這些參數(shù)雁仲。
接下來,我們需要來了解這5個參數(shù)的具體意義琐脏。
ToUserName:文檔上描述的是開發(fā)者微信號攒砖,實(shí)際上,直接把它當(dāng)做你的公眾號的微信號即可日裙,表示的是發(fā)到那個公眾號的意思吹艇。
FromUserName:與ToUserName相反,這是代表是由哪個用戶發(fā)過來的昂拂,同一個用戶發(fā)多條信息過來受神,F(xiàn)romUserName都是不變的。但這并不是用戶的微信號格侯,而是一個OpenID鼻听。
那什么是OpenID呢:當(dāng)用戶和公眾號發(fā)生了交互,微信服務(wù)器會為每個用戶針對每個公眾號產(chǎn)生一個OpenID(也就是指該OpenID是利用兩個因素:用戶和公眾號來產(chǎn)生的联四,也就意味著如果該用戶跟另外一個公眾號交互撑碴,產(chǎn)生的OpenID也是不同的,這樣安全性會比較高)朝墩,如果一個公司有多個公眾號醉拓,并且需要在多公眾號、移動應(yīng)用之間做用戶共通收苏,則需要使用UnionID亿卤,前往微信開放平臺,將這些公眾號和應(yīng)用綁定到一個開放平臺賬號下倒戏,綁定后怠噪,一個用戶雖然對多個公眾號和應(yīng)用有多個不同的OpenID,但他對所有這些同一開放平臺賬號下的公眾號和應(yīng)用杜跷,只有一個UnionID,可以在用戶管理-獲取用戶基本信息(UnionID機(jī)制)文檔了解詳情矫夷。
CreateTime:消息創(chuàng)建時間葛闷,這個沒什么好說的了。
MsgType:用戶發(fā)送的消息的類型双藕,如text代表文本消息淑趾,image代表圖片消息等。
MsgId:用戶發(fā)送的每個消息都有自己的id忧陪,可以用于消息排重扣泊,比如微信服務(wù)器把xml消息包發(fā)送到URL了近范,但是五秒內(nèi)微信服務(wù)器沒有收到我們的響應(yīng),則會重新發(fā)起請求延蟹,總共重試三次评矩。如果不做消息排重,那么用戶可能就收到多條相同的響應(yīng)消息了阱飘。
接下來斥杜,我們可以創(chuàng)建一個封裝消息的實(shí)體類,把所有可接收到的參數(shù)都放進(jìn)入沥匈,其他類型的暫時不演示蔗喂,所以只在最后加入了文本和圖片的參數(shù)。
@Setter
@Getter
public class InMsgEntity {
// 開發(fā)者微信號
protected String FromUserName;
// 發(fā)送方帳號(一個OpenID)
protected String ToUserName;
// 消息創(chuàng)建時間
protected Long CreateTime;
/**
* 消息類型
* text 文本消息
* image 圖片消息
* voice 語音消息
* video 視頻消息
* music 音樂消息
*/
protected String MsgType;
// 消息id
protected Long MsgId;
// 文本內(nèi)容
private String Content;
// 圖片鏈接(由系統(tǒng)生成)
private String PicUrl;
// 圖片消息媒體id高帖,可以調(diào)用多媒體文件下載接口拉取數(shù)據(jù)
private String MediaId;
}
這時候大家可能會有個疑問缰儿,為什么字段名稱都是大寫開頭呢?
因?yàn)槲⑿欧?wù)器傳過來的xml數(shù)據(jù)包中的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>
因?yàn)閤ml解析是大小寫敏感的乖阵,所以為了方便封裝,我直接把字段名設(shè)置為大寫開頭爪飘。
當(dāng)然义起,如果還是想要小寫開頭的字段,也是可以的师崎,我們待會再說處理方式默终。
接收消息
實(shí)體已經(jīng)建好之后,我們就可以開始接收微信傳過來的xml數(shù)據(jù)了犁罩。
第1步:在handleMessage方法的形參上添加InMsgEntity類型的參數(shù)齐蔽,并且貼上@RequestBody注解,如下代碼所示:
/**
* 微信消息處理
*/
@RequestMapping(value = "/weChat", method = RequestMethod.POST)
@ResponseBody
public Object handleMessage(@RequestBody InMsgEntity msg) {
return null;
}
@RequestBody 該注解用于讀取request請求的body部分?jǐn)?shù)據(jù)床估,根據(jù)Content-Type來判斷把數(shù)據(jù)當(dāng)做什么類型來解析含滴,然后把相應(yīng)的數(shù)據(jù)綁定到參數(shù)上。
第2步:需要配合JAXB的注解來解析xml丐巫。
在 InMsgEntity 上添加以下兩個注解:
@XmlRootElement(name="xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class InMsgEntity {
......
}
@XmlRootElement是一個類級別注解谈况,主要屬性為name,意為指定根節(jié)點(diǎn)的名字递胧。
往上面看前面舉了個微信傳過來的xml數(shù)據(jù)的例子里碑韵,里面的根節(jié)點(diǎn)就是"xml",所以這里就直接設(shè)置name="xml"
@XmlAccessorType用于定義這個類中的何種類型需要映射到XML中
XmlAccessType.PROPERTY:代表映射這個類中的屬性(get/set方法)到XML
XmlAccessType.FIELD:代表映射這個類中的所有字段到XML(我選用的缎脾,現(xiàn)在的字段名剛好是大寫開頭了)
另外祝闻,剛才說到如果字段名是小寫,怎么解決封裝問題遗菠?
在每個字段或?qū)傩陨咸砑覢XmlElement注解來指定名稱映射
如:
@XmlElement(name="FromUserName")
protected String fromUserName;
JAXB還有非常多的注解和類型联喘,這里只介紹我所用到的华蜒,如需了解其他請自行百度。
現(xiàn)在我們可以掃描自己的公眾號二維碼來測試發(fā)送消息后臺服務(wù)器是否能接收到豁遭。
通過debug可知叭喜,微信傳過來的xml消息包已經(jīng)成功轉(zhuǎn)換為我們的java對象了。
響應(yīng)消息
現(xiàn)在我們可以先來嘗試回復(fù)一條相同的內(nèi)容給用戶堤框。
打開微信開發(fā)文檔域滥,選擇"被動回復(fù)消息"。
發(fā)送被動消息其實(shí)不是一種接口蜈抓,而是對微信服務(wù)器發(fā)過來消息的一次回復(fù)启绰。
我們可以看到文檔里面接收的普通文本回復(fù)的格式和接收的格式基本是一樣的,但是圖片消息或其他消息的還是有些區(qū)別沟使。
<xml>
<ToUserName>< ![CDATA[toUser] ]></ToUserName>
<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType>< ![CDATA[image] ]></MsgType>
<Image>
<MediaId>< ![CDATA[media_id] ]></MediaId>
</Image>
</xml>
如上例子委可,比之前多了Image的元素,所以我們需要再創(chuàng)建一個類來封裝響應(yīng)的xml消息腊嗡。
這里我是把所有類型的屬性統(tǒng)一放到OutMsgEntity類中着倾,大家也可以抽取一個父類,不同的消息創(chuàng)建不同的子類也可以燕少。
@XmlRootElement(name="xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class OutMsgEntity {
// 發(fā)送方的賬號
protected String FromUserName;
// 接收方的賬號(OpenID)
protected String ToUserName;
// 消息創(chuàng)建時間
protected Long CreateTime;
/**
* 消息類型
* text 文本消息
* image 圖片消息
* voice 語音消息
* video 視頻消息
* music 音樂消息
* news 圖文消息
*/
protected String MsgType;
// 圖片消息媒體id卡者,可以調(diào)用多媒體文件下載接口拉取數(shù)據(jù)
@XmlElementWrapper(name="Image")
private String[] MediaId ;
// 文本內(nèi)容
private String Content;
}
@XmlElementWrapper注解可以在原xml結(jié)點(diǎn)上再包裝一層xml,但僅允許出現(xiàn)在數(shù)組或集合屬性上客们。
實(shí)際上崇决,我們現(xiàn)在的需求比較簡單,用戶給我們發(fā)什么底挫,我們就回復(fù)什么恒傻,只需要把接收到 InMsgEntity 的內(nèi)容設(shè)置到 OutMsgEntity 上,并且把ToUserName與FormUserName的值設(shè)置為相反即可建邓。
代碼如下:
/**
* 微信消息處理
*/
@RequestMapping(value = "/weChat", method = RequestMethod.POST)
@ResponseBody
public Object handleMessage(@RequestBody InMsgEntity msg) {
//創(chuàng)建消息響應(yīng)對象
OutMsgEntity out = new OutMsgEntity();
//把原來的發(fā)送方設(shè)置為接收方
out.setToUserName(msg.getFromUserName());
//把原來的接收方設(shè)置為發(fā)送方
out.setFromUserName(msg.getToUserName());
//獲取接收的消息類型
String msgType = msg.getMsgType();
//設(shè)置消息的響應(yīng)類型
out.setMsgType(msgType);
//設(shè)置消息創(chuàng)建時間
out.setCreateTime(new Date().getTime());
//根據(jù)類型設(shè)置不同的消息數(shù)據(jù)
if("text".equals(msgType)){
out.setContent(msg.getContent());
}else if("image".equals(msgType)){
out.setMediaId(new String[]{msg.getMediaId()});
}
return out;
}
測試效果:
到此盈厘,我們已經(jīng)實(shí)現(xiàn)了對消息的接收和響應(yīng)簡單操作。