dev --- 3.8.0.A版本, openProject引用 ; formao-live --- 3.7.0.B 版本, formallProject引用
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

173 lines
6.2 KiB

  1. package chanjarster.weixin.api;
  2. import java.io.IOException;
  3. import java.util.concurrent.atomic.AtomicBoolean;
  4. import org.apache.commons.lang3.StringUtils;
  5. import org.apache.http.Consts;
  6. import org.apache.http.client.ClientProtocolException;
  7. import org.apache.http.client.methods.CloseableHttpResponse;
  8. import org.apache.http.client.methods.HttpGet;
  9. import org.apache.http.client.methods.HttpPost;
  10. import org.apache.http.entity.ContentType;
  11. import org.apache.http.entity.StringEntity;
  12. import org.apache.http.impl.client.BasicResponseHandler;
  13. import org.apache.http.impl.client.CloseableHttpClient;
  14. import org.apache.http.impl.client.HttpClients;
  15. import chanjarster.weixin.bean.WxAccessToken;
  16. import chanjarster.weixin.bean.WxCustomMessage;
  17. import chanjarster.weixin.bean.WxError;
  18. import chanjarster.weixin.bean.WxMenu;
  19. import chanjarster.weixin.exception.WxErrorException;
  20. import chanjarster.weixin.util.Utf8ResponseHandler;
  21. public class WxServiceImpl implements WxService {
  22. /**
  23. * 全局的是否正在刷新Access Token的flag
  24. * true: 正在刷新
  25. * false: 没有刷新
  26. */
  27. protected static final AtomicBoolean GLOBAL_ACCESS_TOKEN_REFRESH_FLAG = new AtomicBoolean(false);
  28. protected static final CloseableHttpClient httpclient = HttpClients.createDefault();
  29. protected WxConfigStorage wxConfigProvider;
  30. public void refreshAccessToken() throws WxErrorException {
  31. if (!GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.getAndSet(true)) {
  32. try {
  33. String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"
  34. + "&appid=" + wxConfigProvider.getAppId()
  35. + "&secret=" + wxConfigProvider.getSecret()
  36. ;
  37. try {
  38. HttpGet httpGet = new HttpGet(url);
  39. CloseableHttpResponse response = httpclient.execute(httpGet);
  40. String resultContent = new BasicResponseHandler().handleResponse(response);
  41. WxError error = WxError.fromJson(resultContent);
  42. if (error.getErrcode() != 0) {
  43. throw new WxErrorException(error);
  44. }
  45. WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
  46. wxConfigProvider.updateAccessToken(accessToken.getAccess_token(), accessToken.getExpires_in());
  47. } catch (ClientProtocolException e) {
  48. throw new RuntimeException(e);
  49. } catch (IOException e) {
  50. throw new RuntimeException(e);
  51. }
  52. } finally {
  53. GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.set(false);
  54. }
  55. } else {
  56. // 每隔100ms检查一下是否刷新完毕了
  57. while (GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.get()) {
  58. try {
  59. Thread.sleep(100);
  60. } catch (InterruptedException e) {
  61. }
  62. }
  63. // 刷新完毕了,就没他什么事儿了
  64. }
  65. }
  66. public String sendCustomMessage(WxCustomMessage message) throws WxErrorException {
  67. String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send";
  68. return post(url, message.toJson());
  69. }
  70. public String createMenu(WxMenu menu) throws WxErrorException {
  71. String url = "https://api.weixin.qq.com/cgi-bin/menu/create";
  72. return post(url, menu.toJson());
  73. }
  74. public String deleteMenu() throws WxErrorException {
  75. String url = "https://api.weixin.qq.com/cgi-bin/menu/delete";
  76. return get(url, null);
  77. }
  78. public WxMenu getMenu() throws WxErrorException {
  79. String url = "https://api.weixin.qq.com/cgi-bin/menu/get";
  80. try {
  81. String resultContent = get(url, null);
  82. return WxMenu.fromJson(resultContent);
  83. } catch (WxErrorException e) {
  84. // 46003 不存在的菜单数据
  85. if (e.getError().getErrcode() == 46003) {
  86. return null;
  87. }
  88. throw e;
  89. }
  90. }
  91. protected String post(String uri, String data) throws WxErrorException {
  92. return execute("POST", uri, data);
  93. }
  94. protected String get(String uri, String data) throws WxErrorException {
  95. return execute("GET", uri, data);
  96. }
  97. /**
  98. * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
  99. * @param request
  100. * @return 微信服务端返回的结果
  101. * @throws WxErrorException
  102. */
  103. protected String execute(String method, String uri, String data) throws WxErrorException {
  104. if (StringUtils.isBlank(wxConfigProvider.getAccessToken())) {
  105. refreshAccessToken();
  106. }
  107. String accessToken = wxConfigProvider.getAccessToken();
  108. String uriWithAccessToken = uri;
  109. uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
  110. try {
  111. String resultContent = null;
  112. if ("POST".equals(method)) {
  113. HttpPost httpPost = new HttpPost(uriWithAccessToken);
  114. if (data != null) {
  115. StringEntity entity = new StringEntity(data, Consts.UTF_8);
  116. httpPost.setEntity(entity);
  117. }
  118. CloseableHttpResponse response = httpclient.execute(httpPost);
  119. resultContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
  120. } else if ("GET".equals(method)) {
  121. if (data != null) {
  122. uriWithAccessToken += uriWithAccessToken.endsWith("&") ? data : '&' + data;
  123. }
  124. HttpGet httpGet = new HttpGet(uriWithAccessToken);
  125. CloseableHttpResponse response = httpclient.execute(httpGet);
  126. response.setHeader("Content-Type", ContentType.APPLICATION_JSON.toString());
  127. resultContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
  128. }
  129. WxError error = WxError.fromJson(resultContent);
  130. /*
  131. * 发生以下情况时尝试刷新access_token
  132. * 40001 获取access_token时AppSecret错误,或者access_token无效
  133. * 42001 access_token超时
  134. */
  135. if (error.getErrcode() == 42001 || error.getErrcode() == 40001) {
  136. refreshAccessToken();
  137. return execute(method, uri, data);
  138. }
  139. if (error.getErrcode() != 0) {
  140. throw new WxErrorException(error);
  141. }
  142. return resultContent;
  143. } catch (ClientProtocolException e) {
  144. throw new RuntimeException(e);
  145. } catch (IOException e) {
  146. throw new RuntimeException(e);
  147. }
  148. }
  149. public void setWxConfigProvider(WxConfigStorage wxConfigProvider) {
  150. this.wxConfigProvider = wxConfigProvider;
  151. }
  152. }