BeisenApiClient.java 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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. }else {
  99. retryCount++;
  100. break;
  101. }
  102. }
  103. }
  104. return handleResponse;
  105. //不抛出,使用调用的地方存储msg
  106. // throw new IOException("API request failed after " + MAX_RETRIES + " attempts");
  107. }
  108. // 响应处理逻辑-------------------------------------------------------------
  109. private JSONObject handleResponse(Response response, String apiUrl, String method) throws IOException {
  110. String responseBody = response.body() != null ? response.body().string() : "null";
  111. if (!response.isSuccessful()) {
  112. handleHttpError(responseBody, apiUrl, method);
  113. }
  114. JSONObject jsonResponse = parseResponse(responseBody, apiUrl);
  115. validateBusinessStatus(jsonResponse, apiUrl);
  116. return jsonResponse;
  117. }
  118. private void handleHttpError(String errorBody, String apiUrl, String method) throws IOException {
  119. logger.error("API request failed "+
  120. ", URL: " + apiUrl + ", Method: " + method +
  121. ", Body: " + errorBody);
  122. System.out.print("调用错误"+errorBody);
  123. // throw new IOException("HTTP error: " + response.code());
  124. }
  125. private JSONObject parseResponse(String responseBody, String apiUrl) throws IOException {
  126. try {
  127. return JSON.parseObject(responseBody);
  128. } catch (Exception e) {
  129. logger.error("Invalid JSON response from API. URL: " + apiUrl + ", Response: " + responseBody);
  130. throw new IOException("Failed to parse API response", e);
  131. }
  132. }
  133. private void validateBusinessStatus(JSONObject jsonResponse, String apiUrl) throws IOException {
  134. int apiCode = jsonResponse.getIntValue("code");
  135. if (apiCode != 200) {
  136. String apiMessage = jsonResponse.getString("message");
  137. logger.error("API business error. Code: " + apiCode +
  138. ", Message: " + apiMessage + ", URL: " + apiUrl);
  139. // throw new IOException("Business error: " + apiMessage + " (code: " + apiCode + ")");
  140. }
  141. }
  142. // Token重试逻辑-----------------------------------------------------------
  143. private boolean shouldRetry(boolean retryOnTokenExpired, int retryCount, Exception e) {
  144. return retryOnTokenExpired &&
  145. isTokenExpiredError(e) &&
  146. retryCount < MAX_RETRIES;
  147. }
  148. private void handleTokenRefresh(int retryCount) {
  149. logger.warn("Token may have expired, refreshing and retrying... (" +
  150. retryCount + "/" + MAX_RETRIES + ")");
  151. try {
  152. tokenManager.refreshToken();
  153. } catch (IOException ex) {
  154. logger.error("Failed to refresh token during retry", ex);
  155. }
  156. }
  157. // 请求构建----------------------------------------------------------------
  158. private Request buildRequest(String apiUrl, Object requestBody, String accessToken, String method) {
  159. String jsonString = convertToJsonString(requestBody);
  160. RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, jsonString);
  161. if (requestBody instanceof JSONObject) {
  162. if (((JSONObject) requestBody).size() == 0) {
  163. body = null;
  164. }
  165. }
  166. return new Request.Builder()
  167. .url(apiUrl)
  168. .addHeader("Authorization", "Bearer " + accessToken)
  169. .addHeader("Content-Type", "application/json")
  170. .method(method, body)
  171. .build();
  172. }
  173. private String convertToJsonString(Object data) {
  174. if (data instanceof JSONObject) {
  175. return ((JSONObject) data).toJSONString();
  176. } else if (data instanceof JSONArray) {
  177. return ((JSONArray) data).toJSONString();
  178. }
  179. throw new IllegalArgumentException("Unsupported request data type: " + data.getClass());
  180. }
  181. // 辅助方法---------------------------------------------------------------
  182. private boolean isTokenExpiredError(Exception e) {
  183. String msg = e.getMessage();
  184. return msg != null && (msg.contains("401") ||
  185. msg.contains("Unauthorized") ||
  186. msg.contains("token"));
  187. }
  188. private OkHttpClient buildHttpClient() {
  189. return new OkHttpClient.Builder()
  190. .connectTimeout(15, TimeUnit.SECONDS)
  191. .writeTimeout(15, TimeUnit.SECONDS)
  192. .readTimeout(45, TimeUnit.SECONDS)
  193. .connectionPool(new ConnectionPool(50, 5, TimeUnit.MINUTES))
  194. .retryOnConnectionFailure(true)
  195. .build();
  196. }
  197. }