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 cc233308..c97facd3 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 3ecbf155..68d42785 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 82c3d621..d278d1b7 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 c4dc3e54..e8a56787 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 dbf2adc4..636e8f62 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());