@@ -2,6 +2,19 @@ package me.chanjar.weixin.common.session; | |||
public interface InternalSessionManager { | |||
/** | |||
* Return the active Session, associated with this Manager, with the | |||
* specified session id (if any); otherwise return <code>null</code>. | |||
* | |||
* @param id The session id for the session to be returned | |||
* | |||
* @exception IllegalStateException if a new session cannot be | |||
* instantiated for any reason | |||
* @exception java.io.IOException if an input/output error occurs while | |||
* processing this request | |||
*/ | |||
InternalSession findSession(String id); | |||
/** | |||
* Construct and return a new session object, based on the default | |||
* settings specified by this Manager's properties. The session | |||
@@ -68,7 +81,33 @@ public interface InternalSessionManager { | |||
*/ | |||
void setMaxInactiveInterval(int interval); | |||
/** | |||
* <pre> | |||
* Set the manager checks frequency. | |||
* 设置每尝试多少次清理过期session,才真的会执行一次清理动作 | |||
* 要和{@link #setBackgroundProcessorDelay(int)}联合起来看 | |||
* 如果把这个数字设置为6(默认),那么就是说manager要等待 6 * backgroundProcessorDay的时间才会清理过期session | |||
* </pre> | |||
* @param processExpiresFrequency the new manager checks frequency | |||
*/ | |||
void setProcessExpiresFrequency(int processExpiresFrequency); | |||
/** | |||
* <pre> | |||
* Set the manager background processor delay | |||
* 设置manager sleep几秒,尝试执行一次background操作(清理过期session) | |||
* </pre> | |||
* @param backgroundProcessorDelay | |||
*/ | |||
void setBackgroundProcessorDelay(int backgroundProcessorDelay); | |||
/** | |||
* Set the maximum number of active Sessions allowed, or -1 for | |||
* no limit. | |||
* 设置最大活跃session数,默认无限 | |||
* @param max The new maximum number of sessions | |||
*/ | |||
void setMaxActiveSessions(int max); | |||
} |
@@ -6,7 +6,7 @@ import java.util.*; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import java.util.concurrent.atomic.AtomicInteger; | |||
public class SessionImpl implements WxSession, InternalSession { | |||
public class StandardSession implements WxSession, InternalSession { | |||
/** | |||
* The string manager for this package. | |||
@@ -129,7 +129,7 @@ public class SessionImpl implements WxSession, InternalSession { | |||
* The facade associated with this session. NOTE: This value is not | |||
* included in the serialized version of this object. | |||
*/ | |||
protected transient InternalSessionFacade facade = null; | |||
protected transient StandardSessionFacade facade = null; | |||
/** | |||
* The access count for this session. | |||
@@ -137,7 +137,7 @@ public class SessionImpl implements WxSession, InternalSession { | |||
protected transient AtomicInteger accessCount = null; | |||
public SessionImpl(InternalSessionManager manager) { | |||
public StandardSession(InternalSessionManager manager) { | |||
this.manager = manager; | |||
this.accessCount = new AtomicInteger(); | |||
} | |||
@@ -147,7 +147,7 @@ public class SessionImpl implements WxSession, InternalSession { | |||
public WxSession getSession() { | |||
if (facade == null){ | |||
facade = new InternalSessionFacade(this); | |||
facade = new StandardSessionFacade(this); | |||
} | |||
return (facade); | |||
@@ -281,7 +281,6 @@ public class SessionImpl implements WxSession, InternalSession { | |||
@Override | |||
public void setMaxInactiveInterval(int interval) { | |||
int oldMaxInactiveInterval = this.maxInactiveInterval; | |||
this.maxInactiveInterval = interval; | |||
} | |||
@@ -311,9 +310,9 @@ public class SessionImpl implements WxSession, InternalSession { | |||
@Override | |||
public boolean equals(Object o) { | |||
if (this == o) return true; | |||
if (!(o instanceof SessionImpl)) return false; | |||
if (!(o instanceof StandardSession)) return false; | |||
SessionImpl session = (SessionImpl) o; | |||
StandardSession session = (StandardSession) o; | |||
if (creationTime != session.creationTime) return false; | |||
if (expiring != session.expiring) return false; |
@@ -2,17 +2,21 @@ package me.chanjar.weixin.common.session; | |||
import java.util.Enumeration; | |||
public class InternalSessionFacade implements WxSession { | |||
public class StandardSessionFacade implements WxSession { | |||
/** | |||
* Wrapped session object. | |||
*/ | |||
private WxSession session = null; | |||
public InternalSessionFacade(WxSession session) { | |||
public StandardSessionFacade(StandardSession session) { | |||
this.session = session; | |||
} | |||
public InternalSession getInternalSession() { | |||
return (InternalSession) session; | |||
} | |||
@Override | |||
public Object getAttribute(String name) { | |||
return session.getAttribute(name); | |||
@@ -37,4 +41,5 @@ public class InternalSessionFacade implements WxSession { | |||
public void invalidate() { | |||
session.invalidate(); | |||
} | |||
} |
@@ -8,9 +8,12 @@ import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import java.util.concurrent.atomic.AtomicBoolean; | |||
public class InMemorySessionManager implements WxSessionManager, InternalSessionManager { | |||
/** | |||
* 基于内存的session manager | |||
*/ | |||
public class StandardSessionManager implements WxSessionManager, InternalSessionManager { | |||
protected final Logger log = LoggerFactory.getLogger(InMemorySessionManager.class); | |||
protected final Logger log = LoggerFactory.getLogger(StandardSessionManager.class); | |||
protected static final StringManager sm = | |||
StringManager.getManager(Constants.Package); | |||
@@ -128,18 +131,9 @@ public class InMemorySessionManager implements WxSessionManager, InternalSession | |||
} | |||
/** | |||
* Return the active Session, associated with this Manager, with the | |||
* specified session id (if any); otherwise return <code>null</code>. | |||
* | |||
* @param id The session id for the session to be returned | |||
* | |||
* @exception IllegalStateException if a new session cannot be | |||
* instantiated for any reason | |||
* @exception java.io.IOException if an input/output error occurs while | |||
* processing this request | |||
*/ | |||
protected InternalSession findSession(String id) { | |||
@Override | |||
public InternalSession findSession(String id) { | |||
if (id == null) | |||
return (null); | |||
@@ -189,12 +183,11 @@ public class InMemorySessionManager implements WxSessionManager, InternalSession | |||
return (getNewSession()); | |||
} | |||
/** | |||
* Get new session class to be used in the doLoad() method. | |||
*/ | |||
protected InternalSession getNewSession() { | |||
return new SessionImpl(this); | |||
return new StandardSession(this); | |||
} | |||
@@ -312,4 +305,17 @@ public class InMemorySessionManager implements WxSessionManager, InternalSession | |||
} | |||
/** | |||
* Set the maximum number of active Sessions allowed, or -1 for | |||
* no limit. | |||
* | |||
* @param max The new maximum number of sessions | |||
*/ | |||
@Override | |||
public void setMaxActiveSessions(int max) { | |||
this.maxActiveSessions = max; | |||
} | |||
} |
@@ -82,6 +82,9 @@ public class WxMessageInMemoryDuplicateChecker implements WxMessageDuplicateChec | |||
@Override | |||
public boolean isDuplicate(Long wxMsgId) { | |||
if (wxMsgId == null) { | |||
return false; | |||
} | |||
checkBackgroundProcessStarted(); | |||
Long timestamp = msgId2Timestamp.putIfAbsent(wxMsgId, System.currentTimeMillis()); | |||
if (timestamp == null) { | |||
@@ -0,0 +1,132 @@ | |||
package me.chanjar.weixin.common.session; | |||
import org.testng.Assert; | |||
import org.testng.annotations.DataProvider; | |||
import org.testng.annotations.Test; | |||
@Test | |||
public class SessionTest { | |||
@DataProvider | |||
public Object[][] getSessionManager() { | |||
return new Object[][] { | |||
new Object[] { new StandardSessionManager() } | |||
}; | |||
} | |||
@Test(dataProvider = "getSessionManager", expectedExceptions = IllegalStateException.class) | |||
public void testInvalidate(WxSessionManager sessionManager) { | |||
WxSession session = sessionManager.getSession("abc"); | |||
session.invalidate(); | |||
session.getAttributeNames(); | |||
} | |||
@Test(dataProvider = "getSessionManager") | |||
public void testInvalidate2(InternalSessionManager sessionManager) { | |||
Assert.assertEquals(sessionManager.getActiveSessions(), 0); | |||
WxSession session = ((WxSessionManager) sessionManager).getSession("abc"); | |||
Assert.assertEquals(sessionManager.getActiveSessions(), 1); | |||
session.invalidate(); | |||
Assert.assertEquals(sessionManager.getActiveSessions(), 0); | |||
} | |||
@Test(dataProvider = "getSessionManager") | |||
public void testGetSession(WxSessionManager sessionManager) { | |||
WxSession session1 = sessionManager.getSession("abc"); | |||
WxSession session2 = sessionManager.getSession("abc"); | |||
Assert.assertEquals(session1, session2); | |||
Assert.assertTrue(session1 == session2); | |||
WxSession abc1 = sessionManager.getSession("abc1"); | |||
Assert.assertNotEquals(session1, abc1); | |||
WxSession abc1b = sessionManager.getSession("abc1", false); | |||
Assert.assertEquals(abc1, abc1b); | |||
WxSession def = sessionManager.getSession("def", false); | |||
Assert.assertNull(def); | |||
} | |||
@Test(dataProvider = "getSessionManager") | |||
public void testInvalidateAngGet(WxSessionManager sessionManager) { | |||
WxSession session1 = sessionManager.getSession("abc"); | |||
session1.invalidate(); | |||
WxSession session2 = sessionManager.getSession("abc"); | |||
Assert.assertNotEquals(session1, session2); | |||
InternalSessionManager ism = (InternalSessionManager) sessionManager; | |||
Assert.assertEquals(ism.getActiveSessions(), 1); | |||
} | |||
@Test(dataProvider = "getSessionManager") | |||
public void testBackgroundProcess(WxSessionManager sessionManager) throws InterruptedException { | |||
InternalSessionManager ism = (InternalSessionManager) sessionManager; | |||
ism.setMaxInactiveInterval(1); | |||
ism.setProcessExpiresFrequency(1); | |||
ism.setBackgroundProcessorDelay(1); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
InternalSession abc = ism.createSession("abc"); | |||
abc.endAccess(); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
@Test(dataProvider = "getSessionManager") | |||
public void testBackgroundProcess2(WxSessionManager sessionManager) throws InterruptedException { | |||
InternalSessionManager ism = (InternalSessionManager) sessionManager; | |||
ism.setMaxInactiveInterval(100); | |||
ism.setProcessExpiresFrequency(1); | |||
ism.setBackgroundProcessorDelay(1); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
InternalSession abc = ism.createSession("abc"); | |||
abc.setMaxInactiveInterval(1); | |||
abc.endAccess(); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
@Test(dataProvider = "getSessionManager") | |||
public void testMaxActive(WxSessionManager sessionManager) throws InterruptedException { | |||
InternalSessionManager ism = (InternalSessionManager) sessionManager; | |||
ism.setMaxActiveSessions(2); | |||
ism.createSession("abc"); | |||
ism.createSession("abc"); | |||
ism.createSession("def"); | |||
} | |||
@Test(dataProvider = "getSessionManager", expectedExceptions = TooManyActiveSessionsException.class) | |||
public void testMaxActive2(WxSessionManager sessionManager) throws InterruptedException { | |||
InternalSessionManager ism = (InternalSessionManager) sessionManager; | |||
ism.setMaxActiveSessions(2); | |||
ism.createSession("abc"); | |||
ism.createSession("abc"); | |||
ism.createSession("def"); | |||
ism.createSession("xyz"); | |||
} | |||
} |
@@ -1,68 +0,0 @@ | |||
package me.chanjar.weixin.common.session; | |||
import org.testng.Assert; | |||
import org.testng.annotations.DataProvider; | |||
import org.testng.annotations.Test; | |||
@Test | |||
public class TestSession { | |||
@DataProvider | |||
public Object[][] getSessionManager() { | |||
return new Object[][] { | |||
new Object[] { new InMemorySessionManager() } | |||
}; | |||
} | |||
@Test(dataProvider = "getSessionManager", expectedExceptions = IllegalStateException.class) | |||
public void testInvalidate(WxSessionManager sessionManager) { | |||
WxSession session = sessionManager.getSession("abc"); | |||
session.invalidate(); | |||
session.getAttributeNames(); | |||
} | |||
@Test(dataProvider = "getSessionManager") | |||
public void testInvalidate2(InternalSessionManager sessionManager) { | |||
Assert.assertEquals(sessionManager.getActiveSessions(), 0); | |||
WxSession session = ((WxSessionManager) sessionManager).getSession("abc"); | |||
Assert.assertEquals(sessionManager.getActiveSessions(), 1); | |||
session.invalidate(); | |||
Assert.assertEquals(sessionManager.getActiveSessions(), 0); | |||
} | |||
@Test(dataProvider = "getSessionManager") | |||
public void testGetSession(WxSessionManager sessionManager) { | |||
WxSession session1 = sessionManager.getSession("abc"); | |||
WxSession session2 = sessionManager.getSession("abc"); | |||
Assert.assertTrue(session1.equals(session2)); | |||
WxSession abc1 = sessionManager.getSession("abc1"); | |||
Assert.assertFalse(session1.equals(abc1)); | |||
WxSession abc1b = sessionManager.getSession("abc1", false); | |||
Assert.assertTrue(abc1.equals(abc1b)); | |||
WxSession def = sessionManager.getSession("def", false); | |||
Assert.assertNull(def); | |||
} | |||
@Test(dataProvider = "getSessionManager") | |||
public void testBackgroundProcess(WxSessionManager sessionManager) throws InterruptedException { | |||
InternalSessionManager ism = (InternalSessionManager) sessionManager; | |||
ism.setMaxInactiveInterval(1); | |||
ism.setProcessExpiresFrequency(1); | |||
ism.setBackgroundProcessorDelay(1); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
InternalSession abc = ism.createSession("abc"); | |||
abc.endAccess(); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
} |
@@ -8,7 +8,7 @@ | |||
<class name="me.chanjar.weixin.common.bean.WxMenuTest"/> | |||
<class name="me.chanjar.weixin.common.util.crypto.WxCryptUtilTest"/> | |||
<class name="me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateCheckerTest"/> | |||
<class name="me.chanjar.weixin.common.session.TestSession" /> | |||
<class name="me.chanjar.weixin.common.session.SessionTest" /> | |||
</classes> | |||
</test> | |||
</suite> |
@@ -1,5 +1,6 @@ | |||
package me.chanjar.weixin.cp.api; | |||
import me.chanjar.weixin.common.session.WxSession; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
@@ -1,9 +1,6 @@ | |||
package me.chanjar.weixin.cp.api; | |||
import me.chanjar.weixin.common.session.InternalSession; | |||
import me.chanjar.weixin.common.session.WxSession; | |||
import me.chanjar.weixin.common.session.InMemorySessionManager; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.session.*; | |||
import me.chanjar.weixin.common.util.WxMessageDuplicateChecker; | |||
import me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
@@ -69,7 +66,7 @@ public class WxCpMessageRouter { | |||
this.wxCpService = wxCpService; | |||
this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | |||
this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); | |||
this.sessionManager = new InMemorySessionManager(); | |||
this.sessionManager = new StandardSessionManager(); | |||
} | |||
/** | |||
@@ -152,10 +149,12 @@ public class WxCpMessageRouter { | |||
); | |||
} else { | |||
res = rule.service(wxMessage); | |||
// 在同步操作结束,session访问结束 | |||
log.trace("End session access after sync operation finish {}", wxMessage.getFromUserName()); | |||
sessionEndAccess(wxMessage); | |||
} | |||
} | |||
// 告诉session,它已经用不着了 | |||
if (futures.size() > 0) { | |||
executorService.submit(new Runnable() { | |||
@Override | |||
@@ -163,19 +162,17 @@ public class WxCpMessageRouter { | |||
for (Future future : futures) { | |||
try { | |||
future.get(); | |||
log.trace("End session access after async operation finish {}", wxMessage.getFromUserName()); | |||
// 异步操作结束,session访问结束 | |||
sessionEndAccess(wxMessage); | |||
} catch (InterruptedException e) { | |||
log.error("Error happened when wait task finish", e); | |||
} catch (ExecutionException e) { | |||
log.error("Error happened when wait task finish", e); | |||
} | |||
} | |||
// 在这里session再也不会被使用了 | |||
sessionEndAccess(wxMessage); | |||
} | |||
}); | |||
} else { | |||
// 在这里session再也不会被使用了 | |||
sessionEndAccess(wxMessage); | |||
} | |||
return res; | |||
} | |||
@@ -185,10 +182,12 @@ public class WxCpMessageRouter { | |||
* @param wxMessage | |||
*/ | |||
protected void sessionEndAccess(WxCpXmlMessage wxMessage) { | |||
WxSession session = sessionManager.getSession(wxMessage.getFromUserName(), false); | |||
InternalSession session = ((InternalSessionManager)sessionManager).findSession(wxMessage.getFromUserName()); | |||
if (session != null) { | |||
((InternalSession) session).endAccess(); | |||
session.endAccess(); | |||
} | |||
} | |||
public static class Rule { | |||
@@ -392,7 +392,7 @@ public interface WxCpService { | |||
/** | |||
* <pre> | |||
* 设置WxSessionManager,只有当需要使用个性化的WxSessionManager的时候才需要调用此方法, | |||
* WxCpService默认使用的是{@link me.chanjar.weixin.common.session.InMemorySessionManager} | |||
* WxCpService默认使用的是{@link me.chanjar.weixin.common.session.StandardSessionManager} | |||
* </pre> | |||
* @param sessionManager | |||
*/ | |||
@@ -12,7 +12,7 @@ import me.chanjar.weixin.common.bean.WxMenu; | |||
import me.chanjar.weixin.common.bean.result.WxError; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.session.InMemorySessionManager; | |||
import me.chanjar.weixin.common.session.StandardSessionManager; | |||
import me.chanjar.weixin.common.session.WxSession; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.StringUtils; | |||
@@ -66,7 +66,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
private int maxRetryTimes = 5; | |||
protected WxSessionManager sessionManager = new InMemorySessionManager(); | |||
protected WxSessionManager sessionManager = new StandardSessionManager(); | |||
public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { | |||
try { | |||
@@ -1,6 +1,7 @@ | |||
package me.chanjar.weixin.cp.api; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.common.session.StandardSessionManager; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
@@ -159,4 +160,137 @@ public class WxCpMessageRouterTest { | |||
} | |||
@DataProvider | |||
public Object[][] standardSessionManager() { | |||
// 故意把session存活时间变短,清理更频繁 | |||
StandardSessionManager ism = new StandardSessionManager(); | |||
ism.setMaxInactiveInterval(1); | |||
ism.setProcessExpiresFrequency(1); | |||
ism.setBackgroundProcessorDelay(1); | |||
return new Object[][] { | |||
new Object[] { ism } | |||
}; | |||
} | |||
@Test(dataProvider = "standardSessionManager") | |||
public void testSessionClean1(StandardSessionManager ism) throws InterruptedException { | |||
// 两个同步请求,看是否处理完毕后会被清理掉 | |||
final WxCpMessageRouter router = new WxCpMessageRouter(null); | |||
router.setSessionManager(ism); | |||
router | |||
.rule().async(false).handler(new WxSessionMessageHandler()).next() | |||
.rule().async(false).handler(new WxSessionMessageHandler()).end(); | |||
WxCpXmlMessage msg = new WxCpXmlMessage(); | |||
msg.setFromUserName("abc"); | |||
router.route(msg); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
@Test(dataProvider = "standardSessionManager") | |||
public void testSessionClean2(StandardSessionManager ism) throws InterruptedException { | |||
// 1个同步,1个异步请求,看是否处理完毕后会被清理掉 | |||
{ | |||
final WxCpMessageRouter router = new WxCpMessageRouter(null); | |||
router.setSessionManager(ism); | |||
router | |||
.rule().async(false).handler(new WxSessionMessageHandler()).next() | |||
.rule().async(true).handler(new WxSessionMessageHandler()).end(); | |||
WxCpXmlMessage msg = new WxCpXmlMessage(); | |||
msg.setFromUserName("abc"); | |||
router.route(msg); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
{ | |||
final WxCpMessageRouter router = new WxCpMessageRouter(null); | |||
router.setSessionManager(ism); | |||
router | |||
.rule().async(true).handler(new WxSessionMessageHandler()).next() | |||
.rule().async(false).handler(new WxSessionMessageHandler()).end(); | |||
WxCpXmlMessage msg = new WxCpXmlMessage(); | |||
msg.setFromUserName("abc"); | |||
router.route(msg); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
} | |||
@Test(dataProvider = "standardSessionManager") | |||
public void testSessionClean3(StandardSessionManager ism) throws InterruptedException { | |||
// 2个异步请求,看是否处理完毕后会被清理掉 | |||
final WxCpMessageRouter router = new WxCpMessageRouter(null); | |||
router.setSessionManager(ism); | |||
router | |||
.rule().async(true).handler(new WxSessionMessageHandler()).next() | |||
.rule().async(true).handler(new WxSessionMessageHandler()).end(); | |||
WxCpXmlMessage msg = new WxCpXmlMessage(); | |||
msg.setFromUserName("abc"); | |||
router.route(msg); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
@Test(dataProvider = "standardSessionManager") | |||
public void testSessionClean4(StandardSessionManager ism) throws InterruptedException { | |||
// 一个同步请求,看是否处理完毕后会被清理掉 | |||
{ | |||
final WxCpMessageRouter router = new WxCpMessageRouter(null); | |||
router.setSessionManager(ism); | |||
router | |||
.rule().async(false).handler(new WxSessionMessageHandler()).end(); | |||
WxCpXmlMessage msg = new WxCpXmlMessage(); | |||
msg.setFromUserName("abc"); | |||
router.route(msg); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
{ | |||
final WxCpMessageRouter router = new WxCpMessageRouter(null); | |||
router.setSessionManager(ism); | |||
router | |||
.rule().async(true).handler(new WxSessionMessageHandler()).end(); | |||
WxCpXmlMessage msg = new WxCpXmlMessage(); | |||
msg.setFromUserName("abc"); | |||
router.route(msg); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
} | |||
public static class WxSessionMessageHandler implements WxCpMessageHandler { | |||
@Override | |||
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService wxCpService, | |||
WxSessionManager sessionManager) { | |||
sessionManager.getSession(wxMessage.getFromUserName()); | |||
return null; | |||
} | |||
} | |||
} |
@@ -1,9 +1,6 @@ | |||
package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.session.InternalSession; | |||
import me.chanjar.weixin.common.session.InMemorySessionManager; | |||
import me.chanjar.weixin.common.session.WxSession; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.session.*; | |||
import me.chanjar.weixin.common.util.WxMessageDuplicateChecker; | |||
import me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker; | |||
import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | |||
@@ -67,7 +64,7 @@ public class WxMpMessageRouter { | |||
this.wxMpService = wxMpService; | |||
this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | |||
this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); | |||
this.sessionManager = new InMemorySessionManager(); | |||
this.sessionManager = new StandardSessionManager(); | |||
} | |||
/** | |||
@@ -131,29 +128,31 @@ public class WxMpMessageRouter { | |||
} | |||
} | |||
} | |||
if (matchRules.size() == 0) { | |||
return null; | |||
} | |||
WxMpXmlOutMessage res = null; | |||
final List<Future> futures = new ArrayList<Future>(); | |||
for (final Rule rule : matchRules) { | |||
// 返回最后一个非异步的rule的执行结果 | |||
if(rule.async) { | |||
futures.add( | |||
executorService.submit(new Runnable() { | |||
public void run() { | |||
rule.service(wxMessage); | |||
} | |||
}) | |||
executorService.submit(new Runnable() { | |||
public void run() { | |||
rule.service(wxMessage); | |||
} | |||
}) | |||
); | |||
} else { | |||
res = rule.service(wxMessage); | |||
// 在同步操作结束,session访问结束 | |||
log.trace("End session access after sync operation finish {}", wxMessage.getFromUserName()); | |||
sessionEndAccess(wxMessage); | |||
} | |||
} | |||
// 告诉session,它已经用不着了 | |||
if (futures.size() > 0) { | |||
executorService.submit(new Runnable() { | |||
@Override | |||
@@ -161,19 +160,17 @@ public class WxMpMessageRouter { | |||
for (Future future : futures) { | |||
try { | |||
future.get(); | |||
log.trace("End session access after async operation finish {}", wxMessage.getFromUserName()); | |||
// 异步操作结束,session访问结束 | |||
sessionEndAccess(wxMessage); | |||
} catch (InterruptedException e) { | |||
log.error("Error happened when wait task finish", e); | |||
} catch (ExecutionException e) { | |||
log.error("Error happened when wait task finish", e); | |||
} | |||
} | |||
// 在这里session再也不会被使用了 | |||
sessionEndAccess(wxMessage); | |||
} | |||
}); | |||
} else { | |||
// 在这里session再也不会被使用了 | |||
sessionEndAccess(wxMessage); | |||
} | |||
return res; | |||
} | |||
@@ -183,10 +180,12 @@ public class WxMpMessageRouter { | |||
* @param wxMessage | |||
*/ | |||
protected void sessionEndAccess(WxMpXmlMessage wxMessage) { | |||
WxSession session = sessionManager.getSession(wxMessage.getFromUserName(), false); | |||
InternalSession session = ((InternalSessionManager)sessionManager).findSession(wxMessage.getFromUserName()); | |||
if (session != null) { | |||
((InternalSession) session).endAccess(); | |||
session.endAccess(); | |||
} | |||
} | |||
public static class Rule { | |||
@@ -10,7 +10,7 @@ import me.chanjar.weixin.common.bean.WxMenu; | |||
import me.chanjar.weixin.common.bean.result.WxError; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.session.InMemorySessionManager; | |||
import me.chanjar.weixin.common.session.StandardSessionManager; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.StringUtils; | |||
import me.chanjar.weixin.common.util.crypto.SHA1; | |||
@@ -68,7 +68,7 @@ public class WxMpServiceImpl implements WxMpService { | |||
private int maxRetryTimes = 5; | |||
protected WxSessionManager sessionManager = new InMemorySessionManager(); | |||
protected WxSessionManager sessionManager = new StandardSessionManager(); | |||
public boolean checkSignature(String timestamp, String nonce, String signature) { | |||
try { | |||
@@ -1,6 +1,7 @@ | |||
package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.common.session.StandardSessionManager; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | |||
import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | |||
@@ -159,4 +160,136 @@ public class WxMpMessageRouterTest { | |||
} | |||
@DataProvider | |||
public Object[][] standardSessionManager() { | |||
// 故意把session存活时间变短,清理更频繁 | |||
StandardSessionManager ism = new StandardSessionManager(); | |||
ism.setMaxInactiveInterval(1); | |||
ism.setProcessExpiresFrequency(1); | |||
ism.setBackgroundProcessorDelay(1); | |||
return new Object[][] { | |||
new Object[] { ism } | |||
}; | |||
} | |||
@Test(dataProvider = "standardSessionManager") | |||
public void testSessionClean1(StandardSessionManager ism) throws InterruptedException { | |||
// 两个同步请求,看是否处理完毕后会被清理掉 | |||
final WxMpMessageRouter router = new WxMpMessageRouter(null); | |||
router.setSessionManager(ism); | |||
router | |||
.rule().async(false).handler(new WxSessionMessageHandler()).next() | |||
.rule().async(false).handler(new WxSessionMessageHandler()).end(); | |||
WxMpXmlMessage msg = new WxMpXmlMessage(); | |||
msg.setFromUserName("abc"); | |||
router.route(msg); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
@Test(dataProvider = "standardSessionManager") | |||
public void testSessionClean2(StandardSessionManager ism) throws InterruptedException { | |||
// 1个同步,1个异步请求,看是否处理完毕后会被清理掉 | |||
{ | |||
final WxMpMessageRouter router = new WxMpMessageRouter(null); | |||
router.setSessionManager(ism); | |||
router | |||
.rule().async(false).handler(new WxSessionMessageHandler()).next() | |||
.rule().async(true).handler(new WxSessionMessageHandler()).end(); | |||
WxMpXmlMessage msg = new WxMpXmlMessage(); | |||
msg.setFromUserName("abc"); | |||
router.route(msg); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
{ | |||
final WxMpMessageRouter router = new WxMpMessageRouter(null); | |||
router.setSessionManager(ism); | |||
router | |||
.rule().async(true).handler(new WxSessionMessageHandler()).next() | |||
.rule().async(false).handler(new WxSessionMessageHandler()).end(); | |||
WxMpXmlMessage msg = new WxMpXmlMessage(); | |||
msg.setFromUserName("abc"); | |||
router.route(msg); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
} | |||
@Test(dataProvider = "standardSessionManager") | |||
public void testSessionClean3(StandardSessionManager ism) throws InterruptedException { | |||
// 2个异步请求,看是否处理完毕后会被清理掉 | |||
final WxMpMessageRouter router = new WxMpMessageRouter(null); | |||
router.setSessionManager(ism); | |||
router | |||
.rule().async(true).handler(new WxSessionMessageHandler()).next() | |||
.rule().async(true).handler(new WxSessionMessageHandler()).end(); | |||
WxMpXmlMessage msg = new WxMpXmlMessage(); | |||
msg.setFromUserName("abc"); | |||
router.route(msg); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
@Test(dataProvider = "standardSessionManager") | |||
public void testSessionClean4(StandardSessionManager ism) throws InterruptedException { | |||
// 一个同步请求,看是否处理完毕后会被清理掉 | |||
{ | |||
final WxMpMessageRouter router = new WxMpMessageRouter(null); | |||
router.setSessionManager(ism); | |||
router | |||
.rule().async(false).handler(new WxSessionMessageHandler()).end(); | |||
WxMpXmlMessage msg = new WxMpXmlMessage(); | |||
msg.setFromUserName("abc"); | |||
router.route(msg); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
{ | |||
final WxMpMessageRouter router = new WxMpMessageRouter(null); | |||
router.setSessionManager(ism); | |||
router | |||
.rule().async(true).handler(new WxSessionMessageHandler()).end(); | |||
WxMpXmlMessage msg = new WxMpXmlMessage(); | |||
msg.setFromUserName("abc"); | |||
router.route(msg); | |||
Thread.sleep(2000l); | |||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||
} | |||
} | |||
public static class WxSessionMessageHandler implements WxMpMessageHandler { | |||
@Override | |||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, | |||
WxSessionManager sessionManager) { | |||
sessionManager.getSession(wxMessage.getFromUserName()); | |||
return null; | |||
} | |||
} | |||
} |