Browse Source

#844 整合优化ticket管理相关接口代码,方便获取sdk_ticket、jsapi_ticket和card_api_ticket等。

master
Binary Wang 6 years ago
parent
commit
49633cd123
11 changed files with 383 additions and 457 deletions
  1. +19
    -35
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java
  2. +103
    -214
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java
  3. +19
    -52
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInRedisConfigStorage.java
  4. +75
    -55
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
  5. +55
    -19
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java
  6. +26
    -33
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java
  7. +35
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/TicketType.java
  8. +0
    -38
      weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
  9. +21
    -2
      weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImplTest.java
  10. +4
    -9
      weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoInMemoryConfigStorage.java
  11. +26
    -0
      weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java

+ 19
- 35
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java View File

@@ -1,13 +1,14 @@
package me.chanjar.weixin.mp.api; package me.chanjar.weixin.mp.api;


import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;

import java.io.File; import java.io.File;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;


import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.mp.enums.TicketType;

/** /**
* 微信客户端配置存储
* 微信客户端配置存储.
* *
* @author chanjarster * @author chanjarster
*/ */
@@ -20,62 +21,45 @@ public interface WxMpConfigStorage {
boolean isAccessTokenExpired(); boolean isAccessTokenExpired();


/** /**
* 强制将access token过期掉
* 强制将access token过期掉.
*/ */
void expireAccessToken(); void expireAccessToken();


/** /**
* 应该是线程安全的
* 应该是线程安全的.
* *
* @param accessToken 要更新的WxAccessToken对象 * @param accessToken 要更新的WxAccessToken对象
*/ */
void updateAccessToken(WxAccessToken accessToken); void updateAccessToken(WxAccessToken accessToken);


/** /**
* 应该是线程安全的
* 应该是线程安全的.
* *
* @param accessToken 新的accessToken值 * @param accessToken 新的accessToken值
* @param expiresInSeconds 过期时间,以秒为单位 * @param expiresInSeconds 过期时间,以秒为单位
*/ */
void updateAccessToken(String accessToken, int expiresInSeconds); void updateAccessToken(String accessToken, int expiresInSeconds);


String getJsapiTicket();

Lock getJsapiTicketLock();

boolean isJsapiTicketExpired();

/**
* 强制将jsapi ticket过期掉
*/
void expireJsapiTicket();

/**
* 应该是线程安全的
*
* @param jsapiTicket 新的jsapi ticket值
* @param expiresInSeconds 过期时间,以秒为单位
*/
void updateJsapiTicket(String jsapiTicket, int expiresInSeconds);

String getCardApiTicket();
String getTicket(TicketType type);


Lock getCardApiTicketLock();
Lock getTicketLock(TicketType type);


boolean isCardApiTicketExpired();
boolean isTicketExpired(TicketType type);


/** /**
* 强制将卡券api ticket过期掉
* 强制将ticket过期掉.
*/ */
void expireCardApiTicket();
void expireTicket(TicketType type);


/** /**
* 更新ticket.
* 应该是线程安全的 * 应该是线程安全的
* *
* @param cardApiTicket 新的cardApi ticket值
* @param type ticket类型
* @param ticket 新的ticket值
* @param expiresInSeconds 过期时间,以秒为单位 * @param expiresInSeconds 过期时间,以秒为单位
*/ */
void updateCardApiTicket(String cardApiTicket, int expiresInSeconds);
void updateTicket(TicketType type, String ticket, int expiresInSeconds);


String getAppId(); String getAppId();


@@ -102,14 +86,14 @@ public interface WxMpConfigStorage {
File getTmpDirFile(); File getTmpDirFile();


/** /**
* http client builder
* http client builder.
* *
* @return ApacheHttpClientBuilder * @return ApacheHttpClientBuilder
*/ */
ApacheHttpClientBuilder getApacheHttpClientBuilder(); ApacheHttpClientBuilder getApacheHttpClientBuilder();


/** /**
* 是否自动刷新token
* 是否自动刷新token.
*/ */
boolean autoRefreshToken(); boolean autoRefreshToken();




+ 103
- 214
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java View File

@@ -1,19 +1,24 @@
package me.chanjar.weixin.mp.api; package me.chanjar.weixin.mp.api;


import java.io.File; import java.io.File;
import java.io.Serializable;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;


import lombok.Data;
import me.chanjar.weixin.common.bean.WxAccessToken; import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.mp.enums.TicketType;
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;


/** /**
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化.
* *
* @author chanjarster * @author chanjarster
*/ */
public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
@Data
public class WxMpInMemoryConfigStorage implements WxMpConfigStorage, Serializable {
private static final long serialVersionUID = -6646519023303395185L;


protected volatile String appId; protected volatile String appId;
protected volatile String secret; protected volatile String secret;
@@ -33,34 +38,21 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
protected volatile String jsapiTicket; protected volatile String jsapiTicket;
protected volatile long jsapiTicketExpiresTime; protected volatile long jsapiTicketExpiresTime;


protected volatile String sdkTicket;
protected volatile long sdkTicketExpiresTime;

protected volatile String cardApiTicket; protected volatile String cardApiTicket;
protected volatile long cardApiTicketExpiresTime; protected volatile long cardApiTicketExpiresTime;


protected Lock accessTokenLock = new ReentrantLock(); protected Lock accessTokenLock = new ReentrantLock();
protected Lock jsapiTicketLock = new ReentrantLock(); protected Lock jsapiTicketLock = new ReentrantLock();
protected Lock sdkTicketLock = new ReentrantLock();
protected Lock cardApiTicketLock = new ReentrantLock(); protected Lock cardApiTicketLock = new ReentrantLock();


/**
* 临时文件目录
*/
protected volatile File tmpDirFile; protected volatile File tmpDirFile;


protected volatile ApacheHttpClientBuilder apacheHttpClientBuilder; protected volatile ApacheHttpClientBuilder apacheHttpClientBuilder;


@Override
public String getAccessToken() {
return this.accessToken;
}

public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}

@Override
public Lock getAccessTokenLock() {
return this.accessTokenLock;
}

@Override @Override
public boolean isAccessTokenExpired() { public boolean isAccessTokenExpired() {
return System.currentTimeMillis() > this.expiresTime; return System.currentTimeMillis() > this.expiresTime;
@@ -83,167 +75,98 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
} }


@Override @Override
public String getJsapiTicket() {
return this.jsapiTicket;
}

public void setJsapiTicket(String jsapiTicket) {
this.jsapiTicket = jsapiTicket;
}

@Override
public Lock getJsapiTicketLock() {
return this.jsapiTicketLock;
}

@Override
public boolean isJsapiTicketExpired() {
return System.currentTimeMillis() > this.jsapiTicketExpiresTime;
}

@Override
public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) {
this.jsapiTicket = jsapiTicket;
// 预留200秒的时间
this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
}

@Override
public void expireJsapiTicket() {
this.jsapiTicketExpiresTime = 0;
}

/**
* 卡券api_ticket
*/
@Override
public String getCardApiTicket() {
return this.cardApiTicket;
}

public void setCardApiTicket(String cardApiTicket) {
this.cardApiTicket = cardApiTicket;
}

@Override
public Lock getCardApiTicketLock() {
return this.cardApiTicketLock;
}

@Override
public boolean isCardApiTicketExpired() {
return System.currentTimeMillis() > this.cardApiTicketExpiresTime;
}

@Override
public synchronized void updateCardApiTicket(String cardApiTicket, int expiresInSeconds) {
this.cardApiTicket = cardApiTicket;
// 预留200秒的时间
this.cardApiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
}

@Override
public void expireCardApiTicket() {
this.cardApiTicketExpiresTime = 0;
}

@Override
public String getAppId() {
return this.appId;
}

public void setAppId(String appId) {
this.appId = appId;
}

@Override
public String getSecret() {
return this.secret;
}

public void setSecret(String secret) {
this.secret = secret;
}

@Override
public String getToken() {
return this.token;
}

public void setToken(String token) {
this.token = token;
}

@Override
public String getTemplateId() {
return this.templateId;
}

public void setTemplateId(String templateId) {
this.templateId = templateId;
}

@Override
public long getExpiresTime() {
return this.expiresTime;
}

public void setExpiresTime(long expiresTime) {
this.expiresTime = expiresTime;
}

@Override
public String getAesKey() {
return this.aesKey;
}

public void setAesKey(String aesKey) {
this.aesKey = aesKey;
}

@Override
public String getOauth2redirectUri() {
return this.oauth2redirectUri;
}

public void setOauth2redirectUri(String oauth2redirectUri) {
this.oauth2redirectUri = oauth2redirectUri;
}

@Override
public String getHttpProxyHost() {
return this.httpProxyHost;
}

public void setHttpProxyHost(String httpProxyHost) {
this.httpProxyHost = httpProxyHost;
}

@Override
public int getHttpProxyPort() {
return this.httpProxyPort;
}

public void setHttpProxyPort(int httpProxyPort) {
this.httpProxyPort = httpProxyPort;
}

@Override
public String getHttpProxyUsername() {
return this.httpProxyUsername;
}

public void setHttpProxyUsername(String httpProxyUsername) {
this.httpProxyUsername = httpProxyUsername;
}

@Override
public String getHttpProxyPassword() {
return this.httpProxyPassword;
}

public void setHttpProxyPassword(String httpProxyPassword) {
this.httpProxyPassword = httpProxyPassword;
public String getTicket(TicketType type) {
switch (type) {
case SDK:
return this.sdkTicket;
case JSAPI:
return this.jsapiTicket;
case WX_CARD:
return this.cardApiTicket;
default:
return null;
}
}

public void setTicket(TicketType type, String ticket) {
switch (type) {
case JSAPI:
this.jsapiTicket = ticket;
break;
case WX_CARD:
this.cardApiTicket = ticket;
break;
case SDK:
this.sdkTicket = ticket;
break;
default:
}
}

@Override
public Lock getTicketLock(TicketType type) {
switch (type) {
case SDK:
return this.sdkTicketLock;
case JSAPI:
return this.jsapiTicketLock;
case WX_CARD:
return this.cardApiTicketLock;
default:
return null;
}
}

@Override
public boolean isTicketExpired(TicketType type) {
switch (type) {
case SDK:
return System.currentTimeMillis() > this.sdkTicketExpiresTime;
case JSAPI:
return System.currentTimeMillis() > this.jsapiTicketExpiresTime;
case WX_CARD:
return System.currentTimeMillis() > this.cardApiTicketExpiresTime;
default:
return false;
}
}

@Override
public synchronized void updateTicket(TicketType type, String ticket, int expiresInSeconds) {
switch (type) {
case JSAPI:
this.jsapiTicket = ticket;
// 预留200秒的时间
this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
break;
case WX_CARD:
this.cardApiTicket = ticket;
// 预留200秒的时间
this.cardApiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
break;
case SDK:
this.sdkTicket = ticket;
// 预留200秒的时间
this.sdkTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
break;
default:
}
}

@Override
public void expireTicket(TicketType type) {
switch (type) {
case JSAPI:
this.jsapiTicketExpiresTime = 0;
break;
case WX_CARD:
this.cardApiTicketExpiresTime = 0;
break;
case SDK:
this.sdkTicketExpiresTime = 0;
break;
default:
}
} }


@Override @Override
@@ -251,40 +174,6 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
return WxMpGsonBuilder.create().toJson(this); return WxMpGsonBuilder.create().toJson(this);
} }


