From b797580384a09052fb06baefe3085ca2418bcbfd Mon Sep 17 00:00:00 2001
From: jinyu
Date: Sat, 28 May 2016 11:33:05 +0800
Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84Cache=E5=AE=9E=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CHANGE.md | 6 +-
weixin4j-base/pom.xml | 2 +-
.../java/com/foxinmy/weixin4j/api/PayApi.java | 3 +-
.../{token => cache}/CacheCreator.java | 4 +-
.../foxinmy/weixin4j/cache/CacheManager.java | 69 ++++
.../{token => cache}/CacheStorager.java | 18 +-
.../com/foxinmy/weixin4j/cache/Cacheable.java | 28 ++
.../weixin4j/cache/FileCacheStorager.java | 87 +++++
.../MemcacheCacheStorager.java} | 61 ++--
.../weixin4j/cache/MemoryCacheStorager.java | 50 +++
.../java/com/foxinmy/weixin4j/cache/README.md | 13 +
.../weixin4j/cache/RedisCacheStorager.java | 128 +++++++
.../cache/RedisClusterCacheStorager.java | 94 +++++
.../foxinmy/weixin4j/http/weixin/error.xml | 4 +
.../weixin4j/jssdk/JSSDKConfigurator.java | 28 +-
.../com/foxinmy/weixin4j/model/Button.java | 33 +-
.../com/foxinmy/weixin4j/model/Consts.java | 2 +-
.../weixin4j/model/MediaUploadResult.java | 15 +-
.../com/foxinmy/weixin4j/model/Token.java | 89 +++--
.../weixin4j/setting/SystemSettings.java | 25 +-
.../weixin4j/setting/Weixin4jSettings.java | 22 ++
.../sign/AbstractWeixinSignature.java | 14 +-
.../weixin4j/token/FileTokenStorager.java | 95 -----
.../weixin4j/token/MemoryTokenStorager.java | 51 ---
.../java/com/foxinmy/weixin4j/token/README.md | 13 -
.../weixin4j/token/RedisTokenStorager.java | 147 --------
.../foxinmy/weixin4j/token/TokenCreator.java | 17 +-
.../foxinmy/weixin4j/token/TokenHolder.java | 95 -----
.../foxinmy/weixin4j/token/TokenManager.java | 41 +++
.../foxinmy/weixin4j/token/TokenStorager.java | 27 --
.../com/foxinmy/weixin4j/tuple/Article.java | 13 +-
.../com/foxinmy/weixin4j/tuple/MpArticle.java | 111 +++---
.../com/foxinmy/weixin4j/tuple/MpNews.java | 39 +-
.../com/foxinmy/weixin4j/tuple/Music.java | 24 +-
.../java/com/foxinmy/weixin4j/tuple/News.java | 15 +-
.../com/foxinmy/weixin4j/tuple/Video.java | 16 +-
.../weixin4j/util/SerializationUtils.java | 339 ++++++++++++++++++
.../weixin4j/util/Weixin4jConfigUtil.java | 13 +-
weixin4j-example/pom.xml | 5 +
.../Weixin4jServerStartupWithoutThread.java | 4 +-
.../com/foxinmy/weixin4j/mp/WeixinProxy.java | 56 +--
.../foxinmy/weixin4j/mp/api/CustomApi.java | 58 +--
.../com/foxinmy/weixin4j/mp/api/DataApi.java | 10 +-
.../com/foxinmy/weixin4j/mp/api/GroupApi.java | 22 +-
.../foxinmy/weixin4j/mp/api/HelperApi.java | 38 +-
.../com/foxinmy/weixin4j/mp/api/MassApi.java | 20 +-
.../com/foxinmy/weixin4j/mp/api/MediaApi.java | 28 +-
.../com/foxinmy/weixin4j/mp/api/MenuApi.java | 18 +-
.../foxinmy/weixin4j/mp/api/NotifyApi.java | 33 +-
.../com/foxinmy/weixin4j/mp/api/OauthApi.java | 12 +-
.../foxinmy/weixin4j/mp/api/PayOldApi.java | 52 +--
.../com/foxinmy/weixin4j/mp/api/QrApi.java | 10 +-
.../com/foxinmy/weixin4j/mp/api/TagApi.java | 24 +-
.../com/foxinmy/weixin4j/mp/api/TmplApi.java | 20 +-
.../com/foxinmy/weixin4j/mp/api/UserApi.java | 16 +-
.../weixin4j/mp/message/TemplateMessage.java | 21 +-
.../weixin4j/mp/model/AutoReplySetting.java | 36 +-
.../weixin4j/mp/model/MenuSetting.java | 2 +-
.../foxinmy/weixin4j/mp/model/OauthToken.java | 15 +-
.../mp/token/WeixinTicketCreator.java | 24 +-
.../weixin4j/mp/token/WeixinTokenCreator.java | 10 +-
.../foxinmy/weixin4j/mp/test/CustomTest.java | 2 +-
.../foxinmy/weixin4j/mp/test/DataApiTest.java | 2 +-
.../foxinmy/weixin4j/mp/test/GroupTest.java | 2 +-
.../foxinmy/weixin4j/mp/test/HelperTest.java | 2 +-
.../foxinmy/weixin4j/mp/test/MassTest.java | 4 +-
.../foxinmy/weixin4j/mp/test/MediaTest.java | 8 +-
.../foxinmy/weixin4j/mp/test/MenuTest.java | 2 +-
.../foxinmy/weixin4j/mp/test/NotifyTest.java | 4 +-
.../com/foxinmy/weixin4j/mp/test/QRTest.java | 2 +-
.../weixin4j/mp/test/SemanticTest.java | 2 +-
.../com/foxinmy/weixin4j/mp/test/TagTest.java | 2 +-
.../weixin4j/mp/test/TemplateTest.java | 2 +-
.../foxinmy/weixin4j/mp/test/TokenTest.java | 12 +-
.../foxinmy/weixin4j/mp/test/UserTest.java | 2 +-
.../weixin4j/mp/test/XmlstreamTest.java | 4 +-
.../com/foxinmy/weixin4j/qy/WeixinProxy.java | 68 ++--
.../foxinmy/weixin4j/qy/WeixinSuiteProxy.java | 24 +-
.../com/foxinmy/weixin4j/qy/api/AgentApi.java | 14 +-
.../com/foxinmy/weixin4j/qy/api/BatchApi.java | 14 +-
.../com/foxinmy/weixin4j/qy/api/ChatApi.java | 22 +-
.../foxinmy/weixin4j/qy/api/HelperApi.java | 10 +-
.../com/foxinmy/weixin4j/qy/api/MediaApi.java | 24 +-
.../com/foxinmy/weixin4j/qy/api/MenuApi.java | 14 +-
.../foxinmy/weixin4j/qy/api/NotifyApi.java | 28 +-
.../com/foxinmy/weixin4j/qy/api/PartyApi.java | 16 +-
.../foxinmy/weixin4j/qy/api/ProviderApi.java | 41 ++-
.../com/foxinmy/weixin4j/qy/api/SuiteApi.java | 141 ++++----
.../com/foxinmy/weixin4j/qy/api/TagApi.java | 20 +-
.../com/foxinmy/weixin4j/qy/api/UserApi.java | 30 +-
.../qy/jssdk/JSSDKContactConfigurator.java | 48 ++-
.../weixin4j/qy/message/ChatMessage.java | 11 +-
.../foxinmy/weixin4j/qy/model/Callback.java | 2 +-
.../foxinmy/weixin4j/qy/model/ChatMute.java | 13 +-
.../com/foxinmy/weixin4j/qy/model/User.java | 4 +-
...deHolder.java => SuitePerCodeManager.java} | 36 +-
...ketHolder.java => SuiteTicketManager.java} | 35 +-
.../qy/suite/Weixin4jSuiteSettings.java | 18 +
.../qy/suite/WeixinSuitePreCodeCreator.java | 20 +-
.../qy/suite/WeixinSuiteTokenCreator.java | 30 +-
.../qy/suite/WeixinTokenSuiteCreator.java | 35 +-
.../qy/token/WeixinProviderTokenCreator.java | 11 +-
.../qy/token/WeixinTicketCreator.java | 29 +-
.../weixin4j/qy/token/WeixinTokenCreator.java | 10 +-
.../foxinmy/weixin4j/qy/test/AgentTest.java | 2 +-
.../foxinmy/weixin4j/qy/test/BatchTest.java | 4 +-
.../foxinmy/weixin4j/qy/test/ChatTest.java | 2 +-
.../foxinmy/weixin4j/qy/test/HelperTest.java | 2 +-
.../foxinmy/weixin4j/qy/test/MediaTest.java | 2 +-
.../foxinmy/weixin4j/qy/test/MenuTest.java | 2 +-
.../foxinmy/weixin4j/qy/test/NotifyTest.java | 2 +-
.../foxinmy/weixin4j/qy/test/PartyTest.java | 2 +-
.../com/foxinmy/weixin4j/qy/test/TagTest.java | 2 +-
.../foxinmy/weixin4j/qy/test/TokenTest.java | 10 +-
.../foxinmy/weixin4j/qy/test/UserTest.java | 4 +-
.../com/foxinmy/weixin4j/util/AesToken.java | 6 +-
116 files changed, 1932 insertions(+), 1313 deletions(-)
rename weixin4j-base/src/main/java/com/foxinmy/weixin4j/{token => cache}/CacheCreator.java (76%)
create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/CacheManager.java
rename weixin4j-base/src/main/java/com/foxinmy/weixin4j/{token => cache}/CacheStorager.java (65%)
create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/Cacheable.java
create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/FileCacheStorager.java
rename weixin4j-base/src/main/java/com/foxinmy/weixin4j/{token/MemcacheTokenStorager.java => cache/MemcacheCacheStorager.java} (75%)
create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/MemoryCacheStorager.java
create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/README.md
create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/RedisCacheStorager.java
create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/RedisClusterCacheStorager.java
delete mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/FileTokenStorager.java
delete mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/MemoryTokenStorager.java
delete mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/README.md
delete mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/RedisTokenStorager.java
delete mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/TokenHolder.java
create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/TokenManager.java
delete mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/TokenStorager.java
create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/SerializationUtils.java
rename weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/suite/{SuitePerCodeHolder.java => SuitePerCodeManager.java} (56%)
rename weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/suite/{SuiteTicketHolder.java => SuiteTicketManager.java} (55%)
diff --git a/CHANGE.md b/CHANGE.md
index 6a96eda2..9f835539 100644
--- a/CHANGE.md
+++ b/CHANGE.md
@@ -701,4 +701,8 @@
+ weixin4j-base:修改Memcached-Java-Client的依赖
- + weixin4j-base:系统配置类抽象化
\ No newline at end of file
+ + weixin4j-base:系统配置类抽象化
+
+* 2016-05-28
+
+ + 重构Cache实现
\ No newline at end of file
diff --git a/weixin4j-base/pom.xml b/weixin4j-base/pom.xml
index 8f73da1b..76cfa5ad 100644
--- a/weixin4j-base/pom.xml
+++ b/weixin4j-base/pom.xml
@@ -42,7 +42,7 @@
redis.clients
jedis
- 2.6.0
+ 2.8.1
true
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/api/PayApi.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/api/PayApi.java
index b78b1840..b8a909d7 100644
--- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/api/PayApi.java
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/api/PayApi.java
@@ -632,7 +632,8 @@ public class PayApi extends MchApi {
String fileName = String.format("weixin4j_bill_%s_%s_%s.txt",
formatBillDate, billType.name().toLowerCase(),
weixinAccount.getId());
- File file = new File(String.format("%s/%s", billPath, fileName));
+ File file = new File(String.format("%s%s%s", billPath, File.separator,
+ fileName));
if (file.exists()) {
return file;
}
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/CacheCreator.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/CacheCreator.java
similarity index 76%
rename from weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/CacheCreator.java
rename to weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/CacheCreator.java
index 5ec17574..cd524475 100644
--- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/CacheCreator.java
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/CacheCreator.java
@@ -1,4 +1,4 @@
-package com.foxinmy.weixin4j.token;
+package com.foxinmy.weixin4j.cache;
import com.foxinmy.weixin4j.exception.WeixinException;
@@ -11,7 +11,7 @@ import com.foxinmy.weixin4j.exception.WeixinException;
* @since JDK 1.6
* @see
*/
-public interface CacheCreator {
+public interface CacheCreator {
/**
* CacheKey
*
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/CacheManager.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/CacheManager.java
new file mode 100644
index 00000000..808bca27
--- /dev/null
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/CacheManager.java
@@ -0,0 +1,69 @@
+package com.foxinmy.weixin4j.cache;
+
+import com.foxinmy.weixin4j.exception.WeixinException;
+
+/**
+ * 缓存管理类
+ *
+ * @className CacheManager
+ * @author jinyu(foxinmy@gmail.com)
+ * @date 2016年5月27日
+ * @since JDK 1.7
+ * @see
+ */
+public class CacheManager {
+ protected final CacheCreator cacheCreator;
+ protected final CacheStorager cacheStorager;
+
+ public CacheManager(CacheCreator cacheCreator,
+ CacheStorager cacheStorager) {
+ this.cacheCreator = cacheCreator;
+ this.cacheStorager = cacheStorager;
+ }
+
+ /**
+ * 获取缓存对象
+ *
+ * @return 缓存对象
+ * @throws WeixinException
+ */
+ public T getCache() throws WeixinException {
+ String cacheKey = cacheCreator.key();
+ T cache = cacheStorager.lookup(cacheKey);
+ if (cache == null) {
+ cache = cacheCreator.create();
+ cacheStorager.caching(cacheKey, cache);
+ }
+ return cache;
+ }
+
+ /**
+ * 刷新缓存对象
+ *
+ * @return 缓存对象
+ * @throws WeixinException
+ */
+ public T refreshCache() throws WeixinException {
+ String cacheKey = cacheCreator.key();
+ T cache = cacheCreator.create();
+ cacheStorager.caching(cacheKey, cache);
+ return cache;
+ }
+
+ /**
+ * 移除缓存
+ *
+ * @return 被移除的缓存对象
+ */
+ public T evictCache() {
+ String cacheKey = cacheCreator.key();
+ return cacheStorager.evict(cacheKey);
+ }
+
+ /**
+ * 清除所有的缓存(请慎重)
+ */
+ public void clearCache() {
+ cacheStorager.clear();
+ }
+}
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/CacheStorager.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/CacheStorager.java
similarity index 65%
rename from weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/CacheStorager.java
rename to weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/CacheStorager.java
index 6e295892..23878100 100644
--- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/CacheStorager.java
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/CacheStorager.java
@@ -1,4 +1,4 @@
-package com.foxinmy.weixin4j.token;
+package com.foxinmy.weixin4j.cache;
/**
* Cache的存储
@@ -9,7 +9,17 @@ package com.foxinmy.weixin4j.token;
* @since JDK 1.6
* @see
*/
-public interface CacheStorager {
+public interface CacheStorager {
+ /**
+ * 考虑到临界情况,实际缓存的有效时间减去该毫秒数(60秒)
+ */
+ long CUTMS = 60 * 1000l;
+
+ /**
+ * 所有的缓存KEY
+ */
+ String ALLKEY = "weixin4j_cache_keys";
+
/**
* 查找缓存中的对象
*
@@ -42,8 +52,6 @@ public interface CacheStorager {
/**
* 清除所有缓存对象(请慎重)
*
- * @param prefix
- * 缓存key的前缀
*/
- void clear(String prefix);
+ void clear();
}
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/Cacheable.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/Cacheable.java
new file mode 100644
index 00000000..28f1075b
--- /dev/null
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/Cacheable.java
@@ -0,0 +1,28 @@
+package com.foxinmy.weixin4j.cache;
+
+import java.io.Serializable;
+
+/**
+ * 可缓存的对象
+ *
+ * @className Cacheable
+ * @author jinyu(foxinmy@gmail.com)
+ * @date 2016年5月26日
+ * @since JDK 1.6
+ * @see
+ */
+public interface Cacheable extends Serializable {
+ /**
+ * 过期时间(单位:毫秒),值小于0时视为永不过期
+ *
+ * @return 缓存过期时间
+ */
+ public long getExpires();
+
+ /**
+ * 创建时间(单位:毫秒)
+ *
+ * @return 缓存对象创建时间
+ */
+ public long getCreateTime();
+}
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/FileCacheStorager.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/FileCacheStorager.java
new file mode 100644
index 00000000..998c5096
--- /dev/null
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/FileCacheStorager.java
@@ -0,0 +1,87 @@
+package com.foxinmy.weixin4j.cache;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import com.foxinmy.weixin4j.util.SerializationUtils;
+
+/**
+ * 用File保存缓存对象
+ *
+ * @className FileCacheStorager
+ * @author jinyu(foxinmy@gmail.com)
+ * @date 2016年5月27日
+ * @since JDK 1.6
+ * @see
+ */
+public class FileCacheStorager implements CacheStorager {
+
+ private final File tmpdir;
+ private final String SEPARATOR = File.separator;
+
+ public FileCacheStorager(String cachePath) {
+ this.tmpdir = new File(String.format("%s%sweixin4j_token_temp",
+ cachePath, SEPARATOR));
+ this.tmpdir.mkdirs();
+ }
+
+ @Override
+ public T lookup(String cacheKey) {
+ File cacheFile = new File(String.format("%s%s%s",
+ tmpdir.getAbsolutePath(), SEPARATOR, cacheKey));
+ try {
+ if (cacheFile.exists()) {
+ T cache = SerializationUtils.deserialize(new FileInputStream(
+ cacheFile));
+ if (cache.getCreateTime() < 0) {
+ return cache;
+ }
+ if ((cache.getCreateTime() + cache.getExpires() - CUTMS) > System
+ .currentTimeMillis()) {
+ return cache;
+ }
+ }
+ return null;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void caching(String cacheKey, T cache) {
+ try {
+ SerializationUtils.serialize(
+ cache,
+ new FileOutputStream(new File(String.format("%s%s%s",
+ tmpdir.getAbsolutePath(), SEPARATOR, cacheKey))));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public T evict(String cacheKey) {
+ T cache = null;
+ File cacheFile = new File(String.format("%s%s%s",
+ tmpdir.getAbsolutePath(), SEPARATOR, cacheKey));
+ try {
+ if (cacheFile.exists()) {
+ cache = SerializationUtils.deserialize(new FileInputStream(
+ cacheFile));
+ cacheFile.delete();
+ }
+ } catch (IOException e) {
+ ; // ingore
+ }
+ return cache;
+ }
+
+ @Override
+ public void clear() {
+ for (File cache : tmpdir.listFiles()) {
+ cache.delete();
+ }
+ }
+}
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/MemcacheTokenStorager.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/MemcacheCacheStorager.java
similarity index 75%
rename from weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/MemcacheTokenStorager.java
rename to weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/MemcacheCacheStorager.java
index 1cff9439..42895c19 100644
--- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/token/MemcacheTokenStorager.java
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/MemcacheCacheStorager.java
@@ -1,61 +1,82 @@
-package com.foxinmy.weixin4j.token;
+package com.foxinmy.weixin4j.cache;
import java.util.ArrayList;
import java.util.Date;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
-import com.foxinmy.weixin4j.model.Token;
import com.whalin.MemCached.MemCachedClient;
import com.whalin.MemCached.SockIOPool;
/**
- * 用Memcache保存Token信息(推荐使用)
+ * 用Memcache保存缓存对象(推荐使用)
*
- * @className MemcacheTokenStorager
+ * @className MemcacheCacheStorager
* @author jinyu(foxinmy@gmail.com)
* @date 2016年5月11日
* @since JDK 1.6
* @see
*/
-public class MemcacheTokenStorager extends TokenStorager {
+public class MemcacheCacheStorager implements
+ CacheStorager {
private final MemCachedClient mc;
- public MemcacheTokenStorager(MemcachePoolConfig poolConfig) {
+ public MemcacheCacheStorager() {
+ this(new MemcachePoolConfig());
+ }
+
+ public MemcacheCacheStorager(MemcachePoolConfig poolConfig) {
mc = new MemCachedClient();
poolConfig.initSocketIO();
+ mc.set(ALLKEY, new HashSet());
}
+ @SuppressWarnings("unchecked")
@Override
- public Token lookup(String cacheKey) {
- return (Token) mc.get(cacheKey);
+ public T lookup(String cacheKey) {
+ return (T) mc.get(cacheKey);
}
+ @SuppressWarnings("unchecked")
@Override
- public void caching(String cacheKey, Token token) {
- if (token.getExpiresIn() > 0) {
- mc.set(cacheKey, token,
- new Date(token.getCreateTime() + token.getExpiresIn()
- * 1000 - ms()));
+ public void caching(String cacheKey, T cache) {
+ if (cache.getCreateTime() > 0l) {
+ mc.set(cacheKey,
+ cache,
+ new Date(cache.getCreateTime() + cache.getExpires() - CUTMS));
} else {
- mc.set(cacheKey, token);
+ mc.set(cacheKey, cache);
}
+ Set all = (Set) mc.get(ALLKEY);
+ all.add(cacheKey);
+ mc.set(ALLKEY, all);
}
+ @SuppressWarnings("unchecked")
@Override
- public Token evict(String cacheKey) {
- Token token = lookup(cacheKey);
+ public T evict(String cacheKey) {
+ T cache = lookup(cacheKey);
mc.delete(cacheKey);
- return token;
+ Set all = (Set) mc.get(ALLKEY);
+ all.remove(cacheKey);
+ mc.set(ALLKEY, all);
+ return cache;
}
+ @SuppressWarnings("unchecked")
@Override
- public void clear(String prefix) {
- throw new UnsupportedOperationException();
+ public void clear() {
+ Set all = (Set) mc.get(ALLKEY);
+ for (String key : all) {
+ mc.delete(key);
+ }
+ mc.delete(ALLKEY);
}
public static class MemcachePoolConfig {
- public final static String HOST = "localhost";
+ public final static String HOST = "127.0.0.1";
public final static int PORT = 11211;
public final static int WEIGHT = 1;
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/MemoryCacheStorager.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/MemoryCacheStorager.java
new file mode 100644
index 00000000..e0562013
--- /dev/null
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/MemoryCacheStorager.java
@@ -0,0 +1,50 @@
+package com.foxinmy.weixin4j.cache;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 用内存保存缓存对象(不推荐使用)
+ *
+ * @className MemoryCacheStorager
+ * @author jinyu(foxinmy@gmail.com)
+ * @date 2016年1月24日
+ * @since JDK 1.6
+ * @see
+ */
+public class MemoryCacheStorager implements
+ CacheStorager {
+
+ private final Map CONMAP;
+
+ public MemoryCacheStorager() {
+ this.CONMAP = new ConcurrentHashMap();
+ }
+
+ @Override
+ public T lookup(String cacheKey) {
+ T cache = this.CONMAP.get(cacheKey);
+ if (cache != null) {
+ if ((cache.getCreateTime() + cache.getExpires() - CUTMS) > System
+ .currentTimeMillis()) {
+ return cache;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void caching(String cacheKey, T cache) {
+ this.CONMAP.put(cacheKey, cache);
+ }
+
+ @Override
+ public T evict(String cacheKey) {
+ return this.CONMAP.remove(cacheKey);
+ }
+
+ @Override
+ public void clear() {
+ this.CONMAP.clear();
+ }
+}
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/README.md b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/README.md
new file mode 100644
index 00000000..545b17e0
--- /dev/null
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/README.md
@@ -0,0 +1,13 @@
+### CACHE的实现
+
+* CacheCreator 负责创建新的缓存对象
+
+* CacheStorager 负责查找已缓存的对象或者缓存新的对象
+
+* TokenManager 负责对缓存对象的管理(屏蔽细节)
+
+* FileCacheStorager 是系统默认的缓存存储策略实现
+
+* RedisCacheStorager(RedisClusterCacheStorager) 使用redis保存缓存对象(需要自行添加客户端包,[jedis](https://github.com/xetorthio/jedis))
+
+* MemcacheCacheStorager 使用memcache保存缓存对象(需要自行添加客户端包,[Memcached-Java-Client](https://github.com/gwhalin/Memcached-Java-Client))
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/RedisCacheStorager.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/RedisCacheStorager.java
new file mode 100644
index 00000000..d4524f96
--- /dev/null
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/RedisCacheStorager.java
@@ -0,0 +1,128 @@
+package com.foxinmy.weixin4j.cache;
+
+import java.util.Set;
+
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPoolConfig;
+
+import com.foxinmy.weixin4j.model.Consts;
+import com.foxinmy.weixin4j.util.SerializationUtils;
+
+/**
+ * 用Redis保存缓存对象(推荐使用)
+ *
+ * @className RedisCacheStorager
+ * @author jinyu(foxinmy@gmail.com)
+ * @date 2015年1月9日
+ * @since JDK 1.6
+ */
+public class RedisCacheStorager implements
+ CacheStorager {
+
+ private JedisPool jedisPool;
+
+ private final static String HOST = "127.0.0.1";
+ private final static int PORT = 6379;
+ private final static int TIMEOUT = 5000;
+ private final static int MAX_TOTAL = 50;
+ private final static int MAX_IDLE = 5;
+ private final static int MAX_WAIT_MILLIS = 5000;
+ private final static boolean TEST_ON_BORROW = false;
+ private final static boolean TEST_ON_RETURN = true;
+
+ public RedisCacheStorager() {
+ this(HOST, PORT, TIMEOUT);
+ }
+
+ public RedisCacheStorager(String host, int port, int timeout) {
+ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
+ jedisPoolConfig.setMaxTotal(MAX_TOTAL);
+ jedisPoolConfig.setMaxIdle(MAX_IDLE);
+ jedisPoolConfig.setMaxWaitMillis(MAX_WAIT_MILLIS);
+ jedisPoolConfig.setTestOnBorrow(TEST_ON_BORROW);
+ jedisPoolConfig.setTestOnReturn(TEST_ON_RETURN);
+ this.jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout);
+ }
+
+ public RedisCacheStorager(JedisPoolConfig jedisPoolConfig) {
+ this(new JedisPool(jedisPoolConfig, HOST, PORT, TIMEOUT));
+ }
+
+ public RedisCacheStorager(String host, int port, int timeout,
+ JedisPoolConfig jedisPoolConfig) {
+ this(new JedisPool(jedisPoolConfig, host, port, timeout));
+ }
+
+ public RedisCacheStorager(JedisPool jedisPool) {
+ this.jedisPool = jedisPool;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T lookup(String cacheKey) {
+ Jedis jedis = null;
+ try {
+ jedis = jedisPool.getResource();
+ byte[] value = jedis.get(cacheKey.getBytes(Consts.UTF_8));
+ return value != null ? (T) SerializationUtils.deserialize(value)
+ : null;
+ } finally {
+ if (jedis != null) {
+ jedis.close();
+ }
+ }
+ }
+
+ @Override
+ public void caching(String cacheKey, T cache) {
+ Jedis jedis = null;
+ try {
+ jedis = jedisPool.getResource();
+ byte[] key = cacheKey.getBytes(Consts.UTF_8);
+ byte[] value = SerializationUtils.serialize(cache);
+ jedis.set(key, value);
+ if (cache.getExpires() > 0) {
+ jedis.expire(key, (int) (cache.getExpires() - CUTMS) / 1000);
+ }
+ jedis.sadd(ALLKEY, cacheKey);
+ } finally {
+ if (jedis != null) {
+ jedis.close();
+ }
+ }
+ }
+
+ @Override
+ public T evict(String cacheKey) {
+ T cache = lookup(cacheKey);
+ Jedis jedis = null;
+ try {
+ jedis = jedisPool.getResource();
+ jedis.del(cacheKey);
+ jedis.srem(ALLKEY, cacheKey);
+ } finally {
+ if (jedis != null) {
+ jedis.close();
+ }
+ }
+ return cache;
+ }
+
+ @Override
+ public void clear() {
+ Jedis jedis = null;
+ try {
+ jedis = jedisPool.getResource();
+ Set cacheKeys = jedis.smembers(ALLKEY);
+ if (!cacheKeys.isEmpty()) {
+ cacheKeys.add(ALLKEY);
+ jedis.del(cacheKeys.toArray(new String[cacheKeys.size()]));
+ }
+ } finally {
+ if (jedis != null) {
+ jedis.close();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/RedisClusterCacheStorager.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/RedisClusterCacheStorager.java
new file mode 100644
index 00000000..5dc149e7
--- /dev/null
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/cache/RedisClusterCacheStorager.java
@@ -0,0 +1,94 @@
+package com.foxinmy.weixin4j.cache;
+
+import java.util.Set;
+
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.JedisPoolConfig;
+
+import com.foxinmy.weixin4j.model.Consts;
+import com.foxinmy.weixin4j.util.SerializationUtils;
+
+/**
+ * 用Redis(集群)保存缓存对象(推荐使用)
+ *
+ * @className RedisCacheStorager
+ * @author jinyu(foxinmy@gmail.com)
+ * @date 2015年1月9日
+ * @since JDK 1.6
+ */
+public class RedisClusterCacheStorager implements
+ CacheStorager {
+ private final static int CONNECTION_TIMEOUT = 5000;
+ private final static int SO_TIMEOUT = 5000;
+ private final static int MAX_REDIRECTIONS = 5;
+ private final static int MAX_TOTAL = 50;
+ private final static int MAX_IDLE = 5;
+ private final static int MAX_WAIT_MILLIS = 5000;
+ private final static boolean TEST_ON_BORROW = false;
+ private final static boolean TEST_ON_RETURN = true;
+ private final JedisCluster jedisCluster;
+
+ public RedisClusterCacheStorager(Set nodes) {
+ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
+ jedisPoolConfig.setMaxTotal(MAX_TOTAL);
+ jedisPoolConfig.setMaxIdle(MAX_IDLE);
+ jedisPoolConfig.setMaxWaitMillis(MAX_WAIT_MILLIS);
+ jedisPoolConfig.setTestOnBorrow(TEST_ON_BORROW);
+ jedisPoolConfig.setTestOnReturn(TEST_ON_RETURN);
+ this.jedisCluster = new JedisCluster(nodes, CONNECTION_TIMEOUT,
+ SO_TIMEOUT, MAX_REDIRECTIONS, jedisPoolConfig);
+ }
+
+ public RedisClusterCacheStorager(Set nodes,
+ JedisPoolConfig poolConfig) {
+ this(nodes, CONNECTION_TIMEOUT, SO_TIMEOUT, MAX_REDIRECTIONS,
+ poolConfig);
+ }
+
+ public RedisClusterCacheStorager(Set nodes,
+ int connectionTimeout, int soTimeout, int maxRedirections,
+ JedisPoolConfig poolConfig) {
+ this(new JedisCluster(nodes, connectionTimeout, soTimeout,
+ maxRedirections, poolConfig));
+ }
+
+ public RedisClusterCacheStorager(JedisCluster jedisCluster) {
+ this.jedisCluster = jedisCluster;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T lookup(String cacheKey) {
+ byte[] value = jedisCluster.get(cacheKey.getBytes(Consts.UTF_8));
+ return value != null ? (T) SerializationUtils.deserialize(value) : null;
+ }
+
+ @Override
+ public void caching(String cacheKey, T cache) {
+ byte[] key = cacheKey.getBytes(Consts.UTF_8);
+ byte[] value = SerializationUtils.serialize(cache);
+ jedisCluster.set(key, value);
+ if (cache.getExpires() > 0) {
+ jedisCluster.expire(key, (int) (cache.getExpires() - CUTMS) / 1000);
+ }
+ jedisCluster.sadd(ALLKEY, cacheKey);
+ }
+
+ @Override
+ public T evict(String cacheKey) {
+ T cache = lookup(cacheKey);
+ jedisCluster.del(cacheKey);
+ jedisCluster.srem(ALLKEY, cacheKey);
+ return cache;
+ }
+
+ @Override
+ public void clear() {
+ Set cacheKeys = jedisCluster.smembers(ALLKEY);
+ if (!cacheKeys.isEmpty()) {
+ cacheKeys.add(ALLKEY);
+ jedisCluster.del(cacheKeys.toArray(new String[cacheKeys.size()]));
+ }
+ }
+}
\ No newline at end of file
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/error.xml b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/error.xml
index 410cac0f..3fa190b1 100644
--- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/error.xml
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/error.xml
@@ -1431,6 +1431,10 @@
81003
邀请额度已用完
+
+ 81004
+ 部门数量超过上限
+
82001
发送消息或者邀请的参数全部为空或者全部不合法
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/jssdk/JSSDKConfigurator.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/jssdk/JSSDKConfigurator.java
index 9c5cfd5d..dfd70c43 100644
--- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/jssdk/JSSDKConfigurator.java
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/jssdk/JSSDKConfigurator.java
@@ -7,7 +7,7 @@ import java.util.Set;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.exception.WeixinException;
-import com.foxinmy.weixin4j.token.TokenHolder;
+import com.foxinmy.weixin4j.token.TokenManager;
import com.foxinmy.weixin4j.util.DateUtil;
import com.foxinmy.weixin4j.util.DigestUtil;
import com.foxinmy.weixin4j.util.MapUtil;
@@ -17,7 +17,7 @@ import com.foxinmy.weixin4j.util.Weixin4jConfigUtil;
/**
* JSSDK配置类
- *
+ *
* @className JSSDKConfigurator
* @author jinyu(foxinmy@gmail.com)
* @date 2015年12月23日
@@ -25,17 +25,17 @@ import com.foxinmy.weixin4j.util.Weixin4jConfigUtil;
* @see
*/
public class JSSDKConfigurator {
- private final TokenHolder ticketTokenHolder;
+ private final TokenManager ticketTokenManager;
private JSONObject config;
private Set apis;
/**
- * ticket保存类 可调用WeixinProxy#getTicketHolder获取
- *
- * @param ticketTokenHolder
+ * ticket保存类 可调用WeixinProxy#getTicketManager获取
+ *
+ * @param ticketTokenManager
*/
- public JSSDKConfigurator(TokenHolder ticketTokenHolder) {
- this.ticketTokenHolder = ticketTokenHolder;
+ public JSSDKConfigurator(TokenManager ticketTokenManager) {
+ this.ticketTokenManager = ticketTokenManager;
this.config = new JSONObject();
this.apis = new HashSet();
}
@@ -43,7 +43,7 @@ public class JSSDKConfigurator {
/**
* 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,
* 仅在pc端时才会打印。
- *
+ *
* @return
*/
public JSSDKConfigurator debugMode() {
@@ -53,7 +53,7 @@ public class JSSDKConfigurator {
/**
* 公众号的唯一标识 不填则获取weixin4j.properties#account中的id
- *
+ *
* @param appId
* @return
*/
@@ -64,7 +64,7 @@ public class JSSDKConfigurator {
/**
* 需要使用的JS接口列表
- *
+ *
* @see JSSDKAPI
* @param apis
* @return
@@ -78,7 +78,7 @@ public class JSSDKConfigurator {
/**
* 需要使用的JS接口列表
- *
+ *
* @see JSSDKAPI
* @param apis
* @return
@@ -94,7 +94,7 @@ public class JSSDKConfigurator {
/**
* 生成config配置JSON串
- *
+ *
* @param url
* 当前网页的URL,不包含#及其后面部分
* @return jssdk配置JSON字符串
@@ -113,7 +113,7 @@ public class JSSDKConfigurator {
String noncestr = RandomUtil.generateString(24);
signMap.put("timestamp", timestamp);
signMap.put("noncestr", noncestr);
- signMap.put("jsapi_ticket", this.ticketTokenHolder.getAccessToken());
+ signMap.put("jsapi_ticket", this.ticketTokenManager.getAccessToken());
signMap.put("url", url);
String sign = DigestUtil.SHA1(MapUtil.toJoinString(signMap, false,
false));
diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/Button.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/Button.java
index 1fcc3b75..441c1d16 100644
--- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/Button.java
+++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/Button.java
@@ -6,6 +6,7 @@ import java.util.Arrays;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
+import com.foxinmy.weixin4j.tuple.MpArticle;
import com.foxinmy.weixin4j.type.ButtonType;
/**
@@ -38,13 +39,20 @@ public class Button implements Serializable {
/**
* 菜单KEY值,根据type的类型而定
通过公众平台设置的自定义菜单: text:保存文字;
* img、voice:保存媒体ID; video:保存视频URL;
- * news:保存图文消息:List#com.foxinmy.weixin4j.tuple.MpArticle#; view:保存链接URL;
- * 使用API设置的自定义菜单:
+ * news:保存图文消息媒体ID view:保存链接URL;
+ *
+ * 使用API设置的自定义菜单:
+ *
* click、scancode_push、scancode_waitmsg、pic_sysphoto、pic_photo_or_album、
* pic_weixin、location_select:保存key; view:保存链接URL;
* media_id、view_limited:保存媒体ID
*/
- private Serializable content;
+ private String content;
+ /**
+ * 图文列表 只有在公众平台设置的菜单才有
+ */
+ @JSONField(serialize = false, deserialize = false)
+ private List articles;
/**
* 二级菜单数组,个数应为1~5个
*/
@@ -101,14 +109,27 @@ public class Button implements Serializable {
this.type = type;
}
- public Serializable getContent() {
+ public String getContent() {
return content;
}
- public void setContent(Serializable content) {
+ public void setContent(String content) {
this.content = content;
}
+ public List getArticles() {
+ return articles;
+ }
+
+ /**
+ * 创建菜单设置无效
+ *
+ * @param articles
+ */
+ public void setArticles(List articles) {
+ this.articles = articles;
+ }
+
public List