03. 微信公众号消息接收、事件推送与响应处理

1. 消息接收 官方文档
  1. 当普通微信用户向公众账号发消息时,微信服务器将POST消息XML数据包开发者填写的URL上 --> 接口配置信息URL

    URL

  2. 即开发时,接收信息的接口的访问路径微信接入URL一致,但为 POST 请求

  3. 请求参数

    1. 依然会携带请求参数signaturetimestampnonce用于去验证是否是微信服务器发送的消息

    2. 微信发送的普通消息有文本消息图片消息语音消息视频消息小视频消息地理位置信息链接信息,会以XML的格式在请求体中发送过来,可以使用@RequestBody String xml接收,也可以自定义 Bean 来接收。

      // 文本消息格式
      <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>
        <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>
      
      1. 普通文本消息中有 5 个公共的参数:ToUserName、FromUserName、CreateTime、MsgType、MsgId

        参数描述
        ToUserName开发者微信号,表示的是发到哪个公众号,可以当做公众号的微信号
        FromUserName发送方帐号(一个OpenID,用户与公众号交互产生,意味着用户与另一个公众号产生的OpenID不一样),代表是哪一个用户发送过来。
        CreateTime消息创建时间 (整型)
        MsgType用户发送消息的类型
        MsgId消息id,可以用于排重。微信服务器发送消息到我们的服务器时,如果5秒没有回复,微信服务器会重新发起请求,总共重试三次。如果不做消息排重,用户有可能收到多条信息。
      2. 封装实体类接收消息

        import lombok.Data;
        
        import javax.xml.bind.annotation.XmlAccessType;
        import javax.xml.bind.annotation.XmlAccessorType;
        import javax.xml.bind.annotation.XmlRootElement;
        import java.io.Serializable;
        
        /**
         * 微信公众号信息接入参数 bean
         *
         * @author sheng_zs@126.com
         * @date 2021-08-02 16:56
         */
        @Data
        @XmlRootElement(name = "xml")
        @XmlAccessorType(XmlAccessType.FIELD)
        public class WxInMsgBean implements Serializable {
            /**
             * 开发者微信号
             */
            private String ToUserName;
        
            /**
             * 发送方帐号(一个OpenID)
             */
            private String FromUserName;
        
            /**
             * 消息创建时间 (整型)
             */
            private Long CreateTime;
        
            /**
             * 消息类型: {@link com.small.nine.wxmp.config.WxConfig} MSG_TYPE_*
             */
            private String MsgType;
        
            /**
             * 消息id,64位整型
             */
            private Long MsgId;
        
            /**
             * 文本消息内容
             */
            private String Content;
        
            /**
             * 图片链接(由系统生成)
             */
            private String PicUrl;
        
            /**
             * 图片消息媒体id,可以调用获取临时素材接口拉取数据。
             * 语音消息媒体id,可以调用获取临时素材接口拉取数据。
             * 视频消息媒体id,可以调用获取临时素材接口拉取数据。
             */
            private String MediaId;
        
            /**
             * 语音格式,如amr,speex等
             */
            private String Format;
        
            /**
             * 语音识别结果,UTF8编码<br>
             * 需开通语音识别
             */
            private String Recognition;
        
            /**
             * 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。
             */
            private String ThumbMediaId;
        
            /**
             * 地理位置消息:地理位置纬度
             */
            private String Location_X;
        
            /**
             * 地理位置消息:地理位置经度
             */
            private String Location_Y;
        
            /**
             * 地理位置消息:地图缩放大小
             */
            private String Scale;
        
            /**
             * 地理位置消息:地理位置信息
             */
            private String Label;
        
            /**
             * 链接消息:消息标题
             */
            private String Title;
        
            /**
             * 链接消息:消息描述
             */
            private String Description;
        
            /**
             * 链接消息:消息链接
             */
            private String Url;
        
            /**
             * 事件类型 {@link com.small.nine.wxmp.common.constant.WxConstant} EVENT_TYPE_*
             */
            private String Event;
        
            /**
             * 扫描带参数二维码事件:用户未关注时,进行关注后的事件推送:事件KEY值,qrscene_为前缀,后面为二维码的参数值
             * 扫描带参数二维码事件:用户已关注时的事件推送:事件KEY值,是一个32位无符号整数,即创建二维码时的二维码scene_id
             * 自定义菜单事件:点击菜单拉取消息时的事件推送:事件KEY值,与自定义菜单接口中KEY值对应
             * 自定义菜单事件:点击菜单跳转链接时的事件推送:事件KEY值,设置的跳转URL
             */
            private String EventKey;
        
            /**
             * 扫描带参数二维码事件:二维码的ticket,可用来换取二维码图片
             */
            private String Ticket;
        
            /**
             * 上报地理位置事件:地理位置纬度
             */
            private String Latitude;
        
            /**
             * 上报地理位置事件:地理位置经度
             */
            private String Longitude;
        
            /**
             * 上报地理位置事件:地理位置精度
             */
            private String Precision;
        }
        
        1. @XmlRootElement,类级别注解,主要属性为name,作用指定xml根节点的名称,因为微信发送的消息格式根节点为xml,所以设置name = "xml"
        2. @XmlAccessorType用于定义这个类中的哪一种类型需要映射到XMl
          1. XmlAccessType.PROPERTY,映射这个类中的属性(getter/setter方法)到XML

          2. XmlAccessType.FIELD,映射这个类中的所有字段到XML

          3. 如果要按照驼峰命名法来命名属性,那么需要用@XmlElement注解来指定名称映射

            /**
             * 开发者微信号
             */
            @XmlElement("ToUserName")
            private String toUserName;
            