@Override
public File getTmpDirFile() {
return this.tmpDirFile;
}

public void setTmpDirFile(File tmpDirFile) {
this.tmpDirFile = tmpDirFile;
}

@Override
public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
return this.apacheHttpClientBuilder;
}

public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) {
this.apacheHttpClientBuilder = apacheHttpClientBuilder;
}

public long getJsapiTicketExpiresTime() {
return this.jsapiTicketExpiresTime;
}

public void setJsapiTicketExpiresTime(long jsapiTicketExpiresTime) {
this.jsapiTicketExpiresTime = jsapiTicketExpiresTime;
}

public long getCardApiTicketExpiresTime() {
return this.cardApiTicketExpiresTime;
}

public void setCardApiTicketExpiresTime(long cardApiTicketExpiresTime) {
this.cardApiTicketExpiresTime = cardApiTicketExpiresTime;
}

@Override @Override
public boolean autoRefreshToken() { public boolean autoRefreshToken() {
return true; return true;


+ 19
- 52
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInRedisConfigStorage.java View File

@@ -1,51 +1,45 @@
package me.chanjar.weixin.mp.api; package me.chanjar.weixin.mp.api;


import me.chanjar.weixin.mp.enums.TicketType;
import redis.clients.jedis.Jedis; import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPool;


/** /**
* 基于Redis的微信配置provider
* 基于Redis的微信配置provider.
*
* <pre> * <pre>
* 使用说明:本实现仅供参考,并不完整, * 使用说明:本实现仅供参考,并不完整,
* 比如为减少项目依赖,未加入redis分布式锁的实现,如有需要请自行实现。 * 比如为减少项目依赖,未加入redis分布式锁的实现,如有需要请自行实现。
* </pre> * </pre>
*
* @author nickwong * @author nickwong
*/ */
@SuppressWarnings("hiding") @SuppressWarnings("hiding")
public class WxMpInRedisConfigStorage extends WxMpInMemoryConfigStorage { public class WxMpInRedisConfigStorage extends WxMpInMemoryConfigStorage {

private final static String ACCESS_TOKEN_KEY = "wechat_access_token_";

private final static String JSAPI_TICKET_KEY = "wechat_jsapi_ticket_";

private final static String CARDAPI_TICKET_KEY = "wechat_cardapi_ticket_";
private static final String ACCESS_TOKEN_KEY = "wx:access_token:";


/** /**
* 使用连接池保证线程安全
* 使用连接池保证线程安全.
*/ */
protected final JedisPool jedisPool; protected final JedisPool jedisPool;


private String accessTokenKey; private String accessTokenKey;


private String jsapiTicketKey;

private String cardapiTicketKey;

public WxMpInRedisConfigStorage(JedisPool jedisPool) { public WxMpInRedisConfigStorage(JedisPool jedisPool) {
this.jedisPool = jedisPool; this.jedisPool = jedisPool;
} }


/** /**
* 每个公众号生成独有的存储key
*
* @param appId
* 每个公众号生成独有的存储key.
*/ */
@Override @Override
public void setAppId(String appId) { public void setAppId(String appId) {
super.setAppId(appId); super.setAppId(appId);
this.accessTokenKey = ACCESS_TOKEN_KEY.concat(appId); this.accessTokenKey = ACCESS_TOKEN_KEY.concat(appId);
this.jsapiTicketKey = JSAPI_TICKET_KEY.concat(appId);
this.cardapiTicketKey = CARDAPI_TICKET_KEY.concat(appId);
}

private String getTicketRedisKey(TicketType type) {
return String.format("wx:ticket:key:%s:%s", this.appId, type.getCode());
} }


@Override @Override
@@ -77,58 +71,31 @@ public class WxMpInRedisConfigStorage extends WxMpInMemoryConfigStorage {
} }


@Override @Override
public String getJsapiTicket() {
try (Jedis jedis = this.jedisPool.getResource()) {
return jedis.get(this.jsapiTicketKey);
}
}

@Override
public boolean isJsapiTicketExpired() {
try (Jedis jedis = this.jedisPool.getResource()) {
return jedis.ttl(this.jsapiTicketKey) < 2;
}
}

@Override
public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) {
public String getTicket(TicketType type) {
try (Jedis jedis = this.jedisPool.getResource()) { try (Jedis jedis = this.jedisPool.getResource()) {
jedis.setex(this.jsapiTicketKey, expiresInSeconds - 200, jsapiTicket);
return jedis.get(this.getTicketRedisKey(type));
} }
} }


@Override @Override
public void expireJsapiTicket() {
public boolean isTicketExpired(TicketType type) {
try (Jedis jedis = this.jedisPool.getResource()) { try (Jedis jedis = this.jedisPool.getResource()) {
jedis.expire(this.jsapiTicketKey, 0);
return jedis.ttl(this.getTicketRedisKey(type)) < 2;
} }
} }


@Override @Override
public String getCardApiTicket() {
public synchronized void updateTicket(TicketType type, String jsapiTicket, int expiresInSeconds) {
try (Jedis jedis = this.jedisPool.getResource()) { try (Jedis jedis = this.jedisPool.getResource()) {
return jedis.get(this.cardapiTicketKey);
jedis.setex(this.getTicketRedisKey(type), expiresInSeconds - 200, jsapiTicket);
} }
} }


@Override @Override
public boolean isCardApiTicketExpired() {
public void expireTicket(TicketType type) {
try (Jedis jedis = this.jedisPool.getResource()) { try (Jedis jedis = this.jedisPool.getResource()) {
return jedis.ttl(this.cardapiTicketKey) < 2;
jedis.expire(this.getTicketRedisKey(type), 0);
} }
} }


@Override
public synchronized void updateCardApiTicket(String cardApiTicket, int expiresInSeconds) {
try (Jedis jedis = this.jedisPool.getResource()) {
jedis.setex(this.cardapiTicketKey, expiresInSeconds - 200, cardApiTicket);
}
}

@Override
public void expireCardApiTicket() {
try (Jedis jedis = this.jedisPool.getResource()) {
jedis.expire(this.cardapiTicketKey, 0);
}
}
} }

