hello 各位小伙伴,今天我们来继续学习如何通过 Spring Boot 开发微信公众号。还没阅读过上篇文章的小伙伴建议先看看上文,有助于理解本文:

上篇文章中我们将微信服务器和我们自己的服务器对接起来了,并且在自己的服务器上也能收到微信服务器发来的消息,本文我们要看的就是如何给微信服务器回复消息。

消息分类

在讨论如何给微信服务器回复消息之前,我们需要先来了解下微信服务器发来的消息主要有哪些类型以及我们回复给微信的消息都有哪些类型。

在上文中大家了解到,微信发送来的 xml 消息中有一个 MsgType 字段,这个字段就是用来标记消息的类型。这个类型可以标记出这条消息是普通消息还是事件消息还是图文消息等。

普通消息主要是指:

  • 文本消息
  • 图片消息
  • 语音消息
  • 视频消息
  • 小视频消息
  • 地址位置消息
  • 链接消息

不同的消息类型,对应不同的 MsgType,这里我还是以普通消息为例,如下:

消息类型 MsgType
文本消息 text
图片消息 image
语音消息 voice
视频消息 video
小视频消息 shortvideo
地址位置消息 location
链接消息 link

大家千万不要以为不同类型消息的格式是一样的,其实是不一样的,也就是说,MsgType 为 text 的消息和 MsgType 为 image 的消息,微信服务器发给我们的消息内容是不一样的,这样带来一个问题就是我无法使用一个 Bean 去接收不同类型的数据,因此这里我们一般使用 Map 接收即可。

这是消息的接收,除了消息的接收之外,还有一个消息的回复,我们回复的消息也有很多类型,可以回复普通消息,也可以回复图片消息,回复语音消息等,不同的回复消息我们可以进行相应的封装。因为不同的返回消息实例也是有一些共同的属性的,例如消息是谁发来的,发给谁,消息类型,消息 id 等,所以我们可以将这些共同的属性定义成一个父类,然后不同的消息再去继承这个父类。

返回消息类型定义

首先我们来定义一个公共的消息类型:

  1. public class BaseMessage {
  2. private String ToUserName;
  3. private String FromUserName;
  4. private long CreateTime;
  5. private String MsgType;
  6. private long MsgId;
  7. //省略 getter/setter
  8. }

在这里:

  • ToUserName 表示开发者的微信号
  • FromUserName 表示发送方账号(用户的 OpenID)
  • CreateTime 消息的创建时间
  • MsgType 表示消息的类型
  • MsgId 表示消息 id

这是我们的基本消息类型,就是说,我们返回给用户的消息,无论是什么类型的消息,都有这几个基本属性。然后在此基础上,我们再去扩展出文本消息、图片消息 等。

我们来看下文本消息的定义:

  1. public class TextMessage extends BaseMessage {
  2. private String Content;
  3. //省略 getter/setter
  4. }

文本消息在前面消息的基础上多了一个 Content 属性,因此文本消息继承自 BaseMessage ,再额外添加一个 Content 属性即可。

