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