2014-08-21 21:19:24 +08:00
|
|
|
|
package chanjarster.weixin.api;
|
2014-08-18 16:48:20 +08:00
|
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
|
|
|
|
|
|
import org.apache.commons.lang3.StringUtils;
|
2014-08-21 22:13:13 +08:00
|
|
|
|
import org.apache.http.Consts;
|
2014-08-18 16:48:20 +08:00
|
|
|
|
import org.apache.http.client.ClientProtocolException;
|
|
|
|
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
|
|
|
|
import org.apache.http.client.methods.HttpGet;
|
|
|
|
|
import org.apache.http.client.methods.HttpPost;
|
2014-08-21 22:13:13 +08:00
|
|
|
|
import org.apache.http.entity.ContentType;
|
2014-08-18 16:48:20 +08:00
|
|
|
|
import org.apache.http.entity.StringEntity;
|
|
|
|
|
import org.apache.http.impl.client.BasicResponseHandler;
|
|
|
|
|
import org.apache.http.impl.client.CloseableHttpClient;
|
|
|
|
|
import org.apache.http.impl.client.HttpClients;
|
|
|
|
|
|
2014-08-21 21:19:24 +08:00
|
|
|
|
import chanjarster.weixin.bean.WxAccessToken;
|
|
|
|
|
import chanjarster.weixin.bean.WxCustomMessage;
|
|
|
|
|
import chanjarster.weixin.bean.WxError;
|
|
|
|
|
import chanjarster.weixin.bean.WxMenu;
|
2014-08-18 16:48:20 +08:00
|
|
|
|
import chanjarster.weixin.exception.WxErrorException;
|
2014-08-21 22:13:13 +08:00
|
|
|
|
import chanjarster.weixin.util.Utf8StringResponseHandler;
|
2014-08-18 16:48:20 +08:00
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
2014-08-21 22:13:13 +08:00
|
|
|
|
protected WxConfigStorage wxConfigProvider;
|
2014-08-18 16:48:20 +08:00
|
|
|
|
|
|
|
|
|
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=" + wxConfigProvider.getAppId()
|
|
|
|
|
+ "&secret=" + wxConfigProvider.getSecret()
|
|
|
|
|
;
|
|
|
|
|
try {
|
|
|
|
|
HttpGet httpGet = new HttpGet(url);
|
|
|
|
|
CloseableHttpResponse response = httpclient.execute(httpGet);
|
|
|
|
|
String resultContent = new BasicResponseHandler().handleResponse(response);
|
2014-08-21 22:13:13 +08:00
|
|
|
|
WxError error = WxError.fromJson(resultContent);
|
|
|
|
|
if (error.getErrcode() != 0) {
|
|
|
|
|
throw new WxErrorException(error);
|
|
|
|
|
}
|
2014-08-18 16:48:20 +08:00
|
|
|
|
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
|
|
|
|
|
wxConfigProvider.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 String sendCustomMessage(WxCustomMessage message) throws WxErrorException {
|
|
|
|
|
String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send";
|
|
|
|
|
return post(url, message.toJson());
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-21 22:13:13 +08:00
|
|
|
|
public String createMenu(WxMenu menu) throws WxErrorException {
|
|
|
|
|
String url = "https://api.weixin.qq.com/cgi-bin/menu/create";
|
|
|
|
|
return post(url, menu.toJson());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String deleteMenu() throws WxErrorException {
|
|
|
|
|
String url = "https://api.weixin.qq.com/cgi-bin/menu/delete";
|
|
|
|
|
return get(url, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public WxMenu getMenu() throws WxErrorException {
|
|
|
|
|
String url = "https://api.weixin.qq.com/cgi-bin/menu/get";
|
|
|
|
|
try {
|
|
|
|
|
String resultContent = get(url, null);
|
|
|
|
|
return WxMenu.fromJson(resultContent);
|
|
|
|
|
} catch (WxErrorException e) {
|
|
|
|
|
// 46003 不存在的菜单数据
|
|
|
|
|
if (e.getError().getErrcode() == 46003) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-18 16:48:20 +08:00
|
|
|
|
protected String post(String uri, String data) throws WxErrorException {
|
|
|
|
|
return execute("POST", uri, data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected String get(String uri, String data) throws WxErrorException {
|
|
|
|
|
return execute("GET", uri, data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
|
|
|
|
|
* @param request
|
|
|
|
|
* @return 微信服务端返回的结果
|
|
|
|
|
* @throws WxErrorException
|
|
|
|
|
*/
|
|
|
|
|
protected String execute(String method, String uri, String data) throws WxErrorException {
|
|
|
|
|
if (StringUtils.isBlank(wxConfigProvider.getAccessToken())) {
|
|
|
|
|
refreshAccessToken();
|
|
|
|
|
}
|
|
|
|
|
String accessToken = wxConfigProvider.getAccessToken();
|
|
|
|
|
|
|
|
|
|
String uriWithAccessToken = uri;
|
|
|
|
|
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
String resultContent = null;
|
|
|
|
|
if ("POST".equals(method)) {
|
|
|
|
|
HttpPost httpPost = new HttpPost(uriWithAccessToken);
|
2014-08-21 22:13:13 +08:00
|
|
|
|
if (data != null) {
|
|
|
|
|
StringEntity entity = new StringEntity(data, Consts.UTF_8);
|
|
|
|
|
httpPost.setEntity(entity);
|
|
|
|
|
}
|
2014-08-18 16:48:20 +08:00
|
|
|
|
CloseableHttpResponse response = httpclient.execute(httpPost);
|
2014-08-21 22:13:13 +08:00
|
|
|
|
resultContent = Utf8StringResponseHandler.INSTANCE.handleResponse(response);
|
2014-08-18 16:48:20 +08:00
|
|
|
|
} else if ("GET".equals(method)) {
|
2014-08-21 22:13:13 +08:00
|
|
|
|
if (data != null) {
|
|
|
|
|
uriWithAccessToken += uriWithAccessToken.endsWith("&") ? data : '&' + data;
|
|
|
|
|
}
|
2014-08-18 16:48:20 +08:00
|
|
|
|
HttpGet httpGet = new HttpGet(uriWithAccessToken);
|
|
|
|
|
CloseableHttpResponse response = httpclient.execute(httpGet);
|
2014-08-21 22:13:13 +08:00
|
|
|
|
response.setHeader("Content-Type", ContentType.APPLICATION_JSON.toString());
|
|
|
|
|
resultContent = Utf8StringResponseHandler.INSTANCE.handleResponse(response);
|
2014-08-18 16:48:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WxError error = WxError.fromJson(resultContent);
|
|
|
|
|
/*
|
2014-08-21 22:13:13 +08:00
|
|
|
|
* 发生以下情况时尝试刷新access_token
|
|
|
|
|
* 40001 获取access_token时AppSecret错误,或者access_token无效
|
2014-08-18 16:48:20 +08:00
|
|
|
|
* 42001 access_token超时
|
|
|
|
|
*/
|
|
|
|
|
if (error.getErrcode() == 42001 || error.getErrcode() == 40001) {
|
|
|
|
|
refreshAccessToken();
|
|
|
|
|
return execute(method, uri, data);
|
|
|
|
|
}
|
|
|
|
|
if (error.getErrcode() != 0) {
|
|
|
|
|
throw new WxErrorException(error);
|
|
|
|
|
}
|
|
|
|
|
return resultContent;
|
|
|
|
|
} catch (ClientProtocolException e) {
|
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-21 22:13:13 +08:00
|
|
|
|
public void setWxConfigProvider(WxConfigStorage wxConfigProvider) {
|
2014-08-18 16:48:20 +08:00
|
|
|
|
this.wxConfigProvider = wxConfigProvider;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|