当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
请注意:
关于重试的消息排重,推荐使用msgid排重。
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。详情请见“发送消息-被动回复消息”。
如果开发者需要对用户消息在5秒内立即做出回应,即使用“发送消息-被动回复消息”接口向用户被动回复消息时,可以在
公众平台官网的开发者中心处设置消息加密。开启加密后,用户发来的消息和开发者回复的消息都会被加密(但开发者通过客服接口等API调用形式向用户发送消息,则不受影响)。关于消息加解密的详细说明,请见“发送消息-被动回复消息加解密说明”。 各消息类型的推送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>
参数 描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType 消息类型,文本为text
Content 文本消息内容
MsgId 消息id,64位整型
使用网页调试工具调试该接口
图片消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<PicUrl><![CDATA[this is a url]]></PicUrl>
<MediaId><![CDATA[media_id]]></MediaId>
<MsgId>1234567890123456</MsgId>
</xml>
参数 描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType 消息类型,图片为image
PicUrl 图片链接(由系统生成)
MediaId 图片消息媒体id,可以调用获取临时素材接口拉取数据。
MsgId 消息id,64位整型
使用网页调试工具调试该接口
语音消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType><![CDATA[voice]]></MsgType>
<MediaId><![CDATA[media_id]]></MediaId>
<Format><![CDATA[Format]]></Format>
<MsgId>1234567890123456</MsgId>
</xml>
参数 描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType 语音为voice
MediaId 语音消息媒体id,可以调用获取临时素材接口拉取数据。
Format 语音格式,如amr,speex等
MsgId 消息id,64位整型
使用网页调试工具调试该接口
请注意,开通语音识别后,用户每次发送语音给公众号时,微信会在推送的语音消息XML数据包中,增加一个Recognition字段(注:由于客户端缓存,开发者开启或者关闭语音识别功能,对新关注者立刻生效,对已关注用户需要24小时生效。开发者可以重新关注此帐号进行测试)。开启语音识别后的语音XML数据包如下:
<xml>
<ToUserName>< ![CDATA[toUser] ]></ToUserName>
<FromUserName>< ![CDATA[fromUser] ]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType>< ![CDATA[voice] ]></MsgType>
<MediaId>< ![CDATA[media_id] ]></MediaId>
<Format>< ![CDATA[Format] ]></Format>
<Recognition>< ![CDATA[腾讯微信团队] ]></Recognition>
<MsgId>1234567890123456</MsgId>
</xml>
参数说明:
参数 描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType 语音为voice
MediaID 语音消息媒体id,可以调用获取临时素材接口拉取该媒体
Format 语音格式:amr
Recognition 语音识别结果,UTF8编码
MsgId 消息id,64位整型
视频消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType><![CDATA[video]]></MsgType>
<MediaId><![CDATA[media_id]]></MediaId>
<ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>
<MsgId>1234567890123456</MsgId>
</xml>
参数 描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType 视频为video
MediaId 视频消息媒体id,可以调用获取临时素材接口拉取数据。
ThumbMediaId 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。
MsgId 消息id,64位整型
使用网页调试工具调试该接口
小视频消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType><![CDATA[shortvideo]]></MsgType>
<MediaId><![CDATA[media_id]]></MediaId>
<ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>
<MsgId>1234567890123456</MsgId>
</xml>
参数 描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType 小视频为shortvideo
MediaId 视频消息媒体id,可以调用获取临时素材接口拉取数据。
ThumbMediaId 视频消息缩略图的媒体id,可以调用获取临时素材接口拉取数据。
MsgId 消息id,64位整型
使用网页调试工具调试该接口
地理位置消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1351776360</CreateTime>
<MsgType><![CDATA[location]]></MsgType>
<Location_X>23.134521</Location_X>
<Location_Y>113.358803</Location_Y>
<Scale>20</Scale>
<Label><![CDATA[位置信息]]></Label>
<MsgId>1234567890123456</MsgId>
</xml>
参数 描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType 消息类型,地理位置为location
Location_X 地理位置纬度
Location_Y 地理位置经度
Scale 地图缩放大小
Label 地理位置信息
MsgId 消息id,64位整型
使用网页调试工具调试该接口
链接消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1351776360</CreateTime>
<MsgType><![CDATA[link]]></MsgType>
<Title><![CDATA[公众平台官网链接]]></Title>
<Description><![CDATA[公众平台官网链接]]></Description>
<Url><![CDATA[url]]></Url>
<MsgId>1234567890123456</MsgId>
</xml>
参数 描述
ToUserName 接收方微信号
FromUserName 发送方微信号,若为普通用户,则是一个OpenID
CreateTime 消息创建时间
MsgType 消息类型,链接为link
Title 消息标题
Description 消息描述
Url 消息链接
MsgId 消息id,64位整型
实现思路
接下来我们先打开微信的开发文档,选择“消息管理”模块中的”接收普通消息“。
文档中已经告诉我们,当普通微信用户向公众账号发送消息时,微信服务器会把该消息封装成XML数据包通过POST的方式发送到开发者填写的URL上。我们设置的URL仅仅只有一个,上篇文章中是用来做接入验证的,当时是微信服务器发送GET请求过来,而现在是用来做消息处理的,此时微信服务器发送的是POST请求,因此想要区分开来应该做什么事情,只需要根据请求方式来判断即可。
因此,我们需要再创建一个handleMessage方法来做消息处理。
观察图中的两个方法,其实就是请求路径相同,但请求方式不同,一个是GET方式一个是POST方式。
参数理解
现在我们再来看下开发文档,当用户发送普通消息到公众号,微信服务器发送的XML数据中会包含下面的参数。
普通消息*.png
如果是图片消息会包含下面的参数
图片消息.png
实际上,用户可发送的类型还有很多,比如语音,视频,地理位置等等。
我们对比一下不同类型的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了,但是五秒内微信服务器没有收到我们的响应,则会重新发起请求,总共重试三次。如果不做消息排重,那么用户可能就收到多条相同的响应消息了。
接下来,我们可以创建一个封装消息的实体类,把所有可接收到的参数都放进入,其他类型的暂时不演示,所以只在最后加入了文本和图片的参数。
@Setter
@Getter
public class InMsgEntity {
// 开发者微信号
protected String FromUserName;
// 发送方帐号(一个OpenID)
protected String ToUserName;
// 消息创建时间
protected Long CreateTime;
/**
* 消息类型
* text 文本消息
* image 图片消息
* voice 语音消息
* video 视频消息
* music 音乐消息
*/
protected String MsgType;
// 消息id
protected 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方法的形参上添加InMsgEntity类型的参数,并且贴上@RequestBody注解,如下代码所示:
/**
* 微信消息处理
*/
@RequestMapping(value = "/weChat", method = RequestMethod.POST)
@ResponseBody
public Object handleMessage(@RequestBody InMsgEntity msg) {
return null;
}
@RequestBody 该注解用于读取request请求的body部分数据,根据Content-Type来判断把数据当做什么类型来解析,然后把相应的数据绑定到参数上。
第2步:需要配合JAXB的注解来解析xml。
在 InMsgEntity 上添加以下两个注解:
@XmlRootElement(name=“xml”)
@XmlAccessorType(XmlAccessType.FIELD)
public class InMsgEntity {
…
}
@XmlRootElement是一个类级别注解,主要属性为name,意为指定根节点的名字。
往上面看前面举了个微信传过来的xml数据的例子里,里面的根节点就是"xml",所以这里就直接设置name=“xml”
@XmlAccessorType用于定义这个类中的何种类型需要映射到XML中
XmlAccessType.PROPERTY:代表映射这个类中的属性(get/set方法)到XML
XmlAccessType.FIELD:代表映射这个类中的所有字段到XML(我选用的,现在的字段名刚好是大写开头了)
另外,刚才说到如果字段名是小写,怎么解决封装问题?
在每个字段或属性上添加@XmlElement注解来指定名称映射
如:
@XmlElement(name=“FromUserName”)
protected String fromUserName;
JAXB还有非常多的注解和类型,这里只介绍我所用到的,如需了解其他请自行百度。
现在我们可以扫描自己的公众号二维码来测试发送消息后台服务器是否能接收到。
通过debug可知,微信传过来的xml消息包已经成功转换为我们的java对象了。
涉及代码:
EchostrController
/**
* 普通消息
*/
@RequestMapping(value = "wechat",method = RequestMethod.POST)
@ResponseBody
public Object handleMessage(@RequestBody InMsgEntity ime){
System.out.println(ime);
return null;
}
InMsgEntity
package com.camel.ssm.bean;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
/**
* 接收普通消息
*/
@XmlRootElement(name="xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class InMsgEntity {
// 如果要是小写的话 在每个字段都填上
// @XmlRootElement(name="ToUserName")
private String ToUserName; //开发者微信号
private String FromUserName; //发送方帐号(一个OpenID)
private Long CreateTime; //消息创建时间 (整型)
private String MsgType; //消息类型,文本为text
private String Content; //文本消息内容
private Long MsgId; //,64位整型
private String PicUrl; //图片链接(由系统生成)
private String MediaId; //图片消息媒体id,可以调用获取临时素材接口拉取数据。
private String Format; //语音格式,如amr,speex等
private String Recognition; //语音识别结果,UTF8编码
private String ThumbMediaId; //视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。
private String Location_X; //地理位置纬度
private String Location_Y; ;//地理位置经度
private String Scale;// 地图缩放大小
private String Label;// 地理位置信息
private String Title;// 消息标题
private String Description;// 消息描述
private String Url;// 消息链接
public String getToUserName() {
return ToUserName;
}
public void setToUserName(String toUserName) {
ToUserName = toUserName;
}
public String getFromUserName() {
return FromUserName;
}
public void setFromUserName(String fromUserName) {
FromUserName = fromUserName;
}
public Long getCreateTime() {
return CreateTime;
}
public void setCreateTime(Long createTime) {
CreateTime = createTime;
}
public String getMsgType() {
return MsgType;
}
public void setMsgType(String msgType) {
MsgType = msgType;
}
public String getContent() {
return Content;
}
public void setContent(String content) {
Content = content;
}
public Long getMsgId() {
return MsgId;
}
public void setMsgId(Long msgId) {
MsgId = msgId;
}
public String getPicUrl() {
return PicUrl;
}
public void setPicUrl(String picUrl) {
PicUrl = picUrl;
}
public String getMediaId() {
return MediaId;
}
public void setMediaId(String mediaId) {
MediaId = mediaId;
}
public String getFormat() {
return Format;
}
public void setFormat(String format) {
Format = format;
}
public String getRecognition() {
return Recognition;
}
public void setRecognition(String recognition) {
Recognition = recognition;
}
public String getThumbMediaId() {
return ThumbMediaId;
}
public void setThumbMediaId(String thumbMediaId) {
ThumbMediaId = thumbMediaId;
}
public String getLocation_X() {
return Location_X;
}
public void setLocation_X(String location_X) {
Location_X = location_X;
}
public String getLocation_Y() {
return Location_Y;
}
public void setLocation_Y(String location_Y) {
Location_Y = location_Y;
}
public String getScale() {
return Scale;
}
public void setScale(String scale) {
Scale = scale;
}
public String getLabel() {
return Label;
}
public void setLabel(String label) {
Label = label;
}
public String getTitle() {
return Title;
}
public void setTitle(String title) {
Title = title;
}
public String getDescription() {
return Description;
}
public void setDescription(String description) {
Description = description;
}
public String getUrl() {
return Url;
}
public void setUrl(String url) {
Url = url;
}
}