重构Cache实现
This commit is contained in:
@@ -42,7 +42,7 @@
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>2.6.0</version>
|
||||
<version>2.8.1</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+2
-2
@@ -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<T> {
|
||||
public interface CacheCreator<T extends Cacheable> {
|
||||
/**
|
||||
* CacheKey
|
||||
*
|
||||
@@ -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<T extends Cacheable> {
|
||||
protected final CacheCreator<T> cacheCreator;
|
||||
protected final CacheStorager<T> cacheStorager;
|
||||
|
||||
public CacheManager(CacheCreator<T> cacheCreator,
|
||||
CacheStorager<T> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有的缓存(<font color="red">请慎重</font>)
|
||||
*/
|
||||
public void clearCache() {
|
||||
cacheStorager.clear();
|
||||
}
|
||||
}
|
||||
+13
-5
@@ -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<T> {
|
||||
public interface CacheStorager<T extends Cacheable> {
|
||||
/**
|
||||
* 考虑到临界情况,实际缓存的有效时间减去该毫秒数(60秒)
|
||||
*/
|
||||
long CUTMS = 60 * 1000l;
|
||||
|
||||
/**
|
||||
* 所有的缓存KEY
|
||||
*/
|
||||
String ALLKEY = "weixin4j_cache_keys";
|
||||
|
||||
/**
|
||||
* 查找缓存中的对象
|
||||
*
|
||||
@@ -42,8 +52,6 @@ public interface CacheStorager<T> {
|
||||
/**
|
||||
* 清除所有缓存对象(<font color="red">请慎重</font>)
|
||||
*
|
||||
* @param prefix
|
||||
* 缓存key的前缀
|
||||
*/
|
||||
void clear(String prefix);
|
||||
void clear();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
+87
@@ -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<T extends Cacheable> implements CacheStorager<T> {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
-20
@@ -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<T extends Cacheable> implements
|
||||
CacheStorager<T> {
|
||||
|
||||
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<String>());
|
||||
}
|
||||
|
||||
@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<String> all = (Set<String>) 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<String> all = (Set<String>) 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<String> all = (Set<String>) 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;
|
||||
|
||||
+50
@@ -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<T extends Cacheable> implements
|
||||
CacheStorager<T> {
|
||||
|
||||
private final Map<String, T> CONMAP;
|
||||
|
||||
public MemoryCacheStorager() {
|
||||
this.CONMAP = new ConcurrentHashMap<String, T>();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
+128
@@ -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<T extends Cacheable> implements
|
||||
CacheStorager<T> {
|
||||
|
||||
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<String> cacheKeys = jedis.smembers(ALLKEY);
|
||||
if (!cacheKeys.isEmpty()) {
|
||||
cacheKeys.add(ALLKEY);
|
||||
jedis.del(cacheKeys.toArray(new String[cacheKeys.size()]));
|
||||
}
|
||||
} finally {
|
||||
if (jedis != null) {
|
||||
jedis.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -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<T extends Cacheable> implements
|
||||
CacheStorager<T> {
|
||||
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<HostAndPort> 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<HostAndPort> nodes,
|
||||
JedisPoolConfig poolConfig) {
|
||||
this(nodes, CONNECTION_TIMEOUT, SO_TIMEOUT, MAX_REDIRECTIONS,
|
||||
poolConfig);
|
||||
}
|
||||
|
||||
public RedisClusterCacheStorager(Set<HostAndPort> 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<String> cacheKeys = jedisCluster.smembers(ALLKEY);
|
||||
if (!cacheKeys.isEmpty()) {
|
||||
cacheKeys.add(ALLKEY);
|
||||
jedisCluster.del(cacheKeys.toArray(new String[cacheKeys.size()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1431,6 +1431,10 @@
|
||||
<code>81003</code>
|
||||
<text>邀请额度已用完</text>
|
||||
</error>
|
||||
<error>
|
||||
<code>81004</code>
|
||||
<text>部门数量超过上限</text>
|
||||
</error>
|
||||
<error>
|
||||
<code>82001</code>
|
||||
<text>发送消息或者邀请的参数全部为空或者全部不合法</text>
|
||||
|
||||
@@ -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<JSSDKAPI> 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<JSSDKAPI>();
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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的类型而定</p> 通过公众平台设置的自定义菜单:</br> <li>text:保存文字; <li>
|
||||
* img、voice:保存媒体ID; <li>video:保存视频URL; <li>
|
||||
* news:保存图文消息:List#com.foxinmy.weixin4j.tuple.MpArticle#; <li>view:保存链接URL;
|
||||
* <p>使用API设置的自定义菜单:</p> <li>
|
||||
* news:保存图文消息媒体ID <li>view:保存链接URL;
|
||||
* <p>
|
||||
* 使用API设置的自定义菜单:
|
||||
* </p> <li>
|
||||
* click、scancode_push、scancode_waitmsg、pic_sysphoto、pic_photo_or_album、
|
||||
* pic_weixin、location_select:保存key; <li>view:保存链接URL; <li>
|
||||
* media_id、view_limited:保存媒体ID
|
||||
*/
|
||||
private Serializable content;
|
||||
private String content;
|
||||
/**
|
||||
* 图文列表 只有在公众平台设置的菜单才有
|
||||
*/
|
||||
@JSONField(serialize = false, deserialize = false)
|
||||
private List<MpArticle> 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<MpArticle> getArticles() {
|
||||
return articles;
|
||||
}
|
||||
|
||||
/**
|
||||
* <font color="red">创建菜单设置无效</font>
|
||||
*
|
||||
* @param articles
|
||||
*/
|
||||
public void setArticles(List<MpArticle> articles) {
|
||||
this.articles = articles;
|
||||
}
|
||||
|
||||
public List<Button> getSubs() {
|
||||
return subs;
|
||||
}
|
||||
@@ -125,6 +146,6 @@ public class Button implements Serializable {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Button [name=" + name + ", type=" + type + ", content="
|
||||
+ content + ", subs=" + subs + "]";
|
||||
+ content + ", articles=" + articles + ", subs=" + subs + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 常量类
|
||||
*
|
||||
*
|
||||
* @className Consts
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2014年12月3日
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package com.foxinmy.weixin4j.model;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONCreator;
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.foxinmy.weixin4j.type.MediaType;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import com.foxinmy.weixin4j.type.MediaType;
|
||||
|
||||
/**
|
||||
* 媒体文件上传结果
|
||||
*
|
||||
*
|
||||
* @className MediaUploadResult
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2015年7月25日
|
||||
@@ -27,11 +25,8 @@ public class MediaUploadResult implements Serializable {
|
||||
*/
|
||||
private String url;
|
||||
|
||||
@JSONCreator
|
||||
public MediaUploadResult(@JSONField(name = "media_id") String mediaId,
|
||||
@JSONField(name = "type") MediaType mediaType,
|
||||
@JSONField(name = "created_at") Date createdAt,
|
||||
@JSONField(name = "url") String url) {
|
||||
public MediaUploadResult(String mediaId, MediaType mediaType,
|
||||
Date createdAt, String url) {
|
||||
this.mediaId = mediaId;
|
||||
this.mediaType = mediaType;
|
||||
this.createdAt = createdAt;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package com.foxinmy.weixin4j.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.foxinmy.weixin4j.cache.Cacheable;
|
||||
|
||||
/**
|
||||
* access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token,正常情况下access_token有效期为7200秒,
|
||||
* 重复获取将导致上次获取的access_token失效
|
||||
*
|
||||
*
|
||||
* @className Token
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2014年4月5日
|
||||
@@ -17,75 +18,95 @@ import com.alibaba.fastjson.annotation.JSONField;
|
||||
* @see <a
|
||||
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E4%B8%BB%E5%8A%A8%E8%B0%83%E7%94%A8">微信企业号的主动模式</a>
|
||||
*/
|
||||
public class Token implements Serializable {
|
||||
public class Token implements Cacheable {
|
||||
|
||||
private static final long serialVersionUID = -7564855472419104084L;
|
||||
|
||||
/**
|
||||
* 获取到的凭证
|
||||
*/
|
||||
@JSONField(name = "access_token")
|
||||
private String accessToken;
|
||||
/**
|
||||
* 凭证有效时间,单位:秒
|
||||
* 凭证有效时间,单位:毫秒
|
||||
*/
|
||||
@JSONField(name = "expires_in")
|
||||
private int expiresIn;
|
||||
private long expires;
|
||||
/**
|
||||
* token创建的时间,单位:毫秒
|
||||
*/
|
||||
@JSONField(name = "create_time")
|
||||
private long createTime;
|
||||
/**
|
||||
* 请求返回的原始结果
|
||||
* 扩展信息
|
||||
*/
|
||||
@JSONField(name = "original_result")
|
||||
private String originalResult;
|
||||
private Map<String, String> extra;
|
||||
|
||||
protected Token() {
|
||||
// jaxb required
|
||||
/**
|
||||
* 永不过期、创建时间为当前时间戳的token对象
|
||||
*
|
||||
* @param accessToken
|
||||
* 凭证字符串
|
||||
*/
|
||||
public Token(String accessToken) {
|
||||
this(accessToken, -1);
|
||||
}
|
||||
|
||||
public Token(String accessToken) {
|
||||
/**
|
||||
* 有过期时间、创建时间为当前时间戳的token对象
|
||||
*
|
||||
* @param accessToken
|
||||
* 凭证字符串
|
||||
* @param expires
|
||||
* 过期时间 单位毫秒
|
||||
*/
|
||||
public Token(String accessToken, long expires) {
|
||||
this(accessToken, expires, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param accessToken
|
||||
* 凭证字符串
|
||||
* @param expires
|
||||
* 过期时间 单位毫秒
|
||||
* @param createTime
|
||||
* 创建时间戳 单位毫秒
|
||||
*/
|
||||
public Token(String accessToken, long expires, long createTime) {
|
||||
this.accessToken = accessToken;
|
||||
this.createTime = System.currentTimeMillis();
|
||||
this.expires = expires;
|
||||
this.createTime = createTime;
|
||||
this.extra = new HashMap<String, String>();
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public int getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public void setExpiresIn(int expiresIn) {
|
||||
this.expiresIn = expiresIn;
|
||||
@Override
|
||||
public long getExpires() {
|
||||
return expires;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(long createTime) {
|
||||
this.createTime = createTime;
|
||||
public Map<String, String> getExtra() {
|
||||
return extra;
|
||||
}
|
||||
|
||||
public String getOriginalResult() {
|
||||
return originalResult;
|
||||
public void setExtra(Map<String, String> extra) {
|
||||
this.extra = extra;
|
||||
}
|
||||
|
||||
public void setOriginalResult(String originalResult) {
|
||||
this.originalResult = originalResult;
|
||||
public Token pushExtra(String name, String value) {
|
||||
this.extra.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Token [accessToken=" + accessToken + ", expiresIn=" + expiresIn
|
||||
+ ", createTime=" + createTime + "]";
|
||||
return "Token [accessToken=" + accessToken + ", expires=" + expires
|
||||
+ ", createTime=" + createTime + ", extra=" + extra + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package com.foxinmy.weixin4j.setting;
|
||||
|
||||
import com.foxinmy.weixin4j.http.HttpParams;
|
||||
import com.foxinmy.weixin4j.token.FileTokenStorager;
|
||||
import com.foxinmy.weixin4j.token.TokenStorager;
|
||||
|
||||
/**
|
||||
* 系统配置相关
|
||||
@@ -22,10 +20,6 @@ public abstract class SystemSettings<T> {
|
||||
* Http参数
|
||||
*/
|
||||
private HttpParams httpParams;
|
||||
/**
|
||||
* token存储方式 默认为FileTokenStorager
|
||||
*/
|
||||
private TokenStorager tokenStorager;
|
||||
/**
|
||||
* 系统临时目录
|
||||
*/
|
||||
@@ -59,17 +53,6 @@ public abstract class SystemSettings<T> {
|
||||
|
||||
public abstract String getTmpdir0();
|
||||
|
||||
public TokenStorager getTokenStorager() {
|
||||
return tokenStorager;
|
||||
}
|
||||
|
||||
public TokenStorager getTokenStorager0() {
|
||||
if (tokenStorager == null) {
|
||||
return new FileTokenStorager(getTmpdir0());
|
||||
}
|
||||
return tokenStorager;
|
||||
}
|
||||
|
||||
public void setHttpParams(HttpParams httpParams) {
|
||||
this.httpParams = httpParams;
|
||||
}
|
||||
@@ -78,13 +61,9 @@ public abstract class SystemSettings<T> {
|
||||
this.tmpdir = tmpdir;
|
||||
}
|
||||
|
||||
public void setTokenStorager(TokenStorager tokenStorager) {
|
||||
this.tokenStorager = tokenStorager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "account=" + account + ", httpParams=" + httpParams
|
||||
+ ",tokenStorager=" + tokenStorager + ", tmpdir=" + tmpdir;
|
||||
+ ", tmpdir=" + tmpdir;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.foxinmy.weixin4j.setting;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.foxinmy.weixin4j.cache.CacheStorager;
|
||||
import com.foxinmy.weixin4j.cache.FileCacheStorager;
|
||||
import com.foxinmy.weixin4j.model.Token;
|
||||
import com.foxinmy.weixin4j.model.WeixinAccount;
|
||||
import com.foxinmy.weixin4j.model.WeixinPayAccount;
|
||||
import com.foxinmy.weixin4j.util.StringUtil;
|
||||
@@ -20,6 +23,10 @@ public class Weixin4jSettings extends SystemSettings<WeixinAccount> {
|
||||
* 微信支付账号信息
|
||||
*/
|
||||
private WeixinPayAccount weixinPayAccount;
|
||||
/**
|
||||
* Token的存储方式 默认为FileCacheStorager
|
||||
*/
|
||||
private CacheStorager<Token> cacheStorager;
|
||||
/**
|
||||
* 支付接口需要的证书文件(*.p12)
|
||||
*/
|
||||
@@ -81,6 +88,21 @@ public class Weixin4jSettings extends SystemSettings<WeixinAccount> {
|
||||
return getTmpdir();
|
||||
}
|
||||
|
||||
public CacheStorager<Token> getCacheStorager() {
|
||||
return cacheStorager;
|
||||
}
|
||||
|
||||
public CacheStorager<Token> getCacheStorager0() {
|
||||
if (cacheStorager == null) {
|
||||
return new FileCacheStorager<Token>(getTmpdir0());
|
||||
}
|
||||
return cacheStorager;
|
||||
}
|
||||
|
||||
public void setCacheStorager(CacheStorager<Token> cacheStorager) {
|
||||
this.cacheStorager = cacheStorager;
|
||||
}
|
||||
|
||||
public String getCertificateFile() {
|
||||
return certificateFile;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.foxinmy.weixin4j.util.MapUtil;
|
||||
|
||||
/**
|
||||
* 微信签名
|
||||
*
|
||||
*
|
||||
* @className AbstractWeixinSignature
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2016年3月26日
|
||||
@@ -14,25 +14,27 @@ import com.foxinmy.weixin4j.util.MapUtil;
|
||||
public abstract class AbstractWeixinSignature implements WeixinSignature {
|
||||
/**
|
||||
* 是否编码
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @return 默认false不进行编码
|
||||
*/
|
||||
@Override
|
||||
public boolean encoder() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否转换小写
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @return 默认false不转换小写
|
||||
*/
|
||||
@Override
|
||||
public boolean lowerCase() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接字符串
|
||||
*
|
||||
*
|
||||
* @param obj
|
||||
* @return
|
||||
*/
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
package com.foxinmy.weixin4j.token;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.foxinmy.weixin4j.model.Token;
|
||||
import com.foxinmy.weixin4j.util.FileUtil;
|
||||
import com.foxinmy.weixin4j.xml.XmlStream;
|
||||
|
||||
/**
|
||||
* 用File形式保存Token信息
|
||||
*
|
||||
* @className FileTokenStorager
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2015年1月9日
|
||||
* @since JDK 1.6
|
||||
*/
|
||||
public class FileTokenStorager extends TokenStorager {
|
||||
|
||||
private final String cachePath;
|
||||
|
||||
public FileTokenStorager(String cachePath) {
|
||||
this.cachePath = cachePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token lookup(String cacheKey) {
|
||||
File token_file = new File(String.format("%s/%s.xml", cachePath,
|
||||
cacheKey));
|
||||
try {
|
||||
if (token_file.exists()) {
|
||||
Token token = XmlStream.fromXML(
|
||||
new FileInputStream(token_file), Token.class);
|
||||
if (token.getCreateTime() < 0) {
|
||||
return token;
|
||||
}
|
||||
if ((token.getCreateTime() + (token.getExpiresIn() * 1000l) - ms()) > System
|
||||
.currentTimeMillis()) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void caching(String cacheKey, Token token) {
|
||||
try {
|
||||
XmlStream.toXML(
|
||||
token,
|
||||
new FileOutputStream(new File(String.format("%s/%s.xml",
|
||||
cachePath, cacheKey))));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token evict(String cacheKey) {
|
||||
Token token = null;
|
||||
File token_file = new File(String.format("%s/%s.xml", cachePath,
|
||||
cacheKey));
|
||||
try {
|
||||
if (token_file.exists()) {
|
||||
token = XmlStream.fromXML(new FileInputStream(token_file),
|
||||
Token.class);
|
||||
token_file.delete();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
; // ingore
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(final String prefix) {
|
||||
File[] files = new File(cachePath).listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
return file.isFile()
|
||||
&& file.getName().startsWith(prefix)
|
||||
&& "xml".equals(FileUtil.getFileExtension(file
|
||||
.getName()));
|
||||
}
|
||||
});
|
||||
for (File token : files) {
|
||||
token.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.foxinmy.weixin4j.token;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.foxinmy.weixin4j.model.Token;
|
||||
|
||||
/**
|
||||
* 用内存保存Token信息(不推荐使用)
|
||||
*
|
||||
* @className MemoryTokenStorager
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2016年1月24日
|
||||
* @since JDK 1.6
|
||||
* @see
|
||||
*/
|
||||
public class MemoryTokenStorager extends TokenStorager {
|
||||
|
||||
private final Map<String, Token> CONMAP;
|
||||
|
||||
public MemoryTokenStorager() {
|
||||
this.CONMAP = new ConcurrentHashMap<String, Token>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token lookup(String cacheKey) {
|
||||
Token token = this.CONMAP.get(cacheKey);
|
||||
if (token != null) {
|
||||
if ((token.getCreateTime() + (token.getExpiresIn() * 1000l) - ms()) > System
|
||||
.currentTimeMillis()) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void caching(String cacheKey, Token token) {
|
||||
this.CONMAP.put(cacheKey, token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token evict(String cacheKey) {
|
||||
return this.CONMAP.remove(cacheKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(String prefix) {
|
||||
this.CONMAP.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
### TOKEN的实现
|
||||
|
||||
* TokenCreator 负责创建新的token
|
||||
|
||||
* TokenStorager 负责查找已缓存的token或者缓存新的token
|
||||
|
||||
* TokenHolder 负责获取token(屏蔽了获取细节)
|
||||
|
||||
* FileTokenStorager 是系统默认的token存储策略实现
|
||||
|
||||
* RedisTokenStorager 使用redis保存token(需要自行添加客户端包,[jedis](https://github.com/xetorthio/jedis))
|
||||
|
||||
* MemcacheTokenStorager 使用memcache保存token(需要自行添加客户端包,[Memcached-Java-Client](https://github.com/gwhalin/Memcached-Java-Client))
|
||||
@@ -1,147 +0,0 @@
|
||||
package com.foxinmy.weixin4j.token;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
|
||||
import com.foxinmy.weixin4j.model.Token;
|
||||
|
||||
/**
|
||||
* 用Redis保存Token信息(推荐使用)
|
||||
*
|
||||
* @className RedisTokenStorager
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2015年1月9日
|
||||
* @since JDK 1.6
|
||||
*/
|
||||
public class RedisTokenStorager extends TokenStorager {
|
||||
|
||||
private JedisPool jedisPool;
|
||||
|
||||
public final static String HOST = "localhost";
|
||||
public final static int PORT = 6379;
|
||||
public final static int MAX_TOTAL = 50;
|
||||
public final static int MAX_IDLE = 5;
|
||||
public final static int MAX_WAIT_MILLIS = 3000;
|
||||
public final static boolean TEST_ON_BORROW = false;
|
||||
public final static boolean TEST_ON_RETURN = true;
|
||||
|
||||
public RedisTokenStorager() {
|
||||
this(HOST, PORT);
|
||||
}
|
||||
|
||||
public RedisTokenStorager(String host, int port) {
|
||||
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);
|
||||
}
|
||||
|
||||
public RedisTokenStorager(String host, int port,
|
||||
JedisPoolConfig jedisPoolConfig) {
|
||||
this(new JedisPool(jedisPoolConfig, host, port));
|
||||
}
|
||||
|
||||
public RedisTokenStorager(JedisPool jedisPool) {
|
||||
this.jedisPool = jedisPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token lookup(String cacheKey) {
|
||||
Jedis jedis = null;
|
||||
try {
|
||||
jedis = jedisPool.getResource();
|
||||
Map<String, String> map = jedis.hgetAll(cacheKey);
|
||||
if (map != null && !map.isEmpty()) {
|
||||
return map2token(map);
|
||||
}
|
||||
} finally {
|
||||
if (jedis != null) {
|
||||
jedis.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void caching(String cacheKey, Token token) {
|
||||
Jedis jedis = null;
|
||||
try {
|
||||
jedis = jedisPool.getResource();
|
||||
jedis.hmset(cacheKey, token2map(token));
|
||||
if (token.getExpiresIn() > 0) {
|
||||
jedis.expire(cacheKey, token.getExpiresIn()
|
||||
- (int) (ms() / 1000l));
|
||||
}
|
||||
} finally {
|
||||
if (jedis != null) {
|
||||
jedis.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final static String ACCESSTOKEN_KEY = "accessToken";
|
||||
private final static String EXPIRESIN_KEY = "expiresIn";
|
||||
private final static String CREATETIME_KEY = "createTime";
|
||||
private final static String ORIGINAL_KEY = "originalResult";
|
||||
|
||||
protected Map<String, String> token2map(Token token) {
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put(ACCESSTOKEN_KEY, token.getAccessToken());
|
||||
map.put(EXPIRESIN_KEY, Integer.toString(token.getExpiresIn()));
|
||||
map.put(CREATETIME_KEY, Long.toString(token.getCreateTime()));
|
||||
map.put(ORIGINAL_KEY, token.getOriginalResult());
|
||||
return map;
|
||||
}
|
||||
|
||||
protected Token map2token(Map<String, String> map) {
|
||||
Token token = new Token(map.get(ACCESSTOKEN_KEY));
|
||||
token.setExpiresIn(Integer.parseInt(map.get(EXPIRESIN_KEY)));
|
||||
token.setCreateTime(Long.parseLong(map.get(CREATETIME_KEY)));
|
||||
token.setOriginalResult(map.get(ORIGINAL_KEY));
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token evict(String cacheKey) {
|
||||
Token token = lookup(cacheKey);
|
||||
Jedis jedis = null;
|
||||
try {
|
||||
jedis = jedisPool.getResource();
|
||||
jedis.del(cacheKey);
|
||||
} finally {
|
||||
if (jedis != null) {
|
||||
jedis.close();
|
||||
}
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(String prefix) {
|
||||
Jedis jedis = null;
|
||||
try {
|
||||
jedis = jedisPool.getResource();
|
||||
Set<String> cacheKeys = jedis.keys(String.format("%s*", prefix));
|
||||
if (!cacheKeys.isEmpty()) {
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
for (String cacheKey : cacheKeys) {
|
||||
pipeline.del(cacheKey);
|
||||
}
|
||||
pipeline.sync();
|
||||
}
|
||||
} finally {
|
||||
if (jedis != null) {
|
||||
jedis.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.foxinmy.weixin4j.token;
|
||||
|
||||
import com.foxinmy.weixin4j.cache.CacheCreator;
|
||||
import com.foxinmy.weixin4j.http.weixin.WeixinRequestExecutor;
|
||||
import com.foxinmy.weixin4j.model.Token;
|
||||
|
||||
@@ -14,21 +15,17 @@ import com.foxinmy.weixin4j.model.Token;
|
||||
*/
|
||||
public abstract class TokenCreator implements CacheCreator<Token> {
|
||||
|
||||
/**
|
||||
* 缓存KEY前缀
|
||||
*/
|
||||
public final static String CACHEKEY_PREFIX = "weixin4j_";
|
||||
|
||||
protected final WeixinRequestExecutor weixinExecutor;
|
||||
|
||||
public TokenCreator() {
|
||||
this.weixinExecutor = new WeixinRequestExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存key的前缀
|
||||
*
|
||||
* @return 默认为weixin4j_
|
||||
*/
|
||||
public String prefix() {
|
||||
return "weixin4j_";
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存key:附加key前缀
|
||||
*
|
||||
@@ -36,7 +33,7 @@ public abstract class TokenCreator implements CacheCreator<Token> {
|
||||
*/
|
||||
@Override
|
||||
public String key() {
|
||||
return String.format("%s%s", prefix(), key0());
|
||||
return String.format("%s%s", CACHEKEY_PREFIX, key0());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
package com.foxinmy.weixin4j.token;
|
||||
|
||||
import com.foxinmy.weixin4j.exception.WeixinException;
|
||||
import com.foxinmy.weixin4j.model.Token;
|
||||
|
||||
/**
|
||||
* 对token的缓存获取
|
||||
*
|
||||
* @className TokenHolder
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2015年6月12日
|
||||
* @since JDK 1.6
|
||||
* @see TokenCreator
|
||||
* @see TokenStorager
|
||||
*/
|
||||
public class TokenHolder {
|
||||
|
||||
/**
|
||||
* token的创建
|
||||
*/
|
||||
private final TokenCreator tokenCreator;
|
||||
/**
|
||||
* token的存储
|
||||
*/
|
||||
private final TokenStorager tokenStorager;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param tokenCreator
|
||||
* token创建器
|
||||
* @param tokenStorager
|
||||
* token保存器
|
||||
*/
|
||||
public TokenHolder(TokenCreator tokenCreator, TokenStorager tokenStorager) {
|
||||
this.tokenCreator = tokenCreator;
|
||||
this.tokenStorager = tokenStorager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token对象
|
||||
*
|
||||
* @return
|
||||
* @throws WeixinException
|
||||
*/
|
||||
public Token getToken() throws WeixinException {
|
||||
String cacheKey = tokenCreator.key();
|
||||
Token token = tokenStorager.lookup(cacheKey);
|
||||
if (token == null) {
|
||||
token = tokenCreator.create();
|
||||
tokenStorager.caching(cacheKey, token);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token字符串
|
||||
*
|
||||
* @return
|
||||
* @throws WeixinException
|
||||
*/
|
||||
public String getAccessToken() throws WeixinException {
|
||||
return getToken().getAccessToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动刷新token
|
||||
*
|
||||
* @return 刷新后的token
|
||||
* @throws WeixinException
|
||||
*/
|
||||
public Token refreshToken() throws WeixinException {
|
||||
String cacheKey = tokenCreator.key();
|
||||
Token token = tokenCreator.create();
|
||||
tokenStorager.caching(cacheKey, token);
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除token
|
||||
*
|
||||
* @return 被移除的token
|
||||
*/
|
||||
public Token evictToken() {
|
||||
String cacheKey = tokenCreator.key();
|
||||
return tokenStorager.evict(cacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有的token(<font color="red">请慎重</font>)
|
||||
*/
|
||||
public void clearToken() {
|
||||
String prefix = tokenCreator.prefix();
|
||||
tokenStorager.clear(prefix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.foxinmy.weixin4j.token;
|
||||
|
||||
import com.foxinmy.weixin4j.cache.CacheManager;
|
||||
import com.foxinmy.weixin4j.cache.CacheStorager;
|
||||
import com.foxinmy.weixin4j.exception.WeixinException;
|
||||
import com.foxinmy.weixin4j.model.Token;
|
||||
|
||||
/**
|
||||
* 对token的缓存获取
|
||||
*
|
||||
* @className TokenManager
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2015年6月12日
|
||||
* @since JDK 1.6
|
||||
* @see TokenCreator
|
||||
* @see com.foxinmy.weixin4j.cache.CacheStorager
|
||||
*/
|
||||
public class TokenManager extends CacheManager<Token> {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param tokenCreator
|
||||
* 负责微信各种token的创建
|
||||
* @param cacheStorager
|
||||
* 负责token的存储
|
||||
*/
|
||||
public TokenManager(TokenCreator tokenCreator,
|
||||
CacheStorager<Token> cacheStorager) {
|
||||
super(tokenCreator, cacheStorager);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token字符串
|
||||
*
|
||||
* @return
|
||||
* @throws WeixinException
|
||||
*/
|
||||
public String getAccessToken() throws WeixinException {
|
||||
return super.getCache().getAccessToken();
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.foxinmy.weixin4j.token;
|
||||
|
||||
import com.foxinmy.weixin4j.model.Token;
|
||||
|
||||
/**
|
||||
* Token的存储
|
||||
*
|
||||
* @className TokenStorager
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2014年9月27日
|
||||
* @since JDK 1.6
|
||||
* @see com.foxinmy.weixin4j.model.Token
|
||||
* @see MemoryTokenStorager
|
||||
* @see FileTokenStorager
|
||||
* @see RedisTokenStorager
|
||||
* @see MemcacheTokenStorager
|
||||
*/
|
||||
public abstract class TokenStorager implements CacheStorager<Token> {
|
||||
/**
|
||||
* 考虑到临界情况,实际token的有效时间减去该毫秒数
|
||||
*
|
||||
* @return 默认为60秒
|
||||
*/
|
||||
public long ms() {
|
||||
return 60 * 1000l;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import com.alibaba.fastjson.annotation.JSONField;
|
||||
|
||||
/**
|
||||
* 客服消息图文
|
||||
*
|
||||
*
|
||||
* @className Article
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2014年9月29日
|
||||
@@ -43,6 +43,17 @@ public class Article implements Serializable {
|
||||
@XmlElement(name = "Url")
|
||||
private String url;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param title
|
||||
* 标题
|
||||
* @param desc
|
||||
* 描述
|
||||
* @param picUrl
|
||||
* 图片链接
|
||||
* @param url
|
||||
* 跳转URL
|
||||
*/
|
||||
@JSONCreator
|
||||
public Article(@JSONField(name = "title") String title,
|
||||
@JSONField(name = "desc") String desc,
|
||||
|
||||
@@ -2,12 +2,12 @@ package com.foxinmy.weixin4j.tuple;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONCreator;
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
|
||||
/**
|
||||
* 群发消息图文(消息内容存储在微信后台)
|
||||
*
|
||||
* @className MpArticle
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2014年4月26日
|
||||
* @since JDK 1.6
|
||||
@@ -22,7 +22,7 @@ public class MpArticle implements Serializable {
|
||||
@JSONField(name = "thumb_media_id")
|
||||
private String thumbMediaId;
|
||||
/**
|
||||
* 图文消息的封面图片的地址,第三方开发者也可以使用这个URL下载图片到自己服务器中,然后显示在自己网站上
|
||||
* 图文消息的封面图片的地址(不一定有,请关注thumbMediaId)
|
||||
*/
|
||||
@JSONField(name = "thumb_url")
|
||||
private String thumbUrl;
|
||||
@@ -34,6 +34,10 @@ public class MpArticle implements Serializable {
|
||||
* 图文消息的标题 非空
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* 图文页的URL 获取图文消息时
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* 在图文消息页面点击“阅读原文”后的页面 可为空
|
||||
*/
|
||||
@@ -52,50 +56,40 @@ public class MpArticle implements Serializable {
|
||||
*/
|
||||
@JSONField(name = "show_cover_pic")
|
||||
private String showCoverPic;
|
||||
/**
|
||||
* 正文的URL 可为空
|
||||
*/
|
||||
@JSONField(name = "content_url")
|
||||
private String contentUrl;
|
||||
/**
|
||||
* 封面图片的URL 可为空
|
||||
*/
|
||||
@JSONField(name = "cover_url")
|
||||
private String coverUrl;
|
||||
|
||||
protected MpArticle() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param thumbMediaId
|
||||
* 缩略图
|
||||
* @param title
|
||||
* 标题
|
||||
* @param content
|
||||
* 内容
|
||||
*/
|
||||
public MpArticle(String thumbMediaId, String title, String content) {
|
||||
this.thumbMediaId = thumbMediaId;
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@JSONCreator
|
||||
public MpArticle(@JSONField(name = "thumbMediaId") String thumbMediaId,
|
||||
@JSONField(name = "thumbUrl") String thumbUrl,
|
||||
@JSONField(name = "author") String author,
|
||||
@JSONField(name = "title") String title,
|
||||
@JSONField(name = "sourceUrl") String sourceUrl,
|
||||
@JSONField(name = "content") String content,
|
||||
@JSONField(name = "digest") String digest,
|
||||
@JSONField(name = "showCoverPic") String showCoverPic,
|
||||
@JSONField(name = "contentUrl") String contentUrl,
|
||||
@JSONField(name = "coverUrl") String coverUrl) {
|
||||
this.thumbMediaId = thumbMediaId;
|
||||
this.thumbUrl = thumbUrl;
|
||||
this.author = author;
|
||||
this.title = title;
|
||||
this.sourceUrl = sourceUrl;
|
||||
this.content = content;
|
||||
this.digest = digest;
|
||||
this.showCoverPic = showCoverPic;
|
||||
this.contentUrl = contentUrl;
|
||||
this.coverUrl = coverUrl;
|
||||
}
|
||||
|
||||
public String getThumbMediaId() {
|
||||
return thumbMediaId;
|
||||
}
|
||||
|
||||
public void setThumbMediaId(String thumbMediaId) {
|
||||
this.thumbMediaId = thumbMediaId;
|
||||
}
|
||||
|
||||
public String getThumbUrl() {
|
||||
return thumbUrl;
|
||||
}
|
||||
|
||||
public void setThumbUrl(String thumbUrl) {
|
||||
this.thumbUrl = thumbUrl;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
@@ -108,6 +102,18 @@ public class MpArticle implements Serializable {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getSourceUrl() {
|
||||
return sourceUrl;
|
||||
}
|
||||
@@ -120,6 +126,10 @@ public class MpArticle implements Serializable {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getDigest() {
|
||||
return digest;
|
||||
}
|
||||
@@ -132,36 +142,25 @@ public class MpArticle implements Serializable {
|
||||
return showCoverPic;
|
||||
}
|
||||
|
||||
public void setShowCoverPic(String showCoverPic) {
|
||||
this.showCoverPic = showCoverPic;
|
||||
}
|
||||
|
||||
public void setShowCoverPic(boolean showCoverPic) {
|
||||
this.showCoverPic = showCoverPic ? "1" : "0";
|
||||
}
|
||||
|
||||
public String getContentUrl() {
|
||||
return contentUrl;
|
||||
}
|
||||
|
||||
public void setContentUrl(String contentUrl) {
|
||||
this.contentUrl = contentUrl;
|
||||
}
|
||||
|
||||
public String getCoverUrl() {
|
||||
return coverUrl;
|
||||
}
|
||||
|
||||
public void setCoverUrl(String coverUrl) {
|
||||
this.coverUrl = coverUrl;
|
||||
}
|
||||
|
||||
public String getThumbUrl() {
|
||||
return thumbUrl;
|
||||
@JSONField(serialize = false)
|
||||
public boolean getFormatShowCoverPic() {
|
||||
return this.showCoverPic != null && this.showCoverPic.equals("1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MpArticle [thumbMediaId=" + thumbMediaId + ",thumbUrl="
|
||||
return "MpArticle [thumbMediaId=" + thumbMediaId + ", thumbUrl="
|
||||
+ thumbUrl + ", author=" + author + ", title=" + title
|
||||
+ ", sourceUrl=" + sourceUrl + ", content=" + content
|
||||
+ ", digest=" + digest + ", showCoverPic=" + showCoverPic
|
||||
+ ", contentUrl=" + contentUrl + ", coverUrl=" + coverUrl + "]";
|
||||
+ ", url=" + url + ", digest=" + digest + ", showCoverPic="
|
||||
+ showCoverPic + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
package com.foxinmy.weixin4j.tuple;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONCreator;
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
|
||||
/**
|
||||
* 图文对象(mpnews消息与news消息类似,不同的是图文消息内容存储在微信后台,并且支持保密选项。每个应用每天最多可以发送100次)
|
||||
* <p>
|
||||
* <font color="red">可用于「群发消息(其中mediaId与articles请至少保持一个有值)」「企业号的客服消息」</font>
|
||||
* <font color="red">可用于「公众平台的群发消息」「客服消息」</font>
|
||||
* </p>
|
||||
*
|
||||
* <li>当用于发送公众平台的群发消息和客服消息时:其中mediaId与articles请至少保持一个有值 <li>
|
||||
* 当用于发送企业号的客服消息时:其中articles必须有值
|
||||
*
|
||||
* @className MpNews
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2014年9月29日
|
||||
@@ -47,16 +49,35 @@ public class MpNews implements MassTuple, NotifyTuple {
|
||||
@XmlTransient
|
||||
private LinkedList<MpArticle> articles;
|
||||
|
||||
public MpNews() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
@JSONCreator
|
||||
public MpNews(@JSONField(name = "mediaId") String mediaId) {
|
||||
/**
|
||||
* 群发消息、客服消息 预先上传List#MpArticle得到mediaId
|
||||
*
|
||||
* @param mediaId
|
||||
* 群发素材的媒体ID
|
||||
*/
|
||||
public MpNews(String mediaId) {
|
||||
this.mediaId = mediaId;
|
||||
this.articles = new LinkedList<MpArticle>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 群发消息 自动上传List#MpArticle得到mediaId
|
||||
*
|
||||
* @param articles
|
||||
* 文章列表
|
||||
*/
|
||||
public MpNews(MpArticle... articles) {
|
||||
this.articles = new LinkedList<MpArticle>(Arrays.asList(articles));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param thumbMediaId
|
||||
* 缩略图
|
||||
* @param title
|
||||
* 标题
|
||||
* @param content
|
||||
* 内容
|
||||
*/
|
||||
public MpNews addArticle(String thumbMediaId, String title, String content) {
|
||||
return addArticle(new MpArticle(thumbMediaId, title, content));
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.alibaba.fastjson.annotation.JSONField;
|
||||
* <p>
|
||||
* <font color="red">可用于「客服消息」</font>
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @className Music
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2014年9月29日
|
||||
@@ -55,10 +55,32 @@ public class Music implements NotifyTuple {
|
||||
@XmlElement(name = "ThumbMediaId")
|
||||
private String thumbMediaId;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param musicUrl
|
||||
* 音乐链接
|
||||
* @param hqMusicUrl
|
||||
* 高品质音乐链接
|
||||
* @param thumbMediaId
|
||||
* 缩略图
|
||||
*/
|
||||
public Music(String musicUrl, String hqMusicUrl, String thumbMediaId) {
|
||||
this(null, null, musicUrl, hqMusicUrl, thumbMediaId);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param title
|
||||
* 标题
|
||||
* @param desc
|
||||
* 描述
|
||||
* @param musicUrl
|
||||
* 音乐链接
|
||||
* @param hqMusicUrl
|
||||
* 高品质音乐链接
|
||||
* @param thumbMediaId
|
||||
* 缩略图
|
||||
*/
|
||||
public Music(@JSONField(name = "title") String title,
|
||||
@JSONField(name = "desc") String desc,
|
||||
@JSONField(name = "musicUrl") String musicUrl,
|
||||
|
||||
@@ -13,7 +13,7 @@ import com.alibaba.fastjson.annotation.JSONField;
|
||||
* <p>
|
||||
* <font color="red">可用于「客服消息」</font>
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @className News
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2014年11月21日
|
||||
@@ -36,7 +36,7 @@ public class News implements NotifyTuple {
|
||||
|
||||
/**
|
||||
* 图文列表
|
||||
*
|
||||
*
|
||||
* @see com.foxinmy.weixin4j.tuple.Article
|
||||
*/
|
||||
@JSONField(name = "articles")
|
||||
@@ -47,6 +47,17 @@ public class News implements NotifyTuple {
|
||||
this.articles = new LinkedList<Article>();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param title
|
||||
* 标题
|
||||
* @param desc
|
||||
* 描述
|
||||
* @param picUrl
|
||||
* 图片链接
|
||||
* @param url
|
||||
* 跳转URL
|
||||
*/
|
||||
public News addArticle(String title, String desc, String picUrl, String url) {
|
||||
return addArticle(new Article(title, desc, picUrl, url));
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.alibaba.fastjson.annotation.JSONField;
|
||||
* <p>
|
||||
* <font color="red">可用于「客服消息」</font>
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @className Video
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2014年9月29日
|
||||
@@ -53,7 +53,7 @@ public class Video implements NotifyTuple {
|
||||
|
||||
/**
|
||||
* 企业号的视频消息不需要缩略图
|
||||
*
|
||||
*
|
||||
* @param mediaId
|
||||
* 视频媒体文件id,可以调用上传临时素材或者永久素材接口获取
|
||||
* @param title
|
||||
@@ -65,6 +65,18 @@ public class Video implements NotifyTuple {
|
||||
this(mediaId, null, title, desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 公众平台发送视频消息
|
||||
*
|
||||
* @param mediaId
|
||||
* 视频媒体文件id,可以调用上传临时素材或者永久素材接口获取
|
||||
* @param thumbMediaId
|
||||
* 视频缩略图
|
||||
* @param title
|
||||
* 视频标题
|
||||
* @param desc
|
||||
* 视频描述
|
||||
*/
|
||||
@JSONCreator
|
||||
public Video(@JSONField(name = "mediaId") String mediaId,
|
||||
@JSONField(name = "thumbMediaId") String thumbMediaId,
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.foxinmy.weixin4j.util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.ObjectStreamClass;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>Assists with the serialization process and performs additional functionality based
|
||||
* on serialization.</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>Deep clone using serialization
|
||||
* <li>Serialize managing finally and IOException
|
||||
* <li>Deserialize managing finally and IOException
|
||||
* </ul>
|
||||
*
|
||||
* <p>This class throws exceptions for invalid {@code null} inputs.
|
||||
* Each method documents its behaviour in more detail.</p>
|
||||
*
|
||||
* <p>#ThreadSafe#</p>
|
||||
* @since 1.0
|
||||
* @version $Id: SerializationUtils.java 1583482 2014-03-31 22:54:57Z niallp $
|
||||
*/
|
||||
public class SerializationUtils {
|
||||
|
||||
/**
|
||||
* <p>SerializationUtils instances should NOT be constructed in standard programming.
|
||||
* Instead, the class should be used as {@code SerializationUtils.clone(object)}.</p>
|
||||
*
|
||||
* <p>This constructor is public to permit tools that require a JavaBean instance
|
||||
* to operate.</p>
|
||||
* @since 2.0
|
||||
*/
|
||||
public SerializationUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
// Clone
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Deep clone an {@code Object} using serialization.</p>
|
||||
*
|
||||
* <p>This is many times slower than writing clone methods by hand
|
||||
* on all objects in your object graph. However, for complex object
|
||||
* graphs, or for those that don't support deep cloning this can
|
||||
* be a simple alternative implementation. Of course all the objects
|
||||
* must be {@code Serializable}.</p>
|
||||
*
|
||||
* @param <T> the type of the object involved
|
||||
* @param object the {@code Serializable} object to clone
|
||||
* @return the cloned object
|
||||
* @throws SerializationException (runtime) if the serialization fails
|
||||
*/
|
||||
public static <T extends Serializable> T clone(final T object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
final byte[] objectData = serialize(object);
|
||||
final ByteArrayInputStream bais = new ByteArrayInputStream(objectData);
|
||||
|
||||
ClassLoaderAwareObjectInputStream in = null;
|
||||
try {
|
||||
// stream closed in the finally
|
||||
in = new ClassLoaderAwareObjectInputStream(bais, object.getClass().getClassLoader());
|
||||
/*
|
||||
* when we serialize and deserialize an object,
|
||||
* it is reasonable to assume the deserialized object
|
||||
* is of the same type as the original serialized object
|
||||
*/
|
||||
@SuppressWarnings("unchecked") // see above
|
||||
final
|
||||
T readObject = (T) in.readObject();
|
||||
return readObject;
|
||||
|
||||
} catch (final ClassNotFoundException ex) {
|
||||
throw new RuntimeException("ClassNotFoundException while reading cloned object data", ex);
|
||||
} catch (final IOException ex) {
|
||||
throw new RuntimeException("IOException while reading cloned object data", ex);
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
} catch (final IOException ex) {
|
||||
throw new RuntimeException("IOException on closing cloned object data InputStream.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a serialization roundtrip. Serializes and deserializes the given object, great for testing objects that
|
||||
* implement {@link Serializable}.
|
||||
*
|
||||
* @param <T>
|
||||
* the type of the object involved
|
||||
* @param msg
|
||||
* the object to roundtrip
|
||||
* @return the serialized and deseralized object
|
||||
* @since 3.3
|
||||
*/
|
||||
public static <T extends Serializable> T roundtrip(final T msg) {
|
||||
return SerializationUtils.deserialize(SerializationUtils.serialize(msg));
|
||||
}
|
||||
|
||||
// Serialize
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Serializes an {@code Object} to the specified stream.</p>
|
||||
*
|
||||
* <p>The stream will be closed once the object is written.
|
||||
* This avoids the need for a finally clause, and maybe also exception
|
||||
* handling, in the application code.</p>
|
||||
*
|
||||
* <p>The stream passed in is not buffered internally within this method.
|
||||
* This is the responsibility of your application if desired.</p>
|
||||
*
|
||||
* @param obj the object to serialize to bytes, may be null
|
||||
* @param outputStream the stream to write to, must not be null
|
||||
* @throws IllegalArgumentException if {@code outputStream} is {@code null}
|
||||
* @throws SerializationException (runtime) if the serialization fails
|
||||
*/
|
||||
public static void serialize(final Serializable obj, final OutputStream outputStream) {
|
||||
if (outputStream == null) {
|
||||
throw new IllegalArgumentException("The OutputStream must not be null");
|
||||
}
|
||||
ObjectOutputStream out = null;
|
||||
try {
|
||||
// stream closed in the finally
|
||||
out = new ObjectOutputStream(outputStream);
|
||||
out.writeObject(obj);
|
||||
|
||||
} catch (final IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
} finally {
|
||||
try {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
} catch (final IOException ex) { // NOPMD
|
||||
// ignore close exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Serializes an {@code Object} to a byte array for
|
||||
* storage/serialization.</p>
|
||||
*
|
||||
* @param obj the object to serialize to bytes
|
||||
* @return a byte[] with the converted Serializable
|
||||
* @throws SerializationException (runtime) if the serialization fails
|
||||
*/
|
||||
public static byte[] serialize(final Serializable obj) {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
serialize(obj, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
// Deserialize
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>
|
||||
* Deserializes an {@code Object} from the specified stream.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The stream will be closed once the object is written. This avoids the need for a finally clause, and maybe also
|
||||
* exception handling, in the application code.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The stream passed in is not buffered internally within this method. This is the responsibility of your
|
||||
* application if desired.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site.
|
||||
* Without Generics in this declaration, the call site must type cast and can cause the same ClassCastException.
|
||||
* Note that in both cases, the ClassCastException is in the call site, not in this method.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the object type to be deserialized
|
||||
* @param inputStream
|
||||
* the serialized object input stream, must not be null
|
||||
* @return the deserialized object
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code inputStream} is {@code null}
|
||||
* @throws SerializationException
|
||||
* (runtime) if the serialization fails
|
||||
*/
|
||||
public static <T> T deserialize(final InputStream inputStream) {
|
||||
if (inputStream == null) {
|
||||
throw new IllegalArgumentException("The InputStream must not be null");
|
||||
}
|
||||
ObjectInputStream in = null;
|
||||
try {
|
||||
// stream closed in the finally
|
||||
in = new ObjectInputStream(inputStream);
|
||||
@SuppressWarnings("unchecked") // may fail with CCE if serialised form is incorrect
|
||||
final T obj = (T) in.readObject();
|
||||
return obj;
|
||||
|
||||
} catch (final ClassCastException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
} catch (final ClassNotFoundException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
} catch (final IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
} catch (final IOException ex) { // NOPMD
|
||||
// ignore close exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Deserializes a single {@code Object} from an array of bytes.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site.
|
||||
* Without Generics in this declaration, the call site must type cast and can cause the same ClassCastException.
|
||||
* Note that in both cases, the ClassCastException is in the call site, not in this method.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the object type to be deserialized
|
||||
* @param objectData
|
||||
* the serialized object, must not be null
|
||||
* @return the deserialized object
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code objectData} is {@code null}
|
||||
* @throws SerializationException
|
||||
* (runtime) if the serialization fails
|
||||
*/
|
||||
public static <T> T deserialize(final byte[] objectData) {
|
||||
if (objectData == null) {
|
||||
throw new IllegalArgumentException("The byte[] must not be null");
|
||||
}
|
||||
return SerializationUtils.<T>deserialize(new ByteArrayInputStream(objectData));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Custom specialization of the standard JDK {@link java.io.ObjectInputStream}
|
||||
* that uses a custom <code>ClassLoader</code> to resolve a class.
|
||||
* If the specified <code>ClassLoader</code> is not able to resolve the class,
|
||||
* the context classloader of the current thread will be used.
|
||||
* This way, the standard deserialization work also in web-application
|
||||
* containers and application servers, no matter in which of the
|
||||
* <code>ClassLoader</code> the particular class that encapsulates
|
||||
* serialization/deserialization lives. </p>
|
||||
*
|
||||
* <p>For more in-depth information about the problem for which this
|
||||
* class here is a workaround, see the JIRA issue LANG-626. </p>
|
||||
*/
|
||||
static class ClassLoaderAwareObjectInputStream extends ObjectInputStream {
|
||||
private static final Map<String, Class<?>> primitiveTypes =
|
||||
new HashMap<String, Class<?>>();
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param in The <code>InputStream</code>.
|
||||
* @param classLoader classloader to use
|
||||
* @throws IOException if an I/O error occurs while reading stream header.
|
||||
* @see java.io.ObjectInputStream
|
||||
*/
|
||||
public ClassLoaderAwareObjectInputStream(final InputStream in, final ClassLoader classLoader) throws IOException {
|
||||
super(in);
|
||||
this.classLoader = classLoader;
|
||||
|
||||
primitiveTypes.put("byte", byte.class);
|
||||
primitiveTypes.put("short", short.class);
|
||||
primitiveTypes.put("int", int.class);
|
||||
primitiveTypes.put("long", long.class);
|
||||
primitiveTypes.put("float", float.class);
|
||||
primitiveTypes.put("double", double.class);
|
||||
primitiveTypes.put("boolean", boolean.class);
|
||||
primitiveTypes.put("char", char.class);
|
||||
primitiveTypes.put("void", void.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overriden version that uses the parametrized <code>ClassLoader</code> or the <code>ClassLoader</code>
|
||||
* of the current <code>Thread</code> to resolve the class.
|
||||
* @param desc An instance of class <code>ObjectStreamClass</code>.
|
||||
* @return A <code>Class</code> object corresponding to <code>desc</code>.
|
||||
* @throws IOException Any of the usual Input/Output exceptions.
|
||||
* @throws ClassNotFoundException If class of a serialized object cannot be found.
|
||||
*/
|
||||
@Override
|
||||
protected Class<?> resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException {
|
||||
final String name = desc.getName();
|
||||
try {
|
||||
return Class.forName(name, false, classLoader);
|
||||
} catch (final ClassNotFoundException ex) {
|
||||
try {
|
||||
return Class.forName(name, false, Thread.currentThread().getContextClassLoader());
|
||||
} catch (final ClassNotFoundException cnfe) {
|
||||
final Class<?> cls = primitiveTypes.get(name);
|
||||
if (cls != null) {
|
||||
return cls;
|
||||
} else {
|
||||
throw cnfe;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,12 @@ import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.foxinmy.weixin4j.model.Consts;
|
||||
import com.foxinmy.weixin4j.model.WeixinAccount;
|
||||
|
||||
/**
|
||||
* 公众号配置信息 class路径下weixin4j.properties文件
|
||||
*
|
||||
*
|
||||
* @className Weixin4jConfigUtil
|
||||
* @author jinyu(foxinmy@gmail.com)
|
||||
* @date 2014年10月31日
|
||||
@@ -23,7 +24,7 @@ public class Weixin4jConfigUtil {
|
||||
CLASSPATH_VALUE = Thread.currentThread().getContextClassLoader()
|
||||
.getResource("").getPath();
|
||||
try {
|
||||
weixinBundle = ResourceBundle.getBundle("weixin4j");
|
||||
weixinBundle = ResourceBundle.getBundle(Consts.WEIXIN4J);
|
||||
} catch (MissingResourceException e) {
|
||||
;
|
||||
}
|
||||
@@ -40,7 +41,7 @@ public class Weixin4jConfigUtil {
|
||||
|
||||
/**
|
||||
* 获取weixin4j.properties文件中的key值
|
||||
*
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
@@ -51,7 +52,7 @@ public class Weixin4jConfigUtil {
|
||||
|
||||
/**
|
||||
* key不存在时则返回传入的默认值
|
||||
*
|
||||
*
|
||||
* @param key
|
||||
* @param defaultValue
|
||||
* @return
|
||||
@@ -73,7 +74,7 @@ public class Weixin4jConfigUtil {
|
||||
|
||||
/**
|
||||
* 判断属性是否存在[classpath:]如果存在则拼接项目路径后返回 一般用于文件的绝对路径获取
|
||||
*
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
@@ -82,7 +83,7 @@ public class Weixin4jConfigUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param key
|
||||
* @param defaultValue
|
||||
* @return
|
||||
|
||||
Reference in New Issue
Block a user