其他的消息类型也是类似的定义,我就不一一列举了,至于其他消息的格式,大家可以参考微信开放文档(http://1t.click/aPXK)。

返回消息生成

消息类型的 Bean 定义完成之后,接下来就是将实体类生成 XML。

首先我们定义一个消息工具类,将常见的消息类型枚举出来:

  1. /**
  2. * 返回消息类型:文本
  3. */
  4. public static final String RESP_MESSAGE_TYPE_TEXT = "text";
  5. /**
  6. * 返回消息类型:音乐
  7. */
  8. public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
  9. /**
  10. * 返回消息类型:图文
  11. */
  12. public static final String RESP_MESSAGE_TYPE_NEWS = "news";
  13. /**
  14. * 返回消息类型:图片
  15. */
  16. public static final String RESP_MESSAGE_TYPE_Image = "image";
  17. /**
  18. * 返回消息类型:语音
  19. */
  20. public static final String RESP_MESSAGE_TYPE_Voice = "voice";
  21. /**
  22. * 返回消息类型:视频
  23. */
  24. public static final String RESP_MESSAGE_TYPE_Video = "video";
  25. /**
  26. * 请求消息类型:文本
  27. */
  28. public static final String REQ_MESSAGE_TYPE_TEXT = "text";
  29. /**
  30. * 请求消息类型:图片
  31. */
  32. public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
  33. /**
  34. * 请求消息类型:链接
  35. */
  36. public static final String REQ_MESSAGE_TYPE_LINK = "link";
  37. /**
  38. * 请求消息类型:地理位置
  39. */
  40. public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
  41. /**
  42. * 请求消息类型:音频
  43. */
  44. public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
  45. /**
  46. * 请求消息类型:视频
  47. */
  48. public static final String REQ_MESSAGE_TYPE_VIDEO = "video";
  49. /**
  50. * 请求消息类型:推送
  51. */
  52. public static final String REQ_MESSAGE_TYPE_EVENT = "event";
  53. /**
  54. * 事件类型:subscribe(订阅)
  55. */
  56. public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
  57. /**
  58. * 事件类型:unsubscribe(取消订阅)
  59. */
  60. public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
  61. /**
  62. * 事件类型:CLICK(自定义菜单点击事件)
  63. */
  64. public static final String EVENT_TYPE_CLICK = "CLICK";
  65. /**
  66. * 事件类型:VIEW(自定义菜单 URl 视图)
  67. */
  68. public static final String EVENT_TYPE_VIEW = "VIEW";
  69. /**
  70. * 事件类型:LOCATION(上报地理位置事件)
  71. */
  72. public static final String EVENT_TYPE_LOCATION = "LOCATION";
  73. /**
  74. * 事件类型:LOCATION(上报地理位置事件)
  75. */
  76. public static final String EVENT_TYPE_SCAN = "SCAN";

大家注意这里消息类型的定义,以 RESP 开头的表示返回的消息类型,以 REQ 表示微信服务器发来的消息类型。然后在这个工具类中再定义两个方法,用来将返回的对象转换成 XML:

  1. public static String textMessageToXml(TextMessage textMessage) {
  2. xstream.alias("xml", textMessage.getClass());
  3. return xstream.toXML(textMessage);
  4. }
  5. private static XStream xstream = new XStream(new XppDriver() {
  6. public HierarchicalStreamWriter createWriter(Writer out) {
  7. return new PrettyPrintWriter(out) {
  8. boolean cdata = true;
  9. @SuppressWarnings("rawtypes")
  10. public void startNode(String name, Class clazz) {
  11. super.startNode(name, clazz);
  12. }
  13. protected void writeText(QuickWriter writer, String text) {
  14. if (cdata) {
  15. writer.write("<![CDATA[");
  16. writer.write(text);
  17. writer.write("]]>");
  18. } else {
  19. writer.write(text);
  20. }
  21. }
  22. };
  23. }
  24. });

textMessageToXML 方法用来将 TextMessage 对象转成 XML 返回给微信服务器,类似的方法我们还需要定义 imageMessageToXml、voiceMessageToXml 等,不过定义的方式都基本类似,我就不一一列出来了。

返回消息分发

由于用户发来的消息可能存在多种情况,我们需要分类进行处理,这个就涉及到返回消息的分发问题。因此我在这里再定义一个返回消息分发的工具类,如下:

  1. public class MessageDispatcher {
  2. public static String processMessage(Map<String, String> map) {
  3. String openid = map.get("FromUserName"); //用户 openid
  4. String mpid = map.get("ToUserName"); //公众号原始 ID
  5. if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
  6. //普通文本消息
  7. TextMessage txtmsg = new TextMessage();
  8. txtmsg.setToUserName(openid);
  9. txtmsg.setFromUserName(mpid);
  10. txtmsg.setCreateTime(new Date().getTime());
  11. txtmsg.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
  12. txtmsg.setContent("这是返回消息");
  13. return MessageUtil.textMessageToXml(txtmsg);
  14. }
  15. return null;
  16. }
  17. public String processEvent(Map<String, String> map) {
  18. //在这里处理事件
  19. }
  20. }

这里我们还可以多加几个 elseif 去判断不同的消息类型,我这里因为只有普通文本消息,所以一个 if 就够用了。

在这里返回值我写死了,实际上这里需要根据微信服务端传来的 Content 去数据中查询,将查询结果返回,数据库查询这一套相信大家都能搞定,我这里就不重复介绍了。

最后在消息接收 Controller 中调用该方法,如下:

  1. @PostMapping(value = "/verify_wx_token",produces = "application/xml;charset=utf-8")
  2. public String handler(HttpServletRequest request, HttpServletResponse response) throws Exception {
  3. request.setCharacterEncoding("UTF-8");
  4. Map<String, String> map = MessageUtil.parseXml(request);
  5. String msgType = map.get("MsgType");
  6. if (MessageUtil.REQ_MESSAGE_TYPE_EVENT.equals(msgType)) {
  7. return messageDispatcher.processEvent(map);
  8. }else{
  9. return messageDispatcher.processMessage(map);
  10. }
  11. }

在 Controller 中,我们首先判断消息是否是事件,如果是事件,进入到事件处理通道,如果不是事件,则进入到消息处理通道。

注意,这里需要配置一下返回消息的编码,否则可能会出现中文乱码。

如此之后,我们的服务器就可以给公众号返回消息了。

上篇文章发出后,有小伙伴问松哥这个会不会开源,我可以负责任的告诉大家,肯定会开源,这个系列截稿后,我把代码处理下就上传到 GitHub。

好了,本文我们就先说到这里。

关注公众号【江南一点雨】,专注于 Spring Boot+微服务以及前后端分离等全栈技术,定期视频教程分享,关注后回复 Java ,领取松哥为你精心准备的 Java 干货!

