Daniel Qian 10 роки тому
батько
коміт
cef8ac01e8

+ 28 - 6
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]);

+ 22 - 2
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);
+
 }

+ 22 - 2
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{" +

+ 28 - 3
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;
-  
+
+  /**
+   * <pre>
+   * 获得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
+   * </pre>
+   * @return
+   * @throws WxErrorException
+   */
+  public String getJsapiTicket() throws WxErrorException;
+
+  /**
+   * <pre>
+   * 创建调用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
+   * </pre>
+   * @param timestamp 时间戳
+   * @param noncestr  用户自己生成的随机字符串
+   * @param url       url
+   * @return
+   */
+  public String createJsapiSignature(String timestamp, String noncestr, String url) throws WxErrorException;
+
   /**
    * <pre>
    * 上传多媒体文件
-   * 
+   *
    * 上传的多媒体文件有格式和大小限制,如下:
    *   图片(image): 1M,支持JPG格式
    *   语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式
    *   视频(video):10MB,支持MP4格式
    *   缩略图(thumb):64KB,支持JPG格式
-   *    
+   *
    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件
    * </pre>
    * @param mediaType         媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts}

+ 33 - 1
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());