浏览代码

fix(core): 优化第三方接口调用日志和异常处理

- 在调用第三方接口时添加详细请求与响应日志
- 优化异常捕获,记录异常信息并抛出运行时异常
- 增强获取Token流程中的日志输出,涵盖请求构造、加密、签名及响应解析各环节
- 修正请求参数和响应实体中的JSON字段映射,确保正确序列化与反序列化
- 修改OkHttp配置,增加连接与读取超时时间及重试拦截器,支持指数退避重试策略
- 优化OkHttpUtil中POST请求的异常捕获与日志打印,保证异常信息完整记录
- 调整LogAspect中过滤空对象的逻辑,提高日志切面过滤的准确性
SheepHy 2 周之前
父节点
当前提交
b8c7efb5ce

+ 48 - 35
src/main/java/com/zsElectric/boot/common/util/OkHttpUtil.java

@@ -72,58 +72,71 @@ public class OkHttpUtil {
     /**
      * 执行 POST 请求(表单数据),返回 JsonObject
      */
-    public JsonObject doPostForm(String url, String json, Map<String, String> headers) throws IOException {
-        RequestBody body = RequestBody.create(json, JSON);
+    public JsonObject doPostForm(String url, String json, Map<String, String> headers) {
+        try {
+            RequestBody body = RequestBody.create(json, JSON);
 
-        Request.Builder requestBuilder = new Request.Builder()
-                .url(url)
-                .post(body);
+            Request.Builder requestBuilder = new Request.Builder()
+                    .url(url)
+                    .post(body);
 
-        // 设置 headers
-        if (MapUtil.isNotEmpty(headers)) {
-            headers.forEach(requestBuilder::addHeader);
-        }
-        requestBuilder.addHeader("Content-Type", "application/json;charset=utf-8");
+            // 设置 headers
+            if (MapUtil.isNotEmpty(headers)) {
+                headers.forEach(requestBuilder::addHeader);
+            }
+            requestBuilder.addHeader("Content-Type", "application/json;charset=utf-8");
 
-        Request request = requestBuilder.build();
-        log.info("POST 请求 URL: " + url);
-        log.info("POST 请求 JSON 数据: " + json);
-        try (Response response = okHttpClient.newCall(request).execute()) {
+            Request request = requestBuilder.build();
+            log.info("POST 请求 URL: " + url);
+            log.info("POST 请求 JSON 数据: " + json);
+            Response response = okHttpClient.newCall(request).execute();
             if (!response.isSuccessful()) {
-                throw new IOException("Unexpected code: " + response);
+                String errorBody = response.body() != null ? response.body().string() : "";
+                log.error("HTTP 请求失败,URL: {}, 状态码: {}, 响应: {}", url, response.code(), errorBody);
+                throw new IOException("请求失败,状态码: " + response.code() + ", 响应: " + errorBody);
             }
             String responseBody = response.body().string();
+            log.info("HTTP 请求成功,URL: {}, 响应: {}", url, responseBody);
+            response.close();
             return JsonParser.parseString(responseBody).getAsJsonObject();
+        } catch (IOException e) {
+            log.error("HTTP 请求异常,URL: {}", url, e);
+            throw new RuntimeException("请求异常: " + e.getMessage(), e);
         }
     }
 
-    public JsonObject doPostForm(String url, Map<String, Object> formData, Map<String, String> headers) throws IOException {
-        FormBody.Builder formBodyBuilder = new FormBody.Builder();
-        if (formData != null) {
-            formData.forEach((k, v) -> formBodyBuilder.add(k, String.valueOf(v)));
-        }
-
-        Request.Builder requestBuilder = new Request.Builder()
-                .url(url)
-                .post(formBodyBuilder.build());
+    public JsonObject doPostForm(String url, Map<String, Object> formData, Map<String, String> headers) {
+        try {
+            FormBody.Builder formBodyBuilder = new FormBody.Builder();
+            if (formData != null) {
+                formData.forEach((k, v) -> formBodyBuilder.add(k, String.valueOf(v)));
+            }
 
-        // 正确设置 Content-Type header
-        if (MapUtil.isNotEmpty(headers)) {
-            headers.forEach(requestBuilder::addHeader);
-        }
+            Request.Builder requestBuilder = new Request.Builder()
+                    .url(url)
+                    .post(formBodyBuilder.build());
 
-        // 如果需要覆盖 Content-Type,应该在这里设置
-        requestBuilder.addHeader("Content-Type", "application/json");
+            // 正确设置 Content-Type header
+            if (MapUtil.isNotEmpty(headers)) {
+                headers.forEach(requestBuilder::addHeader);
+            }
 
-        Request request = requestBuilder.build();
-        log.info("POST 请求 URL: " + url);
-        log.info("POST 请求表单数据: " + formData);
-        try (Response response = okHttpClient.newCall(request).execute()) {
+            Request request = requestBuilder.build();
+            log.info("POST 请求 URL: " + url);
+            log.info("POST 请求表单数据: " + formData);
+            Response response = okHttpClient.newCall(request).execute();
             if (!response.isSuccessful()) {
-                throw new IOException("Unexpected code: " + response);
+                String errorBody = response.body() != null ? response.body().string() : "";
+                log.error("HTTP 请求失败,URL: {}, 状态码: {}, 响应: {}", url, response.code(), errorBody);
+                throw new IOException("请求失败,状态码: " + response.code() + ", 响应: " + errorBody);
             }
             String responseBody = response.body().string();
+            log.info("HTTP 请求成功,URL: {}, 响应: {}", url, responseBody);
+            response.close();
             return JsonParser.parseString(responseBody).getAsJsonObject();
+        } catch (IOException e) {
+            log.error("HTTP 请求异常,URL: {}", url, e);
+            throw new RuntimeException("请求异常: " + e.getMessage(), e);
         }
     }
 

+ 7 - 3
src/main/java/com/zsElectric/boot/common/util/electric/ChargingUtil.java

@@ -50,15 +50,19 @@ public class ChargingUtil {
                             requestParms.getTimeStamp(),
                             requestParms.getSeq(),ConnectivityConstants.PLATFORM_SIG_SECRET));
 
-            JsonObject response = okHttpUtil.doPostForm(url, new Gson().toJson(requestParms), headers);
+            String requestJson = new Gson().toJson(requestParms);
+            log.info("调用第三方接口,URL: {}, 请求参数: {}", url, requestJson);
+            JsonObject response = okHttpUtil.doPostForm(url, requestJson, headers);
 
             if (Objects.isNull(response)) {
-                log.error("调用第三方接口获取Token失败");
+                log.error("调用第三方接口返回为空,URL: {}", url);
                 return null;
             }
+            log.info("调用第三方接口成功,URL: {}, 响应: {}", url, response);
             return response;
         }catch (Exception e){
-            throw new RuntimeException("调用第三方接口发生异常", e);
+            log.error("调用第三方接口发生异常,URL: {}", url, e);
+            throw new RuntimeException("调用第三方接口发生异常: " + e.getMessage(), e);
         }
     }
 

+ 57 - 11
src/main/java/com/zsElectric/boot/common/util/electric/ElectricTokenManager.java

@@ -149,35 +149,65 @@ public class ElectricTokenManager {
      * 调用第三方接口获取新Token
      */
     private ApiToken fetchNewTokenFromRemote() {
+        log.info("========== 开始调用第三方接口获取Token ==========");
         try {
             //调用第三方API获取Token
             QueryTokenParms queryTokenParms = new QueryTokenParms();
             queryTokenParms
                     .setOperatorID(ConnectivityConstants.OPERATOR_ID)
-                    .setOperatorSecret(ConnectivityConstants.OPERATOR_SECRET);
+                    .setOperatorSecret(ConnectivityConstants.PLATFORM_OPERATOR_SECRET);
+            log.info("构造请求参数: OperatorID={}, OperatorSecret={}", 
+                ConnectivityConstants.OPERATOR_ID, ConnectivityConstants.PLATFORM_OPERATOR_SECRET);
 
             RequestParmsEntity requestParms = new RequestParmsEntity();
             SequenceGenUtil.SequenceResult result = SequenceGenUtil.generate();
+            log.info("生成序列号: TimeStamp={}, Seq={}", result.getTimestamp(), result.getSequence());
 
             requestParms
                     .setOperatorID(ConnectivityConstants.OPERATOR_ID);
-            requestParms.setData(AESCryptoUtils.encrypt(new Gson().toJson(queryTokenParms), ConnectivityConstants.PLATFORM_DATA_SECRET,
-                            ConnectivityConstants.PLATFORM_DATA_SECRET_IV));
+            
+            // 加密Data字段
+            String dataToEncrypt = new Gson().toJson(queryTokenParms);
+            log.info("待加密的Data: {}", dataToEncrypt);
+            String encryptedData = AESCryptoUtils.encrypt(dataToEncrypt, ConnectivityConstants.PLATFORM_DATA_SECRET,
+                    ConnectivityConstants.PLATFORM_DATA_SECRET_IV);
+            log.info("加密后的Data: {}", encryptedData);
+            requestParms.setData(encryptedData);
+            
             requestParms.setTimeStamp(result.getTimestamp());
             requestParms.setSeq(result.getSequence());
-            requestParms.setSig(HmacMD5Util.genSign(requestParms.getOperatorID(), requestParms.getData(), requestParms.getTimeStamp(),
-                            requestParms.getSeq(), ConnectivityConstants.PLATFORM_SIG_SECRET));
-
-            JsonObject response = okHttpUtil.doPostForm(ConnectivityConstants.TEST_DOMAIN + ConnectivityConstants.QUERY_TOKEN, BeanUtil.beanToMap(requestParms), null);
+            
+            // 生成签名
+            String sig = HmacMD5Util.genSign(requestParms.getOperatorID(), requestParms.getData(), requestParms.getTimeStamp(),
+                    requestParms.getSeq(), ConnectivityConstants.PLATFORM_SIG_SECRET);
+            log.info("生成的签名: {}", sig);
+            requestParms.setSig(sig);
+
+            String requestUrl = ConnectivityConstants.TEST_DOMAIN + ConnectivityConstants.QUERY_TOKEN;
+            log.info("调用第三方接口获取Token,URL: {}", requestUrl);
+            
+            // 直接将请求对象序列化为JSON字符串,确保大驼峰命名
+            String requestJson = new Gson().toJson(requestParms);
+            log.info("请求参数JSON: {}", requestJson);
+            
+            JsonObject response = okHttpUtil.doPostForm(requestUrl, requestJson, null);
 
             if (Objects.isNull(response)) {
-                log.error("调用第三方接口获取Token失败");
+                log.error("调用第三方接口获取Token失败,响应为空");
                 return null;
             }
-
+            log.info("第三方接口响应: {}", response);
 
             Gson gson = new Gson();
             ResponseParmsEntity responseParms = gson.fromJson(response, ResponseParmsEntity.class);
+            
+            if (Objects.isNull(responseParms)) {
+                log.error("解析响应失败,responseParms为null,原始响应: {}", response);
+                return null;
+            }
+            
+            log.info("解析响应成功,ret: {}, msg: {}, data: {}", responseParms.getRet(), responseParms.getMsg(), responseParms.getData());
+            
             String data = responseParms.getRet() + responseParms.getMsg() + responseParms.getData();
             boolean verify = HmacMD5Util.verify(data, ConnectivityConstants.PLATFORM_SIG_SECRET, responseParms.getSig());
             if (!verify) {
@@ -204,11 +234,23 @@ public class ElectricTokenManager {
                     case 500:
                         log.error("系统错误");
                         break;
+                    default:
+                        log.error("未知错误码: {}", responseParms.getRet());
+                        break;
                 }
+                return null;
             }
+            
             String decodeData = AESCryptoUtils.decrypt(responseParms.getData(), ConnectivityConstants.PLATFORM_DATA_SECRET,
                     ConnectivityConstants.PLATFORM_DATA_SECRET_IV);
+            log.info("解密后的数据: {}", decodeData);
+            
             QueryTokenResponseData responseData = gson.fromJson(decodeData, QueryTokenResponseData.class);
+            
+            if (Objects.isNull(responseData)) {
+                log.error("解析解密数据失败,responseData为null,解密数据: {}", decodeData);
+                return null;
+            }
 
             if (responseData.getSuccStat() == 1) {
                 //0-无,1-OperatorID无效
@@ -228,11 +270,15 @@ public class ElectricTokenManager {
             apiToken.setTokenAvailableTime(responseData.getTokenAvailableTime());
             apiToken.setObtainTime(LocalDateTime.now());
             apiToken.setExpireTime(apiToken.getObtainTime().plusSeconds(apiToken.getTokenAvailableTime()));
+            
+            log.info("成功获取Token: AccessToken={}, 有效期={}s, 过期时间={}",
+                apiToken.getAccessToken(), apiToken.getTokenAvailableTime(), apiToken.getExpireTime());
+            log.info("========== 第三方接口调用成功 ==========");
 
             return apiToken;
         } catch (Exception e) {
-            log.error("获取新Token时发生异常,tokenKey: {}", TOKEN_KEY, e);
-            throw new RuntimeException("调用第三方接口失败", e);
+            log.error("获取新Token时发生异常,tokenKey: {}, 异常信息: {}", TOKEN_KEY, e.getMessage(), e);
+            throw new RuntimeException("调用第三方接口失败: " + e.getMessage(), e);
         }
     }
 

+ 3 - 0
src/main/java/com/zsElectric/boot/common/util/electric/QueryTokenParms.java

@@ -1,5 +1,6 @@
 package com.zsElectric.boot.common.util.electric;
 
+import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 import lombok.ToString;
 import lombok.experimental.Accessors;
@@ -18,10 +19,12 @@ public class QueryTokenParms implements Serializable {
     /**
      * 运营商ID
      */
+    @SerializedName("OperatorID")
     private String OperatorID;
 
     /**
      * 运营商密钥
      */
+    @SerializedName("OperatorSecret")
     private String OperatorSecret;
 }

+ 6 - 0
src/main/java/com/zsElectric/boot/common/util/electric/QueryTokenResponseData.java

@@ -1,5 +1,6 @@
 package com.zsElectric.boot.common.util.electric;
 
+import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 
 import java.io.Serial;
@@ -14,26 +15,31 @@ public class QueryTokenResponseData implements Serializable {
     /**
      * 运营商ID
      */
+    @SerializedName("OperatorID")
     private String OperatorID;
 
     /**
      * 响应状态 0-成功,1-失败
      */
+    @SerializedName("SuccStat")
     private Integer SuccStat;
 
     /**
      * 访问令牌
      */
+    @SerializedName("AccessToken")
     private String AccessToken;
 
     /**
      * 令牌有效期,单位秒
      */
+    @SerializedName("TokenAvailableTime")
     private Integer TokenAvailableTime;
 
     /**
      * 错误原因 0-无,1-OperatorID无效
      */
+    @SerializedName("FailReason")
     private Integer FailReason;
 
 }

+ 12 - 5
src/main/java/com/zsElectric/boot/common/util/electric/RequestParmsEntity.java

@@ -1,6 +1,7 @@
 package com.zsElectric.boot.common.util.electric;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 import lombok.experimental.Accessors;
 
@@ -12,6 +13,7 @@ import java.io.Serializable;
  * @date 2025/11/25
  * @description 请求参数实体
  */
+@Data
 @Accessors(chain = true)
 public class RequestParmsEntity implements Serializable {
 
@@ -21,29 +23,38 @@ public class RequestParmsEntity implements Serializable {
     /**
      * 运营商标识
      */
+    @SerializedName("OperatorID")
+    @JsonProperty("OperatorID")
     private String OperatorID;
 
     /**
      * 加密后的参数
      */
+    @SerializedName("Data")
+    @JsonProperty("Data")
     private String Data;
 
     /**
      * 时间戳
      */
+    @SerializedName("TimeStamp")
+    @JsonProperty("TimeStamp")
     private String TimeStamp;
 
     /**
      * 序列号
      */
+    @SerializedName("Seq")
+    @JsonProperty("Seq")
     private String Seq;
 
     /**
      * 签名
      */
+    @SerializedName("Sig")
+    @JsonProperty("Sig")
     private String Sig;
 
-    @JsonProperty("OperatorID")
     public String getOperatorID() {
         return OperatorID;
     }
@@ -52,7 +63,6 @@ public class RequestParmsEntity implements Serializable {
         OperatorID = operatorID;
     }
 
-    @JsonProperty("Data")
     public String getData() {
         return Data;
     }
@@ -61,7 +71,6 @@ public class RequestParmsEntity implements Serializable {
         Data = data;
     }
 
-    @JsonProperty("TimeStamp")
     public String getTimeStamp() {
         return TimeStamp;
     }
@@ -70,7 +79,6 @@ public class RequestParmsEntity implements Serializable {
         TimeStamp = timeStamp;
     }
 
-    @JsonProperty("Seq")
     public String getSeq() {
         return Seq;
     }
@@ -79,7 +87,6 @@ public class RequestParmsEntity implements Serializable {
         Seq = seq;
     }
 
-    @JsonProperty("Sig")
     public String getSig() {
         return Sig;
     }

+ 10 - 5
src/main/java/com/zsElectric/boot/common/util/electric/ResponseParmsEntity.java

@@ -1,13 +1,14 @@
 package com.zsElectric.boot.common.util.electric;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 import lombok.experimental.Accessors;
 
 import java.io.Serial;
 import java.io.Serializable;
 
-
+@Data
 public class ResponseParmsEntity implements Serializable {
 
     @Serial
@@ -24,24 +25,31 @@ public class ResponseParmsEntity implements Serializable {
      * 500:系统异常
      *
      */
+    @SerializedName("Ret")
+    @JsonProperty("Ret")
     private Integer Ret;
 
     /**
      * 响应消息
      */
+    @SerializedName("Msg")
+    @JsonProperty("Msg")
     private String Msg;
 
     /**
      * 响应加密数据
      */
+    @SerializedName("Data")
+    @JsonProperty("Data")
     private String Data;
 
     /**
      * 响应签名
      */
+    @SerializedName("Sig")
+    @JsonProperty("Sig")
     private String Sig;
 
-    @JsonProperty("Ret")
     public Integer getRet() {
         return Ret;
     }
@@ -50,7 +58,6 @@ public class ResponseParmsEntity implements Serializable {
         Ret = ret;
     }
 
-    @JsonProperty("Msg")
     public String getMsg() {
         return Msg;
     }
@@ -59,7 +66,6 @@ public class ResponseParmsEntity implements Serializable {
         Msg = msg;
     }
 
-    @JsonProperty("Data")
     public String getData() {
         return Data;
     }
@@ -68,7 +74,6 @@ public class ResponseParmsEntity implements Serializable {
         Data = data;
     }
 
-    @JsonProperty("Sig")
     public String getSig() {
         return Sig;
     }

+ 71 - 3
src/main/java/com/zsElectric/boot/config/OkHttpConfig.java

@@ -1,24 +1,31 @@
 package com.zsElectric.boot.config;
 
+import lombok.extern.slf4j.Slf4j;
 import okhttp3.ConnectionPool;
+import okhttp3.Interceptor;
 import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+import java.io.IOException;
+import java.net.SocketTimeoutException;
 import java.time.Duration;
 import java.util.concurrent.TimeUnit;
 
+@Slf4j
 @Configuration
 public class OkHttpConfig {
     
-    @Value("${okhttp.connect-timeout:10}")
+    @Value("${okhttp.connect-timeout:30}")
     private Duration connectTimeout;
     
-    @Value("${okhttp.read-timeout:30}")
+    @Value("${okhttp.read-timeout:120}")
     private Duration readTimeout;
     
-    @Value("${okhttp.write-timeout:30}")
+    @Value("${okhttp.write-timeout:60}")
     private Duration writeTimeout;
     
     @Value("${okhttp.retry-on-connection-failure:true}")
@@ -29,6 +36,9 @@ public class OkHttpConfig {
     
     @Value("${okhttp.connection-pool.keep-alive-duration:300}")
     private Duration keepAliveDuration;
+    
+    @Value("${okhttp.max-retry-count:3}")
+    private int maxRetryCount;
 
     @Bean
     public ConnectionPool connectionPool() {
@@ -43,6 +53,64 @@ public class OkHttpConfig {
                 .writeTimeout(writeTimeout)
                 .retryOnConnectionFailure(retryOnConnectionFailure)
                 .connectionPool(connectionPool)
+                .addInterceptor(retryInterceptor())
                 .build();
     }
+    
+    /**
+     * 重试拦截器
+     */
+    private Interceptor retryInterceptor() {
+        return chain -> {
+            Request request = chain.request();
+            Response response = null;
+            IOException lastException = null;
+            
+            for (int i = 0; i <= maxRetryCount; i++) {
+                try {
+                    if (i > 0) {
+                        log.warn("第 {} 次重试请求: {}", i, request.url());
+                        // 重试前等待一段时间,使用指数退避策略
+                        Thread.sleep((long) Math.pow(2, i - 1) * 1000);
+                    }
+                    response = chain.proceed(request);
+                    // 如果响应成功,直接返回
+                    if (response.isSuccessful()) {
+                        if (i > 0) {
+                            log.info("第 {} 次重试成功: {}", i, request.url());
+                        }
+                        return response;
+                    }
+                    // 如果是客户端错误(4xx),不重试
+                    if (response.code() >= 400 && response.code() < 500) {
+                        log.warn("客户端错误,不重试: {}, 状态码: {}", request.url(), response.code());
+                        return response;
+                    }
+                } catch (SocketTimeoutException e) {
+                    lastException = e;
+                    log.error("请求超时: {}, 第 {} 次尝试", request.url(), i + 1, e);
+                    if (response != null) {
+                        response.close();
+                    }
+                } catch (IOException e) {
+                    lastException = e;
+                    log.error("请求失败: {}, 第 {} 次尝试", request.url(), i + 1, e);
+                    if (response != null) {
+                        response.close();
+                    }
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    throw new IOException("重试被中断", e);
+                }
+            }
+            
+            // 所有重试都失败了
+            if (lastException != null) {
+                log.error("所有重试都失败了: {}, 总共尝试 {} 次", request.url(), maxRetryCount + 1);
+                throw lastException;
+            }
+            
+            return response;
+        };
+    }
 }

+ 3 - 0
src/main/java/com/zsElectric/boot/core/aspect/LogAspect.java

@@ -193,6 +193,9 @@ public class LogAspect {
      * @return 如果是需要过滤的对象,则返回true;否则返回false。
      */
     private boolean shouldFilterObject(Object obj) {
+        if (obj == null) {
+            return false;
+        }
         Class<?> clazz = obj.getClass();
         if (clazz.isArray()) {
             return MultipartFile.class.isAssignableFrom(clazz.getComponentType());