微信公众号开发教程(二)消息接收与响应处理

作者:陈惠,叩丁狼教育高级讲师。原创文章,转载请注明出处。

上一篇文章我们已经实现了URL接入,接下来就可以利用微信官方的开发文档来实现不同的案例。(注意:若还没有进行接入操作,请参考开发教程(一))

实现思路

接下来我们先打开微信的开发文档,选择“消息管理”模块中的”接收普通消息“

文档.png

文档中已经告诉我们,当普通微信用户向公众账号发送消息时,微信服务器会把该消息封装成XML数据包通过POST的方式发送到开发者填写的URL上。我们设置的URL仅仅只有一个,上篇文章中是用来做接入验证的,当时是微信服务器发送GET请求过来,而现在是用来做消息处理的,此时微信服务器发送的是POST请求,因此想要区分开来应该做什么事情,只需要根据请求方式来判断即可。

因此,我们需要再创建一个handleMessage方法来做消息处理。

方法签名.png

观察图中的两个方法,其实就是请求路径相同,但请求方式不同,一个是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对象了。


图片.png

响应消息

现在我们可以先来尝试回复一条相同的内容给用户。

打开微信开发文档,选择"被动回复消息"。

发送被动消息其实不是一种接口,而是对微信服务器发过来消息的一次回复。

图片.png

我们可以看到文档里面接收的普通文本回复的格式和接收的格式基本是一样的,但是图片消息或其他消息的还是有些区别。

<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;
    }

测试效果:

图片.png

到此,我们已经实现了对消息的接收和响应简单操作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容