| @@ -0,0 +1,18 @@ | |||||
| package me.chanjar.weixin.common.util; | |||||
| /** | |||||
| * <pre> | |||||
| * 消息重复检查器 | |||||
| * 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次 | |||||
| * </pre> | |||||
| */ | |||||
| public interface WxMsgIdDuplicateChecker { | |||||
| /** | |||||
| * 检查消息ID是否重复 | |||||
| * @param wxMsgId | |||||
| * @return 如果是重复消息,返回true,否则返回false | |||||
| */ | |||||
| public boolean isDuplicate(Long wxMsgId); | |||||
| } | |||||
| @@ -0,0 +1,84 @@ | |||||
| package me.chanjar.weixin.common.util; | |||||
| import java.util.Map; | |||||
| import java.util.concurrent.ConcurrentHashMap; | |||||
| /** | |||||
| * <pre> | |||||
| * 默认消息重复检查器 | |||||
| * 将每个消息id保存在内存里,每隔5秒清理已经过期的消息id,每个消息id的过期时间是15秒 | |||||
| * </pre> | |||||
| */ | |||||
| public class WxMsgIdInMemoryDuplicateChecker implements WxMsgIdDuplicateChecker { | |||||
| /** | |||||
| * 一个消息ID在内存的过期时间:15秒 | |||||
| */ | |||||
| private final Long TIME_TO_LIVE; | |||||
| /** | |||||
| * 每隔多少周期检查消息ID是否过期:5秒 | |||||
| */ | |||||
| private final Long CLEAR_PERIOD; | |||||
| private final ConcurrentHashMap<Long, Long> msgId2Timestamp = new ConcurrentHashMap<Long, Long>(); | |||||
| /** | |||||
| * WxMsgIdInMemoryDuplicateChecker构造函数 | |||||
| * <pre> | |||||
| * 一个消息ID在内存的过期时间:15秒 | |||||
| * 每隔多少周期检查消息ID是否过期:5秒 | |||||
| * </pre> | |||||
| */ | |||||
| public WxMsgIdInMemoryDuplicateChecker() { | |||||
| this.TIME_TO_LIVE = 15 * 1000l; | |||||
| this.CLEAR_PERIOD = 5 * 1000l; | |||||
| this.start(); | |||||
| } | |||||
| /** | |||||
| * WxMsgIdInMemoryDuplicateChecker构造函数 | |||||
| * @param timeToLive 一个消息ID在内存的过期时间:毫秒 | |||||
| * @param clearPeriod 每隔多少周期检查消息ID是否过期:毫秒 | |||||
| */ | |||||
| public WxMsgIdInMemoryDuplicateChecker(Long timeToLive, Long clearPeriod) { | |||||
| this.TIME_TO_LIVE = timeToLive; | |||||
| this.CLEAR_PERIOD = clearPeriod; | |||||
| this.start(); | |||||
| } | |||||
| private void start() { | |||||
| Thread t = new Thread(new Runnable() { | |||||
| @Override | |||||
| public void run() { | |||||
| try { | |||||
| while (true) { | |||||
| Thread.sleep(CLEAR_PERIOD); | |||||
| Long now = System.currentTimeMillis(); | |||||
| for (Map.Entry<Long, Long> entry : msgId2Timestamp.entrySet()) { | |||||
| if (now - entry.getValue() > TIME_TO_LIVE) { | |||||
| msgId2Timestamp.entrySet().remove(entry); | |||||
| } | |||||
| } | |||||
| } | |||||
| } catch (InterruptedException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| }); | |||||
| t.setDaemon(true); | |||||
| t.start(); | |||||
| } | |||||
| @Override | |||||
| public boolean isDuplicate(Long wxMsgId) { | |||||
| Long timestamp = msgId2Timestamp.putIfAbsent(wxMsgId, System.currentTimeMillis()); | |||||
| if (timestamp == null) { | |||||
| // 第一次接收到这个消息 | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| package me.chanjar.weixin.common.util; | |||||
| import org.testng.Assert; | |||||
| import org.testng.annotations.Test; | |||||
| /** | |||||
| * Created by qianjia on 15/1/20. | |||||
| */ | |||||
| @Test | |||||
| public class WxMsgIdInMemoryDuplicateCheckerTest { | |||||
| public void test() throws InterruptedException { | |||||
| Long[] msgIds = new Long[] { 1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l }; | |||||
| WxMsgIdInMemoryDuplicateChecker checker = new WxMsgIdInMemoryDuplicateChecker(2000l, 1000l); | |||||
| // 第一次检查 | |||||
| for (Long msgId : msgIds) { | |||||
| boolean result = checker.isDuplicate(msgId); | |||||
| Assert.assertFalse(result); | |||||
| } | |||||
| // 过1秒再检查 | |||||
| Thread.sleep(1000l); | |||||
| for (Long msgId : msgIds) { | |||||
| boolean result = checker.isDuplicate(msgId); | |||||
| Assert.assertTrue(result); | |||||
| } | |||||
| // 过1.5秒再检查 | |||||
| Thread.sleep(1500l); | |||||
| for (Long msgId : msgIds) { | |||||
| boolean result = checker.isDuplicate(msgId); | |||||
| Assert.assertFalse(result); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -7,6 +7,7 @@ | |||||
| <class name="me.chanjar.weixin.common.bean.WxErrorTest" /> | <class name="me.chanjar.weixin.common.bean.WxErrorTest" /> | ||||
| <class name="me.chanjar.weixin.common.bean.WxMenuTest" /> | <class name="me.chanjar.weixin.common.bean.WxMenuTest" /> | ||||
| <class name="me.chanjar.weixin.common.util.crypto.WxCryptUtilTest" /> | <class name="me.chanjar.weixin.common.util.crypto.WxCryptUtilTest" /> | ||||
| <class name="me.chanjar.weixin.common.util.WxMsgIdInMemoryDuplicateCheckerTest" /> | |||||
| </classes> | </classes> | ||||
| </test> | </test> | ||||
| </suite> | </suite> | ||||
| @@ -1,5 +1,7 @@ | |||||
| package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
| import me.chanjar.weixin.common.util.WxMsgIdDuplicateChecker; | |||||
| import me.chanjar.weixin.common.util.WxMsgIdInMemoryDuplicateChecker; | |||||
| import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | ||||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | ||||
| @@ -45,18 +47,38 @@ public class WxCpMessageRouter { | |||||
| private final List<Rule> rules = new ArrayList<Rule>(); | private final List<Rule> rules = new ArrayList<Rule>(); | ||||
| private final ExecutorService executorService; | |||||
| private final WxCpService wxCpService; | private final WxCpService wxCpService; | ||||
| private ExecutorService executorService; | |||||
| private WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker; | |||||
| public WxCpMessageRouter(WxCpService wxCpService) { | public WxCpMessageRouter(WxCpService wxCpService) { | ||||
| this.wxCpService = wxCpService; | this.wxCpService = wxCpService; | ||||
| this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | ||||
| this.wxMsgIdDuplicateChecker = new WxMsgIdInMemoryDuplicateChecker(); | |||||
| } | } | ||||
| public WxCpMessageRouter(WxCpService wxMpService, int threadPoolSize) { | public WxCpMessageRouter(WxCpService wxMpService, int threadPoolSize) { | ||||
| this.wxCpService = wxMpService; | this.wxCpService = wxMpService; | ||||
| this.executorService = Executors.newFixedThreadPool(threadPoolSize); | this.executorService = Executors.newFixedThreadPool(threadPoolSize); | ||||
| this.wxMsgIdDuplicateChecker = new WxMsgIdInMemoryDuplicateChecker(); | |||||
| } | |||||
| /** | |||||
| * 设置自定义的ExecutorService | |||||
| * @param executorService | |||||
| */ | |||||
| public void setExecutorService(ExecutorService executorService) { | |||||
| this.executorService = executorService; | |||||
| } | |||||
| /** | |||||
| * 设置自定义的WxMsgIdDuplicateChecker | |||||
| * @param wxMsgIdDuplicateChecker | |||||
| */ | |||||
| public void setWxMsgIdDuplicateChecker(WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker) { | |||||
| this.wxMsgIdDuplicateChecker = wxMsgIdDuplicateChecker; | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -72,6 +94,11 @@ public class WxCpMessageRouter { | |||||
| * @param wxMessage | * @param wxMessage | ||||
| */ | */ | ||||
| public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) { | public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) { | ||||
| if (wxMsgIdDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { | |||||
| // 如果是重复消息,那么就不做处理 | |||||
| return null; | |||||
| } | |||||
| final List<Rule> matchRules = new ArrayList<Rule>(); | final List<Rule> matchRules = new ArrayList<Rule>(); | ||||
| // 收集匹配的规则 | // 收集匹配的规则 | ||||
| for (final Rule rule : rules) { | for (final Rule rule : rules) { | ||||
| @@ -1,88 +0,0 @@ | |||||
| package me.chanjar.weixin.mp.api; | |||||
| import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | |||||
| import sun.applet.Main; | |||||
| import java.net.SocketTimeoutException; | |||||
| import java.util.Map; | |||||
| import java.util.Random; | |||||
| import java.util.concurrent.ConcurrentHashMap; | |||||
| /** | |||||
| * <pre> | |||||
| * 消息去重拦截器 | |||||
| * 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次 | |||||
| * 使用方法: | |||||
| * WxMpMessageRouter router = new WxMpMessageRouter(); | |||||
| * router | |||||
| * .rule() | |||||
| * .interceptor(new WxMpDuplicateMessageInterceptor()) | |||||
| * .next() | |||||
| * .rule() | |||||
| * .msgType("MSG_TYPE").event("EVENT").eventKey("EVENT_KEY").content("CONTENT") | |||||
| * .interceptor(interceptor, ...).handler(handler, ...) | |||||
| * .end() | |||||
| * .rule() | |||||
| * // 另外一个匹配规则 | |||||
| * .end() | |||||
| * ; | |||||
| * </pre> | |||||
| */ | |||||
| public class WxMpDuplicateMessageInterceptor implements WxMpMessageInterceptor { | |||||
| private static final Long PERIOD = 15 * 1000l; | |||||
| private final ConcurrentHashMap<Long, Long> msgId2timestamp; | |||||
| public WxMpDuplicateMessageInterceptor() { | |||||
| this.msgId2timestamp = new ConcurrentHashMap<Long, Long>(); | |||||
| Thread t = new Thread(new Runnable() { | |||||
| @Override | |||||
| public void run() { | |||||
| try { | |||||
| while (true) { | |||||
| Thread.sleep(PERIOD); | |||||
| Long now = System.currentTimeMillis(); | |||||
| for (Map.Entry<Long, Long> entry : msgId2timestamp.entrySet()) { | |||||
| if (now - entry.getValue() > PERIOD) { | |||||
| msgId2timestamp.entrySet().remove(entry); | |||||
| } | |||||
| } | |||||
| msgId2timestamp.clear(); | |||||
| } | |||||
| } catch (InterruptedException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| } | |||||
| }); | |||||
| t.setDaemon(true); | |||||
| t.start(); | |||||
| } | |||||
| @Override | |||||
| public boolean intercept(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService) { | |||||
| Long now = System.currentTimeMillis(); | |||||
| Long timestamp = msgId2timestamp.putIfAbsent(wxMessage.getMsgId(), now); | |||||
| if (timestamp == null) { | |||||
| return true; | |||||
| } | |||||
| if (timestamp.equals(now)) { | |||||
| // 第一次接收到这个消息 | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| public static void main(String[] args) { | |||||
| WxMpDuplicateMessageInterceptor d = new WxMpDuplicateMessageInterceptor(); | |||||
| Long endTime = System.currentTimeMillis() + 30 * 1000; | |||||
| Random r = new Random(); | |||||
| while(System.currentTimeMillis() < endTime) { | |||||
| WxMpXmlMessage m = new WxMpXmlMessage(); | |||||
| m.setMsgId(r.nextLong() % 100); | |||||
| d.intercept(m, null, null); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,5 +1,7 @@ | |||||
| package me.chanjar.weixin.mp.api; | package me.chanjar.weixin.mp.api; | ||||
| import me.chanjar.weixin.common.util.WxMsgIdDuplicateChecker; | |||||
| import me.chanjar.weixin.common.util.WxMsgIdInMemoryDuplicateChecker; | |||||
| import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | ||||
| import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | ||||
| @@ -45,18 +47,38 @@ public class WxMpMessageRouter { | |||||
| private final List<Rule> rules = new ArrayList<Rule>(); | private final List<Rule> rules = new ArrayList<Rule>(); | ||||
| private final ExecutorService executorService; | |||||
| private final WxMpService wxMpService; | private final WxMpService wxMpService; | ||||
| private ExecutorService executorService; | |||||
| private WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker; | |||||
| public WxMpMessageRouter(WxMpService wxMpService) { | public WxMpMessageRouter(WxMpService wxMpService) { | ||||
| this.wxMpService = wxMpService; | this.wxMpService = wxMpService; | ||||
| this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | ||||
| this.wxMsgIdDuplicateChecker = new WxMsgIdInMemoryDuplicateChecker(); | |||||
| } | } | ||||
| public WxMpMessageRouter(WxMpService wxMpService, int threadPoolSize) { | public WxMpMessageRouter(WxMpService wxMpService, int threadPoolSize) { | ||||
| this.wxMpService = wxMpService; | this.wxMpService = wxMpService; | ||||
| this.executorService = Executors.newFixedThreadPool(threadPoolSize); | this.executorService = Executors.newFixedThreadPool(threadPoolSize); | ||||
| this.wxMsgIdDuplicateChecker = new WxMsgIdInMemoryDuplicateChecker(); | |||||
| } | |||||
| /** | |||||
| * 设置自定义的ExecutorService | |||||
| * @param executorService | |||||
| */ | |||||
| public void setExecutorService(ExecutorService executorService) { | |||||
| this.executorService = executorService; | |||||
| } | |||||
| /** | |||||
| * 设置自定义的WxMsgIdDuplicateChecker | |||||
| * @param wxMsgIdDuplicateChecker | |||||
| */ | |||||
| public void setWxMsgIdDuplicateChecker(WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker) { | |||||
| this.wxMsgIdDuplicateChecker = wxMsgIdDuplicateChecker; | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -72,6 +94,11 @@ public class WxMpMessageRouter { | |||||
| * @param wxMessage | * @param wxMessage | ||||
| */ | */ | ||||
| public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage) { | public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage) { | ||||
| if (wxMsgIdDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { | |||||
| // 如果是重复消息,那么就不做处理 | |||||
| return null; | |||||
| } | |||||
| final List<Rule> matchRules = new ArrayList<Rule>(); | final List<Rule> matchRules = new ArrayList<Rule>(); | ||||
| // 收集匹配的规则 | // 收集匹配的规则 | ||||
| for (final Rule rule : rules) { | for (final Rule rule : rules) { | ||||