+ 75
- 55
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java View File

@@ -10,76 +10,79 @@ import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult; import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult;
import me.chanjar.weixin.mp.bean.result.WxMpUser; import me.chanjar.weixin.mp.bean.result.WxMpUser;
import me.chanjar.weixin.mp.enums.TicketType;


/** /**
* 微信API的Service
* 微信公众号API的Service.
*
* @author chanjarster
*/ */
public interface WxMpService { public interface WxMpService {
/** /**
* 获取access_token
* 获取access_token.
*/ */
String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"; String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
/** /**
* 获得jsapi_ticket
* 获得各种类型的ticket.
*/ */
String GET_JSAPI_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi";
String GET_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=";
/** /**
* 长链接转短链接接口
* 长链接转短链接接口.
*/ */
String SHORTURL_API_URL = "https://api.weixin.qq.com/cgi-bin/shorturl"; String SHORTURL_API_URL = "https://api.weixin.qq.com/cgi-bin/shorturl";
/** /**
* 语义查询接口
* 语义查询接口.
*/ */
String SEMANTIC_SEMPROXY_SEARCH_URL = "https://api.weixin.qq.com/semantic/semproxy/search"; String SEMANTIC_SEMPROXY_SEARCH_URL = "https://api.weixin.qq.com/semantic/semproxy/search";
/** /**
* 用code换取oauth2的access token
* 用code换取oauth2的access token.
*/ */
String OAUTH2_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code"; String OAUTH2_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
/** /**
* 刷新oauth2的access token
* 刷新oauth2的access token.
*/ */
String OAUTH2_REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s"; String OAUTH2_REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s";
/** /**
* 用oauth2获取用户信息
* 用oauth2获取用户信息.
*/ */
String OAUTH2_USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=%s"; String OAUTH2_USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=%s";
/** /**
* 验证oauth2的access token是否有效
* 验证oauth2的access token是否有效.
*/ */
String OAUTH2_VALIDATE_TOKEN_URL = "https://api.weixin.qq.com/sns/auth?access_token=%s&openid=%s"; String OAUTH2_VALIDATE_TOKEN_URL = "https://api.weixin.qq.com/sns/auth?access_token=%s&openid=%s";
/** /**
* 获取微信服务器IP地址
* 获取微信服务器IP地址.
*/ */
String GET_CALLBACK_IP_URL = "https://api.weixin.qq.com/cgi-bin/getcallbackip"; String GET_CALLBACK_IP_URL = "https://api.weixin.qq.com/cgi-bin/getcallbackip";
/** /**
* 第三方使用网站应用授权登录的url
* 第三方使用网站应用授权登录的url.
*/ */
String QRCONNECT_URL = "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect"; String QRCONNECT_URL = "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect";
/** /**
* oauth2授权的url连接
* oauth2授权的url连接.
*/ */
String CONNECT_OAUTH2_AUTHORIZE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s&connect_redirect=1#wechat_redirect"; String CONNECT_OAUTH2_AUTHORIZE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s&connect_redirect=1#wechat_redirect";


/** /**
* 获取公众号的自动回复规则
* 获取公众号的自动回复规则.
*/ */
String GET_CURRENT_AUTOREPLY_INFO_URL = "https://api.weixin.qq.com/cgi-bin/get_current_autoreply_info"; String GET_CURRENT_AUTOREPLY_INFO_URL = "https://api.weixin.qq.com/cgi-bin/get_current_autoreply_info";


/** /**
* 公众号调用或第三方平台帮公众号调用对公众号的所有api调用(包括第三方帮其调用)次数进行清零
* 公众号调用或第三方平台帮公众号调用对公众号的所有api调用(包括第三方帮其调用)次数进行清零.
*/ */
String CLEAR_QUOTA_URL = "https://api.weixin.qq.com/cgi-bin/clear_quota"; String CLEAR_QUOTA_URL = "https://api.weixin.qq.com/cgi-bin/clear_quota";


/** /**
* <pre> * <pre>
* 验证消息的确来自微信服务器
* 验证消息的确来自微信服务器.
* 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN
* </pre> * </pre>
*/ */
boolean checkSignature(String timestamp, String nonce, String signature); boolean checkSignature(String timestamp, String nonce, String signature);


/** /**
* 获取access_token, 不强制刷新access_token
* 获取access_token, 不强制刷新access_token.
* *
* @see #getAccessToken(boolean) * @see #getAccessToken(boolean)
*/ */
@@ -87,7 +90,7 @@ public interface WxMpService {


/** /**
* <pre> * <pre>
* 获取access_token,本方法线程安全
* 获取access_token,本方法线程安全.
* 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限 * 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限
* *
* 另:本service的所有方法都会在access_token过期时调用此方法 * 另:本service的所有方法都会在access_token过期时调用此方法
@@ -102,7 +105,24 @@ public interface WxMpService {
String getAccessToken(boolean forceRefresh) throws WxErrorException; String getAccessToken(boolean forceRefresh) throws WxErrorException;


/** /**
* 获得jsapi_ticket,不强制刷新jsapi_ticket
* 获得ticket,不强制刷新ticket.
*
* @see #getTicket(TicketType, boolean)
*/
String getTicket(TicketType type) throws WxErrorException;

/**
* <pre>
* 获得ticket.
* 获得时会检查 Token是否过期,如果过期了,那么就刷新一下,否则就什么都不干
* </pre>
*
* @param forceRefresh 强制刷新
*/
String getTicket(TicketType type, boolean forceRefresh) throws WxErrorException;

/**
* 获得jsapi_ticket,不强制刷新jsapi_ticket.
* *
* @see #getJsapiTicket(boolean) * @see #getJsapiTicket(boolean)
*/ */
@@ -110,7 +130,7 @@ public interface WxMpService {


/** /**
* <pre> * <pre>
* 获得jsapi_ticket
* 获得jsapi_ticket.
* 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干 * 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
* *
* 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
@@ -122,7 +142,7 @@ public interface WxMpService {


/** /**
* <pre> * <pre>
* 创建调用jsapi时所需要的签名
* 创建调用jsapi时所需要的签名.
* *
* 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
* </pre> * </pre>
@@ -131,7 +151,7 @@ public interface WxMpService {


/** /**
* <pre> * <pre>
* 长链接转短链接接口
* 长链接转短链接接口.
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=长链接转短链接接口 * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=长链接转短链接接口
* </pre> * </pre>
*/ */
@@ -139,7 +159,7 @@ public interface WxMpService {


/** /**
* <pre> * <pre>
* 语义查询接口
* 语义查询接口.
* 详情请见:http://mp.weixin.qq.com/wiki/index.php?title=语义理解 * 详情请见:http://mp.weixin.qq.com/wiki/index.php?title=语义理解
* </pre> * </pre>
*/ */
@@ -147,7 +167,7 @@ public interface WxMpService {


/** /**
* <pre> * <pre>
* 构造第三方使用网站应用授权登录的url
* 构造第三方使用网站应用授权登录的url.
* 详情请见: <a href="https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN">网站应用微信登录开发指南</a> * 详情请见: <a href="https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN">网站应用微信登录开发指南</a>
* URL格式为:https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect * URL格式为:https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
* </pre> * </pre>
@@ -161,7 +181,7 @@ public interface WxMpService {


/** /**
* <pre> * <pre>
* 构造oauth2授权的url连接
* 构造oauth2授权的url连接.
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=网页授权获取用户基本信息 * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=网页授权获取用户基本信息
* </pre> * </pre>
* *
@@ -172,7 +192,7 @@ public interface WxMpService {


/** /**
* <pre> * <pre>
* 用code换取oauth2的access token
* 用code换取oauth2的access token.
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=网页授权获取用户基本信息 * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=网页授权获取用户基本信息
* </pre> * </pre>
*/ */
@@ -180,14 +200,14 @@ public interface WxMpService {


/** /**
* <pre> * <pre>
* 刷新oauth2的access token
* 刷新oauth2的access token.
* </pre> * </pre>
*/ */
WxMpOAuth2AccessToken oauth2refreshAccessToken(String refreshToken) throws WxErrorException; WxMpOAuth2AccessToken oauth2refreshAccessToken(String refreshToken) throws WxErrorException;


/** /**
* <pre> * <pre>
* 用oauth2获取用户信息, 当前面引导授权时的scope是snsapi_userinfo的时候才可以
* 用oauth2获取用户信息, 当前面引导授权时的scope是snsapi_userinfo的时候才可以.
* </pre> * </pre>
* *
* @param lang zh_CN, zh_TW, en * @param lang zh_CN, zh_TW, en
@@ -196,7 +216,7 @@ public interface WxMpService {


/** /**
* <pre> * <pre>
* 验证oauth2的access token是否有效
* 验证oauth2的access token是否有效.
* </pre> * </pre>
*/ */
boolean oauth2validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken); boolean oauth2validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken);
@@ -211,7 +231,7 @@ public interface WxMpService {


/** /**
* <pre> * <pre>
* 获取公众号的自动回复规则
* 获取公众号的自动回复规则.
* http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751299&token=&lang=zh_CN * http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751299&token=&lang=zh_CN
* 开发者可以通过该接口,获取公众号当前使用的自动回复规则,包括关注后自动回复、消息自动回复(60分钟内触发一次)、关键词自动回复。 * 开发者可以通过该接口,获取公众号当前使用的自动回复规则,包括关注后自动回复、消息自动回复(60分钟内触发一次)、关键词自动回复。
* 请注意: * 请注意:
@@ -240,12 +260,12 @@ public interface WxMpService {
void clearQuota(String appid) throws WxErrorException; void clearQuota(String appid) throws WxErrorException;


/** /**
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求.
*/ */
String get(String url, String queryParam) throws WxErrorException; String get(String url, String queryParam) throws WxErrorException;


/** /**
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求.
*/ */
String post(String url, String postData) throws WxErrorException; String post(String url, String postData) throws WxErrorException;


@@ -260,7 +280,7 @@ public interface WxMpService {


/** /**
* <pre> * <pre>
* 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试
* 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试.
* @param retrySleepMillis 默认:1000ms * @param retrySleepMillis 默认:1000ms
* </pre> * </pre>
*/ */
@@ -268,131 +288,131 @@ public interface WxMpService {


/** /**
* <pre> * <pre>
* 设置当微信系统响应系统繁忙时,最大重试次数
* 设置当微信系统响应系统繁忙时,最大重试次数.
* 默认:5次 * 默认:5次
* </pre> * </pre>
*/ */
void setMaxRetryTimes(int maxRetryTimes); void setMaxRetryTimes(int maxRetryTimes);


/** /**
* 获取WxMpConfigStorage 对象
* 获取WxMpConfigStorage 对象.
* *
* @return WxMpConfigStorage * @return WxMpConfigStorage
*/ */
WxMpConfigStorage getWxMpConfigStorage(); WxMpConfigStorage getWxMpConfigStorage();


/** /**
* 注入 {@link WxMpConfigStorage} 的实现
* 注入 {@link WxMpConfigStorage} 的实现.
*/ */
void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider); void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider);


/** /**
* 返回客服接口方法实现类,以方便调用其各个接口
* 返回客服接口方法实现类,以方便调用其各个接口.
* *
* @return WxMpKefuService * @return WxMpKefuService
*/ */
WxMpKefuService getKefuService(); WxMpKefuService getKefuService();


/** /**
* 返回素材相关接口方法的实现类对象,以方便调用其各个接口
* 返回素材相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpMaterialService * @return WxMpMaterialService
*/ */
WxMpMaterialService getMaterialService(); WxMpMaterialService getMaterialService();


/** /**
* 返回菜单相关接口方法的实现类对象,以方便调用其各个接口
* 返回菜单相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpMenuService * @return WxMpMenuService
*/ */
WxMpMenuService getMenuService(); WxMpMenuService getMenuService();


/** /**
* 返回用户相关接口方法的实现类对象,以方便调用其各个接口
* 返回用户相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpUserService * @return WxMpUserService
*/ */
WxMpUserService getUserService(); WxMpUserService getUserService();


/** /**
* 返回用户标签相关接口方法的实现类对象,以方便调用其各个接口
* 返回用户标签相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpUserTagService * @return WxMpUserTagService
*/ */
WxMpUserTagService getUserTagService(); WxMpUserTagService getUserTagService();


/** /**
* 返回二维码相关接口方法的实现类对象,以方便调用其各个接口
* 返回二维码相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpQrcodeService * @return WxMpQrcodeService
*/ */
WxMpQrcodeService getQrcodeService(); WxMpQrcodeService getQrcodeService();


/** /**
* 返回卡券相关接口方法的实现类对象,以方便调用其各个接口
* 返回卡券相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpCardService * @return WxMpCardService
*/ */
WxMpCardService getCardService(); WxMpCardService getCardService();


/** /**
* 返回数据分析统计相关接口方法的实现类对象,以方便调用其各个接口
* 返回数据分析统计相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpDataCubeService * @return WxMpDataCubeService
*/ */
WxMpDataCubeService getDataCubeService(); WxMpDataCubeService getDataCubeService();


/** /**
* 返回用户黑名单管理相关接口方法的实现类对象,以方便调用其各个接口
* 返回用户黑名单管理相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpUserBlacklistService * @return WxMpUserBlacklistService
*/ */
WxMpUserBlacklistService getBlackListService(); WxMpUserBlacklistService getBlackListService();


/** /**
* 返回门店管理相关接口方法的实现类对象,以方便调用其各个接口
* 返回门店管理相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpStoreService * @return WxMpStoreService
*/ */
WxMpStoreService getStoreService(); WxMpStoreService getStoreService();


/** /**
* 返回模板消息相关接口方法的实现类对象,以方便调用其各个接口
* 返回模板消息相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpTemplateMsgService * @return WxMpTemplateMsgService
*/ */
WxMpTemplateMsgService getTemplateMsgService(); WxMpTemplateMsgService getTemplateMsgService();


/** /**
* 返回一次性订阅消息相关接口方法的实现类对象,以方便调用其各个接口
* 返回一次性订阅消息相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpSubscribeMsgService * @return WxMpSubscribeMsgService
*/ */
WxMpSubscribeMsgService getSubscribeMsgService(); WxMpSubscribeMsgService getSubscribeMsgService();


/** /**
* 返回硬件平台相关接口方法的实现类对象,以方便调用其各个接口
* 返回硬件平台相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpDeviceService * @return WxMpDeviceService
*/ */
WxMpDeviceService getDeviceService(); WxMpDeviceService getDeviceService();


/** /**
* 返回摇一摇周边相关接口方法的实现类对象,以方便调用其各个接口
* 返回摇一摇周边相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpShakeService * @return WxMpShakeService
*/ */
WxMpShakeService getShakeService(); WxMpShakeService getShakeService();


/** /**
* 返回会员卡相关接口方法的实现类对象,以方便调用其各个接口
* 返回会员卡相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpMemberCardService * @return WxMpMemberCardService
*/ */
WxMpMemberCardService getMemberCardService(); WxMpMemberCardService getMemberCardService();


/** /**
* 初始化http请求对象
* 初始化http请求对象.
*/ */
void initHttp(); void initHttp();


@@ -402,21 +422,21 @@ public interface WxMpService {
RequestHttp getRequestHttp(); RequestHttp getRequestHttp();


/** /**
* 返回群发消息相关接口方法的实现类对象,以方便调用其各个接口
* 返回群发消息相关接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpMassMessageService * @return WxMpMassMessageService
*/ */
WxMpMassMessageService getMassMessageService(); WxMpMassMessageService getMassMessageService();


/** /**
* 返回AI开放接口方法的实现类对象,以方便调用其各个接口
* 返回AI开放接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpAiOpenService * @return WxMpAiOpenService
*/ */
WxMpAiOpenService getAiOpenService(); WxMpAiOpenService getAiOpenService();


/** /**
* 返回WIFI接口方法的实现类对象,以方便调用其各个接口
* 返回WIFI接口方法的实现类对象,以方便调用其各个接口.
* *
* @return WxMpWifiService * @return WxMpWifiService
*/ */


+ 55
- 19
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java View File

@@ -1,5 +1,12 @@
package me.chanjar.weixin.mp.api.impl; package me.chanjar.weixin.mp.api.impl;


import java.io.IOException;
import java.util.concurrent.locks.Lock;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
@@ -12,19 +19,37 @@ import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.common.util.DataUtils; import me.chanjar.weixin.common.util.DataUtils;
import me.chanjar.weixin.common.util.RandomUtils; import me.chanjar.weixin.common.util.RandomUtils;
import me.chanjar.weixin.common.util.crypto.SHA1; import me.chanjar.weixin.common.util.crypto.SHA1;
import me.chanjar.weixin.common.util.http.*;
import me.chanjar.weixin.mp.api.*;
import me.chanjar.weixin.common.util.http.RequestExecutor;
import me.chanjar.weixin.common.util.http.RequestHttp;
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
import me.chanjar.weixin.common.util.http.URIUtil;
import me.chanjar.weixin.mp.api.WxMpAiOpenService;
import me.chanjar.weixin.mp.api.WxMpCardService;
import me.chanjar.weixin.mp.api.WxMpConfigStorage;
import me.chanjar.weixin.mp.api.WxMpDataCubeService;
import me.chanjar.weixin.mp.api.WxMpDeviceService;
import me.chanjar.weixin.mp.api.WxMpKefuService;
import me.chanjar.weixin.mp.api.WxMpMassMessageService;
import me.chanjar.weixin.mp.api.WxMpMaterialService;
import me.chanjar.weixin.mp.api.WxMpMemberCardService;
import me.chanjar.weixin.mp.api.WxMpMenuService;
import me.chanjar.weixin.mp.api.WxMpQrcodeService;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.WxMpShakeService;
import me.chanjar.weixin.mp.api.WxMpStoreService;
import me.chanjar.weixin.mp.api.WxMpSubscribeMsgService;
import me.chanjar.weixin.mp.api.WxMpTemplateMsgService;
import me.chanjar.weixin.mp.api.WxMpUserBlacklistService;
import me.chanjar.weixin.mp.api.WxMpUserService;
import me.chanjar.weixin.mp.api.WxMpUserTagService;
import me.chanjar.weixin.mp.api.WxMpWifiService;
import me.chanjar.weixin.mp.bean.WxMpSemanticQuery; import me.chanjar.weixin.mp.bean.WxMpSemanticQuery;
import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo; import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult; import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult;
import me.chanjar.weixin.mp.bean.result.WxMpUser; import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.concurrent.locks.Lock;
import me.chanjar.weixin.mp.enums.TicketType;


/** /**
* @author someone * @author someone
@@ -71,31 +96,42 @@ public abstract class BaseWxMpServiceImpl<H, P> implements WxMpService, RequestH
} }


@Override @Override
public String getJsapiTicket() throws WxErrorException {
return getJsapiTicket(false);
public String getTicket(TicketType type) throws WxErrorException {
return this.getTicket(type, false);
} }


@Override @Override
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
Lock lock = this.getWxMpConfigStorage().getJsapiTicketLock();
public String getTicket(TicketType type, boolean forceRefresh) throws WxErrorException {
Lock lock = this.getWxMpConfigStorage().getTicketLock(type);
try { try {
lock.lock(); lock.lock();
if (forceRefresh) { if (forceRefresh) {
this.getWxMpConfigStorage().expireJsapiTicket();
this.getWxMpConfigStorage().expireTicket(type);
} }


if (this.getWxMpConfigStorage().isJsapiTicketExpired()) {
String responseContent = execute(SimpleGetRequestExecutor.create(this), WxMpService.GET_JSAPI_TICKET_URL, null);
JsonElement tmpJsonElement = JSON_PARSER.parse(responseContent);
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
if (this.getWxMpConfigStorage().isTicketExpired(type)) {
String responseContent = execute(SimpleGetRequestExecutor.create(this),
WxMpService.GET_TICKET_URL + type.getCode(), null);
JsonObject tmpJsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject();
String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); String jsapiTicket = tmpJsonObject.get("ticket").getAsString();
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
this.getWxMpConfigStorage().updateJsapiTicket(jsapiTicket, expiresInSeconds);
this.getWxMpConfigStorage().updateTicket(type, jsapiTicket, expiresInSeconds);
} }
} finally { } finally {
lock.unlock(); lock.unlock();
} }
return this.getWxMpConfigStorage().getJsapiTicket();

return this.getWxMpConfigStorage().getTicket(type);
}

@Override
public String getJsapiTicket() throws WxErrorException {
return this.getJsapiTicket(false);
}

@Override
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
return this.getTicket(TicketType.JSAPI, forceRefresh);
} }


@Override @Override


+ 26
- 33
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java View File

@@ -26,13 +26,15 @@ import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateRequest;
import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateResult; import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateResult;
import me.chanjar.weixin.mp.bean.card.WxMpCardQrcodeCreateResult; import me.chanjar.weixin.mp.bean.card.WxMpCardQrcodeCreateResult;
import me.chanjar.weixin.mp.bean.result.WxMpCardResult; import me.chanjar.weixin.mp.bean.result.WxMpCardResult;
import me.chanjar.weixin.mp.enums.TicketType;
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;


/** /**
* Created by Binary Wang on 2016/7/27. * Created by Binary Wang on 2016/7/27.
*
* @author BinaryWang
*/ */
public class WxMpCardServiceImpl implements WxMpCardService { public class WxMpCardServiceImpl implements WxMpCardService {

private final Logger log = LoggerFactory.getLogger(WxMpCardServiceImpl.class); private final Logger log = LoggerFactory.getLogger(WxMpCardServiceImpl.class);


private WxMpService wxMpService; private WxMpService wxMpService;
@@ -49,7 +51,7 @@ public class WxMpCardServiceImpl implements WxMpCardService {
} }


/** /**
* 获得卡券api_ticket,不强制刷新卡券api_ticket
* 获得卡券api_ticket,不强制刷新卡券api_ticket.
* *
* @return 卡券api_ticket * @return 卡券api_ticket
* @see #getCardApiTicket(boolean) * @see #getCardApiTicket(boolean)
@@ -61,7 +63,7 @@ public class WxMpCardServiceImpl implements WxMpCardService {


/** /**
* <pre> * <pre>
* 获得卡券api_ticket
* 获得卡券api_ticket.
* 获得时会检查卡券apiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干 * 获得时会检查卡券apiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
* *
* 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD * 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD
@@ -74,26 +76,28 @@ public class WxMpCardServiceImpl implements WxMpCardService {
*/ */
@Override @Override
public String getCardApiTicket(boolean forceRefresh) throws WxErrorException { public String getCardApiTicket(boolean forceRefresh) throws WxErrorException {
Lock lock = getWxMpService().getWxMpConfigStorage().getCardApiTicketLock();
final TicketType type = TicketType.WX_CARD;
Lock lock = getWxMpService().getWxMpConfigStorage().getTicketLock(type);
try { try {
lock.lock(); lock.lock();


if (forceRefresh) { if (forceRefresh) {
this.getWxMpService().getWxMpConfigStorage().expireCardApiTicket();
this.getWxMpService().getWxMpConfigStorage().expireTicket(type);
} }


if (this.getWxMpService().getWxMpConfigStorage().isCardApiTicketExpired()) {
String responseContent = this.wxMpService.execute(SimpleGetRequestExecutor.create(this.getWxMpService().getRequestHttp()), CARD_GET_TICKET, null);
if (this.getWxMpService().getWxMpConfigStorage().isTicketExpired(type)) {
String responseContent = this.wxMpService.execute(SimpleGetRequestExecutor
.create(this.getWxMpService().getRequestHttp()), CARD_GET_TICKET, null);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent); JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
String cardApiTicket = tmpJsonObject.get("ticket").getAsString(); String cardApiTicket = tmpJsonObject.get("ticket").getAsString();
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
this.getWxMpService().getWxMpConfigStorage().updateCardApiTicket(cardApiTicket, expiresInSeconds);
this.getWxMpService().getWxMpConfigStorage().updateTicket(type, cardApiTicket, expiresInSeconds);
} }
} finally { } finally {
lock.unlock(); lock.unlock();
} }
return this.getWxMpService().getWxMpConfigStorage().getCardApiTicket();
return this.getWxMpService().getWxMpConfigStorage().getTicket(type);
} }


/** /**
@@ -147,7 +151,7 @@ public class WxMpCardServiceImpl implements WxMpCardService {
} }


/** /**
* 卡券Code查询
* 卡券Code查询.
* *
* @param cardId 卡券ID代表一类卡券 * @param cardId 卡券ID代表一类卡券
* @param code 单张卡券的唯一标准 * @param code 单张卡券的唯一标准
@@ -247,11 +251,11 @@ public class WxMpCardServiceImpl implements WxMpCardService {
} }


/** /**
* 添加测试白名单
* 添加测试白名单.
* *
* @param openid 用户的openid * @param openid 用户的openid
* @return
*/ */
@Override
public String addTestWhiteList(String openid) throws WxErrorException { public String addTestWhiteList(String openid) throws WxErrorException {
JsonArray array = new JsonArray(); JsonArray array = new JsonArray();
array.add(openid); array.add(openid);
@@ -262,25 +266,21 @@ public class WxMpCardServiceImpl implements WxMpCardService {
} }


/** /**
* 创建卡券二维码
*
* @param cardId
* @param outerStr
* @return
* 创建卡券二维码.
*/ */
@Override
public WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr) throws WxErrorException { public WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr) throws WxErrorException {
return createQrcodeCard(cardId, outerStr, 0); return createQrcodeCard(cardId, outerStr, 0);
} }


/** /**
* 创建卡券二维码
* 创建卡券二维码.
* *
* @param cardId 卡券编号 * @param cardId 卡券编号
* @param outerStr 二维码标识 * @param outerStr 二维码标识
* @param expiresIn 失效时间,单位秒,不填默认365天 * @param expiresIn 失效时间,单位秒,不填默认365天
* @return
* @throws WxErrorException
*/ */
@Override
public WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr, int expiresIn) throws WxErrorException { public WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr, int expiresIn) throws WxErrorException {
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action_name", "QR_CARD"); jsonObject.addProperty("action_name", "QR_CARD");
@@ -293,16 +293,11 @@ public class WxMpCardServiceImpl implements WxMpCardService {
cardJson.addProperty("outer_str", outerStr); cardJson.addProperty("outer_str", outerStr);
actionInfoJson.add("card", cardJson); actionInfoJson.add("card", cardJson);
jsonObject.add("action_info", actionInfoJson); jsonObject.add("action_info", actionInfoJson);
String response = this.wxMpService.post(CARD_QRCODE_CREATE, GSON.toJson(jsonObject));
return WxMpCardQrcodeCreateResult.fromJson(response);
return WxMpCardQrcodeCreateResult.fromJson(this.wxMpService.post(CARD_QRCODE_CREATE, GSON.toJson(jsonObject)));
} }


/** /**
* 创建卡券货架接口
*
* @param request
* @return
* @throws WxErrorException
* 创建卡券货架接口.
*/ */
@Override @Override
public WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateRequest request) throws WxErrorException { public WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateRequest request) throws WxErrorException {
@@ -311,24 +306,22 @@ public class WxMpCardServiceImpl implements WxMpCardService {
} }


/** /**
* 将用户的卡券设置为失效状态
* 将用户的卡券设置为失效状态.
* 详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025272&anchor=9 * 详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025272&anchor=9
* *
* @param cardId 卡券编号 * @param cardId 卡券编号
* @param code 用户会员卡号 * @param code 用户会员卡号
* @param reason 设置为失效的原因 * @param reason 设置为失效的原因
* @return
* @throws WxErrorException
*/ */
@Override @Override
public String unavailableCardCode(String cardId, String code, String reason) throws WxErrorException { public String unavailableCardCode(String cardId, String code, String reason) throws WxErrorException {
if (StringUtils.isAnyBlank(cardId, code, reason))
if (StringUtils.isAnyBlank(cardId, code, reason)) {
throw new WxErrorException(WxError.builder().errorCode(41012).errorMsg("参数不完整").build()); throw new WxErrorException(WxError.builder().errorCode(41012).errorMsg("参数不完整").build());
}
JsonObject jsonRequest = new JsonObject(); JsonObject jsonRequest = new JsonObject();
jsonRequest.addProperty("card_id", cardId); jsonRequest.addProperty("card_id", cardId);
jsonRequest.addProperty("code", code); jsonRequest.addProperty("code", code);
jsonRequest.addProperty("reason", reason); jsonRequest.addProperty("reason", reason);
String response = this.wxMpService.post(CARD_CODE_UNAVAILABLE, GSON.toJson(jsonRequest));
return response;
return this.wxMpService.post(CARD_CODE_UNAVAILABLE, GSON.toJson(jsonRequest));
} }
} }

+ 35
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/TicketType.java View File

@@ -0,0 +1,35 @@
package me.chanjar.weixin.mp.enums;

import lombok.Getter;

/**
* <pre>
* ticket类型枚举
* Created by Binary Wang on 2018/11/18.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Getter
public enum TicketType {
/**
* jsapi
*/
JSAPI("jsapi"),
/**
* sdk
*/
SDK("2"),
/**
* 微信卡券
*/
WX_CARD("wx_card");
/**
* type代码
*/
private String code;

TicketType(String code) {
this.code = code;
}
}

+ 0
- 38
weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java View File

@@ -1,38 +0,0 @@
package me.chanjar.weixin.mp.api;

import com.google.inject.Inject;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.test.ApiTestModule;
import org.apache.commons.lang3.StringUtils;
import org.testng.*;
import org.testng.annotations.*;

/**
* 基础API测试
*
* @author chanjarster
*/
@Test(groups = "baseAPI")
@Guice(modules = ApiTestModule.class)
public class WxMpBaseAPITest {

@Inject
protected WxMpService wxService;

public void testRefreshAccessToken() throws WxErrorException {
WxMpConfigStorage configStorage = this.wxService.getWxMpConfigStorage();
String before = configStorage.getAccessToken();
this.wxService.getAccessToken(false);

String after = configStorage.getAccessToken();
Assert.assertNotEquals(before, after);
Assert.assertTrue(StringUtils.isNotBlank(after));
}

public void testJsapiTicket() throws WxErrorException {
String jsapiTicket = this.wxService.getJsapiTicket(false);
System.out.println(jsapiTicket);
Assert.assertNotNull(jsapiTicket);
}

}

+ 21
- 2
weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImplTest.java View File

@@ -1,14 +1,18 @@
package me.chanjar.weixin.mp.api.impl; package me.chanjar.weixin.mp.api.impl;


import org.apache.commons.lang3.StringUtils;
import org.testng.*;
import org.testng.annotations.*;

import com.google.inject.Inject; import com.google.inject.Inject;
import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpConfigStorage;
import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.test.ApiTestModule; import me.chanjar.weixin.mp.api.test.ApiTestModule;
import me.chanjar.weixin.mp.api.test.TestConfigStorage; import me.chanjar.weixin.mp.api.test.TestConfigStorage;
import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo; import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo;
import org.testng.*;
import org.testng.annotations.*;
import me.chanjar.weixin.mp.enums.TicketType;


import static org.testng.Assert.*; import static org.testng.Assert.*;


@@ -40,4 +44,19 @@ public class WxMpServiceImplTest {
System.out.println(qrConnectUrl); System.out.println(qrConnectUrl);
} }


