@@ -39,11 +39,9 @@ public interface InternalSession { | |||
void access(); | |||
/** | |||
* Set the <code>isNew</code> flag for this session. | |||
* | |||
* @param isNew The new value for the <code>isNew</code> flag | |||
* End the access. | |||
*/ | |||
void setNew(boolean isNew); | |||
void endAccess(); | |||
/** | |||
* Set the creation time for this session. This method is called by the | |||
@@ -4,6 +4,7 @@ import me.chanjar.weixin.common.util.res.StringManager; | |||
import java.util.*; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import java.util.concurrent.atomic.AtomicInteger; | |||
public class SessionImpl implements WxSession, InternalSession { | |||
@@ -90,11 +91,6 @@ public class SessionImpl implements WxSession, InternalSession { | |||
*/ | |||
protected volatile boolean isValid = false; | |||
/** | |||
* Flag indicating whether this session is new or not. | |||
*/ | |||
protected boolean isNew = false; | |||
/** | |||
* We are currently processing a session expiration, so bypass | |||
* certain IllegalStateException tests. NOTE: This value is not | |||
@@ -123,11 +119,6 @@ public class SessionImpl implements WxSession, InternalSession { | |||
*/ | |||
protected volatile long thisAccessedTime = creationTime; | |||
/** | |||
* The last accessed time for this Session. | |||
*/ | |||
protected volatile long lastAccessedTime = creationTime; | |||
/** | |||
* The default maximum inactive interval for Sessions created by | |||
* this Manager. | |||
@@ -140,9 +131,15 @@ public class SessionImpl implements WxSession, InternalSession { | |||
*/ | |||
protected transient InternalSessionFacade facade = null; | |||
/** | |||
* The access count for this session. | |||
*/ | |||
protected transient AtomicInteger accessCount = null; | |||
public SessionImpl(InternalSessionManager manager) { | |||
this.manager = manager; | |||
this.accessCount = new AtomicInteger(); | |||
} | |||
@@ -176,7 +173,28 @@ public class SessionImpl implements WxSession, InternalSession { | |||
@Override | |||
public boolean isValid() { | |||
return isValid; | |||
if (!this.isValid) { | |||
return false; | |||
} | |||
if (this.expiring) { | |||
return true; | |||
} | |||
if (accessCount.get() > 0) { | |||
return true; | |||
} | |||
if (maxInactiveInterval > 0) { | |||
long timeNow = System.currentTimeMillis(); | |||
int timeIdle; | |||
timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L); | |||
if (timeIdle >= maxInactiveInterval) { | |||
expire(); | |||
} | |||
} | |||
return this.isValid; | |||
} | |||
@Override | |||
@@ -215,6 +233,8 @@ public class SessionImpl implements WxSession, InternalSession { | |||
// Mark this session as "being expired" | |||
expiring = true; | |||
accessCount.set(0); | |||
// Remove this session from our manager's active sessions | |||
manager.remove(this, true); | |||
@@ -238,23 +258,23 @@ public class SessionImpl implements WxSession, InternalSession { | |||
public void access() { | |||
this.thisAccessedTime = System.currentTimeMillis(); | |||
accessCount.incrementAndGet(); | |||
} | |||
@Override | |||
public void setNew(boolean isNew) { | |||
public void endAccess() { | |||
this.isNew = isNew; | |||
this.thisAccessedTime = System.currentTimeMillis(); | |||
accessCount.decrementAndGet(); | |||
} | |||
@Override | |||
public void setCreationTime(long time) { | |||
this.creationTime = time; | |||
this.lastAccessedTime = time; | |||
this.thisAccessedTime = time; | |||
} | |||
@@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory; | |||
import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import java.util.concurrent.atomic.AtomicBoolean; | |||
public class SessionManagerImpl implements WxSessionManager, InternalSessionManager { | |||
@@ -104,6 +105,11 @@ public class SessionManagerImpl implements WxSessionManager, InternalSessionMana | |||
*/ | |||
protected int processExpiresFrequency = 6; | |||
/** | |||
* 后台清理线程是否已经开启 | |||
*/ | |||
private final AtomicBoolean backgroundProcessStarted = new AtomicBoolean(false); | |||
@Override | |||
public void remove(InternalSession session) { | |||
remove(session, false); | |||
@@ -155,7 +161,6 @@ public class SessionManagerImpl implements WxSessionManager, InternalSessionMana | |||
InternalSession session = createEmptySession(); | |||
// Initialize the properties of the new session and return it | |||
session.setNew(true); | |||
session.setValid(true); | |||
session.setCreationTime(System.currentTimeMillis()); | |||
session.setMaxInactiveInterval(this.maxInactiveInterval); | |||
@@ -191,6 +196,26 @@ public class SessionManagerImpl implements WxSessionManager, InternalSessionMana | |||
@Override | |||
public void add(InternalSession session) { | |||
// 当第一次有session创建的时候,开启session清理线程 | |||
if (!backgroundProcessStarted.getAndSet(true)) { | |||
Thread t = new Thread(new Runnable() { | |||
@Override | |||
public void run() { | |||
while (true) { | |||
try { | |||
// 每秒清理一次 | |||
Thread.sleep(1000l); | |||
backgroundProcess(); | |||
} catch (InterruptedException e) { | |||
log.error("SessionManagerImpl.backgroundProcess error", e); | |||
} | |||
} | |||
} | |||
}); | |||
t.setDaemon(true); | |||
t.start(); | |||
} | |||
sessions.put(session.getIdInternal(), session); | |||
int size = getActiveSessions(); | |||
if( size > maxActive ) { | |||
@@ -2,9 +2,19 @@ package me.chanjar.weixin.common.session; | |||
public interface WxSessionManager { | |||
/** | |||
* 获取某个sessionId对应的session,如果sessionId没有对应的session,则新建一个并返回。 | |||
* @param sessionId | |||
* @return | |||
*/ | |||
public WxSession getSession(String sessionId); | |||
/** | |||
* 获取某个sessionId对应的session,如果sessionId没有对应的session,若create为true则新建一个,否则返回null。 | |||
* @param sessionId | |||
* @param create | |||
* @return | |||
*/ | |||
public WxSession getSession(String sessionId, boolean create); | |||
@@ -2,6 +2,7 @@ package me.chanjar.weixin.common.util; | |||
import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import java.util.concurrent.atomic.AtomicBoolean; | |||
/** | |||
* <pre> | |||
@@ -21,8 +22,16 @@ public class WxMsgIdMemoryDuplicateChecker implements WxMsgIdDuplicateChecker { | |||
*/ | |||
private final Long clearPeriod; | |||
/** | |||
* 消息id->消息时间戳的map | |||
*/ | |||
private final ConcurrentHashMap<Long, Long> msgId2Timestamp = new ConcurrentHashMap<Long, Long>(); | |||
/** | |||
* 后台清理线程是否已经开启 | |||
*/ | |||
private final AtomicBoolean backgroundProcessStarted = new AtomicBoolean(false); | |||
/** | |||
* WxMsgIdInMemoryDuplicateChecker构造函数 | |||
* <pre> | |||
@@ -33,7 +42,6 @@ public class WxMsgIdMemoryDuplicateChecker implements WxMsgIdDuplicateChecker { | |||
public WxMsgIdMemoryDuplicateChecker() { | |||
this.timeToLive = 15 * 1000l; | |||
this.clearPeriod = 5 * 1000l; | |||
this.start(); | |||
} | |||
/** | |||
@@ -44,10 +52,12 @@ public class WxMsgIdMemoryDuplicateChecker implements WxMsgIdDuplicateChecker { | |||
public WxMsgIdMemoryDuplicateChecker(Long timeToLive, Long clearPeriod) { | |||
this.timeToLive = timeToLive; | |||
this.clearPeriod = clearPeriod; | |||
this.start(); | |||
} | |||
private void start() { | |||
protected void checkBackgroundProcessStarted() { | |||
if (backgroundProcessStarted.getAndSet(true)) { | |||
return; | |||
} | |||
Thread t = new Thread(new Runnable() { | |||
@Override | |||
public void run() { | |||
@@ -72,6 +82,7 @@ public class WxMsgIdMemoryDuplicateChecker implements WxMsgIdDuplicateChecker { | |||
@Override | |||
public boolean isDuplicate(Long wxMsgId) { | |||
checkBackgroundProcessStarted(); | |||
Long timestamp = msgId2Timestamp.putIfAbsent(wxMsgId, System.currentTimeMillis()); | |||
if (timestamp == null) { | |||
// 第一次接收到这个消息 | |||
@@ -3,6 +3,8 @@ package me.chanjar.weixin.cp.api; | |||
import me.chanjar.weixin.common.bean.WxMenu; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.session.WxSession; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import me.chanjar.weixin.cp.bean.WxCpDepart; | |||
import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
@@ -371,4 +373,28 @@ public interface WxCpService { | |||
* @param maxRetryTimes | |||
*/ | |||
void setMaxRetryTimes(int maxRetryTimes); | |||
/** | |||
* 获取某个sessionId对应的session,如果sessionId没有对应的session,则新建一个并返回。 | |||
* @param id id可以为任意字符串,建议使用FromUserName作为id | |||
* @return | |||
*/ | |||
WxSession getSession(String id); | |||
/** | |||
* 获取某个sessionId对应的session,如果sessionId没有对应的session,若create为true则新建一个,否则返回null。 | |||
* @param id id可以为任意字符串,建议使用FromUserName作为id | |||
* @param create | |||
* @return | |||
*/ | |||
WxSession getSession(String id, boolean create); | |||
/** | |||
* <pre> | |||
* 设置WxSessionManager,只有当需要使用个性化的WxSessionManager的时候才需要调用此方法, | |||
* WxCpService默认使用的是{@link me.chanjar.weixin.common.session.SessionManagerImpl} | |||
* </pre> | |||
* @param sessionManager | |||
*/ | |||
void setSessionManager(WxSessionManager sessionManager); | |||
} |
@@ -12,6 +12,9 @@ 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.SessionManagerImpl; | |||
import me.chanjar.weixin.common.session.WxSession; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.StringUtils; | |||
import me.chanjar.weixin.common.util.crypto.SHA1; | |||
import me.chanjar.weixin.common.util.fs.FileUtils; | |||
@@ -64,6 +67,8 @@ public class WxCpServiceImpl implements WxCpService { | |||
private int maxRetryTimes = 5; | |||
protected WxSessionManager sessionManager = new SessionManagerImpl(); | |||
public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { | |||
try { | |||
return SHA1.gen(wxCpConfigStorage.getToken(), timestamp, nonce, data).equals(msgSignature); | |||
@@ -473,4 +478,26 @@ public class WxCpServiceImpl implements WxCpService { | |||
this.maxRetryTimes = maxRetryTimes; | |||
} | |||
@Override | |||
public WxSession getSession(String id) { | |||
if (sessionManager == null) { | |||
return null; | |||
} | |||
return sessionManager.getSession(id); | |||
} | |||
@Override | |||
public WxSession getSession(String id, boolean create) { | |||
if (sessionManager == null) { | |||
return null; | |||
} | |||
return sessionManager.getSession(id, create); | |||
} | |||
@Override | |||
public void setSessionManager(WxSessionManager sessionManager) { | |||
this.sessionManager = sessionManager; | |||
} | |||
} |
@@ -1,16 +1,25 @@ | |||
package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.session.InternalSession; | |||
import me.chanjar.weixin.common.session.SessionManagerImpl; | |||
import me.chanjar.weixin.common.session.WxSession; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.WxMsgIdDuplicateChecker; | |||
import me.chanjar.weixin.common.util.WxMsgIdMemoryDuplicateChecker; | |||
import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | |||
import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import javax.swing.text.StyledEditorKit; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.concurrent.ExecutionException; | |||
import java.util.concurrent.ExecutorService; | |||
import java.util.concurrent.Executors; | |||
import java.util.concurrent.Future; | |||
import java.util.regex.Pattern; | |||
/** | |||
@@ -41,6 +50,8 @@ import java.util.regex.Pattern; | |||
*/ | |||
public class WxMpMessageRouter { | |||
protected final Logger log = LoggerFactory.getLogger(WxMpMessageRouter.class); | |||
private static final int DEFAULT_THREAD_POOL_SIZE = 20; | |||
private final List<Rule> rules = new ArrayList<Rule>(); | |||
@@ -51,6 +62,8 @@ public class WxMpMessageRouter { | |||
private WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker; | |||
protected WxSessionManager sessionManager = new SessionManagerImpl(); | |||
public WxMpMessageRouter(WxMpService wxMpService) { | |||
this.wxMpService = wxMpService; | |||
this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | |||
@@ -113,21 +126,54 @@ public class WxMpMessageRouter { | |||
} | |||
WxMpXmlOutMessage res = null; | |||
final List<Future> futures = new ArrayList<Future>(); | |||
for (final Rule rule : matchRules) { | |||
// 返回最后一个非异步的rule的执行结果 | |||
if(rule.async) { | |||
executorService.submit(new Runnable() { | |||
public void run() { | |||
rule.service(wxMessage); | |||
} | |||
}); | |||
futures.add( | |||
executorService.submit(new Runnable() { | |||
public void run() { | |||
rule.service(wxMessage); | |||
} | |||
}) | |||
); | |||
} else { | |||
res = rule.service(wxMessage); | |||
} | |||
} | |||
// 告诉session,它已经用不着了 | |||
if (futures.size() > 0) { | |||
executorService.submit(new Runnable() { | |||
@Override | |||
public void run() { | |||
for (Future future : futures) { | |||
try { | |||
future.get(); | |||
} 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; | |||
} | |||
protected void sessionEndAccess(WxMpXmlMessage wxMessage) { | |||
WxSession session = sessionManager.getSession(wxMessage.getFromUserName(), false); | |||
if (session != null) { | |||
((InternalSession) session).endAccess(); | |||
} | |||
} | |||
public static class Rule { | |||
private final WxMpMessageRouter routerBuilder; | |||
@@ -3,6 +3,8 @@ package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.bean.WxMenu; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.session.WxSession; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import me.chanjar.weixin.mp.bean.*; | |||
import me.chanjar.weixin.mp.bean.result.*; | |||
@@ -456,10 +458,10 @@ public interface WxMpService { | |||
*/ | |||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException; | |||
/** | |||
* 注入 {@link WxMpConfigStorage} 的实现 | |||
* @param wxConfigProvider | |||
*/ | |||
/** | |||
* 注入 {@link WxMpConfigStorage} 的实现 | |||
* @param wxConfigProvider | |||
*/ | |||
public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider); | |||
/** | |||
@@ -479,4 +481,5 @@ public interface WxMpService { | |||
* @param maxRetryTimes | |||
*/ | |||
void setMaxRetryTimes(int maxRetryTimes); | |||
} |
@@ -10,6 +10,9 @@ 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.SessionManagerImpl; | |||
import me.chanjar.weixin.common.session.WxSession; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.StringUtils; | |||
import me.chanjar.weixin.common.util.crypto.SHA1; | |||
import me.chanjar.weixin.common.util.fs.FileUtils; | |||
@@ -66,6 +69,8 @@ public class WxMpServiceImpl implements WxMpService { | |||
private int maxRetryTimes = 5; | |||
protected WxSessionManager sessionManager = new SessionManagerImpl(); | |||
public boolean checkSignature(String timestamp, String nonce, String signature) { | |||
try { | |||
return SHA1.gen(wxMpConfigStorage.getToken(), timestamp, nonce).equals(signature); | |||
@@ -545,7 +550,6 @@ public class WxMpServiceImpl implements WxMpService { | |||
} | |||
} | |||
@Override | |||
public void setRetrySleepMillis(int retrySleepMillis) { | |||
this.retrySleepMillis = retrySleepMillis; | |||