mirror of
				https://gitee.com/binary/weixin-java-tools.git
				synced 2025-10-31 10:38:42 +08:00 
			
		
		
		
	refactor: mp 调整access token刷新策略
This commit is contained in:
		| @ -5,7 +5,7 @@ | |||||||
|     <token>企业号应用Token</token> |     <token>企业号应用Token</token> | ||||||
|     <aesKey>企业号应用EncodingAESKey</aesKey> |     <aesKey>企业号应用EncodingAESKey</aesKey> | ||||||
|     <accessToken>可以不填写</accessToken> |     <accessToken>可以不填写</accessToken> | ||||||
|     <expiresIn>可以不填写</expiresIn> |     <expiresTime>可以不填写</expiresTime> | ||||||
|     <userId>企业号通讯录里的某个userid</userId> |     <userId>企业号通讯录里的某个userid</userId> | ||||||
|     <departmentId>企业号通讯录的某个部门id</departmentId> |     <departmentId>企业号通讯录的某个部门id</departmentId> | ||||||
|     <tagId>企业号通讯录里的某个tagid</tagId> |     <tagId>企业号通讯录里的某个tagid</tagId> | ||||||
|  | |||||||
| @ -9,6 +9,15 @@ import me.chanjar.weixin.common.bean.WxAccessToken; | |||||||
|  */ |  */ | ||||||
| public interface WxMpConfigStorage { | public interface WxMpConfigStorage { | ||||||
|  |  | ||||||
|  |   public String getAccessToken(); | ||||||
|  |  | ||||||
|  |   public boolean isAccessTokenExpired(); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 强制将access token过期掉 | ||||||
|  |    */ | ||||||
|  |   public void expireAccessToken(); | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * 应该是线程安全的 |    * 应该是线程安全的 | ||||||
|    * @param accessToken |    * @param accessToken | ||||||
| @ -22,7 +31,20 @@ public interface WxMpConfigStorage { | |||||||
|    */ |    */ | ||||||
|   public void updateAccessToken(String accessToken, int expiresIn); |   public void updateAccessToken(String accessToken, int expiresIn); | ||||||
|  |  | ||||||
|   public String getAccessToken(); |   public String getJsapiTicket(); | ||||||
|  |  | ||||||
|  |   public boolean isJsapiTicketExpired(); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 强制将jsapi ticket过期掉 | ||||||
|  |    */ | ||||||
|  |   public void expireJsapiTicket(); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 应该是线程安全的 | ||||||
|  |    * @param jsapiTicket | ||||||
|  |    */ | ||||||
|  |   public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); | ||||||
|  |  | ||||||
|   public String getAppId(); |   public String getAppId(); | ||||||
|  |  | ||||||
| @ -32,7 +54,7 @@ public interface WxMpConfigStorage { | |||||||
|  |  | ||||||
|   public String getAesKey(); |   public String getAesKey(); | ||||||
|  |  | ||||||
|   public int getExpiresIn(); |   public long getExpiresTime(); | ||||||
|  |  | ||||||
|   public String getOauth2redirectUri(); |   public String getOauth2redirectUri(); | ||||||
|  |  | ||||||
| @ -42,17 +64,7 @@ public interface WxMpConfigStorage { | |||||||
|  |  | ||||||
|   public String getHttp_proxy_username(); |   public String getHttp_proxy_username(); | ||||||
|  |  | ||||||
|  |  | ||||||
|   public String getHttp_proxy_password(); |   public String getHttp_proxy_password(); | ||||||
|  |  | ||||||
|  |  | ||||||
|   public String getJsapiTicket(); |  | ||||||
|  |  | ||||||
|   public boolean isJsapiTokenExpired(); |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * 应该是线程安全的 |  | ||||||
|    * @param jsapiTicket |  | ||||||
|    */ |  | ||||||
|   public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||||
|   protected String token; |   protected String token; | ||||||
|   protected String accessToken; |   protected String accessToken; | ||||||
|   protected String aesKey; |   protected String aesKey; | ||||||
|   protected int expiresIn; |   protected long expiresTime; | ||||||
|  |  | ||||||
|   protected String oauth2redirectUri; |   protected String oauth2redirectUri; | ||||||
|  |  | ||||||
| @ -28,17 +28,43 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||||
|   protected String jsapiTicket; |   protected String jsapiTicket; | ||||||
|   protected long jsapiTicketExpiresTime; |   protected long jsapiTicketExpiresTime; | ||||||
|  |  | ||||||
|  |   public String getAccessToken() { | ||||||
|  |     return this.accessToken; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public boolean isAccessTokenExpired() { | ||||||
|  |     return System.currentTimeMillis() > this.expiresTime; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   public synchronized void updateAccessToken(WxAccessToken accessToken) { |   public synchronized void updateAccessToken(WxAccessToken accessToken) { | ||||||
|     updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); |     updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   public synchronized void updateAccessToken(String accessToken, int expiresIn) { |   public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { | ||||||
|     this.accessToken = accessToken; |     this.accessToken = accessToken; | ||||||
|     this.expiresIn = expiresIn; |     this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public String getAccessToken() { |   public void expireAccessToken() { | ||||||
|     return this.accessToken; |     this.expiresTime = 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public String getJsapiTicket() { | ||||||
|  |     return jsapiTicket; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public boolean isJsapiTicketExpired() { | ||||||
|  |     return System.currentTimeMillis() > this.jsapiTicketExpiresTime; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { | ||||||
|  |     this.jsapiTicket = jsapiTicket; | ||||||
|  |     // 预留200秒的时间 | ||||||
|  |     this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public void expireJsapiTicket() { | ||||||
|  |     this.jsapiTicketExpiresTime = 0; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public String getAppId() { |   public String getAppId() { | ||||||
| @ -53,8 +79,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||||
|     return this.token; |     return this.token; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public int getExpiresIn() { |   public long getExpiresTime() { | ||||||
|     return this.expiresIn; |     return this.expiresTime; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public void setAppId(String appId) { |   public void setAppId(String appId) { | ||||||
| @ -81,8 +107,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||||
|     this.accessToken = accessToken; |     this.accessToken = accessToken; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public void setExpiresIn(int expiresIn) { |   public void setExpiresTime(long expiresTime) { | ||||||
|     this.expiresIn = expiresIn; |     this.expiresTime = expiresTime; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Override |   @Override | ||||||
| @ -127,20 +153,6 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|   public String getJsapiTicket() { |  | ||||||
|     return jsapiTicket; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public boolean isJsapiTokenExpired() { |  | ||||||
|     return System.currentTimeMillis() > this.jsapiTicketExpiresTime; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { |  | ||||||
|     this.jsapiTicket = jsapiTicket; |  | ||||||
|     // 预留200秒的时间 |  | ||||||
|     this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @Override |   @Override | ||||||
|   public String toString() { |   public String toString() { | ||||||
|     return "WxMpInMemoryConfigStorage{" + |     return "WxMpInMemoryConfigStorage{" + | ||||||
| @ -149,7 +161,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||||
|         ", token='" + token + '\'' + |         ", token='" + token + '\'' + | ||||||
|         ", accessToken='" + accessToken + '\'' + |         ", accessToken='" + accessToken + '\'' + | ||||||
|         ", aesKey='" + aesKey + '\'' + |         ", aesKey='" + aesKey + '\'' + | ||||||
|         ", expiresIn=" + expiresIn + |         ", expiresTime=" + expiresTime + | ||||||
|         ", http_proxy_host='" + http_proxy_host + '\'' + |         ", http_proxy_host='" + http_proxy_host + '\'' + | ||||||
|         ", http_proxy_port=" + http_proxy_port + |         ", http_proxy_port=" + http_proxy_port + | ||||||
|         ", http_proxy_username='" + http_proxy_username + '\'' + |         ", http_proxy_username='" + http_proxy_username + '\'' + | ||||||
|  | |||||||
| @ -40,9 +40,10 @@ public interface WxMpService { | |||||||
|  |  | ||||||
|    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token |    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token | ||||||
|    * </pre> |    * </pre> | ||||||
|  |    * @return | ||||||
|    * @throws me.chanjar.weixin.common.exception.WxErrorException |    * @throws me.chanjar.weixin.common.exception.WxErrorException | ||||||
|    */ |    */ | ||||||
|   public void accessTokenRefresh() throws WxErrorException; |   public String getAccessToken() throws WxErrorException; | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * <pre> |    * <pre> | ||||||
|  | |||||||
| @ -44,11 +44,14 @@ import java.util.concurrent.atomic.AtomicBoolean; | |||||||
| public class WxMpServiceImpl implements WxMpService { | public class WxMpServiceImpl implements WxMpService { | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * 全局的是否正在刷新Access Token的flag |    * 全局的是否正在刷新access token的锁 | ||||||
|    * true: 正在刷新 |  | ||||||
|    * false: 没有刷新 |  | ||||||
|    */ |    */ | ||||||
|   protected static final AtomicBoolean GLOBAL_ACCESS_TOKEN_REFRESH_FLAG = new AtomicBoolean(false); |   protected static final Object GLOBAL_ACCESS_TOKEN_REFRESH_LOCK = new Object(); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 全局的是否正在刷新jsapi_ticket的锁 | ||||||
|  |    */ | ||||||
|  |   protected static final Object GLOBAL_JSAPI_TICKET_REFRESH_LOCK = new Object(); | ||||||
|  |  | ||||||
|   protected WxMpConfigStorage wxMpConfigStorage; |   protected WxMpConfigStorage wxMpConfigStorage; | ||||||
|    |    | ||||||
| @ -66,9 +69,10 @@ public class WxMpServiceImpl implements WxMpService { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   public void accessTokenRefresh() throws WxErrorException { |   public String getAccessToken() throws WxErrorException { | ||||||
|     if (!GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.getAndSet(true)) { |     if (wxMpConfigStorage.isAccessTokenExpired()) { | ||||||
|       try { |       synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) { | ||||||
|  |         if (wxMpConfigStorage.isAccessTokenExpired()) { | ||||||
|           String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" |           String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" | ||||||
|               + "&appid=" + wxMpConfigStorage.getAppId() |               + "&appid=" + wxMpConfigStorage.getAppId() | ||||||
|               + "&secret=" + wxMpConfigStorage.getSecret() |               + "&secret=" + wxMpConfigStorage.getSecret() | ||||||
| @ -93,25 +97,17 @@ public class WxMpServiceImpl implements WxMpService { | |||||||
|           } catch (IOException e) { |           } catch (IOException e) { | ||||||
|             throw new RuntimeException(e); |             throw new RuntimeException(e); | ||||||
|           } |           } | ||||||
|       } finally { |  | ||||||
|         GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.set(false); |  | ||||||
|       } |  | ||||||
|     } else { |  | ||||||
|       // 每隔100ms检查一下是否刷新完毕了 |  | ||||||
|       while (GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.get()) { |  | ||||||
|         try { |  | ||||||
|           Thread.sleep(100); |  | ||||||
|         } catch (InterruptedException e) { |  | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       // 刷新完毕了,就没他什么事儿了 |  | ||||||
|     } |     } | ||||||
|  |     return wxMpConfigStorage.getAccessToken(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|   public String getJsapiTicket() throws WxErrorException { |   public String getJsapiTicket() throws WxErrorException { | ||||||
|     if (wxMpConfigStorage.isJsapiTokenExpired()) { |     if (wxMpConfigStorage.isJsapiTicketExpired()) { | ||||||
|       synchronized (wxMpConfigStorage) { |       synchronized (GLOBAL_JSAPI_TICKET_REFRESH_LOCK) { | ||||||
|         if (wxMpConfigStorage.isJsapiTokenExpired()) { |         if (wxMpConfigStorage.isJsapiTicketExpired()) { | ||||||
|           String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi"; |           String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi"; | ||||||
|           String responseContent = execute(new SimpleGetRequestExecutor(), url, null); |           String responseContent = execute(new SimpleGetRequestExecutor(), url, null); | ||||||
|           JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); |           JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | ||||||
| @ -440,10 +436,7 @@ public class WxMpServiceImpl implements WxMpService { | |||||||
|    * @throws WxErrorException |    * @throws WxErrorException | ||||||
|    */ |    */ | ||||||
|   public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { |   public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | ||||||
|     if (StringUtils.isBlank(wxMpConfigStorage.getAccessToken())) { |     String accessToken = getAccessToken(); | ||||||
|       accessTokenRefresh(); |  | ||||||
|     } |  | ||||||
|     String accessToken = wxMpConfigStorage.getAccessToken(); |  | ||||||
|      |      | ||||||
|     String uriWithAccessToken = uri; |     String uriWithAccessToken = uri; | ||||||
|     uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; |     uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; | ||||||
| @ -458,7 +451,8 @@ public class WxMpServiceImpl implements WxMpService { | |||||||
|        * 42001 access_token超时 |        * 42001 access_token超时 | ||||||
|        */ |        */ | ||||||
|       if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { |       if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { | ||||||
|         accessTokenRefresh(); |         // 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | ||||||
|  |         wxMpConfigStorage.expireAccessToken(); | ||||||
|         return execute(executor, uri, data); |         return execute(executor, uri, data); | ||||||
|       } |       } | ||||||
|       /** |       /** | ||||||
|  | |||||||
| @ -65,7 +65,7 @@ public class WxMpOAuth2AccessToken { | |||||||
|   public String toString() { |   public String toString() { | ||||||
|     return "WxMpOAuth2AccessToken{" + |     return "WxMpOAuth2AccessToken{" + | ||||||
|         "accessToken='" + accessToken + '\'' + |         "accessToken='" + accessToken + '\'' + | ||||||
|         ", expiresIn=" + expiresIn + |         ", expiresTime=" + expiresIn + | ||||||
|         ", refreshToken='" + refreshToken + '\'' + |         ", refreshToken='" + refreshToken + '\'' + | ||||||
|         ", openId='" + openId + '\'' + |         ", openId='" + openId + '\'' + | ||||||
|         ", scope='" + scope + '\'' + |         ", scope='" + scope + '\'' + | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ public class ApiTestModule implements Module { | |||||||
|     @Override |     @Override | ||||||
|     public String toString() { |     public String toString() { | ||||||
|       return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken |       return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken | ||||||
|           + ", expiresIn=" + expiresIn + ", token=" + token + ", openId=" + openId + "]"; |           + ", expiresTime=" + expiresTime + ", token=" + token + ", openId=" + openId + "]"; | ||||||
|     } |     } | ||||||
|       |       | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -22,7 +22,7 @@ public class WxMpBaseAPITest { | |||||||
|   public void testRefreshAccessToken() throws WxErrorException { |   public void testRefreshAccessToken() throws WxErrorException { | ||||||
|     WxMpConfigStorage configStorage = wxService.wxMpConfigStorage; |     WxMpConfigStorage configStorage = wxService.wxMpConfigStorage; | ||||||
|     String before = configStorage.getAccessToken(); |     String before = configStorage.getAccessToken(); | ||||||
|     wxService.accessTokenRefresh(); |     wxService.getAccessToken(); | ||||||
|  |  | ||||||
|     String after = configStorage.getAccessToken(); |     String after = configStorage.getAccessToken(); | ||||||
|  |  | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ class WxMpDemoInMemoryConfigStorage extends WxMpInMemoryConfigStorage { | |||||||
|   @Override |   @Override | ||||||
|   public String toString() { |   public String toString() { | ||||||
|     return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken |     return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken | ||||||
|         + ", expiresIn=" + expiresIn + ", token=" + token + ", aesKey=" + aesKey + "]"; |         + ", expiresTime=" + expiresTime + ", token=" + token + ", aesKey=" + aesKey + "]"; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
|     <token>公众号Token</token> |     <token>公众号Token</token> | ||||||
|     <aesKey>公众号EncodingAESKey</aesKey> |     <aesKey>公众号EncodingAESKey</aesKey> | ||||||
|     <accessToken>可以不填写</accessToken> |     <accessToken>可以不填写</accessToken> | ||||||
|     <expiresIn>可以不填写</expiresIn> |     <expiresTime>可以不填写</expiresTime> | ||||||
|     <openId>某个加你公众号的用户的openId</openId> |     <openId>某个加你公众号的用户的openId</openId> | ||||||
|     <oauth2redirectUri>网页授权获取用户信息回调地址</oauth2redirectUri> |     <oauth2redirectUri>网页授权获取用户信息回调地址</oauth2redirectUri> | ||||||
| </xml> | </xml> | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Daniel Qian
					Daniel Qian