2. 事件推送 官方文档
  1. 在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息。–> 与消息接收的接口一致
  2. 事件推送有关注/取消关注事件、扫描带参数二维码事件、上报地理位置事件、自定义菜单事件、点击菜单拉取消息时的事件推送、点击菜单跳转链接时的事件推送。会以XML的格式在请求体中发送过来。
    1. 事件推送有5个公共参数

      参数描述
      ToUserName开发者微信号,表示的是发到哪个公众号,可以当做公众号的微信号
      FromUserName发送方帐号(一个OpenID,用户与公众号交互产生,意味着用户与另一个公众号产生的OpenID不一样),代表是哪一个用户发送过来。
      CreateTime消息创建时间 (整型)
      MsgType用户发送消息的类型,为event
      Event事件类型
    2. 事件参数跟消息接收的参数封装在一起。

3. 被动回复信息 官方文档
  1. 回复信息

    1. 当用户发送普通信息或者与公众号交互所产生的事件推送时,这都会给接口配置信息URL的资源发送信息,而我们返回的信息就是被动回复信息,且只能返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐

    2. 微信服务器在5秒内收不到响应会断掉连接,并且重新发起请求,总共重试3次

    3. 遇到以下情况,微信都会在公众号会话中,向用户下发系统提示该公众号暂时无法提供服务,请稍后再试

      1. 开发者在5秒内未回复任何内容
      2. 开发者回复了异常数据,比如JSON数据XML 内容缺失
    4. 关于重试的消息排重,有msgid的消息推荐使用msgid排重事件类型消息推荐使用FromUserName + CreateTime 排重

    5. 当作出以下回应时,微信服务器不会作任何处理,且不会发起重试。

      • 直接回复success(推荐)
      • 回复空串""
    6. 4个公共参数

      参数描述
      ToUserName接收方帐号(收到的OpenID,用户与公众号交互产生,意味着用户与另一个公众号产生的OpenID不一样),代表是哪一个用户发送过来。
      FromUserName开发者微信号,表示的是哪个公众号作出响应,可以当做公众号的微信号
      CreateTime消息创建时间 (整型)
      MsgType用户发送消息的类型
  2. 代码

    1. 使用xstreambean 转为 xml

      <!-- xml -->
      <dependency>
          <groupId>com.thoughtworks.xstream</groupId>
          <artifactId>xstream</artifactId>
          <version>1.4.17</version>
      </dependency>    
      
    2. Controller

      /**
       * 接收微信公众号关注者的消息并且给予回应<br>
       * 返回 xml 格式<br>
       * 路径与`测试号 --> 接口配置信息 --> URL`一致,即与`微信接入`的`URL`一致,但为 `POST` 请求
       * <p>还有一个参数:消息发送者 openId,@RequestParam(required = false) String openid,请求体里面有了,且如果加密,该参数为空</p>
       *
       * @param bean      {@link WxInMsgBean},公众号关注者发送的消息
       * @param signature 加密后的字符串
       * @param timestamp 时间戳
       * @param nonce     随机数
       * @return 返回 XMl 字符串
       */
      @PostMapping(value = "/weChat", produces = "application/xml; charset=UTF-8")
      public String msgHandle(@RequestBody WxInMsgBean bean,
                              @RequestParam(required = false) String signature,
                              @RequestParam(required = false) String timestamp,
                              @RequestParam(required = false) String nonce) {
          return wxService.msgHandle(bean, signature, timestamp, nonce);
      }    
      
    3. service

      /**
       * 处理发送的消息
       *
       * @param bean      {@link WxInMsgBean},公众号关注者发送的消息
       * @param signature 加密后的字符串
       * @param timestamp 时间戳
       * @param nonce     随机数
       * @return 返回 XMl 字符串
       */
      String msgHandle(WxInMsgBean bean, String signature, String timestamp, String nonce);    
      
      @Override
      public String msgHandle(WxInMsgBean bean, String signature, String timestamp, String nonce) {
          /*
              验证是否是正常请求
           */
          if (!checkToken(signature, timestamp, nonce, wxConfig.getToken())) {
              throw new IllegalArgumentException("微信-非法请求,可能属于伪造请求");
          }
          log.info("WxInMsgBean: {}", bean.toString());
      
          String msg = WxConstant.MSG_SUCCESS;
          switch (bean.getMsgType()) {
              case WxConstant.MSG_TYPE_TEXT:
                  // 处理 文本信息
                  msg = WxTextMsgBean.getInstance(bean.getFromUserName(), bean.getToUserName(), bean.getCreateTime(), bean.getContent()).toXml();
                  break;
              case WxConstant.MSG_TYPE_IMAGE:
                  // 处理图片信息
                  msg = WxImageMsgBean.getInstance(bean.getFromUserName(), bean.getToUserName(), bean.getCreateTime(), bean.getMediaId()).toXml();
                  break;
              case WxConstant.MSG_TYPE_EVENT:
                  // 处理事件
                  switch (bean.getEvent()) {
                      case WxConstant.EVENT_TYPE_SUBSCRIBE:
                          // 用户关注公众号
                          ArticlesBean articlesBean = ArticlesBean.getInstance("标题", "http://mmbiz.qpic.cn/mmbiz_jpg/8IpUia88gYWiaj07aFP5jcHH5gDlicXhlgNwS2gxfEbC1T4NLGAr9qowOPLHIXBHFhewUxaogqcDMHgcmnZyt5MibA/0", "图文信息", "http://www.baidu.com");
                          ArticlesBean articlesBean1 = ArticlesBean.getInstance("标题", "http://mmbiz.qpic.cn/mmbiz_jpg/8IpUia88gYWiaj07aFP5jcHH5gDlicXhlgNwS2gxfEbC1T4NLGAr9qowOPLHIXBHFhewUxaogqcDMHgcmnZyt5MibA/0", "图文信息", "http://www.baidu.com");
                          ArticlesBean articlesBean2 = ArticlesBean.getInstance("标题", "http://mmbiz.qpic.cn/mmbiz_jpg/8IpUia88gYWiaj07aFP5jcHH5gDlicXhlgNwS2gxfEbC1T4NLGAr9qowOPLHIXBHFhewUxaogqcDMHgcmnZyt5MibA/0", "图文信息", "http://www.baidu.com");
      
                          msg = WxNewsMsgBean.getInstance(bean.getFromUserName(), bean.getToUserName(), bean.getCreateTime(), articlesBean, articlesBean1, articlesBean2).toXml();
                          break;
                      case WxConstant.EVENT_TYPE_UNSUBSCRIBE:
                          // 用户取消关注
                          // 加入黑名单、删除数据库等操作
                          break;
                      default:
                          break;
                  }
                  break;
              default:
                  msg = WxTextMsgBean.getInstance(bean.getFromUserName(), bean.getToUserName(), bean.getCreateTime(), "暂时只支持文本、图片消息").toXml();
                  break;
          }
          log.info("回复信息 msg: {}", msg);
          return msg;
      }    
      
    4. 封装结果集

      1. baseBean

        import com.thoughtworks.xstream.annotations.XStreamAlias;
        import lombok.Data;
        
        import java.io.Serializable;
        
        /**
         * 微信被动回复信息-基础 bean
         *
         * @author sheng_zs@126.com
         * @date 2021-08-02 17:19
         */
        @Data
        public abstract class WxOutMsgBaseBean implements Serializable {
            /**
             * 开发者微信号
             */
            @XStreamAlias("ToUserName")
            protected String toUserName;
        
            /**
             * 发送方帐号(一个OpenID)
             */
            @XStreamAlias("FromUserName")
            protected String fromUserName;
        
            /**
             * 消息创建时间 (整型)
             */
            @XStreamAlias("CreateTime")
            protected Long createTime;
        
            /**
             * 消息类型: {@link com.small.nine.wxmp.common.constant.WxConstant} MSG_TYPE_*
             */
            @XStreamAlias("MsgType")
            protected String msgType;
        
            /**
             * 转化为 XML 字符串
             *
             * @return XML 字符串
             */
            public abstract String toXml();
        }        
        
      2. 文本信息及图片信息

        1. 文本信息

          import com.small.nine.wxmp.common.constant.WxConstant;
          import com.small.nine.wxmp.utils.wx.XmlUtils;
          import com.thoughtworks.xstream.annotations.XStreamAlias;
          import lombok.Data;
          import lombok.EqualsAndHashCode;
          
          /**
           * 回复文本消息
           *
           * @author sheng_zs@126.com
           * @date 2021-08-02 17:22
           */
          @Data
          @EqualsAndHashCode(callSuper = true)
          @XStreamAlias("xml")
          public class WxTextMsgBean extends WxOutMsgBaseBean {
              /**
               * 回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示)
               */
              @XStreamAlias("Content")
              private String content;
          
              @Override
              public String toXml() {
                  return XmlUtils.beanToXml(this);
              }
          
              /**
               * 初始化
               *
               * @param toUser     接收者,openID
               * @param fromUser   发送者
               * @param createTime 时间戳
               * @param content    文本信息
               * @return {@link WxTextMsgBean}
               */
              public static WxTextMsgBean getInstance(String toUser, String fromUser, Long createTime, String content) {
                  WxTextMsgBean msgBean = new WxTextMsgBean();
                  msgBean.setToUserName(toUser);
                  msgBean.setFromUserName(fromUser);
                  msgBean.setMsgType(WxConstant.MSG_TYPE_TEXT);
                  msgBean.setCreateTime(createTime);
                  msgBean.setContent(content);
                  return msgBean;
              }
          }
          
        2. 图片信息

          import com.thoughtworks.xstream.annotations.XStreamAlias;
          import lombok.Data;
          
          import java.io.Serializable;
          
          /**
           * 图片信息
           *
           * @author sheng_zs@126.com
           * @date 2021-08-02 17:29
           */
          @Data
          @XStreamAlias("Image")
          public class ImageBean implements Serializable {
              /**
               * 通过素材管理中的接口上传多媒体文件,得到的id。
               */
              @XStreamAlias("MediaId")
              private String mediaId;
          }
          
          import com.small.nine.wxmp.common.constant.WxConstant;
          import com.small.nine.wxmp.utils.wx.XmlUtils;
          import com.thoughtworks.xstream.annotations.XStreamAlias;
          import lombok.Data;
          import lombok.EqualsAndHashCode;
          
          /**
           * 回复图片消息
           *
           * @author sheng_zs@126.com
           * @date 2021-08-02 17:30
           */
          @Data
          @EqualsAndHashCode(callSuper = true)
          @XStreamAlias("xml")
          public class WxImageMsgBean extends WxOutMsgBaseBean {
              /**
               * Image 节点
               */
              @XStreamAlias("Image")
              private ImageBean image;
          
              @Override
              public String toXml() {
                  return XmlUtils.beanToXml(this);
              }
          
              /**
               * 初始化
               *
               * @param toUser     接收者,openID
               * @param fromUser   发送者
               * @param createTime 时间戳
               * @param mediaId    图片ID
               * @return {@link WxImageMsgBean}
               */
              public static WxImageMsgBean getInstance(String toUser, String fromUser, Long createTime, String mediaId) {
                  WxImageMsgBean msgBean = new WxImageMsgBean();
                  msgBean.setToUserName(toUser);
                  msgBean.setFromUserName(fromUser);
                  msgBean.setMsgType(WxConstant.MSG_TYPE_IMAGE);
                  msgBean.setCreateTime(createTime);
                  ImageBean bean = new ImageBean();
                  bean.setMediaId(mediaId);
                  msgBean.setImage(bean);
                  return msgBean;
              }
          }
          
      3. 工具类

        import com.thoughtworks.xstream.XStream;
        
        /**
         * xml 相关工具类
         *
         * @author sheng_zs@126.com
         * @date 2021-08-02 17:23
         */
        public class XmlUtils {
            /**
             * 将 Java Bean 转化为 XML
             *
             * @param bean {@link Object}
             * @param cls  传入对象的字节码
             * @return XML 字符串
             */
            public static <T> String beanToXml(Object bean, Class<T> cls) {
                XStream stream = new XStream();
                stream.processAnnotations(cls);
                return stream.toXML(bean);
            }
        
            /**
             * 将 Java Bean 转化为 XML
             *
             * @param bean {@link Object}
             * @return XML 字符串
             */
            public static String beanToXml(Object bean) {
                return beanToXml(bean, bean.getClass());
            }
        }        
        
    5. 源码

  • 12
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值