@@ -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]); | |||
@@ -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); | |||
} |
@@ -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{" + | |||
@@ -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} | |||
@@ -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()); | |||