Spring Boot 如何给微信公众号返回消息的更多相关文章

  1. 微信小程序结合微信公众号进行消息发送

    微信小程序结合微信公众号进行消息发送 由于小程序的模板消息已经废弃了,官方让使用订阅消息功能.而订阅消息的使用限制比较大,用户必须得订阅.需要获取用户同意接收消息的权限.用户必须得和小程序有交互的时候 ...

  2. C#实现微信公众号群发消息(解决一天只能发一次的限制)

    经过几天研究网上的代码和谢灿大神的帮忙,今天终于用C#实现了微信公众号群发消息,现在整理一下. 总体思路:1.首先必须要在微信公众平台上申请一个公众号. 2.然后进行模拟登陆.(由于我对http传输原 ...

  3. 2014-07-24 .NET实现微信公众号的消息回复与自定义菜单

    今天是在吾索实习的第12天.我们在这一天中,基本实现了微信公众号的消息回复与自定义菜单的创建. 首先,是实现消息回复,其关键点如下: 读取POST来的数据流:Stream 数据流变量 = HttpCo ...

  4. Java微信公众号安全模式消息解密

    这篇文章主要为大家详细介绍了Java微信公众号安全模式消息解密,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 1.微信公众平台下载解密工具,导入项目中,根据demo解密消息 public stat ...

  5. 【C#版本】微信公众号模板消息对接(一)(图文详解)

    特此说明:本篇文章为个人原创文章,创作不易,未经作者本人同意.许可等条件,不得以任何形式搬运.转载.抄袭(等包括但不限于此手段)本文章,否则保留追究有关侵权人责任的权利 一.认识微信公众号模板消息 什 ...

  6. .net微信公众号开发——消息与事件

    作者:王先荣    本文介绍如何处理微信公众号开发中的消息与事件,包括:(1)消息(事件)概况:(2)验证消息的真实性:(3)解析消息:(4)被动回复消息:(5)发送其他消息.    开源项目地址:h ...

  7. 微信公众号发送消息给用户 php

    1.微信公众号 这里得话 一开始先去看了 微信公众号的接口 发现网页授权需要时认证的服务号,一开始想的是那去申请一个认证的服务号岂不是很费事,然后网上搜了搜,发现了还有微信公众号个人测试号这个东西,所 ...

  8. Java对接微信公众号模板消息推送

    内容有点多,请耐心! 最近公司的有这个业务需求,又很凑巧让我来完成: 首先想要对接,先要一个公众号,再就是开发文档了:https://developers.weixin.qq.com/doc/offi ...

  9. 【C#版本】微信公众号模板消息对接(二)(图文详解)

    本篇文章承接上一篇文章内容,点击此段文字传送至上一篇文章. 特此说明:本篇文章为个人原创文章,创作不易,未经作者本人同意.许可等条件,不得以任何形式搬运.转载.抄袭(等包括但不限于上述手段)本文章,否 ...

随机推荐

  1. Terminal MultipleXer---终端复用器tmux基本使用

    Terminal MultipleXer---终端复用器tmux 使用场景:1.scp大文件 2:编译大文件 3:多窗口对比文件 1.安装tmux [root@localhost ~]# yum in ...

  2. ORM增删改查

    目录 orm django 连接mysql顺序 1 settings配置文件中 2 项目文件夹下的init文件中写上下面内容, 补充 3 models文件中创建一个类(类名就是表名) 4.执行数据库同 ...

  3. 关于CSS书写规范、顺序

    关于CSS的书写规范和顺序,是大部分前端er都必须要攻克的一门关卡,如果没有按照良好的CSS书写规范来写CSS代码,会影响代码的阅读体验.这里总结了一个CSS书写规范.CSS书写顺序供大家参考,这些是 ...

  4. js数组和表的基本操作

    数组 var v = [3, 6, "hello"]; console.log(v.length); 数组的遍历1 function ss() { for (var i = 0; ...

  5. Scala 学习笔记之集合(3)

    建立一个Java类,为了演示Java集合类型向Scala集合的转换: import java.util.ArrayList; import java.util.List; public class S ...

  6. springboot+thymeleaf国际化方法一:LocaleResolver

    springboot中大部分有默认配置所以开发起项目来非常迅速,仅对需求项做单独配置覆盖即可 spring采用的默认区域解析器是AcceptHeaderLocaleResolver,根据request ...

  7. abp(net core)+easyui+efcore实现仓储管理系统——EasyUI之货物管理四 (二十二)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

  8. linux分区与挂载

    分区是将一个硬盘驱动器分成若干个逻辑驱动器,分区是把硬盘连续的区块当做一个独立的磁盘使用.分区表是一个硬盘分区的索引,分区的信息都会写进分区表.通常情况下,为磁盘分区通常使用fdisk,它是对基于MB ...

  9. .net mvc web api Autofac依赖注入框架-戈多编程

    今天自己搭了一套基于三层的依赖注入mvc web api 的依赖注入框架,在此总结下相关配置 1.设置应用程序的.net Framework版本为 4.5 2.通过Nuget 安装autofac包 I ...

  10. 推荐一款超好用的工具cmder

    今天来推荐一个超级好用的命令行工具:cmder 一款Windows环境下非常简洁美观易用的cmd替代者,它支持了大部分的Linux命令.支持ssh连接linux,使用起来非常方便.比起cmd.powe ...