1. 消息接收 官方文档
-
当普通微信用户向公众账号发消息时,微信服务器将
POST消息
的XML数据包
到开发者填写的URL
上 -->接口配置信息
的URL
-
即开发时,接收信息的接口的
访问路径
与微信接入
的URL
一致,但为POST
请求 -
请求参数
-
依然会携带请求参数
signature
、timestamp
、nonce
用于去验证是否是微信服务器发送的消息 -
微信发送的普通消息有
文本消息
、图片消息
、语音消息
、视频消息
、小视频消息
、地理位置信息
、链接信息
,会以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>
-
普通文本消息中有 5 个公共的参数:
ToUserName、FromUserName、CreateTime、MsgType、MsgId
参数 描述 ToUserName 开发者微信号,表示的是发到哪个公众号,可以当做公众号的微信号 FromUserName 发送方帐号(一个 OpenID
,用户与公众号交互产生,意味着用户与另一个公众号产生的OpenID
不一样),代表是哪一个用户发送过来。CreateTime 消息创建时间 (整型) MsgType 用户发送消息的类型 MsgId 消息id,可以用于排重。微信服务器发送消息到我们的服务器时,如果 5秒
没有回复,微信服务器会重新发起请求,总共重试三次。如果不做消息排重,用户有可能收到多条信息。 -
封装实体类接收消息
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; }
@XmlRootElement
,类级别注解,主要属性为name
,作用指定xml
根节点的名称,因为微信发送的消息格式根节点为xml
,所以设置name = "xml"
@XmlAccessorType
用于定义这个类中的哪一种类型需要映射到XMl
中-
XmlAccessType.PROPERTY
,映射这个类中的属性(getter/setter
方法)到XML
-
XmlAccessType.FIELD
,映射这个类中的所有字段到XML
-
如果要按照
驼峰命名法
来命名属性,那么需要用@XmlElement
注解来指定名称映射/** * 开发者微信号 */ @XmlElement("ToUserName") private String toUserName;
-
-
-
2. 事件推送 官方文档
- 在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息。–>
与消息接收的接口一致
- 事件推送有
关注/取消关注事件、扫描带参数二维码事件、上报地理位置事件、自定义菜单事件、点击菜单拉取消息时的事件推送、点击菜单跳转链接时的事件推送
。会以XML的格式在请求体中发送过来。-
事件推送有
5个
公共参数参数 描述 ToUserName 开发者微信号,表示的是发到哪个公众号,可以当做公众号的微信号 FromUserName 发送方帐号(一个 OpenID
,用户与公众号交互产生,意味着用户与另一个公众号产生的OpenID
不一样),代表是哪一个用户发送过来。CreateTime 消息创建时间 (整型) MsgType 用户发送消息的类型,为 event
Event 事件类型 -
事件参数跟消息接收的参数封装在一起。
-
3. 被动回复信息 官方文档
-
回复信息
-
当用户发送普通信息或者与公众号交互所产生的事件推送时,这都会给
接口配置信息
的URL
的资源发送信息,而我们返回的信息就是被动回复信息,且只能返回特定XML结构
,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐
) -
微信服务器在
5秒
内收不到响应会断掉连接,并且重新发起请求,总共重试3次
-
遇到以下情况,微信都会在公众号会话中,向用户下发系统提示
该公众号暂时无法提供服务,请稍后再试
- 开发者在5秒内未回复任何内容
- 开发者回复了异常数据,比如
JSON数据
、XML 内容缺失
等
-
关于重试的消息排重,有
msgid
的消息推荐使用msgid排重
。事件类型消息
推荐使用FromUserName + CreateTime 排重
-
当作出以下回应时,微信服务器不会作任何处理,且不会发起重试。
- 直接回复
success
(推荐) - 回复空串
""
- 直接回复
-
4
个公共参数参数 描述 ToUserName 接收方帐号(收到的 OpenID
,用户与公众号交互产生,意味着用户与另一个公众号产生的OpenID
不一样),代表是哪一个用户发送过来。FromUserName 开发者微信号,表示的是哪个公众号作出响应,可以当做公众号的微信号 CreateTime 消息创建时间 (整型) MsgType 用户发送消息的类型
-
-
代码
-
使用
xstream
将bean
转为xml
<!-- xml --> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.17</version> </dependency>
-
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); }
-
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; }
-
封装结果集
-
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(); }
-
文本信息及图片信息
-
文本信息
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; } }
-
图片信息
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; } }
-
-
工具类
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()); } }
-
-