public void testGetTicket() throws WxErrorException {
String ticket = this.wxService.getTicket(TicketType.SDK, false);
System.out.println(ticket);
Assert.assertNotNull(ticket);
}

public void testRefreshAccessToken() throws WxErrorException {
WxMpConfigStorage configStorage = this.wxService.getWxMpConfigStorage();
String before = configStorage.getAccessToken();
this.wxService.getAccessToken(false);

String after = configStorage.getAccessToken();
Assert.assertNotEquals(before, after);
Assert.assertTrue(StringUtils.isNotBlank(after));
}
} }

+ 4
- 9
weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoInMemoryConfigStorage.java View File

@@ -1,18 +1,19 @@
package me.chanjar.weixin.mp.demo; package me.chanjar.weixin.mp.demo;


import java.io.InputStream;
import java.util.concurrent.locks.ReentrantLock;

import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
import me.chanjar.weixin.common.util.xml.XStreamInitializer; import me.chanjar.weixin.common.util.xml.XStreamInitializer;
import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage; import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;


import java.io.InputStream;
import java.util.concurrent.locks.ReentrantLock;

/** /**
* @author Daniel Qian * @author Daniel Qian
*/ */
@XStreamAlias("xml") @XStreamAlias("xml")
class WxMpDemoInMemoryConfigStorage extends WxMpInMemoryConfigStorage { class WxMpDemoInMemoryConfigStorage extends WxMpInMemoryConfigStorage {
private static final long serialVersionUID = -3706236839197109704L;


public static WxMpDemoInMemoryConfigStorage fromXml(InputStream is) { public static WxMpDemoInMemoryConfigStorage fromXml(InputStream is) {
XStream xstream = XStreamInitializer.getInstance(); XStream xstream = XStreamInitializer.getInstance();
@@ -24,10 +25,4 @@ class WxMpDemoInMemoryConfigStorage extends WxMpInMemoryConfigStorage {
return wxMpDemoInMemoryConfigStorage; return wxMpDemoInMemoryConfigStorage;
} }


@Override
public String toString() {
return "SimpleWxConfigProvider [appId=" + this.appId + ", secret=" + this.secret + ", accessToken=" + this.accessToken
+ ", expiresTime=" + this.expiresTime + ", token=" + this.token + ", aesKey=" + this.aesKey + ", templateId=" + this.templateId + "]";
}

} }

+ 26
- 0
weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java View File

@@ -11,6 +11,7 @@ import cn.binarywang.wx.miniapp.config.WxMaConfig;
import me.chanjar.weixin.common.bean.WxAccessToken; import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.mp.api.WxMpConfigStorage; import me.chanjar.weixin.mp.api.WxMpConfigStorage;
import me.chanjar.weixin.mp.enums.TicketType;
import me.chanjar.weixin.open.api.WxOpenConfigStorage; import me.chanjar.weixin.open.api.WxOpenConfigStorage;
import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken;
import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken;
@@ -322,6 +323,31 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
wxOpenConfigStorage.updateAuthorizerAccessToken(appId, accessToken, expiresInSeconds); wxOpenConfigStorage.updateAuthorizerAccessToken(appId, accessToken, expiresInSeconds);
} }


@Override
public String getTicket(TicketType type) {
return null;
}

@Override
public Lock getTicketLock(TicketType type) {
return null;
}

@Override
public boolean isTicketExpired(TicketType type) {
return false;
}

@Override
public void expireTicket(TicketType type) {

}

@Override
public void updateTicket(TicketType type, String ticket, int expiresInSeconds) {

}

@Override @Override
public String getAppid() { public String getAppid() {
return this.appId; return this.appId;


Loading…
Cancel
Save