dev --- 3.8.0.A版本, openProject引用 ; formao-live --- 3.7.0.B 版本, formallProject引用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
5.8 KiB

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