当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。
微信服务器在将用户的消息发给公众号的开发者服务器地址(开发者中心处配置)后,微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次,如果在调试中,发现用户无法收到响应的消息,可以检查是否消息处理超时。关于重试的消息排重,有msgid的消息推荐使用msgid排重。事件类型消息推荐使用FromUserName + CreateTime 排重。
如果开发者希望增强安全性,可以在开发者中心处开启消息加密,这样,用户发给公众号的消息以及公众号被动回复用户消息都会继续加密,详见被动回复消息加解密说明。
假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。详见下面说明:
1、直接回复success(推荐方式) 2、直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)
一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:
1、开发者在5秒内未回复任何内容 2、开发者回复了异常数据,比如JSON数据等
另外,请注意,回复图片(不支持gif动图)等多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器,可以使用素材管理中的临时素材,也可以使用永久素材。
各消息类型需要的XML数据包结构如下:
目录
1 回复文本消息
2 回复图片消息
3 回复语音消息
4 回复视频消息
5 回复音乐消息
6 回复图文消息
回复文本消息
<![CDATA[toUser]]>
<![CDATA[fromUser]]>
12345678
<![CDATA[text]]>
<![CDATA[你好]]>
参数 是否必须 描述
ToUserName 是 接收方帐号(收到的OpenID)
FromUserName 是 开发者微信号
CreateTime 是 消息创建时间 (整型)
MsgType 是 消息类型,文本为text
Content 是 回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示)
回复图片消息
<![CDATA[toUser]]>
<![CDATA[fromUser]]>
12345678
<![CDATA[image]]>
<![CDATA[media_id]]>
参数 是否必须 说明
ToUserName 是 接收方帐号(收到的OpenID)
FromUserName 是 开发者微信号
CreateTime 是 消息创建时间 (整型)
MsgType 是 消息类型,图片为image
MediaId 是 通过素材管理中的接口上传多媒体文件,得到的id。
回复语音消息
<![CDATA[toUser]]>
<![CDATA[fromUser]]>
12345678
<![CDATA[voice]]>
<![CDATA[media_id]]>
参数 是否必须 说明
ToUserName 是 接收方帐号(收到的OpenID)
FromUserName 是 开发者微信号
CreateTime 是 消息创建时间戳 (整型)
MsgType 是 消息类型,语音为voice
MediaId 是 通过素材管理中的接口上传多媒体文件,得到的id
回复视频消息
<![CDATA[toUser]]>
<![CDATA[fromUser]]>
12345678
<![CDATA[video]]>
<![CDATA[media_id]]>
<![CDATA[title]]>
<![CDATA[description]]>
参数 是否必须 说明
ToUserName 是 接收方帐号(收到的OpenID)
FromUserName 是 开发者微信号
CreateTime 是 消息创建时间 (整型)
MsgType 是 消息类型,视频为video
MediaId 是 通过素材管理中的接口上传多媒体文件,得到的id
Title 否 视频消息的标题
Description 否 视频消息的描述
回复音乐消息
<![CDATA[toUser]]>
<![CDATA[fromUser]]>
12345678
<![CDATA[music]]>
<![CDATA[TITLE]]>
<![CDATA[DESCRIPTION]]>
<![CDATA[MUSIC_Url]]>
<![CDATA[HQ_MUSIC_Url]]>
<![CDATA[media_id]]>
参数 是否必须 说明
ToUserName 是 接收方帐号(收到的OpenID)
FromUserName 是 开发者微信号
CreateTime 是 消息创建时间 (整型)
MsgType 是 消息类型,音乐为music
Title 否 音乐标题
Description 否 音乐描述
MusicURL 否 音乐链接
HQMusicUrl 否 高质量音乐链接,WIFI环境优先使用该链接播放音乐
ThumbMediaId 是 缩略图的媒体id,通过素材管理中的接口上传多媒体文件,得到的id
回复图文消息
<![CDATA[toUser]]>
<![CDATA[fromUser]]>
12345678
<![CDATA[news]]>
1
<![CDATA[title1]]>
<![CDATA[description1]]>
<![CDATA[picurl]]>
<![CDATA[url]]>
参数 是否必须 说明
ToUserName 是 接收方帐号(收到的OpenID)
FromUserName 是 开发者微信号
CreateTime 是 消息创建时间 (整型)
MsgType 是 消息类型,图文为news
ArticleCount 是 图文消息个数;当用户发送文本、图片、语音、视频、图文、地理位置这六种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息
Articles 是 图文消息信息,注意,如果图文数超过限制,则将只发限制内的条数
Title 是 图文消息标题
Description 是 图文消息描述
PicUrl 是 图片链接,支持JPG、PNG格式,较好的效果为大图360200,小图200200
Url 是 点击图文消息跳转链接
现在我们可以先来尝试回复一条相同的内容给用户。
打开微信开发文档,选择"被动回复消息"。
发送被动消息其实不是一种接口,而是对微信服务器发过来消息的一次回复。
我们可以看到文档里面接收的普通文本回复的格式和接收的格式基本是一样的,但是图片消息或其他消息的还是有些区别。
<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的元素,所以我们需要再创建一个类来封装响应的xml消息。
这里我是把所有类型的属性统一放到OutMsgEntity类中,大家也可以抽取一个父类,不同的消息创建不同的子类也可以。
@XmlRootElement(name="xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class OutMsgEntity {
// 发送方的账号
protected String FromUserName;
// 接收方的账号(OpenID)
protected String ToUserName;
// 消息创建时间
protected Long CreateTime;
/**
* 消息类型
* text 文本消息
* image 图片消息
* voice 语音消息
* video 视频消息
* music 音乐消息
* news 图文消息
*/
protected String MsgType;
// 图片消息媒体id,可以调用多媒体文件下载接口拉取数据
@XmlElementWrapper(name="Image")
private String[] MediaId ;
// 文本内容
private String Content;
}
@XmlElementWrapper注解可以在原xml结点上再包装一层xml,但仅允许出现在数组或集合属性上。
实际上,我们现在的需求比较简单,用户给我们发什么,我们就回复什么,只需要把接收到 InMsgEntity 的内容设置到 OutMsgEntity 上,并且把ToUserName与FormUserName的值设置为相反即可。
代码如下:
/**
* 微信消息处理
*/
@RequestMapping(value = "/weChat", method = RequestMethod.POST)
@ResponseBody
public Object handleMessage(@RequestBody InMsgEntity msg) {
//创建消息响应对象
OutMsgEntity out = new OutMsgEntity();
//把原来的发送方设置为接收方
out.setToUserName(msg.getFromUserName());
//把原来的接收方设置为发送方
out.setFromUserName(msg.getToUserName());
//获取接收的消息类型
String msgType = msg.getMsgType();
//设置消息的响应类型
out.setMsgType(msgType);
//设置消息创建时间
out.setCreateTime(new Date().getTime());
//根据类型设置不同的消息数据
if("text".equals(msgType)){
out.setContent(msg.getContent());
}else if("image".equals(msgType)){
out.setMediaId(new String[]{msg.getMediaId()});
}
return out;
}
测试效果:
代码:
OutMsgEntity
package com.camel.ssm.bean;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
/**
* 接收普通消息
*/
@XmlRootElement(name="xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class OutMsgEntity {
// 如果要是小写的话 在每个字段都填上
// @XmlRootElement(name="ToUserName")
private String ToUserName; //开发者微信号
private String FromUserName; //发送方帐号(一个OpenID)
private Long CreateTime; //消息创建时间 (整型)
private String Content; //文本消息内容
private String MsgType; //消息类型,文本为text
@XmlElementWrapper(name="Image")
private String[] MediaId; //图片消息媒体id,可以调用获取临时素材接口拉取数据。
private String ThumbMediaId; //视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。
private String Scale;// 地图缩放大小
private String Label;// 地理位置信息
private String Title;// 消息标题
private String Description;// 消息描述
private String Url;// 消息链接
private String MusicURL; //否 音乐链接
private String HQMusicUrl; //否 高质量音乐链接,WIFI环境优先使用该链接播放音乐
private String ArticleCount; //是 图文消息个数;当用户发送文本、图片、语音、视频、图文、地理位置这六种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息
private String Articles; //是 图文消息信息,注意,如果图文数超过限制,则将只发限制内的条数
private String PicUrl ;//是 图片链接,支持JPG、PNG格式,较好的效果为大图360*200,小图200*200
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 getContent() {
return Content;
}
public void setContent(String content) {
Content = content;
}
public String getMsgType() {
return MsgType;
}
public void setMsgType(String msgType) {
MsgType = msgType;
}
public String[] getMediaId() {
return MediaId;
}
public void setMediaId(String[] mediaId) {
MediaId = mediaId;
}
public String getThumbMediaId() {
return ThumbMediaId;
}
public void setThumbMediaId(String thumbMediaId) {
ThumbMediaId = thumbMediaId;
}
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;
}
public String getMusicURL() {
return MusicURL;
}
public void setMusicURL(String musicURL) {
MusicURL = musicURL;
}
public String getHQMusicUrl() {
return HQMusicUrl;
}
public void setHQMusicUrl(String HQMusicUrl) {
this.HQMusicUrl = HQMusicUrl;
}
public String getArticleCount() {
return ArticleCount;
}
public void setArticleCount(String articleCount) {
ArticleCount = articleCount;
}
public String getArticles() {
return Articles;
}
public void setArticles(String articles) {
Articles = articles;
}
public String getPicUrl() {
return PicUrl;
}
public void setPicUrl(String picUrl) {
PicUrl = picUrl;
}
}
EchostrController
/**
* 普通消息+被动回复用户信息
*/
@RequestMapping(value = "wechat",method = RequestMethod.POST)
@ResponseBody
public Object handleMessage(@RequestBody InMsgEntity ime){
OutMsgEntity outMsg=new OutMsgEntity();
//发送方
outMsg.setFromUserName(ime.getToUserName());
//接收方
outMsg.setToUserName(ime.getFromUserName());
//消息创建时间
outMsg.setCreateTime(new Date().getTime());
//判断一下回复的图片还是文本
String msType= ime.getMsgType();
if(msType.equals("text")){
//设置消息类型
outMsg.setMsgType("text");
//设置消息内容
outMsg.setContent(ime.getContent());
} else if(msType.equals("image")){
outMsg.setMsgType("image");
outMsg.setMediaId(new String[]{ime.getMediaId()});
}
return outMsg;
}