微信公众平台开发时,微信推送消息的机制是推送过来后如果5秒内收不到响应则认为没有推送成功,会再次推送,总共推送三次。如果我们服务器接收到消息没有及时响应,就会触发微信的“重推机制”,这就需要我们在服务端对微信推送过来的消息进行排重。
我们不应该让服务器出现处理时间过长的情况,对于业务复杂、处理时间长度不可控的代码可以进行异步处理,每次接收到微信推送后都立即返回空串,当然这是另外一回事了,不是今天要说的。
先看微信公众号开发文档原文
普通消息
1、关于重试的消息排重,推荐使用msgid排重。
2、微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。
事件推送:
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
关于重试的消息排重,推荐使用FromUserName + CreateTime 排重。
假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。
微信推送过来的消息无外乎上面两种,微信文档也给出了排重方法,下面说说我的方案:
方案一
- 创建判断重复消息的DuplicateRemovalMessage类;
- 把微信推送的消息解析赋值给DuplicateRemovalMessage对象实例;
- 用静态变量List当缓存,判断DuplicateRemovalMessage实例是否存在于缓存list中,如果存在则为重复消息,如果不存在则不是重复消息并把消息放到缓存List中。
此方案弊端:
- 缓存list会无限增大,所以用setMessageToCache方法限制了list最大容量为1000;
- 解决了list最大容量依然还有弊端,就是两条重复的消息之间如果有超过999个DuplicateRemovalMessage对象依然会判断不准,只能增大list容量来缓解。
实现代码:
别忘记复写hashcode、equals方法
public class DuplicateRemovalMessage {
private String MsgId;
private String FromUserName;
private String CreateTime;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((CreateTime == null) ? 0 : CreateTime.hashCode());
result = prime * result + ((FromUserName == null) ? 0 : FromUserName.hashCode());
result = prime * result + ((MsgId == null) ? 0 : MsgId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DuplicateRemovalMessage other = (DuplicateRemovalMessage) obj;
if (CreateTime == null) {
if (other.CreateTime != null)
return false;
} else if (!CreateTime.equals(other.CreateTime))
return false;
if (FromUserName == null) {
if (other.FromUserName != null)
return false;
} else if (!FromUserName.equals(other.FromUserName))
return false;
if (MsgId == null) {
if (other.MsgId != null)
return false;
} else if (!MsgId.equals(other.MsgId))
return false;
return true;
}
省略set和get方法...
}
方案一升级版
方案一的解决并不优雅,而且无法在负载均衡环境下使用。
改进方式是在【方案一】的基础上,使用redis替换List作为存储中介,将DuplicateRemovalMessage对象存储到redis中,存储的key可以用msgId,事件消息可以用FromUserName + CreateTime 拼接,同时给对象设置一个15秒的超时时间,这样就不会有List那种集合存储元素无限增多的情况。
最后
微信推送消息的CreateTime 精确到秒值,这就可能出现一个用户正常操作的情况下触发两条FromUserName 和CreateTime 完全相同的消息,也就是说按照微信文档上提供的排重方法会过滤掉合法数据,我在线上环境遇到过一次。
我们微信公众号开启了获取用户地理位置的功能,用户打开微信后点击菜单进入了某个页面,此时微信后台向我们推送了用户地理位置事件消息和用户点击公众号菜单事件消息,这两个消息的CreateTime 和FromUserName 相同。
所以最后建议做排重的时候,事件消息使用FromUserName + CreateTime+Event三个字段进行排重。
本文最初发布在iteye,由于iteye编辑博客内容提示我有敏感词,可是又不告诉我是哪个敏感词(真2b),无法编辑老文章,所以文章和更新以后都发到这里了。
(全文完)