mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2025-04-24 18:04:38 +08:00
Merge branch 'feature/jsapi' into develop
This commit is contained in:
commit
f3cdf2287f
@ -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,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();
|
||||
|
||||
|
@ -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 + '\'' +
|
||||
|
@ -51,10 +51,11 @@ public interface WxCpService {
|
||||
* 程序员在非必要情况下尽量不要主动调用此方法
|
||||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
|
||||
* </pre>
|
||||
*
|
||||
* @param forceRefresh 强制刷新
|
||||
* @return
|
||||
* @throws me.chanjar.weixin.common.exception.WxErrorException
|
||||
*/
|
||||
public void accessTokenRefresh() throws WxErrorException;
|
||||
public String getAccessToken(boolean forceRefresh) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
|
@ -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,40 @@ 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);
|
||||
}
|
||||
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) {
|
||||
}
|
||||
}
|
||||
// 刷新完毕了,就没他什么事儿了
|
||||
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
|
||||
if (forceRefresh) {
|
||||
wxCpConfigStorage.expireAccessToken();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return wxCpConfigStorage.getAccessToken();
|
||||
}
|
||||
|
||||
public void messageSend(WxCpMessage message) throws WxErrorException {
|
||||
@ -369,10 +362,7 @@ public class WxCpServiceImpl implements WxCpService {
|
||||
* @throws WxErrorException
|
||||
*/
|
||||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
|
||||
if (StringUtils.isBlank(wxCpConfigStorage.getAccessToken())) {
|
||||
accessTokenRefresh();
|
||||
}
|
||||
String accessToken = wxCpConfigStorage.getAccessToken();
|
||||
String accessToken = getAccessToken(false);
|
||||
|
||||
String uriWithAccessToken = uri;
|
||||
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
|
||||
@ -387,7 +377,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);
|
||||
}
|
||||
/**
|
||||
|
@ -22,7 +22,7 @@ public class WxCpBaseAPITest {
|
||||
public void testRefreshAccessToken() throws WxErrorException {
|
||||
WxCpConfigStorage configStorage = wxService.wxCpConfigStorage;
|
||||
String before = configStorage.getAccessToken();
|
||||
wxService.accessTokenRefresh();
|
||||
wxService.getAccessToken(false);
|
||||
|
||||
String after = configStorage.getAccessToken();
|
||||
|
||||
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
<token>企业号应用Token</token>
|
||||
<aesKey>企业号应用EncodingAESKey</aesKey>
|
||||
<accessToken>可以不填写</accessToken>
|
||||
<expiresIn>可以不填写</expiresIn>
|
||||
<expiresTime>可以不填写</expiresTime>
|
||||
<userId>企业号通讯录里的某个userid</userId>
|
||||
<departmentId>企业号通讯录的某个部门id</departmentId>
|
||||
<tagId>企业号通讯录里的某个tagid</tagId>
|
||||
|
@ -9,21 +9,52 @@ import me.chanjar.weixin.common.bean.WxAccessToken;
|
||||
*/
|
||||
public interface WxMpConfigStorage {
|
||||
|
||||
public void updateAccessToken(WxAccessToken accessToken);
|
||||
|
||||
public void updateAccessToken(String accessToken, int expiresIn);
|
||||
|
||||
public String getAccessToken();
|
||||
|
||||
|
||||
public boolean isAccessTokenExpired();
|
||||
|
||||
/**
|
||||
* 强制将access token过期掉
|
||||
*/
|
||||
public void expireAccessToken();
|
||||
|
||||
/**
|
||||
* 应该是线程安全的
|
||||
* @param accessToken
|
||||
*/
|
||||
public void updateAccessToken(WxAccessToken accessToken);
|
||||
|
||||
/**
|
||||
* 应该是线程安全的
|
||||
* @param accessToken
|
||||
* @param expiresIn
|
||||
*/
|
||||
public void updateAccessToken(String accessToken, int expiresIn);
|
||||
|
||||
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();
|
||||
|
||||
@ -33,6 +64,7 @@ public interface WxMpConfigStorage {
|
||||
|
||||
public String getHttp_proxy_username();
|
||||
|
||||
|
||||
public String getHttp_proxy_password();
|
||||
|
||||
}
|
||||
|
@ -9,12 +9,14 @@ 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;
|
||||
protected String accessToken;
|
||||
protected String aesKey;
|
||||
protected int expiresIn;
|
||||
protected long expiresTime;
|
||||
|
||||
protected String oauth2redirectUri;
|
||||
|
||||
@ -23,19 +25,48 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
|
||||
protected String http_proxy_username;
|
||||
protected String http_proxy_password;
|
||||
|
||||
public void updateAccessToken(WxAccessToken accessToken) {
|
||||
updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
|
||||
}
|
||||
|
||||
public void updateAccessToken(String accessToken, int expiresIn) {
|
||||
this.accessToken = accessToken;
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
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 expiresInSeconds) {
|
||||
this.accessToken = accessToken;
|
||||
this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l;
|
||||
}
|
||||
|
||||
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() {
|
||||
return this.appId;
|
||||
}
|
||||
@ -48,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) {
|
||||
@ -76,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
|
||||
@ -121,6 +152,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
|
||||
this.http_proxy_password = http_proxy_password;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WxMpInMemoryConfigStorage{" +
|
||||
@ -129,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 + '\'' +
|
||||
|
@ -40,20 +40,48 @@ public interface WxMpService {
|
||||
|
||||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
|
||||
* </pre>
|
||||
* @param forceRefresh 强制刷新
|
||||
* @return
|
||||
* @throws me.chanjar.weixin.common.exception.WxErrorException
|
||||
*/
|
||||
public void accessTokenRefresh() throws WxErrorException;
|
||||
|
||||
public String getAccessToken(boolean forceRefresh) 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>
|
||||
* @param forceRefresh 强制刷新
|
||||
* @return
|
||||
* @throws WxErrorException
|
||||
*/
|
||||
public String getJsapiTicket(boolean forceRefresh) 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(long 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;
|
||||
@ -43,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<Integer> retryTimes = new ThreadLocal<Integer>();
|
||||
@ -65,48 +69,78 @@ 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(boolean forceRefresh) throws WxErrorException {
|
||||
if (forceRefresh) {
|
||||
wxMpConfigStorage.expireAccessToken();
|
||||
}
|
||||
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(boolean forceRefresh) throws WxErrorException {
|
||||
if (forceRefresh) {
|
||||
wxMpConfigStorage.expireJsapiTicket();
|
||||
}
|
||||
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)));
|
||||
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(long timestamp, String noncestr, String url) throws WxErrorException {
|
||||
String jsapiTicket = getJsapiTicket(false);
|
||||
try {
|
||||
return SHA1.genWithAmple(
|
||||
"jsapi_ticket=" + jsapiTicket,
|
||||
"noncestr=" + noncestr,
|
||||
"timestamp=" + timestamp,
|
||||
"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());
|
||||
@ -408,10 +442,7 @@ public class WxMpServiceImpl implements WxMpService {
|
||||
* @throws WxErrorException
|
||||
*/
|
||||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
|
||||
if (StringUtils.isBlank(wxMpConfigStorage.getAccessToken())) {
|
||||
accessTokenRefresh();
|
||||
}
|
||||
String accessToken = wxMpConfigStorage.getAccessToken();
|
||||
String accessToken = getAccessToken(false);
|
||||
|
||||
String uriWithAccessToken = uri;
|
||||
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
|
||||
@ -426,7 +457,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);
|
||||
}
|
||||
/**
|
||||
|
@ -65,7 +65,7 @@ public class WxMpOAuth2AccessToken {
|
||||
public String toString() {
|
||||
return "WxMpOAuth2AccessToken{" +
|
||||
"accessToken='" + accessToken + '\'' +
|
||||
", expiresIn=" + expiresIn +
|
||||
", expiresTime=" + expiresIn +
|
||||
", refreshToken='" + refreshToken + '\'' +
|
||||
", openId='" + openId + '\'' +
|
||||
", scope='" + scope + '\'' +
|
||||
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,10 +22,9 @@ public class WxMpBaseAPITest {
|
||||
public void testRefreshAccessToken() throws WxErrorException {
|
||||
WxMpConfigStorage configStorage = wxService.wxMpConfigStorage;
|
||||
String before = configStorage.getAccessToken();
|
||||
wxService.accessTokenRefresh();
|
||||
wxService.getAccessToken(false);
|
||||
|
||||
String after = configStorage.getAccessToken();
|
||||
|
||||
Assert.assertNotEquals(before, after);
|
||||
Assert.assertTrue(StringUtils.isNotBlank(after));
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
<token>公众号Token</token>
|
||||
<aesKey>公众号EncodingAESKey</aesKey>
|
||||
<accessToken>可以不填写</accessToken>
|
||||
<expiresIn>可以不填写</expiresIn>
|
||||
<expiresTime>可以不填写</expiresTime>
|
||||
<openId>某个加你公众号的用户的openId</openId>
|
||||
<oauth2redirectUri>网页授权获取用户信息回调地址</oauth2redirectUri>
|
||||
</xml>
|
||||
|
@ -13,6 +13,7 @@
|
||||
<class name="me.chanjar.weixin.mp.api.WxMpQrCodeAPITest" />
|
||||
<class name="me.chanjar.weixin.mp.api.WxMpShortUrlAPITest" />
|
||||
<class name="me.chanjar.weixin.mp.api.WxMpMessageRouterTest" />
|
||||
<class name="me.chanjar.weixin.mp.api.WxMpJsAPITest" />
|
||||
</classes>
|
||||
</test>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user