BeisenApiClient.java 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. package com.kingdee.eas.custom.beisen.utils;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONArray;
  4. import com.alibaba.fastjson.JSONObject;
  5. import okhttp3.*;
  6. import org.apache.log4j.Logger;
  7. import java.io.IOException;
  8. import java.util.concurrent.TimeUnit;
  9. /**
  10. * 北森API调用工具类(单例模式)
  11. */
  12. public class BeisenApiClient {
  13. private static final Logger logger = Logger.getLogger(BeisenApiClient.class);
  14. private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8");
  15. private static final int MAX_RETRIES = 3;
  16. // 单例实例
  17. private static volatile BeisenApiClient instance;
  18. private final OkHttpClient httpClient;
  19. private final BeisenTokenManager tokenManager;
  20. // 私有构造函数
  21. private BeisenApiClient() {
  22. this.httpClient = buildHttpClient();
  23. this.tokenManager = BeisenTokenManager.getInstance();
  24. }
  25. /**
  26. * 获取单例实例
  27. */
  28. public static BeisenApiClient getInstance() {
  29. if (instance == null) {
  30. synchronized (BeisenApiClient.class) {
  31. if (instance == null) {
  32. instance = new BeisenApiClient();
  33. }
  34. }
  35. }
  36. return instance;
  37. }
  38. /**
  39. * api调用qun
  40. *
  41. * @param apiUrl
  42. * @param requestData
  43. * @return
  44. * @throws IOException
  45. */
  46. public JSONObject callApi(String apiUrl, JSONObject requestData) throws IOException {
  47. return callApi(apiUrl, requestData, "POST");
  48. }
  49. public JSONObject callApi(String apiUrl, JSONArray dataArray) throws IOException {
  50. return callApi(apiUrl, dataArray, "POST");
  51. }
  52. public JSONObject callPutApi(String apiUrl, JSONObject requestData) throws IOException {
  53. return callApi(apiUrl, requestData, "PUT");
  54. }
  55. public JSONObject callGetApi(String apiUrl, JSONObject requestData) throws IOException {
  56. return callApi(apiUrl, requestData, "GET");
  57. }
  58. public JSONObject callGetApi(String apiUrl, JSONArray dataArray) throws IOException {
  59. return callApi(apiUrl, dataArray, "GET");
  60. }
  61. /**
  62. * api调用
  63. *
  64. * @param apiUrl
  65. * @param requestData
  66. * @return
  67. * @throws IOException
  68. */
  69. public JSONObject callApi(String apiUrl, JSONObject requestData, String method) throws IOException {
  70. return callApiInternal(apiUrl, requestData, true, method);
  71. }
  72. public JSONObject callApi(String apiUrl, JSONArray dataArray, String method) throws IOException {
  73. return callApiInternal(apiUrl, dataArray, true, method);
  74. }
  75. // 核心API调用逻辑----------------------------------------------------------
  76. /**
  77. * 通用API调用方法
  78. *
  79. * @param requestBody 支持JSONObject/JSONArray
  80. */
  81. private JSONObject callApiInternal(String apiUrl, Object requestBody, boolean retryOnTokenExpired, String method)
  82. throws IOException {
  83. int retryCount = 0;
  84. JSONObject handleResponse = new JSONObject();
  85. while (retryCount <= MAX_RETRIES) {
  86. try {
  87. String accessToken = tokenManager.getAccessToken();
  88. Request request = buildRequest(apiUrl, requestBody, accessToken, method);
  89. try (Response response = httpClient.newCall(request).execute()) {
  90. handleResponse = handleResponse(response, apiUrl, method);
  91. return handleResponse;
  92. }
  93. } catch (IOException e) {
  94. if (shouldRetry(retryOnTokenExpired, retryCount, e)) {
  95. retryCount++;
  96. handleTokenRefresh(retryCount);
  97. continue;
  98. }
  99. }
  100. }
  101. return handleResponse;
  102. //不抛出,使用调用的地方存储msg
  103. // throw new IOException("API request failed after " + MAX_RETRIES + " attempts");
  104. }
  105. // 响应处理逻辑-------------------------------------------------------------
  106. private JSONObject handleResponse(Response response, String apiUrl, String method) throws IOException {
  107. if (!response.isSuccessful()) {
  108. handleHttpError(response, apiUrl, method);
  109. }
  110. String responseBody = response.body().string();
  111. JSONObject jsonResponse = parseResponse(responseBody, apiUrl);
  112. validateBusinessStatus(jsonResponse, apiUrl);
  113. return jsonResponse;
  114. }
  115. private void handleHttpError(Response response, String apiUrl, String method) throws IOException {
  116. String errorBody = response.body() != null ? response.body().string() : "null";
  117. logger.error("API request failed. Code: " + response.code() +
  118. ", URL: " + apiUrl + ", Method: " + method +
  119. ", Body: " + errorBody);
  120. // throw new IOException("HTTP error: " + response.code());
  121. }
  122. private JSONObject parseResponse(String responseBody, String apiUrl) throws IOException {
  123. try {
  124. return JSON.parseObject(responseBody);
  125. } catch (Exception e) {
  126. logger.error("Invalid JSON response from API. URL: " + apiUrl + ", Response: " + responseBody);
  127. throw new IOException("Failed to parse API response", e);
  128. }
  129. }
  130. private void validateBusinessStatus(JSONObject jsonResponse, String apiUrl) throws IOException {
  131. int apiCode = jsonResponse.getIntValue("code");
  132. if (apiCode != 200) {
  133. String apiMessage = jsonResponse.getString("message");
  134. logger.error("API business error. Code: " + apiCode +
  135. ", Message: " + apiMessage + ", URL: " + apiUrl);
  136. // throw new IOException("Business error: " + apiMessage + " (code: " + apiCode + ")");
  137. }
  138. }
  139. // Token重试逻辑-----------------------------------------------------------
  140. private boolean shouldRetry(boolean retryOnTokenExpired, int retryCount, Exception e) {
  141. return retryOnTokenExpired &&
  142. isTokenExpiredError(e) &&
  143. retryCount < MAX_RETRIES;
  144. }
  145. private void handleTokenRefresh(int retryCount) {
  146. logger.warn("Token may have expired, refreshing and retrying... (" +
  147. retryCount + "/" + MAX_RETRIES + ")");
  148. try {
  149. tokenManager.refreshToken();
  150. } catch (IOException ex) {
  151. logger.error("Failed to refresh token during retry", ex);
  152. }
  153. }
  154. // 请求构建----------------------------------------------------------------
  155. private Request buildRequest(String apiUrl, Object requestBody, String accessToken, String method) {
  156. String jsonString = convertToJsonString(requestBody);
  157. RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, jsonString);
  158. if (requestBody instanceof JSONObject) {
  159. if (((JSONObject) requestBody).size() == 0) {
  160. body = null;
  161. }
  162. }
  163. return new Request.Builder()
  164. .url(apiUrl)
  165. .addHeader("Authorization", "Bearer " + accessToken)
  166. .addHeader("Content-Type", "application/json")
  167. .method(method, body)
  168. .build();
  169. }
  170. private String convertToJsonString(Object data) {
  171. if (data instanceof JSONObject) {
  172. return ((JSONObject) data).toJSONString();
  173. } else if (data instanceof JSONArray) {
  174. return ((JSONArray) data).toJSONString();
  175. }
  176. throw new IllegalArgumentException("Unsupported request data type: " + data.getClass());
  177. }
  178. // 辅助方法---------------------------------------------------------------
  179. private boolean isTokenExpiredError(Exception e) {
  180. String msg = e.getMessage();
  181. return msg != null && (msg.contains("401") ||
  182. msg.contains("Unauthorized") ||
  183. msg.contains("token"));
  184. }
  185. private OkHttpClient buildHttpClient() {
  186. return new OkHttpClient.Builder()
  187. .connectTimeout(15, TimeUnit.SECONDS)
  188. .writeTimeout(15, TimeUnit.SECONDS)
  189. .readTimeout(45, TimeUnit.SECONDS)
  190. .connectionPool(new ConnectionPool(50, 5, TimeUnit.MINUTES))
  191. .retryOnConnectionFailure(true)
  192. .build();
  193. }
  194. }