@@ -1,4 +1,4 @@ | |||
package me.chanjar.weixin.common.util; | |||
package me.chanjar.weixin.common.api; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
@@ -1,4 +1,4 @@ | |||
package me.chanjar.weixin.common.util; | |||
package me.chanjar.weixin.common.api; | |||
/** | |||
* <pre> |
@@ -1,4 +1,4 @@ | |||
package me.chanjar.weixin.common.util; | |||
package me.chanjar.weixin.common.api; | |||
import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; |
@@ -1,9 +1,9 @@ | |||
package me.chanjar.weixin.mp.bean.result; | |||
package me.chanjar.weixin.common.bean; | |||
/** | |||
* jspai signature | |||
*/ | |||
public class WxMpJsapiSignature { | |||
public class WxJsapiSignature { | |||
private String noncestr; | |||
@@ -1,5 +1,6 @@ | |||
package me.chanjar.weixin.common.util; | |||
import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
@@ -0,0 +1,17 @@ | |||
package me.chanjar.weixin.common.util; | |||
public class RandomUtils { | |||
private static final String RANDOM_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; | |||
private static final java.util.Random RANDOM = new java.util.Random(); | |||
public static String getRandomStr() { | |||
StringBuilder sb = new StringBuilder(); | |||
for (int i = 0; i < 16; i++) { | |||
sb.append(RANDOM_STR.charAt(RANDOM.nextInt(RANDOM_STR.length()))); | |||
} | |||
return sb.toString(); | |||
} | |||
} |
@@ -1,5 +1,6 @@ | |||
package me.chanjar.weixin.common.util; | |||
import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; | |||
import org.testng.Assert; | |||
import org.testng.annotations.Test; | |||
@@ -22,6 +22,20 @@ public interface WxCpConfigStorage { | |||
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 getCorpId(); | |||
@@ -25,6 +25,9 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
protected volatile String http_proxy_username; | |||
protected volatile String http_proxy_password; | |||
protected volatile String jsapiTicket; | |||
protected volatile long jsapiTicketExpiresTime; | |||
public String getAccessToken() { | |||
return this.accessToken; | |||
} | |||
@@ -46,6 +49,37 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; | |||
} | |||
@Override | |||
public String getJsapiTicket() { | |||
return jsapiTicket; | |||
} | |||
public void setJsapiTicket(String jsapiTicket) { | |||
this.jsapiTicket = jsapiTicket; | |||
} | |||
public long getJsapiTicketExpiresTime() { | |||
return jsapiTicketExpiresTime; | |||
} | |||
public void setJsapiTicketExpiresTime(long jsapiTicketExpiresTime) { | |||
this.jsapiTicketExpiresTime = jsapiTicketExpiresTime; | |||
} | |||
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 getCorpId() { | |||
return this.corpId; | |||
} | |||
@@ -153,6 +187,8 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
", http_proxy_port=" + http_proxy_port + | |||
", http_proxy_username='" + http_proxy_username + '\'' + | |||
", http_proxy_password='" + http_proxy_password + '\'' + | |||
", jsapiTicket='" + jsapiTicket + '\'' + | |||
", jsapiTicketExpiresTime='" + jsapiTicketExpiresTime + '\'' + | |||
'}'; | |||
} | |||
@@ -5,9 +5,9 @@ import me.chanjar.weixin.common.session.InternalSessionManager; | |||
import me.chanjar.weixin.common.session.StandardSessionManager; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.LogExceptionHandler; | |||
import me.chanjar.weixin.common.util.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.util.WxMessageDuplicateChecker; | |||
import me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker; | |||
import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; | |||
import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
import org.slf4j.Logger; | |||
@@ -87,8 +87,8 @@ public class WxCpMessageRouter { | |||
/** | |||
* <pre> | |||
* 设置自定义的 {@link me.chanjar.weixin.common.util.WxMessageDuplicateChecker} | |||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker} | |||
* 设置自定义的 {@link me.chanjar.weixin.common.api.WxMessageDuplicateChecker} | |||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker} | |||
* </pre> | |||
* @param messageDuplicateChecker | |||
*/ | |||
@@ -109,7 +109,7 @@ public class WxCpMessageRouter { | |||
/** | |||
* <pre> | |||
* 设置自定义的{@link me.chanjar.weixin.common.util.WxErrorExceptionHandler} | |||
* 设置自定义的{@link me.chanjar.weixin.common.api.WxErrorExceptionHandler} | |||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.LogExceptionHandler} | |||
* </pre> | |||
* @param exceptionHandler | |||
@@ -2,7 +2,7 @@ package me.chanjar.weixin.cp.api; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
@@ -8,6 +8,7 @@ import com.google.gson.internal.Streams; | |||
import com.google.gson.reflect.TypeToken; | |||
import com.google.gson.stream.JsonReader; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
import me.chanjar.weixin.common.bean.WxMenu; | |||
import me.chanjar.weixin.common.bean.result.WxError; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
@@ -15,6 +16,7 @@ import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.session.StandardSessionManager; | |||
import me.chanjar.weixin.common.session.WxSession; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.RandomUtils; | |||
import me.chanjar.weixin.common.util.StringUtils; | |||
import me.chanjar.weixin.common.util.crypto.SHA1; | |||
import me.chanjar.weixin.common.util.fs.FileUtils; | |||
@@ -44,6 +46,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; | |||
@@ -54,7 +57,12 @@ public class WxCpServiceImpl implements WxCpService { | |||
/** | |||
* 全局的是否正在刷新access token的锁 | |||
*/ | |||
protected static final Object GLOBAL_ACCESS_TOKEN_REFRESH_LOCK = new Object(); | |||
protected final Object globalAccessTokenRefreshLock = new Object(); | |||
/** | |||
* 全局的是否正在刷新jsapi_ticket的锁 | |||
*/ | |||
protected final Object globalJsapiTicketRefreshLock = new Object(); | |||
protected WxCpConfigStorage wxCpConfigStorage; | |||
@@ -90,7 +98,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
wxCpConfigStorage.expireAccessToken(); | |||
} | |||
if (wxCpConfigStorage.isAccessTokenExpired()) { | |||
synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) { | |||
synchronized (globalAccessTokenRefreshLock) { | |||
if (wxCpConfigStorage.isAccessTokenExpired()) { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||
+ "&corpid=" + wxCpConfigStorage.getCorpId() | |||
@@ -121,6 +129,52 @@ public class WxCpServiceImpl implements WxCpService { | |||
return wxCpConfigStorage.getAccessToken(); | |||
} | |||
public String getJsapiTicket() throws WxErrorException { | |||
return getJsapiTicket(false); | |||
} | |||
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { | |||
if (forceRefresh) { | |||
wxCpConfigStorage.expireJsapiTicket(); | |||
} | |||
if (wxCpConfigStorage.isJsapiTicketExpired()) { | |||
synchronized (globalJsapiTicketRefreshLock) { | |||
if (wxCpConfigStorage.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(); | |||
wxCpConfigStorage.updateJsapiTicket(jsapiTicket, expiresInSeconds); | |||
} | |||
} | |||
} | |||
return wxCpConfigStorage.getJsapiTicket(); | |||
} | |||
public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException { | |||
long timestamp = System.currentTimeMillis() / 1000; | |||
String noncestr = RandomUtils.getRandomStr(); | |||
String jsapiTicket = getJsapiTicket(false); | |||
try { | |||
String signature = SHA1.genWithAmple( | |||
"jsapi_ticket=" + jsapiTicket, | |||
"noncestr=" + noncestr, | |||
"timestamp=" + timestamp, | |||
"url=" + url | |||
); | |||
WxJsapiSignature jsapiSignature = new WxJsapiSignature(); | |||
jsapiSignature.setTimestamp(timestamp); | |||
jsapiSignature.setNoncestr(noncestr); | |||
jsapiSignature.setUrl(url); | |||
jsapiSignature.setSignature(signature); | |||
return jsapiSignature; | |||
} catch (NoSuchAlgorithmException e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
public void messageSend(WxCpMessage message) throws WxErrorException { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send"; | |||
post(url, message.toJson()); | |||
@@ -477,7 +531,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
* 42001 access_token超时 | |||
*/ | |||
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { | |||
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||
// 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||
wxCpConfigStorage.expireAccessToken(); | |||
return execute(executor, uri, data); | |||
} | |||
@@ -0,0 +1,68 @@ | |||
/** | |||
* Created by qianjia on 15/1/25. | |||
*/ | |||
public class TestNonAtomicLongAssignment { | |||
private static final long HI = 1l << 32; | |||
private static final long LO = 1l; | |||
private static final long TEST_NUMBER = HI | LO; | |||
private static long assignee = 0l; | |||
public static void main(String[] args) { | |||
Thread writer = new Thread(new Runnable() { | |||
@Override | |||
public void run() { | |||
while (true) { | |||
assignee = TEST_NUMBER; | |||
} | |||
} | |||
}); | |||
writer.setDaemon(true); | |||
Thread reader = new Thread(new Runnable() { | |||
@Override | |||
public void run() { | |||
long i = 0; | |||
while (true) { | |||
i++; | |||
long test = assignee; | |||
if (test != TEST_NUMBER) { | |||
System.out.print(i + " times:" + toBin(test)); | |||
break; | |||
} | |||
} | |||
} | |||
}); | |||
// Thread worker = new Thread(new Runnable() { | |||
// @Override | |||
// public void run() { | |||
// double d = 89009808877238948224343435452333323113131313133434434341212323232424243434335354232390490189190420928348910913094983.323334401928d; | |||
// while(true) { | |||
// Math.cbrt(d); | |||
// d = d - 1l; | |||
// } | |||
// } | |||
// }); | |||
// worker.setDaemon(true); | |||
// worker.start(); | |||
writer.start(); | |||
reader.start(); | |||
} | |||
public static String toBin(long n) { | |||
StringBuilder sb = new StringBuilder(Long.toBinaryString(n)); | |||
int padding = 64 - sb.length(); | |||
while (padding > 0) { | |||
sb.insert(0, '0'); | |||
padding--; | |||
} | |||
return sb.toString(); | |||
} | |||
} |
@@ -51,6 +51,18 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
return jsapiTicket; | |||
} | |||
public void setJsapiTicket(String jsapiTicket) { | |||
this.jsapiTicket = jsapiTicket; | |||
} | |||
public long getJsapiTicketExpiresTime() { | |||
return jsapiTicketExpiresTime; | |||
} | |||
public void setJsapiTicketExpiresTime(long jsapiTicketExpiresTime) { | |||
this.jsapiTicketExpiresTime = jsapiTicketExpiresTime; | |||
} | |||
public boolean isJsapiTicketExpired() { | |||
return System.currentTimeMillis() > this.jsapiTicketExpiresTime; | |||
} | |||
@@ -167,5 +179,4 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
", jsapiTicketExpiresTime='" + jsapiTicketExpiresTime + '\'' + | |||
'}'; | |||
} | |||
} |
@@ -5,9 +5,9 @@ import me.chanjar.weixin.common.session.InternalSessionManager; | |||
import me.chanjar.weixin.common.session.StandardSessionManager; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.LogExceptionHandler; | |||
import me.chanjar.weixin.common.util.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.util.WxMessageDuplicateChecker; | |||
import me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker; | |||
import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; | |||
import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; | |||
import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | |||
import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | |||
import org.slf4j.Logger; | |||
@@ -87,8 +87,8 @@ public class WxMpMessageRouter { | |||
/** | |||
* <pre> | |||
* 设置自定义的 {@link me.chanjar.weixin.common.util.WxMessageDuplicateChecker} | |||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker} | |||
* 设置自定义的 {@link me.chanjar.weixin.common.api.WxMessageDuplicateChecker} | |||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker} | |||
* </pre> | |||
* @param messageDuplicateChecker | |||
*/ | |||
@@ -109,7 +109,7 @@ public class WxMpMessageRouter { | |||
/** | |||
* <pre> | |||
* 设置自定义的{@link me.chanjar.weixin.common.util.WxErrorExceptionHandler} | |||
* 设置自定义的{@link me.chanjar.weixin.common.api.WxErrorExceptionHandler} | |||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.LogExceptionHandler} | |||
* </pre> | |||
* @param exceptionHandler | |||
@@ -2,7 +2,7 @@ package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | |||
import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | |||
@@ -1,10 +1,9 @@ | |||
package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.bean.WxMenu; | |||
import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.session.WxSession; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import me.chanjar.weixin.mp.bean.*; | |||
import me.chanjar.weixin.mp.bean.result.*; | |||
@@ -86,7 +85,7 @@ public interface WxMpService { | |||
* @param url url | |||
* @return | |||
*/ | |||
public WxMpJsapiSignature createJsapiSignature(String url) throws WxErrorException; | |||
public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException; | |||
/** | |||
* <pre> | |||
@@ -7,11 +7,13 @@ import com.google.gson.reflect.TypeToken; | |||
import com.google.gson.stream.JsonReader; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
import me.chanjar.weixin.common.bean.WxMenu; | |||
import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
import me.chanjar.weixin.common.bean.result.WxError; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.session.StandardSessionManager; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.RandomUtils; | |||
import me.chanjar.weixin.common.util.StringUtils; | |||
import me.chanjar.weixin.common.util.crypto.SHA1; | |||
import me.chanjar.weixin.common.util.fs.FileUtils; | |||
@@ -42,15 +44,10 @@ import java.io.InputStream; | |||
import java.io.StringReader; | |||
import java.security.NoSuchAlgorithmException; | |||
import java.util.List; | |||
import java.util.Random; | |||
import java.util.UUID; | |||
public class WxMpServiceImpl implements WxMpService { | |||
protected final String RANDOM_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; | |||
protected final Random RANDOM = new Random(); | |||
protected final Logger log = LoggerFactory.getLogger(WxMpServiceImpl.class); | |||
/** | |||
@@ -148,9 +145,9 @@ public class WxMpServiceImpl implements WxMpService { | |||
return wxMpConfigStorage.getJsapiTicket(); | |||
} | |||
public WxMpJsapiSignature createJsapiSignature(String url) throws WxErrorException { | |||
public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException { | |||
long timestamp = System.currentTimeMillis() / 1000; | |||
String noncestr = getRandomStr(); | |||
String noncestr = RandomUtils.getRandomStr(); | |||
String jsapiTicket = getJsapiTicket(false); | |||
try { | |||
String signature = SHA1.genWithAmple( | |||
@@ -159,7 +156,7 @@ public class WxMpServiceImpl implements WxMpService { | |||
"timestamp=" + timestamp, | |||
"url=" + url | |||
); | |||
WxMpJsapiSignature jsapiSignature = new WxMpJsapiSignature(); | |||
WxJsapiSignature jsapiSignature = new WxJsapiSignature(); | |||
jsapiSignature.setTimestamp(timestamp); | |||
jsapiSignature.setNoncestr(noncestr); | |||
jsapiSignature.setUrl(url); | |||
@@ -170,14 +167,6 @@ public class WxMpServiceImpl implements WxMpService { | |||
} | |||
} | |||
protected String getRandomStr() { | |||
StringBuilder sb = new StringBuilder(); | |||
for (int i = 0; i < 16; i++) { | |||
sb.append(RANDOM_STR.charAt(RANDOM.nextInt(RANDOM_STR.length()))); | |||
} | |||
return sb.toString(); | |||
} | |||
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()); | |||