1
0
mirror of synced 2025-12-25 20:17:56 +08:00
Files
WxJava/src/main/java/chanjarster/weixin/api/WxServiceImpl.java
2014-08-25 16:58:21 +08:00

253 lines
9.6 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package chanjarster.weixin.api;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import chanjarster.weixin.bean.WxAccessToken;
import chanjarster.weixin.bean.WxCustomMessage;
import chanjarster.weixin.bean.WxMassGroupMessage;
import chanjarster.weixin.bean.WxMassNews;
import chanjarster.weixin.bean.WxMassOpenIdsMessage;
import chanjarster.weixin.bean.WxMassVideo;
import chanjarster.weixin.bean.WxMenu;
import chanjarster.weixin.bean.result.WxError;
import chanjarster.weixin.bean.result.WxMassSendResult;
import chanjarster.weixin.bean.result.WxMassUploadResult;
import chanjarster.weixin.bean.result.WxMediaUploadResult;
import chanjarster.weixin.exception.WxErrorException;
import chanjarster.weixin.util.fs.FileUtil;
import chanjarster.weixin.util.http.MediaDownloadRequestExecutor;
import chanjarster.weixin.util.http.MediaUploadRequestExecutor;
import chanjarster.weixin.util.http.RequestExecutor;
import chanjarster.weixin.util.http.SimpleGetRequestExecutor;
import chanjarster.weixin.util.http.SimplePostRequestExecutor;
public class WxServiceImpl implements WxService {
/**
* 全局的是否正在刷新Access Token的flag
* true: 正在刷新
* false: 没有刷新
*/
protected static final AtomicBoolean GLOBAL_ACCESS_TOKEN_REFRESH_FLAG = new AtomicBoolean(false);
protected static final CloseableHttpClient httpclient = HttpClients.createDefault();
protected WxConfigStorage wxConfigStorage;
protected final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>();
public boolean checkSignature(String timestamp, String nonce, String signature) {
try {
String token = wxConfigStorage.getToken();
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
String[] arr = new String[] { token, timestamp, nonce };
Arrays.sort(arr);
StringBuilder sb = new StringBuilder();
for(String a : arr) {
sb.append(a);
}
sha1.update(sb.toString().getBytes());
byte[] output = sha1.digest();
return bytesToHex(output).equals(signature);
} catch (Exception e) {
return false;
}
}
protected String bytesToHex(byte[] b) {
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]);
buf.append(hexDigit[b[j] & 0x0f]);
}
return buf.toString();
}
public void refreshAccessToken() 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=" + wxConfigStorage.getAppId()
+ "&secret=" + wxConfigStorage.getSecret()
;
try {
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse response = httpclient.execute(httpGet);
String resultContent = new BasicResponseHandler().handleResponse(response);
WxError error = WxError.fromJson(resultContent);
if (error.getErrcode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
wxConfigStorage.updateAccessToken(accessToken.getAccess_token(), accessToken.getExpires_in());
} 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 void sendCustomMessage(WxCustomMessage message) throws WxErrorException {
String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send";
execute(new SimplePostRequestExecutor(), url, message.toJson());
}
public void createMenu(WxMenu menu) throws WxErrorException {
String url = "https://api.weixin.qq.com/cgi-bin/menu/create";
execute(new SimplePostRequestExecutor(), url, menu.toJson());
}
public void deleteMenu() throws WxErrorException {
String url = "https://api.weixin.qq.com/cgi-bin/menu/delete";
execute(new SimpleGetRequestExecutor(), url, null);
}
public WxMenu getMenu() throws WxErrorException {
String url = "https://api.weixin.qq.com/cgi-bin/menu/get";
try {
String resultContent = execute(new SimpleGetRequestExecutor(), url, null);
return WxMenu.fromJson(resultContent);
} catch (WxErrorException e) {
// 46003 不存在的菜单数据
if (e.getError().getErrcode() == 46003) {
return null;
}
throw e;
}
}
public WxMediaUploadResult uploadMedia(String mediaType, String fileType, InputStream inputStream) throws WxErrorException, IOException {
return uploadMedia(mediaType,FileUtil.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType));
}
public WxMediaUploadResult uploadMedia(String mediaType, File file) throws WxErrorException {
String url = "http://file.api.weixin.qq.com/cgi-bin/media/upload?type=" + mediaType;
return execute(new MediaUploadRequestExecutor(), url, file);
}
public File downloadMedia(String media_id) throws WxErrorException {
String url = "http://file.api.weixin.qq.com/cgi-bin/media/get";
return execute(new MediaDownloadRequestExecutor(), url, "media_id=" + media_id);
}
public WxMassUploadResult uploadMassNews(WxMassNews news) throws WxErrorException {
String url = "https://api.weixin.qq.com/cgi-bin/media/uploadnews";
String responseContent = execute(new SimplePostRequestExecutor(), url, news.toJson());
return WxMassUploadResult.fromJson(responseContent);
}
public WxMassUploadResult uploadMassVideo(WxMassVideo video) throws WxErrorException {
String url = "http://file.api.weixin.qq.com/cgi-bin/media/uploadvideo";
String responseContent = execute(new SimplePostRequestExecutor(), url, video.toJson());
return WxMassUploadResult.fromJson(responseContent);
}
public WxMassSendResult sendMassMessageByGroup(WxMassGroupMessage message) throws WxErrorException {
String url = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall";
String responseContent = execute(new SimplePostRequestExecutor(), url, message.toJson());
return WxMassSendResult.fromJson(responseContent);
}
public WxMassSendResult sendMassMessageByOpenIds(WxMassOpenIdsMessage message) throws WxErrorException {
String url = "https://api.weixin.qq.com/cgi-bin/message/mass/send";
String responseContent = execute(new SimplePostRequestExecutor(), url, message.toJson());
return WxMassSendResult.fromJson(responseContent);
}
/**
* 向微信端发送请求在这里执行的策略是当发生access_token过期时才去刷新然后重新执行请求而不是全局定时请求
* @param executor
* @param uri
* @param data
* @return
* @throws WxErrorException
*/
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
if (StringUtils.isBlank(wxConfigStorage.getAccessToken())) {
refreshAccessToken();
}
String accessToken = wxConfigStorage.getAccessToken();
String uriWithAccessToken = uri;
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
try {
return executor.execute(uriWithAccessToken, data);
} catch (WxErrorException e) {
WxError error = e.getError();
/*
* 发生以下情况时尝试刷新access_token
* 40001 获取access_token时AppSecret错误或者access_token无效
* 42001 access_token超时
*/
if (error.getErrcode() == 42001 || error.getErrcode() == 40001) {
refreshAccessToken();
return execute(executor, uri, data);
}
/**
* -1 系统繁忙, 1000ms后重试
*/
if (error.getErrcode() == -1) {
if(retryTimes.get() == null) {
retryTimes.set(0);
}
if (retryTimes.get() > 5) {
retryTimes.set(0);
throw new RuntimeException("微信服务端异常,超出重试次数");
}
int sleepMillis = 1000 * (1 >> (retryTimes.get() - 1));
try {
System.out.println("微信系统繁忙," + sleepMillis + "ms后重试");
Thread.sleep(sleepMillis);
retryTimes.set(retryTimes.get() + 1);
return execute(executor, uri, data);
} catch (InterruptedException e1) {
throw new RuntimeException(e1);
}
}
if (error.getErrcode() != 0) {
throw new WxErrorException(error);
}
return null;
} catch (ClientProtocolException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void setWxConfigStorage(WxConfigStorage wxConfigProvider) {
this.wxConfigStorage = wxConfigProvider;
}
}