From cef8ac01e89307bd37784bae46836785bfc43435 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Tue, 20 Jan 2015 12:01:33 +0800 Subject: [PATCH 1/7] issue #74; git commit -m issue --- .../weixin/common/util/crypto/SHA1.java | 34 +++++++++++++++---- .../weixin/mp/api/WxMpConfigStorage.java | 24 +++++++++++-- .../mp/api/WxMpInMemoryConfigStorage.java | 24 +++++++++++-- .../me/chanjar/weixin/mp/api/WxMpService.java | 31 +++++++++++++++-- .../weixin/mp/api/WxMpServiceImpl.java | 34 ++++++++++++++++++- 5 files changed, 133 insertions(+), 14 deletions(-) diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java index cc2333086..c97facd30 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java @@ -10,27 +10,49 @@ import java.util.Arrays; public class SHA1 { /** - * 生成SHA1签名 + * 串接arr参数,生成sha1 digest + * * @param arr * @return */ public static String gen(String... arr) throws NoSuchAlgorithmException { Arrays.sort(arr); StringBuilder sb = new StringBuilder(); - for(String a : arr) { + for (String a : arr) { sb.append(a); } + return genStr(sb.toString()); + } + /** + * 用&串接arr参数,生成sha1 digest + * + * @param arr + * @return + */ + public static String genWithAmple(String... arr) throws NoSuchAlgorithmException { + Arrays.sort(arr); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < arr.length; i++) { + String a = arr[i]; + sb.append(a); + if (i != arr.length - 1) { + sb.append('&'); + } + } + return genStr(sb.toString()); + } + + public static String genStr(String str) throws NoSuchAlgorithmException { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); - sha1.update(sb.toString().getBytes()); + sha1.update(str.getBytes()); byte[] output = sha1.digest(); return bytesToHex(output); } - protected static String bytesToHex(byte[] b) { - char hexDigit[] = {'0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + char hexDigit[] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; StringBuffer buf = new StringBuffer(); for (int j = 0; j < b.length; j++) { buf.append(hexDigit[(b[j] >> 4) & 0x0f]); diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java index 3ecbf155a..68d427851 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java @@ -9,10 +9,19 @@ import me.chanjar.weixin.common.bean.WxAccessToken; */ public interface WxMpConfigStorage { + /** + * 应该是线程安全的 + * @param accessToken + */ public void updateAccessToken(WxAccessToken accessToken); - + + /** + * 应该是线程安全的 + * @param accessToken + * @param expiresIn + */ public void updateAccessToken(String accessToken, int expiresIn); - + public String getAccessToken(); public String getAppId(); @@ -35,4 +44,15 @@ public interface WxMpConfigStorage { public String getHttp_proxy_password(); + + public String getJsapiTicket(); + + public boolean isJsapiTokenExpired(); + + /** + * 应该是线程安全的 + * @param jsapiTicket + */ + public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); + } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java index 82c3d6212..d278d1b73 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java @@ -9,6 +9,8 @@ import me.chanjar.weixin.common.bean.WxAccessToken; */ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { + private static final long l = 7000 * 1000l; + protected String appId; protected String secret; protected String token; @@ -23,11 +25,14 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { protected String http_proxy_username; protected String http_proxy_password; - public void updateAccessToken(WxAccessToken accessToken) { + protected String jsapiTicket; + protected long jsapiTicketExpiresTime; + + public synchronized void updateAccessToken(WxAccessToken accessToken) { updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); } - public void updateAccessToken(String accessToken, int expiresIn) { + public synchronized void updateAccessToken(String accessToken, int expiresIn) { this.accessToken = accessToken; this.expiresIn = expiresIn; } @@ -121,6 +126,21 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { this.http_proxy_password = http_proxy_password; } + + 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 public String toString() { return "WxMpInMemoryConfigStorage{" + diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java index c4dc3e541..e8a56787e 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java @@ -43,17 +43,42 @@ public interface WxMpService { * @throws me.chanjar.weixin.common.exception.WxErrorException */ public void accessTokenRefresh() throws WxErrorException; - + + /** + *
+   * 获得jsapi_ticket
+   * 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
+   *
+   * 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95
+   * 
+ * @return + * @throws WxErrorException + */ + public String getJsapiTicket() throws WxErrorException; + + /** + *
+   * 创建调用jsapi时所需要的签名
+   *
+   * 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95
+   * 
+ * @param timestamp 时间戳 + * @param noncestr 用户自己生成的随机字符串 + * @param url url + * @return + */ + public String createJsapiSignature(String timestamp, String noncestr, String url) throws WxErrorException; + /** *
    * 上传多媒体文件
-   * 
+   *
    * 上传的多媒体文件有格式和大小限制,如下:
    *   图片(image): 1M,支持JPG格式
    *   语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式
    *   视频(video):10MB,支持MP4格式
    *   缩略图(thumb):64KB,支持JPG格式
-   *    
+   *
    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件
    * 
* @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java index dbf2adc40..636e8f622 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java @@ -36,6 +36,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; +import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; @@ -106,7 +107,38 @@ public class WxMpServiceImpl implements WxMpService { // 刷新完毕了,就没他什么事儿了 } } - + + public String getJsapiTicket() throws WxErrorException { + if (wxMpConfigStorage.isJsapiTokenExpired()) { + synchronized (wxMpConfigStorage) { + if (wxMpConfigStorage.isJsapiTokenExpired()) { + String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi"; + String responseContent = execute(new SimpleGetRequestExecutor(), url, null); + JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); + JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); + String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); + int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); + wxMpConfigStorage.updateJsapiTicket(jsapiTicket, expiresInSeconds); + } + } + } + return wxMpConfigStorage.getJsapiTicket(); + } + + public String createJsapiSignature(String timestamp, String noncestr, String url) throws WxErrorException { + String jsapiTicket = getJsapiTicket(); + try { + return SHA1.genWithAmple( + "jsapi_ticket=" + jsapiTicket, + "timestamp=" + timestamp, + "noncestr=" + noncestr, + "url=" + url + ); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + public void customMessageSend(WxMpCustomMessage message) throws WxErrorException { String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send"; execute(new SimplePostRequestExecutor(), url, message.toJson()); From 1bca854b52e416be66916d4e85dec998bd734298 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Tue, 20 Jan 2015 13:52:40 +0800 Subject: [PATCH 2/7] =?UTF-8?q?refactor:=20mp=20=E8=B0=83=E6=95=B4access?= =?UTF-8?q?=20token=E5=88=B7=E6=96=B0=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/resources/test-config.sample.xml | 2 +- .../weixin/mp/api/WxMpConfigStorage.java | 44 +++++---- .../mp/api/WxMpInMemoryConfigStorage.java | 60 +++++++----- .../me/chanjar/weixin/mp/api/WxMpService.java | 3 +- .../weixin/mp/api/WxMpServiceImpl.java | 92 +++++++++---------- .../mp/bean/result/WxMpOAuth2AccessToken.java | 2 +- .../chanjar/weixin/mp/api/ApiTestModule.java | 2 +- .../weixin/mp/api/WxMpBaseAPITest.java | 2 +- .../demo/WxMpDemoInMemoryConfigStorage.java | 2 +- .../src/test/resources/test-config.sample.xml | 2 +- 10 files changed, 115 insertions(+), 96 deletions(-) diff --git a/weixin-java-cp/src/test/resources/test-config.sample.xml b/weixin-java-cp/src/test/resources/test-config.sample.xml index 02c98f3f7..aa99a962b 100644 --- a/weixin-java-cp/src/test/resources/test-config.sample.xml +++ b/weixin-java-cp/src/test/resources/test-config.sample.xml @@ -5,7 +5,7 @@ 企业号应用Token 企业号应用EncodingAESKey 可以不填写 - 可以不填写 + 可以不填写 企业号通讯录里的某个userid 企业号通讯录的某个部门id 企业号通讯录里的某个tagid diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java index 68d427851..c54a119db 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java @@ -9,6 +9,15 @@ import me.chanjar.weixin.common.bean.WxAccessToken; */ public interface WxMpConfigStorage { + public String getAccessToken(); + + public boolean isAccessTokenExpired(); + + /** + * 强制将access token过期掉 + */ + public void expireAccessToken(); + /** * 应该是线程安全的 * @param accessToken @@ -22,17 +31,30 @@ public interface WxMpConfigStorage { */ 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 getSecret(); - + public String getToken(); public String getAesKey(); - public int getExpiresIn(); + public long getExpiresTime(); public String getOauth2redirectUri(); @@ -42,17 +64,7 @@ public interface WxMpConfigStorage { public String getHttp_proxy_username(); + public String getHttp_proxy_password(); - - public String getJsapiTicket(); - - public boolean isJsapiTokenExpired(); - - /** - * 应该是线程安全的 - * @param jsapiTicket - */ - public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); - } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java index d278d1b73..c694dde84 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java @@ -16,7 +16,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { protected String token; protected String accessToken; protected String aesKey; - protected int expiresIn; + protected long expiresTime; protected String oauth2redirectUri; @@ -28,17 +28,43 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { protected String jsapiTicket; protected long jsapiTicketExpiresTime; + public String getAccessToken() { + return this.accessToken; + } + + public boolean isAccessTokenExpired() { + return System.currentTimeMillis() > this.expiresTime; + } + public synchronized void updateAccessToken(WxAccessToken accessToken) { 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.expiresIn = expiresIn; + this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; } - public String getAccessToken() { - return this.accessToken; + public void expireAccessToken() { + 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() { @@ -53,8 +79,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { return this.token; } - public int getExpiresIn() { - return this.expiresIn; + public long getExpiresTime() { + return this.expiresTime; } public void setAppId(String appId) { @@ -81,8 +107,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { this.accessToken = accessToken; } - public void setExpiresIn(int expiresIn) { - this.expiresIn = expiresIn; + public void setExpiresTime(long expiresTime) { + this.expiresTime = expiresTime; } @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 public String toString() { return "WxMpInMemoryConfigStorage{" + @@ -149,7 +161,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { ", token='" + token + '\'' + ", accessToken='" + accessToken + '\'' + ", aesKey='" + aesKey + '\'' + - ", expiresIn=" + expiresIn + + ", expiresTime=" + expiresTime + ", http_proxy_host='" + http_proxy_host + '\'' + ", http_proxy_port=" + http_proxy_port + ", http_proxy_username='" + http_proxy_username + '\'' + diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java index e8a56787e..cef912ee5 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java @@ -40,9 +40,10 @@ public interface WxMpService { * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token * + * @return * @throws me.chanjar.weixin.common.exception.WxErrorException */ - public void accessTokenRefresh() throws WxErrorException; + public String getAccessToken() throws WxErrorException; /** *
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
index 636e8f622..0bb1c550a 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
@@ -44,12 +44,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
 public class WxMpServiceImpl implements WxMpService {
 
   /**
-   * 全局的是否正在刷新Access Token的flag
-   * true: 正在刷新
-   * false: 没有刷新
+   * 全局的是否正在刷新access token的锁
    */
-  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 final ThreadLocal retryTimes = new ThreadLocal();
@@ -66,52 +69,45 @@ public class WxMpServiceImpl implements WxMpService {
     }
   }
   
-  public void accessTokenRefresh() throws WxErrorException {
-    if (!GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.getAndSet(true)) {
-      try {
-        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"
-            + "&appid=" + wxMpConfigStorage.getAppId()
-            + "&secret=" + wxMpConfigStorage.getSecret()
-            ;
-        try {
-          HttpGet httpGet = new HttpGet(url);
-          if (httpProxy != null) {
-            RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
-            httpGet.setConfig(config);
+  public String getAccessToken() throws WxErrorException {
+    if (wxMpConfigStorage.isAccessTokenExpired()) {
+      synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) {
+        if (wxMpConfigStorage.isAccessTokenExpired()) {
+          String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"
+              + "&appid=" + wxMpConfigStorage.getAppId()
+              + "&secret=" + wxMpConfigStorage.getSecret()
+              ;
+          try {
+            HttpGet httpGet = new HttpGet(url);
+            if (httpProxy != null) {
+              RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
+              httpGet.setConfig(config);
+            }
+            CloseableHttpClient httpclient = getHttpclient();
+            CloseableHttpResponse response = httpclient.execute(httpGet);
+            String resultContent = new BasicResponseHandler().handleResponse(response);
+            WxError error = WxError.fromJson(resultContent);
+            if (error.getErrorCode() != 0) {
+              throw new WxErrorException(error);
+            }
+            WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
+            wxMpConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
+          } catch (ClientProtocolException e) {
+            throw new RuntimeException(e);
+          } catch (IOException e) {
+            throw new RuntimeException(e);
           }
-          CloseableHttpClient httpclient = getHttpclient();
-          CloseableHttpResponse response = httpclient.execute(httpGet);
-          String resultContent = new BasicResponseHandler().handleResponse(response);
-          WxError error = WxError.fromJson(resultContent);
-          if (error.getErrorCode() != 0) {
-            throw new WxErrorException(error);
-          }
-          WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
-          wxMpConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
-        } catch (ClientProtocolException e) {
-          throw new RuntimeException(e);
-        } catch (IOException 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 {
-    if (wxMpConfigStorage.isJsapiTokenExpired()) {
-      synchronized (wxMpConfigStorage) {
-        if (wxMpConfigStorage.isJsapiTokenExpired()) {
+    if (wxMpConfigStorage.isJsapiTicketExpired()) {
+      synchronized (GLOBAL_JSAPI_TICKET_REFRESH_LOCK) {
+        if (wxMpConfigStorage.isJsapiTicketExpired()) {
           String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi";
           String responseContent = execute(new SimpleGetRequestExecutor(), url, null);
           JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
@@ -440,10 +436,7 @@ public class WxMpServiceImpl implements WxMpService {
    * @throws WxErrorException
    */
   public  T execute(RequestExecutor executor, String uri, E data) throws WxErrorException {
-    if (StringUtils.isBlank(wxMpConfigStorage.getAccessToken())) {
-      accessTokenRefresh();
-    }
-    String accessToken = wxMpConfigStorage.getAccessToken();
+    String accessToken = getAccessToken();
     
     String uriWithAccessToken = uri;
     uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
@@ -458,7 +451,8 @@ public class WxMpServiceImpl implements WxMpService {
        * 42001 access_token超时
        */
       if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) {
-        accessTokenRefresh();
+        // 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
+        wxMpConfigStorage.expireAccessToken();
         return execute(executor, uri, data);
       }
       /**
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java
index c2a285469..d2a6ef8ec 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java
@@ -65,7 +65,7 @@ public class WxMpOAuth2AccessToken {
   public String toString() {
     return "WxMpOAuth2AccessToken{" +
         "accessToken='" + accessToken + '\'' +
-        ", expiresIn=" + expiresIn +
+        ", expiresTime=" + expiresIn +
         ", refreshToken='" + refreshToken + '\'' +
         ", openId='" + openId + '\'' +
         ", scope='" + scope + '\'' +
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/ApiTestModule.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/ApiTestModule.java
index ad006acd4..e6df8e343 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/ApiTestModule.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/ApiTestModule.java
@@ -42,7 +42,7 @@ public class ApiTestModule implements Module {
     @Override
     public String toString() {
       return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken
-          + ", expiresIn=" + expiresIn + ", token=" + token + ", openId=" + openId + "]";
+          + ", expiresTime=" + expiresTime + ", token=" + token + ", openId=" + openId + "]";
     }
      
   }
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
index ef9590e69..e773f117b 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
@@ -22,7 +22,7 @@ public class WxMpBaseAPITest {
   public void testRefreshAccessToken() throws WxErrorException {
     WxMpConfigStorage configStorage = wxService.wxMpConfigStorage;
     String before = configStorage.getAccessToken();
-    wxService.accessTokenRefresh();
+    wxService.getAccessToken();
 
     String after = configStorage.getAccessToken();
 
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoInMemoryConfigStorage.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoInMemoryConfigStorage.java
index 22d87dd49..becfbeb4d 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoInMemoryConfigStorage.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoInMemoryConfigStorage.java
@@ -16,7 +16,7 @@ class WxMpDemoInMemoryConfigStorage extends WxMpInMemoryConfigStorage {
   @Override
   public String toString() {
     return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken
-        + ", expiresIn=" + expiresIn + ", token=" + token + ", aesKey=" + aesKey + "]";
+        + ", expiresTime=" + expiresTime + ", token=" + token + ", aesKey=" + aesKey + "]";
   }
 
 
diff --git a/weixin-java-mp/src/test/resources/test-config.sample.xml b/weixin-java-mp/src/test/resources/test-config.sample.xml
index 19535f3c7..6ec4825a9 100644
--- a/weixin-java-mp/src/test/resources/test-config.sample.xml
+++ b/weixin-java-mp/src/test/resources/test-config.sample.xml
@@ -4,7 +4,7 @@
     公众号Token
     公众号EncodingAESKey
     可以不填写
-    可以不填写
+    可以不填写
     某个加你公众号的用户的openId
     网页授权获取用户信息回调地址
 

From 1daf3a63a1051d4d5d81bc792e57a01a1ae08478 Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Tue, 20 Jan 2015 14:13:11 +0800
Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20CP=20=E8=B0=83=E6=95=B4access?=
 =?UTF-8?q?=20token=E5=88=B7=E6=96=B0=E7=AD=96=E7=95=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../weixin/cp/api/WxCpConfigStorage.java      | 20 +++--
 .../cp/api/WxCpInMemoryConfigStorage.java     | 32 +++++---
 .../me/chanjar/weixin/cp/api/WxCpService.java |  3 +-
 .../weixin/cp/api/WxCpServiceImpl.java        | 76 ++++++++-----------
 .../weixin/cp/api/WxCpBaseAPITest.java        |  2 +-
 .../demo/WxCpDemoInMemoryConfigStorage.java   |  2 +-
 6 files changed, 70 insertions(+), 65 deletions(-)

diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java
index 36c9cfbab..44123a36a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java
@@ -9,12 +9,20 @@ import me.chanjar.weixin.common.bean.WxAccessToken;
  */
 public interface WxCpConfigStorage {
 
-  public void updateAccessToken(WxAccessToken accessToken);
-  
-  public void updateAccessToken(String accessToken, int expiresIn);
-  
   public String getAccessToken();
-  
+
+  public boolean isAccessTokenExpired();
+
+  /**
+   * 强制将access token过期掉
+   */
+  public void expireAccessToken();
+
+  public void updateAccessToken(WxAccessToken accessToken);
+
+  public void updateAccessToken(String accessToken, int expiresIn);
+
+
   public String getCorpId();
   
   public String getCorpSecret();
@@ -25,7 +33,7 @@ public interface WxCpConfigStorage {
 
   public String getAesKey();
 
-  public int getExpiresIn();
+  public long getExpiresTime();
 
   public String getOauth2redirectUri();
 
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java
index 9ff1f1878..dd4882e29 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java
@@ -16,7 +16,7 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {
   protected String accessToken;
   protected String aesKey;
   protected String agentId;
-  protected int expiresIn;
+  protected long expiresTime;
 
   protected String oauth2redirectUri;
 
@@ -25,17 +25,25 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {
   protected String http_proxy_username;
   protected String http_proxy_password;
 
+  public String getAccessToken() {
+    return this.accessToken;
+  }
+
+  public boolean isAccessTokenExpired() {
+    return System.currentTimeMillis() > this.expiresTime;
+  }
+
+  public void expireAccessToken() {
+    this.expiresTime = 0;
+  }
+
   public void updateAccessToken(WxAccessToken accessToken) {
     updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
   }
   
-  public void updateAccessToken(String accessToken, int expiresIn) {
+  public void updateAccessToken(String accessToken, int expiresInSeconds) {
     this.accessToken = accessToken;
-    this.expiresIn = expiresIn;
-  }
-
-  public String getAccessToken() {
-    return this.accessToken;
+    this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l;
   }
 
   public String getCorpId() {
@@ -50,8 +58,8 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {
     return this.token;
   }
 
-  public int getExpiresIn() {
-    return this.expiresIn;
+  public long getExpiresTime() {
+    return this.expiresTime;
   }
 
   public void setCorpId(String corpId) {
@@ -78,8 +86,8 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {
     this.accessToken = accessToken;
   }
 
-  public void setExpiresIn(int expiresIn) {
-    this.expiresIn = expiresIn;
+  public void setExpiresTime(long expiresTime) {
+    this.expiresTime = expiresTime;
   }
 
   public String getAgentId() {
@@ -140,7 +148,7 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {
         ", accessToken='" + accessToken + '\'' +
         ", aesKey='" + aesKey + '\'' +
         ", agentId='" + agentId + '\'' +
-        ", expiresIn=" + expiresIn +
+        ", expiresTime=" + expiresTime +
         ", http_proxy_host='" + http_proxy_host + '\'' +
         ", http_proxy_port=" + http_proxy_port +
         ", http_proxy_username='" + http_proxy_username + '\'' +
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
index 808eb6ce3..c81bf715e 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
@@ -52,9 +52,10 @@ public interface WxCpService {
    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
    * 
* + * @return * @throws me.chanjar.weixin.common.exception.WxErrorException */ - public void accessTokenRefresh() throws WxErrorException; + public String getAccessToken() throws WxErrorException; /** *
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java
index 2e0738c72..c38ea65fb 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java
@@ -46,11 +46,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
 public class WxCpServiceImpl implements WxCpService {
 
   /**
-   * 全局的是否正在刷新Access Token的flag
-   * true: 正在刷新
-   * false: 没有刷新
+   * 全局的是否正在刷新access token的锁
    */
-  protected static final AtomicBoolean GLOBAL_ACCESS_TOKEN_REFRESH_FLAG = new AtomicBoolean(false);
+  protected static final Object GLOBAL_ACCESS_TOKEN_REFRESH_LOCK = new Object();
 
   protected WxCpConfigStorage wxCpConfigStorage;
 
@@ -73,45 +71,37 @@ public class WxCpServiceImpl implements WxCpService {
     execute(new SimpleGetRequestExecutor(), url, null);
   }
 
-  public void accessTokenRefresh() throws WxErrorException {
-    if (!GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.getAndSet(true)) {
-      try {
-        String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
-            + "&corpid=" + wxCpConfigStorage.getCorpId()
-            + "&corpsecret=" + wxCpConfigStorage.getCorpSecret();
-        try {
-          HttpGet httpGet = new HttpGet(url);
-          if (httpProxy != null) {
-            RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
-            httpGet.setConfig(config);
+  public String getAccessToken() throws WxErrorException {
+    if (wxCpConfigStorage.isAccessTokenExpired()) {
+      synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) {
+        if (wxCpConfigStorage.isAccessTokenExpired()) {
+          String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
+              + "&corpid=" + wxCpConfigStorage.getCorpId()
+              + "&corpsecret=" + wxCpConfigStorage.getCorpSecret();
+          try {
+            HttpGet httpGet = new HttpGet(url);
+            if (httpProxy != null) {
+              RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
+              httpGet.setConfig(config);
+            }
+            CloseableHttpClient httpclient = getHttpclient();
+            CloseableHttpResponse response = httpclient.execute(httpGet);
+            String resultContent = new BasicResponseHandler().handleResponse(response);
+            WxError error = WxError.fromJson(resultContent);
+            if (error.getErrorCode() != 0) {
+              throw new WxErrorException(error);
+            }
+            WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
+            wxCpConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
+          } catch (ClientProtocolException e) {
+            throw new RuntimeException(e);
+          } catch (IOException e) {
+            throw new RuntimeException(e);
           }
-          CloseableHttpClient httpclient = getHttpclient();
-          CloseableHttpResponse response = httpclient.execute(httpGet);
-          String resultContent = new BasicResponseHandler().handleResponse(response);
-          WxError error = WxError.fromJson(resultContent);
-          if (error.getErrorCode() != 0) {
-            throw new WxErrorException(error);
-          }
-          WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
-          wxCpConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
-        } catch (ClientProtocolException e) {
-          throw new RuntimeException(e);
-        } catch (IOException 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 wxCpConfigStorage.getAccessToken();
   }
 
   public void messageSend(WxCpMessage message) throws WxErrorException {
@@ -369,10 +359,7 @@ public class WxCpServiceImpl implements WxCpService {
    * @throws WxErrorException
    */
   public  T execute(RequestExecutor executor, String uri, E data) throws WxErrorException {
-    if (StringUtils.isBlank(wxCpConfigStorage.getAccessToken())) {
-      accessTokenRefresh();
-    }
-    String accessToken = wxCpConfigStorage.getAccessToken();
+    String accessToken = getAccessToken();
 
     String uriWithAccessToken = uri;
     uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
@@ -387,7 +374,8 @@ public class WxCpServiceImpl implements WxCpService {
        * 42001 access_token超时
        */
       if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) {
-        accessTokenRefresh();
+        // 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
+        wxCpConfigStorage.expireAccessToken();
         return execute(executor, uri, data);
       }
       /**
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java
index 6ed438a96..606cca9b1 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java
@@ -22,7 +22,7 @@ public class WxCpBaseAPITest {
   public void testRefreshAccessToken() throws WxErrorException {
     WxCpConfigStorage configStorage = wxService.wxCpConfigStorage;
     String before = configStorage.getAccessToken();
-    wxService.accessTokenRefresh();
+    wxService.getAccessToken();
 
     String after = configStorage.getAccessToken();
 
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java
index 1d9103a5f..522198683 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java
@@ -16,7 +16,7 @@ class WxCpDemoInMemoryConfigStorage extends WxCpInMemoryConfigStorage {
   @Override
   public String toString() {
     return "SimpleWxConfigProvider [appidOrCorpid=" + corpId + ", corpSecret=" + corpSecret + ", accessToken=" + accessToken
-        + ", expiresIn=" + expiresIn + ", token=" + token + ", aesKey=" + aesKey + "]";
+        + ", expiresTime=" + expiresTime + ", token=" + token + ", aesKey=" + aesKey + "]";
   }
 
 

From ae99bcdb639ee73c9816d66059f4971236c8b376 Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Tue, 20 Jan 2015 14:15:24 +0800
Subject: [PATCH 4/7] issue #74

---
 .../src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
index 0bb1c550a..18d3bcdd2 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
@@ -126,8 +126,8 @@ public class WxMpServiceImpl implements WxMpService {
     try {
       return SHA1.genWithAmple(
           "jsapi_ticket=" + jsapiTicket,
-          "timestamp=" + timestamp,
           "noncestr=" + noncestr,
+          "timestamp=" + timestamp,
           "url=" + url
       );
     } catch (NoSuchAlgorithmException e) {

From 5c7448f212d556e677454f26c42dad99d546a16c Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Tue, 20 Jan 2015 14:30:44 +0800
Subject: [PATCH 5/7] =?UTF-8?q?=E9=92=88=E5=AF=B9AccessToken=EF=BC=8Cjsapi?=
 =?UTF-8?q?=20ticket=E6=B7=BB=E5=8A=A0=E5=BC=BA=E5=88=B6=E5=88=B7=E6=96=B0?=
 =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../java/me/chanjar/weixin/cp/api/WxCpService.java |  4 ++--
 .../me/chanjar/weixin/cp/api/WxCpServiceImpl.java  |  7 +++++--
 .../java/me/chanjar/weixin/mp/api/WxMpService.java |  6 ++++--
 .../me/chanjar/weixin/mp/api/WxMpServiceImpl.java  | 14 ++++++++++----
 4 files changed, 21 insertions(+), 10 deletions(-)

diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
index c81bf715e..36c78f0c4 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
@@ -51,11 +51,11 @@ public interface WxCpService {
    * 程序员在非必要情况下尽量不要主动调用此方法
    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
    * 
- * + * @param forceRefresh 强制刷新 * @return * @throws me.chanjar.weixin.common.exception.WxErrorException */ - public String getAccessToken() throws WxErrorException; + public String getAccessToken(boolean forceRefresh) throws WxErrorException; /** *
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java
index c38ea65fb..fb7255c6c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java
@@ -71,7 +71,10 @@ public class WxCpServiceImpl implements WxCpService {
     execute(new SimpleGetRequestExecutor(), url, null);
   }
 
-  public String getAccessToken() throws WxErrorException {
+  public String getAccessToken(boolean forceRefresh) throws WxErrorException {
+    if (forceRefresh) {
+      wxCpConfigStorage.expireAccessToken();
+    }
     if (wxCpConfigStorage.isAccessTokenExpired()) {
       synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) {
         if (wxCpConfigStorage.isAccessTokenExpired()) {
@@ -359,7 +362,7 @@ public class WxCpServiceImpl implements WxCpService {
    * @throws WxErrorException
    */
   public  T execute(RequestExecutor executor, String uri, E data) throws WxErrorException {
-    String accessToken = getAccessToken();
+    String accessToken = getAccessToken(false);
 
     String uriWithAccessToken = uri;
     uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
index cef912ee5..5500376c9 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
@@ -40,10 +40,11 @@ public interface WxMpService {
 
    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
    * 
+ * @param forceRefresh 强制刷新 * @return * @throws me.chanjar.weixin.common.exception.WxErrorException */ - public String getAccessToken() throws WxErrorException; + public String getAccessToken(boolean forceRefresh) throws WxErrorException; /** *
@@ -52,10 +53,11 @@ public interface WxMpService {
    *
    * 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95
    * 
+ * @param forceRefresh 强制刷新 * @return * @throws WxErrorException */ - public String getJsapiTicket() throws WxErrorException; + public String getJsapiTicket(boolean forceRefresh) throws WxErrorException; /** *
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
index 18d3bcdd2..f1260f642 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
@@ -69,7 +69,10 @@ public class WxMpServiceImpl implements WxMpService {
     }
   }
   
-  public String getAccessToken() throws WxErrorException {
+  public String getAccessToken(boolean forceRefresh) throws WxErrorException {
+    if (forceRefresh) {
+      wxMpConfigStorage.expireAccessToken();
+    }
     if (wxMpConfigStorage.isAccessTokenExpired()) {
       synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) {
         if (wxMpConfigStorage.isAccessTokenExpired()) {
@@ -104,7 +107,10 @@ public class WxMpServiceImpl implements WxMpService {
   }
 
 
-  public String getJsapiTicket() throws WxErrorException {
+  public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
+    if (forceRefresh) {
+      wxMpConfigStorage.expireJsapiTicket();
+    }
     if (wxMpConfigStorage.isJsapiTicketExpired()) {
       synchronized (GLOBAL_JSAPI_TICKET_REFRESH_LOCK) {
         if (wxMpConfigStorage.isJsapiTicketExpired()) {
@@ -122,7 +128,7 @@ public class WxMpServiceImpl implements WxMpService {
   }
 
   public String createJsapiSignature(String timestamp, String noncestr, String url) throws WxErrorException {
-    String jsapiTicket = getJsapiTicket();
+    String jsapiTicket = getJsapiTicket(false);
     try {
       return SHA1.genWithAmple(
           "jsapi_ticket=" + jsapiTicket,
@@ -436,7 +442,7 @@ public class WxMpServiceImpl implements WxMpService {
    * @throws WxErrorException
    */
   public  T execute(RequestExecutor executor, String uri, E data) throws WxErrorException {
-    String accessToken = getAccessToken();
+    String accessToken = getAccessToken(false);
     
     String uriWithAccessToken = uri;
     uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;

From d0825ca3969a91182713b3368f483ac5744069c7 Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Tue, 20 Jan 2015 14:31:52 +0800
Subject: [PATCH 6/7] update

---
 .../src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java | 2 +-
 .../src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java
index 606cca9b1..d8757f340 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java
@@ -22,7 +22,7 @@ public class WxCpBaseAPITest {
   public void testRefreshAccessToken() throws WxErrorException {
     WxCpConfigStorage configStorage = wxService.wxCpConfigStorage;
     String before = configStorage.getAccessToken();
-    wxService.getAccessToken();
+    wxService.getAccessToken(false);
 
     String after = configStorage.getAccessToken();
 
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
index e773f117b..cf01bfbc9 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
@@ -22,7 +22,7 @@ public class WxMpBaseAPITest {
   public void testRefreshAccessToken() throws WxErrorException {
     WxMpConfigStorage configStorage = wxService.wxMpConfigStorage;
     String before = configStorage.getAccessToken();
-    wxService.getAccessToken();
+    wxService.getAccessToken(false);
 
     String after = configStorage.getAccessToken();
 

From 4f292da60cdd8114b360b81707f3360e58e09ac9 Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Tue, 20 Jan 2015 14:52:52 +0800
Subject: [PATCH 7/7] =?UTF-8?q?issue=20#74=20=E6=B7=BB=E5=8A=A0=E6=B5=8B?=
 =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../me/chanjar/weixin/mp/api/WxMpService.java |  2 +-
 .../weixin/mp/api/WxMpServiceImpl.java        |  2 +-
 .../weixin/mp/api/WxMpBaseAPITest.java        |  1 -
 .../chanjar/weixin/mp/api/WxMpJsAPITest.java  | 48 +++++++++++++++++++
 weixin-java-mp/src/test/resources/testng.xml  |  1 +
 5 files changed, 51 insertions(+), 3 deletions(-)
 create mode 100644 weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpJsAPITest.java

diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
index 5500376c9..2f0e68446 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
@@ -70,7 +70,7 @@ public interface WxMpService {
    * @param url       url
    * @return
    */
-  public String createJsapiSignature(String timestamp, String noncestr, String url) throws WxErrorException;
+  public String createJsapiSignature(long timestamp, String noncestr, String url) throws WxErrorException;
 
   /**
    * 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
index f1260f642..7b2563040 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
@@ -127,7 +127,7 @@ public class WxMpServiceImpl implements WxMpService {
     return wxMpConfigStorage.getJsapiTicket();
   }
 
-  public String createJsapiSignature(String timestamp, String noncestr, String url) throws WxErrorException {
+  public String createJsapiSignature(long timestamp, String noncestr, String url) throws WxErrorException {
     String jsapiTicket = getJsapiTicket(false);
     try {
       return SHA1.genWithAmple(
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
index cf01bfbc9..c4210b57d 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
@@ -25,7 +25,6 @@ public class WxMpBaseAPITest {
     wxService.getAccessToken(false);
 
     String after = configStorage.getAccessToken();
-
     Assert.assertNotEquals(before, after);
     Assert.assertTrue(StringUtils.isNotBlank(after));
   }
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpJsAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpJsAPITest.java
new file mode 100644
index 000000000..16506caa9
--- /dev/null
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpJsAPITest.java
@@ -0,0 +1,48 @@
+package me.chanjar.weixin.mp.api;
+
+import com.google.inject.Inject;
+import me.chanjar.weixin.common.exception.WxErrorException;
+import me.chanjar.weixin.common.util.crypto.SHA1;
+import me.chanjar.weixin.mp.bean.WxMpGroup;
+import org.testng.Assert;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+
+/**
+ * 测试jsapi ticket接口
+ * 
+ * @author chanjarster
+ */
+@Test(groups = "jsAPI", dependsOnGroups = "baseAPI")
+@Guice(modules = ApiTestModule.class)
+public class WxMpJsAPITest {
+
+  @Inject
+  protected WxMpServiceImpl wxService;
+
+
+  public void testJsapiTicket() throws WxErrorException {
+    String jsapiTicket = wxService.getJsapiTicket(false);
+    System.out.println(jsapiTicket);
+    Assert.assertNotNull(jsapiTicket);
+  }
+
+  public void test() throws NoSuchAlgorithmException {
+    long timestamp = 1419835025l;
+    String url = "http://omstest.vmall.com:23568/thirdparty/wechat/vcode/gotoshare?quantity=1&batchName=MATE7";
+    String noncestr = "82693e11-b9bc-448e-892f-f5289f46cd0f";
+    String jsapiTicket = "bxLdikRXVbTPdHSM05e5u4RbEYQn7pNQMPrfzl8lJNb1foLDa3HIwI3BRMkQmSO_5F64VFa75uURcq6Uz7QHgA";
+    String result = SHA1.genWithAmple(
+        "jsapi_ticket=" + jsapiTicket,
+        "noncestr=" + noncestr,
+        "timestamp=" + timestamp,
+        "url=" + url
+    );
+
+    Assert.assertEquals(result, "c6f04b64d6351d197b71bd23fb7dd2d44c0db486");
+  }
+
+}
diff --git a/weixin-java-mp/src/test/resources/testng.xml b/weixin-java-mp/src/test/resources/testng.xml
index 17308c788..333400e48 100644
--- a/weixin-java-mp/src/test/resources/testng.xml
+++ b/weixin-java-mp/src/test/resources/testng.xml
@@ -13,6 +13,7 @@
 			
 			
 			
+