| @@ -10,27 +10,49 @@ import java.util.Arrays; | |||||
| public class SHA1 { | public class SHA1 { | ||||
| /** | /** | ||||
| * 生成SHA1签名 | |||||
| * 串接arr参数,生成sha1 digest | |||||
| * | |||||
| * @param arr | * @param arr | ||||
| * @return | * @return | ||||
| */ | */ | ||||
| public static String gen(String... arr) throws NoSuchAlgorithmException { | public static String gen(String... arr) throws NoSuchAlgorithmException { | ||||
| Arrays.sort(arr); | Arrays.sort(arr); | ||||
| StringBuilder sb = new StringBuilder(); | StringBuilder sb = new StringBuilder(); | ||||
| for(String a : arr) { | |||||
| for (String a : arr) { | |||||
| sb.append(a); | 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"); | MessageDigest sha1 = MessageDigest.getInstance("SHA1"); | ||||
| sha1.update(sb.toString().getBytes()); | |||||
| sha1.update(str.getBytes()); | |||||
| byte[] output = sha1.digest(); | byte[] output = sha1.digest(); | ||||
| return bytesToHex(output); | return bytesToHex(output); | ||||
| } | } | ||||
| protected static String bytesToHex(byte[] b) { | 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(); | StringBuffer buf = new StringBuffer(); | ||||
| for (int j = 0; j < b.length; j++) { | for (int j = 0; j < b.length; j++) { | ||||
| buf.append(hexDigit[(b[j] >> 4) & 0x0f]); | buf.append(hexDigit[(b[j] >> 4) & 0x0f]); | ||||
| @@ -9,10 +9,19 @@ import me.chanjar.weixin.common.bean.WxAccessToken; | |||||
| */ | */ | ||||
| public interface WxMpConfigStorage { | public interface WxMpConfigStorage { | ||||
| /** | |||||
| * 应该是线程安全的 | |||||
| * @param accessToken | |||||
| */ | |||||
| public void updateAccessToken(WxAccessToken accessToken); | public void updateAccessToken(WxAccessToken accessToken); | ||||
| /** | |||||
| * 应该是线程安全的 | |||||
| * @param accessToken | |||||
| * @param expiresIn | |||||
| */ | |||||
| public void updateAccessToken(String accessToken, int expiresIn); | public void updateAccessToken(String accessToken, int expiresIn); | ||||
| public String getAccessToken(); | public String getAccessToken(); | ||||
| public String getAppId(); | public String getAppId(); | ||||
| @@ -35,4 +44,15 @@ public interface WxMpConfigStorage { | |||||
| 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); | |||||
| } | } | ||||
| @@ -9,6 +9,8 @@ import me.chanjar.weixin.common.bean.WxAccessToken; | |||||
| */ | */ | ||||
| public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | ||||
| private static final long l = 7000 * 1000l; | |||||
| protected String appId; | protected String appId; | ||||
| protected String secret; | protected String secret; | ||||
| protected String token; | protected String token; | ||||
| @@ -23,11 +25,14 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
| protected String http_proxy_username; | protected String http_proxy_username; | ||||
| protected String http_proxy_password; | 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()); | updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | ||||
| } | } | ||||
| public void updateAccessToken(String accessToken, int expiresIn) { | |||||
| public synchronized void updateAccessToken(String accessToken, int expiresIn) { | |||||
| this.accessToken = accessToken; | this.accessToken = accessToken; | ||||
| this.expiresIn = expiresIn; | this.expiresIn = expiresIn; | ||||
| } | } | ||||
| @@ -121,6 +126,21 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
| this.http_proxy_password = http_proxy_password; | 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 | @Override | ||||
| public String toString() { | public String toString() { | ||||
| return "WxMpInMemoryConfigStorage{" + | return "WxMpInMemoryConfigStorage{" + | ||||
| @@ -43,17 +43,42 @@ public interface WxMpService { | |||||
| * @throws me.chanjar.weixin.common.exception.WxErrorException | * @throws me.chanjar.weixin.common.exception.WxErrorException | ||||
| */ | */ | ||||
| public void accessTokenRefresh() throws 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> | * <pre> | ||||
| * 上传多媒体文件 | * 上传多媒体文件 | ||||
| * | |||||
| * | |||||
| * 上传的多媒体文件有格式和大小限制,如下: | * 上传的多媒体文件有格式和大小限制,如下: | ||||
| * 图片(image): 1M,支持JPG格式 | * 图片(image): 1M,支持JPG格式 | ||||
| * 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式 | * 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式 | ||||
| * 视频(video):10MB,支持MP4格式 | * 视频(video):10MB,支持MP4格式 | ||||
| * 缩略图(thumb):64KB,支持JPG格式 | * 缩略图(thumb):64KB,支持JPG格式 | ||||
| * | |||||
| * | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | ||||
| * </pre> | * </pre> | ||||
| * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} | * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} | ||||
| @@ -36,6 +36,7 @@ import java.io.File; | |||||
| import java.io.IOException; | import java.io.IOException; | ||||
| import java.io.InputStream; | import java.io.InputStream; | ||||
| import java.io.StringReader; | import java.io.StringReader; | ||||
| import java.security.NoSuchAlgorithmException; | |||||
| import java.util.List; | import java.util.List; | ||||
| import java.util.UUID; | import java.util.UUID; | ||||
| import java.util.concurrent.atomic.AtomicBoolean; | 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 { | public void customMessageSend(WxMpCustomMessage message) throws WxErrorException { | ||||
| String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send"; | String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send"; | ||||
| execute(new SimplePostRequestExecutor(), url, message.toJson()); | execute(new SimplePostRequestExecutor(), url, message.toJson()); | ||||