Browse Source

feat(logging): 新增第三方接口请求日志功能

- 新增ThirdPartyApiLog实体及数据库映射,记录接口请求和响应详情
- 实现ThirdPartyApiLogMapper用于持久化操作
- 定义ThirdPartyApiLogService接口,抽象日志保存服务
- 添加ThirdPartyApiLogAspect切面,拦截指定控制器接口请求
- 自动捕获和记录HTTP请求头、参数、响应体及异常信息
- 支持异步保存日志并提取运营商ID、充电站ID、充电桩ID及业务流水号
- 修改充电相关实体及VO增加@JsonAutoDetect和@JsonProperty注解,保证JSON序列化字段首字母大写规范
SheepHy 1 week ago
parent
commit
af96232fd7

+ 240 - 0
src/main/java/com/zsElectric/boot/charging/aspect/ThirdPartyApiLogAspect.java

@@ -0,0 +1,240 @@
+package com.zsElectric.boot.charging.aspect;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.zsElectric.boot.charging.entity.ThirdPartyApiLog;
+import com.zsElectric.boot.charging.service.ThirdPartyApiLogService;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import java.time.LocalDateTime;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 第三方接口日志记录切面
+ * 拦截 LinkDataController 和 ChargingBusinessController 的所有方法,自动记录请求和响应日志
+ *
+ * @author system
+ * @since 2025-01-02
+ */
+@Slf4j
+@Aspect
+@Component
+@RequiredArgsConstructor
+public class ThirdPartyApiLogAspect {
+
+    private final ThirdPartyApiLogService thirdPartyApiLogService;
+    private final ObjectMapper objectMapper;
+
+    /**
+     * 定义切入点: LinkDataController 的所有方法
+     */
+    @Pointcut("execution(* com.zsElectric.boot.charging.controller.LinkDataController.*(..))")
+    public void linkDataControllerPointcut() {
+    }
+
+    /**
+     * 定义切入点: ChargingBusinessController 的所有方法
+     */
+    @Pointcut("execution(* com.zsElectric.boot.charging.controller.ChargingBusinessController.*(..))")
+    public void chargingBusinessControllerPointcut() {
+    }
+
+    /**
+     * 环绕通知: 记录 LinkDataController 接口日志
+     */
+    @Around("linkDataControllerPointcut()")
+    public Object aroundLinkDataController(ProceedingJoinPoint joinPoint) throws Throwable {
+        return recordApiLog(joinPoint, "LinkDataController", 2); // 2-接收推送
+    }
+
+    /**
+     * 环绕通知: 记录 ChargingBusinessController 接口日志
+     */
+    @Around("chargingBusinessControllerPointcut()")
+    public Object aroundChargingBusinessController(ProceedingJoinPoint joinPoint) throws Throwable {
+        return recordApiLog(joinPoint, "ChargingBusinessController", 1); // 1-请求出去
+    }
+
+    /**
+     * 记录接口日志的通用方法
+     */
+    private Object recordApiLog(ProceedingJoinPoint joinPoint, String controllerName, Integer logType) throws Throwable {
+        long startTime = System.currentTimeMillis();
+        ThirdPartyApiLog apiLog = new ThirdPartyApiLog();
+        
+        // 获取 HttpServletRequest
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        HttpServletRequest request = attributes != null ? attributes.getRequest() : null;
+        
+        try {
+            // 设置基本信息
+            apiLog.setLogType(logType);
+            apiLog.setControllerName(controllerName);
+            apiLog.setCreatedTime(LocalDateTime.now());
+            
+            if (request != null) {
+                // 请求信息
+                apiLog.setRequestMethod(request.getMethod());
+                apiLog.setRequestUrl(request.getRequestURL().toString());
+                apiLog.setInterfaceName(request.getRequestURI());
+                apiLog.setClientIp(getClientIp(request));
+                apiLog.setUserAgent(request.getHeader("User-Agent"));
+                
+                // 请求头
+                apiLog.setRequestHeaders(getRequestHeaders(request));
+                
+                // 请求参数
+                Map<String, String[]> parameterMap = request.getParameterMap();
+                if (!parameterMap.isEmpty()) {
+                    apiLog.setRequestParams(objectMapper.writeValueAsString(parameterMap));
+                }
+                
+                // 请求体(从切点参数中获取)
+                Object[] args = joinPoint.getArgs();
+                if (args != null && args.length > 0) {
+                    for (Object arg : args) {
+                        if (arg != null && !(arg instanceof HttpServletRequest) && !(arg instanceof jakarta.servlet.http.HttpServletResponse)) {
+                            apiLog.setRequestBody(objectMapper.writeValueAsString(arg));
+                            
+                            // 尝试提取业务字段
+                            extractBusinessFields(arg, apiLog);
+                            break;
+                        }
+                    }
+                }
+            }
+            
+            // 执行目标方法
+            Object result = joinPoint.proceed();
+            
+            // 记录响应
+            long endTime = System.currentTimeMillis();
+            apiLog.setResponseTime(endTime - startTime);
+            apiLog.setResponseStatus(200);
+            apiLog.setIsSuccess(1);
+            
+            if (result != null) {
+                apiLog.setResponseBody(objectMapper.writeValueAsString(result));
+            }
+            
+            // 异步保存日志
+            thirdPartyApiLogService.saveLogAsync(apiLog);
+            
+            return result;
+            
+        } catch (Exception e) {
+            // 记录异常
+            long endTime = System.currentTimeMillis();
+            apiLog.setResponseTime(endTime - startTime);
+            apiLog.setResponseStatus(500);
+            apiLog.setIsSuccess(0);
+            apiLog.setErrorMessage(e.getMessage());
+            
+            // 异步保存日志
+            thirdPartyApiLogService.saveLogAsync(apiLog);
+            
+            throw e;
+        }
+    }
+
+    /**
+     * 提取业务字段
+     */
+    private void extractBusinessFields(Object requestBody, ThirdPartyApiLog apiLog) {
+        try {
+            String json = objectMapper.writeValueAsString(requestBody);
+            Map<String, Object> map = objectMapper.readValue(json, Map.class);
+            
+            // 提取运营商ID
+            if (map.containsKey("OperatorID")) {
+                apiLog.setOperatorId(String.valueOf(map.get("OperatorID")));
+            } else if (map.containsKey("operatorID")) {
+                apiLog.setOperatorId(String.valueOf(map.get("operatorID")));
+            }
+            
+            // 提取充电站ID
+            if (map.containsKey("StationID")) {
+                apiLog.setStationId(String.valueOf(map.get("StationID")));
+            } else if (map.containsKey("stationID")) {
+                apiLog.setStationId(String.valueOf(map.get("stationID")));
+            }
+            
+            // 提取充电桩ID
+            if (map.containsKey("ConnectorID")) {
+                apiLog.setConnectorId(String.valueOf(map.get("ConnectorID")));
+            } else if (map.containsKey("connectorID")) {
+                apiLog.setConnectorId(String.valueOf(map.get("connectorID")));
+            }
+            
+            // 提取业务流水号
+            if (map.containsKey("StartChargeSeq")) {
+                apiLog.setBusinessSeq(String.valueOf(map.get("StartChargeSeq")));
+            } else if (map.containsKey("startChargeSeq")) {
+                apiLog.setBusinessSeq(String.valueOf(map.get("startChargeSeq")));
+            } else if (map.containsKey("EquipAuthSeq")) {
+                apiLog.setBusinessSeq(String.valueOf(map.get("EquipAuthSeq")));
+            } else if (map.containsKey("equipAuthSeq")) {
+                apiLog.setBusinessSeq(String.valueOf(map.get("equipAuthSeq")));
+            } else if (map.containsKey("EquipBizSeq")) {
+                apiLog.setBusinessSeq(String.valueOf(map.get("EquipBizSeq")));
+            } else if (map.containsKey("equipBizSeq")) {
+                apiLog.setBusinessSeq(String.valueOf(map.get("equipBizSeq")));
+            }
+            
+        } catch (Exception e) {
+            log.debug("提取业务字段失败: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 获取请求头信息
+     */
+    private String getRequestHeaders(HttpServletRequest request) {
+        try {
+            Map<String, String> headers = new HashMap<>();
+            Enumeration<String> headerNames = request.getHeaderNames();
+            while (headerNames.hasMoreElements()) {
+                String headerName = headerNames.nextElement();
+                headers.put(headerName, request.getHeader(headerName));
+            }
+            return objectMapper.writeValueAsString(headers);
+        } catch (Exception e) {
+            log.error("获取请求头失败: {}", e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 获取客户端真实IP
+     */
+    private String getClientIp(HttpServletRequest request) {
+        String ip = request.getHeader("X-Forwarded-For");
+        if (StrUtil.isNotBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
+            // 多次反向代理后会有多个IP值,第一个为真实IP
+            int index = ip.indexOf(',');
+            if (index != -1) {
+                return ip.substring(0, index);
+            }
+            return ip;
+        }
+        
+        ip = request.getHeader("X-Real-IP");
+        if (StrUtil.isNotBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
+            return ip;
+        }
+        
+        return request.getRemoteAddr();
+    }
+}

+ 12 - 0
src/main/java/com/zsElectric/boot/charging/entity/ConnectorInfo.java

@@ -1,5 +1,7 @@
 package com.zsElectric.boot.charging.entity;
 
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import jakarta.validation.constraints.*;
 import lombok.Data;
 
@@ -8,48 +10,58 @@ import lombok.Data;
  * 充电设备接口信息实体类 - 字段首字母大写格式
  */
 @Data
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
 public class ConnectorInfo {
     
     /** 充电设备接口编码 - 同一运营商内唯一,≤64字符 */
     @NotBlank(message = "接口编码不能为空")
     @Size(max = 64, message = "接口编码长度不能超过64字符")
+    @JsonProperty("ConnectorID")
     private String ConnectorID;
     
     /** 充电设备接口名称 - ≤64字符 */
     @NotBlank(message = "接口名称不能为空")
     @Size(max = 64, message = "接口名称长度不能超过64字符")
+    @JsonProperty("ConnectorName")
     private String ConnectorName;
     
     /** 充电设备接口类型 */
     @NotNull(message = "接口类型不能为空")
+    @JsonProperty("ConnectorType")
     private Integer ConnectorType;
     
     /** 额定电压上限 - 单位:V */
     @NotNull(message = "额定电压上限不能为空")
     @Positive(message = "电压上限必须大于0")
+    @JsonProperty("VoltageUpperLimits")
     private Integer VoltageUpperLimits;
     
     /** 额定电压下限 - 单位:V */
     @NotNull(message = "额定电压下限不能为空")
     @Positive(message = "电压下限必须大于0")
+    @JsonProperty("VoltageLowerLimits")
     private Integer VoltageLowerLimits;
     
     /** 额定电流 - 单位:A */
     @NotNull(message = "额定电流不能为空")
     @Positive(message = "额定电流必须大于0")
+    @JsonProperty("Current")
     private Integer Current;
     
     /** 额定功率 - 单位:kW,保留小数点后一位 */
     @NotNull(message = "额定功率不能为空")
     @Positive(message = "额定功率必须大于0")
     @Digits(integer = 4, fraction = 1, message = "功率最多保留1位小数")
+    @JsonProperty("Power")
     private Double Power;
     
     /** 车位号 - 停车场车位编号,10字符 */
     @Size(max = 10, message = "车位号长度不能超过10字符")
+    @JsonProperty("ParkNo")
     private String ParkNo;
     
     /** 国家标准 - 1-2011,2-2015 */
     @NotNull(message = "国家标准不能为空")
+    @JsonProperty("NationalStandard")
     private Integer NationalStandard;
 }

+ 5 - 0
src/main/java/com/zsElectric/boot/charging/entity/ConnectorStatusInfo.java

@@ -1,11 +1,16 @@
 package com.zsElectric.boot.charging.entity;
 
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 
 @Data
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
 public class ConnectorStatusInfo {
 
+    @JsonProperty("ConnectorID")
     private String ConnectorID;
 
+    @JsonProperty("Status")
     private Integer Status;
 }

+ 14 - 0
src/main/java/com/zsElectric/boot/charging/entity/EquipmentInfo.java

@@ -1,5 +1,7 @@
 package com.zsElectric.boot.charging.entity;
 
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.*;
 import lombok.Data;
@@ -10,57 +12,69 @@ import java.util.List;
  * 充电设备信息实体类 - 字段首字母大写格式
  */
 @Data
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
 public class EquipmentInfo {
     
     /** 设备编码 - 对同一运营商保证唯一,≤64字符 */
     @NotBlank(message = "设备编码不能为空")
     @Size(max = 64, message = "设备编码长度不能超过64字符")
+    @JsonProperty("EquipmentID")
     private String EquipmentID;
     
     /** 设备生产商组织机构代码 - 9字符 */
     @Pattern(regexp = "\\d{0,9}", message = "设备生产商组织机构代码必须为9位数字")
+    @JsonProperty("ManufacturerID")
     private String ManufacturerID;
     
     /** 设备生产商名称 - ≤128字符 */
     @Size(max = 128, message = "设备生产商名称长度不能超过128字符")
+    @JsonProperty("ManufacturerName")
     private String ManufacturerName;
     
     /** 设备型号 - ≤64字符 */
     @Size(max = 64, message = "设备型号长度不能超过64字符")
+    @JsonProperty("EquipmentModel")
     private String EquipmentModel;
     
     /** 设备生产日期 - YYYY-MM-DD格式 */
     @Pattern(regexp = "\\d{4}-\\d{2}-\\d{2}", message = "生产日期格式应为YYYY-MM-DD")
+    @JsonProperty("ProductionDate")
     private String ProductionDate;
     
     /** 设备类型 - 1-直流设备,2-交流设备,3-交直流一体设备,4-无线设备,5-其他 */
     @NotNull(message = "设备类型不能为空")
+    @JsonProperty("EquipmentType")
     private Integer EquipmentType;
     
     /** 充电设备接口列表 */
     @Valid
     @NotEmpty(message = "充电设备接口信息不能为空")
+    @JsonProperty("ConnectorInfos")
     private List<ConnectorInfo> ConnectorInfos;
     
     /** 充电设备经度 - GCJ-02坐标系 */
     @DecimalMin(value = "-180.0", message = "经度范围无效")
     @DecimalMax(value = "180.0", message = "经度范围无效")
     @Digits(integer = 3, fraction = 6, message = "经度最多保留6位小数")
+    @JsonProperty("EquipmentLng")
     private Double EquipmentLng;
     
     /** 充电设备纬度 - GCJ-02坐标系 */
     @DecimalMin(value = "-90.0", message = "纬度范围无效")
     @DecimalMax(value = "90.0", message = "纬度范围无效")
     @Digits(integer = 2, fraction = 6, message = "纬度最多保留6位小数")
+    @JsonProperty("EquipmentLat")
     private Double EquipmentLat;
     
     /** 充电设备总功率 - 单位:kW,保留小数点后1位 */
     @NotNull(message = "设备总功率不能为空")
     @Positive(message = "设备总功率必须大于0")
     @Digits(integer = 4, fraction = 1, message = "功率最多保留1位小数")
+    @JsonProperty("Power")
     private Double Power;
     
     /** 充电设备名称 - ≤128字符 */
     @Size(max = 128, message = "设备名称长度不能超过128字符")
+    @JsonProperty("EquipmentName")
     private String EquipmentName;
 }

+ 28 - 0
src/main/java/com/zsElectric/boot/charging/entity/StationInfo.java

@@ -1,5 +1,7 @@
 package com.zsElectric.boot.charging.entity;
 
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.*;
 import lombok.Data;
@@ -9,120 +11,146 @@ import java.util.List;
  * 充电站信息实体类 - 字段首字母大写格式
  */
 @Data
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
 public class StationInfo {
     
     /** 充电站ID - 运营商自定义的唯一编码,≤64字符 */
     @NotBlank(message = "充电站ID不能为空")
     @Size(max = 64, message = "充电站ID长度不能超过64字符")
+    @JsonProperty("StationID")
     private String StationID;
     
     /** 运营商ID - 9字符 */
     @NotBlank(message = "运营商ID不能为空")
     @Pattern(regexp = "\\d{9}", message = "运营商ID必须为9位数字")
+    @JsonProperty("OperatorID")
     private String OperatorID;
     
     /** 设备所属方ID - 设备所属运营平台组织机构代码,9字符 */
     @NotBlank(message = "设备所属方ID不能为空")
     @Pattern(regexp = "\\d{9}", message = "设备所属方ID必须为9位数字")
+    @JsonProperty("EquipmentOwnerID")
     private String EquipmentOwnerID;
     
     /** 充电站名称 - ≤128字符 */
     @NotBlank(message = "充电站名称不能为空")
     @Size(max = 128, message = "充电站名称长度不能超过128字符")
+    @JsonProperty("StationName")
     private String StationName;
     
     /** 充电站国家代码 - 比如CN,≤32字符 */
     @NotBlank(message = "国家代码不能为空")
     @Size(max = 32, message = "国家代码长度不能超过32字符")
+    @JsonProperty("CountryCode")
     private String CountryCode;
     
     /** 充电站省市辖区编码 - 参照GB/T2260-2015,20字符 */
     @NotBlank(message = "区域编码不能为空")
     @Size(max = 20, message = "区域编码长度不能超过20字符")
+    @JsonProperty("AreaCode")
     private String AreaCode;
     
     /** 详细地址 - ≤255字符 */
     @NotBlank(message = "详细地址不能为空")
     @Size(max = 255, message = "详细地址长度不能超过255字符")
+    @JsonProperty("Address")
     private String Address;
     
     /** 站点电话 - ≤128字符 */
     @Size(max = 128, message = "站点电话长度不能超过128字符")
+    @JsonProperty("StationTel")
     private String StationTel;
     
     /** 服务电话 - 平台服务电话,≤128字符 */
     @NotBlank(message = "服务电话不能为空")
     @Size(max = 128, message = "服务电话长度不能超过128字符")
+    @JsonProperty("ServiceTel")
     private String ServiceTel;
     
     /** 站点类型 */
     @NotNull(message = "站点类型不能为空")
+    @JsonProperty("StationType")
     private Integer StationType;
     
     /** 站点状态 */
     @NotNull(message = "站点状态不能为空")
+    @JsonProperty("StationStatus")
     private Integer StationStatus;
     
     /** 车位数量 - 可停放进行充电的车位总数 */
     @Min(value = 0, message = "车位数量不能小于0")
+    @JsonProperty("ParkNums")
     private Integer ParkNums = 0;
     
     /** 经度 - GCJ-02坐标系,保留小数点后6位 */
     @DecimalMin(value = "-180.0", message = "经度范围无效")
     @DecimalMax(value = "180.0", message = "经度范围无效")
     @Digits(integer = 3, fraction = 6, message = "经度最多保留6位小数")
+    @JsonProperty("StationLng")
     private Double StationLng;
     
     /** 纬度 - GCJ-02坐标系,保留小数点后6位 */
     @DecimalMin(value = "-90.0", message = "纬度范围无效")
     @DecimalMax(value = "90.0", message = "纬度范围无效")
     @Digits(integer = 2, fraction = 6, message = "纬度最多保留6位小数")
+    @JsonProperty("StationLat")
     private Double StationLat;
     
     /** 站点引导 - ≤1024字符 */
     @Size(max = 1024, message = "站点引导长度不能超过1024字符")
+    @JsonProperty("SiteGuide")
     private String SiteGuide;
     
     /** 建设场所 */
     @NotNull(message = "建设场所不能为空")
+    @JsonProperty("Construction")
     private Integer Construction;
     
     /** 站点照片URL列表 */
+    @JsonProperty("Pictures")
     private List<@Size(max = 512) String> Pictures;
     
     /** 营业时间描述 - ≤128字符 */
     @NotBlank(message = "营业时间不能为空")
     @Size(max = 128, message = "营业时间描述长度不能超过128字符")
+    @JsonProperty("BusineHours")
     private String BusineHours;
     
     /** 充电电费率描述 - ≤255字符 */
     @NotBlank(message = "充电电费率不能为空")
     @Size(max = 255, message = "充电电费率描述长度不能超过255字符")
+    @JsonProperty("ElectricityFee")
     private String ElectricityFee;
     
     /** 服务费率描述 - ≤255字符 */
     @NotBlank(message = "服务费率不能为空")
     @Size(max = 255, message = "服务费率描述长度不能超过255字符")
+    @JsonProperty("ServiceFee")
     private String ServiceFee;
     
     /** 停车费率描述 - ≤255字符 */
     @NotBlank(message = "停车费不能为空")
     @Size(max = 255, message = "停车费率描述长度不能超过255字符")
+    @JsonProperty("ParkFee")
     private String ParkFee;
     
     /** 支付方式描述 - ≤64字符 */
     @Size(max = 64, message = "支付方式描述长度不能超过64字符")
+    @JsonProperty("Payment")
     private String Payment;
     
     /** 是否支持预约 - 0-不支持,1-支持 */
+    @JsonProperty("SupportOrder")
     private Integer SupportOrder = 0;
     
     /** 备注信息 - ≤255字符 */
     @Size(max = 255, message = "备注信息长度不能超过255字符")
+    @JsonProperty("Remark")
     private String Remark;
     
     /** 充电设备信息列表 */
     @Valid
     @NotEmpty(message = "充电设备信息不能为空")
+    @JsonProperty("EquipmentInfos")
     private List<EquipmentInfo> EquipmentInfos;
 }

+ 5 - 0
src/main/java/com/zsElectric/boot/charging/entity/StationStatusInfo.java

@@ -1,13 +1,18 @@
 package com.zsElectric.boot.charging.entity;
 
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 
 import java.util.List;
 
 @Data
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
 public class StationStatusInfo {
 
+    @JsonProperty("StationID")
     private String StationID;
 
+    @JsonProperty("ConnectorStatusInfos")
     private List<ConnectorStatusInfo> ConnectorStatusInfos;
 }

+ 145 - 0
src/main/java/com/zsElectric/boot/charging/entity/ThirdPartyApiLog.java

@@ -0,0 +1,145 @@
+package com.zsElectric.boot.charging.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 第三方接口请求日志实体类
+ *
+ * @author system
+ * @since 2025-01-02
+ */
+@Data
+@TableName("third_party_api_log")
+public class ThirdPartyApiLog implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 日志类型(1-请求出去,2-接收推送)
+     */
+    private Integer logType;
+
+    /**
+     * 控制器名称(LinkDataController/ChargingBusinessController)
+     */
+    private String controllerName;
+
+    /**
+     * 接口名称/路径
+     */
+    private String interfaceName;
+
+    /**
+     * 请求方法(GET/POST等)
+     */
+    private String requestMethod;
+
+    /**
+     * 请求完整URL
+     */
+    private String requestUrl;
+
+    /**
+     * 请求头信息(JSON格式)
+     */
+    private String requestHeaders;
+
+    /**
+     * 请求参数(JSON格式)
+     */
+    private String requestParams;
+
+    /**
+     * 请求体内容
+     */
+    private String requestBody;
+
+    /**
+     * 响应状态码
+     */
+    private Integer responseStatus;
+
+    /**
+     * 响应头信息(JSON格式)
+     */
+    private String responseHeaders;
+
+    /**
+     * 响应体内容
+     */
+    private String responseBody;
+
+    /**
+     * 响应耗时(毫秒)
+     */
+    private Long responseTime;
+
+    /**
+     * 是否成功(0-失败,1-成功)
+     */
+    private Integer isSuccess;
+
+    /**
+     * 错误信息
+     */
+    private String errorMessage;
+
+    /**
+     * 运营商ID
+     */
+    private String operatorId;
+
+    /**
+     * 充电站ID
+     */
+    private String stationId;
+
+    /**
+     * 充电桩ID
+     */
+    private String connectorId;
+
+    /**
+     * 业务流水号(如充电订单号、认证流水号等)
+     */
+    private String businessSeq;
+
+    /**
+     * 客户端IP地址
+     */
+    private String clientIp;
+
+    /**
+     * 用户代理信息
+     */
+    private String userAgent;
+
+    /**
+     * 创建时间
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createdTime;
+
+    /**
+     * 更新时间
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updatedTime;
+
+    /**
+     * 备注信息
+     */
+    private String remark;
+}

+ 16 - 0
src/main/java/com/zsElectric/boot/charging/mapper/ThirdPartyApiLogMapper.java

@@ -0,0 +1,16 @@
+package com.zsElectric.boot.charging.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zsElectric.boot.charging.entity.ThirdPartyApiLog;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 第三方接口请求日志 Mapper
+ *
+ * @author system
+ * @since 2025-01-02
+ */
+@Mapper
+public interface ThirdPartyApiLogMapper extends BaseMapper<ThirdPartyApiLog> {
+
+}

+ 26 - 0
src/main/java/com/zsElectric/boot/charging/service/ThirdPartyApiLogService.java

@@ -0,0 +1,26 @@
+package com.zsElectric.boot.charging.service;
+
+import com.zsElectric.boot.charging.entity.ThirdPartyApiLog;
+
+/**
+ * 第三方接口请求日志服务
+ *
+ * @author system
+ * @since 2025-01-02
+ */
+public interface ThirdPartyApiLogService {
+
+    /**
+     * 保存第三方接口请求日志
+     *
+     * @param log 日志对象
+     */
+    void saveLog(ThirdPartyApiLog log);
+
+    /**
+     * 异步保存第三方接口请求日志
+     *
+     * @param log 日志对象
+     */
+    void saveLogAsync(ThirdPartyApiLog log);
+}

+ 38 - 0
src/main/java/com/zsElectric/boot/charging/service/impl/ThirdPartyApiLogServiceImpl.java

@@ -0,0 +1,38 @@
+package com.zsElectric.boot.charging.service.impl;
+
+import com.zsElectric.boot.charging.entity.ThirdPartyApiLog;
+import com.zsElectric.boot.charging.mapper.ThirdPartyApiLogMapper;
+import com.zsElectric.boot.charging.service.ThirdPartyApiLogService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+/**
+ * 第三方接口请求日志服务实现
+ *
+ * @author system
+ * @since 2025-01-02
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ThirdPartyApiLogServiceImpl implements ThirdPartyApiLogService {
+
+    private final ThirdPartyApiLogMapper thirdPartyApiLogMapper;
+
+    @Override
+    public void saveLog(ThirdPartyApiLog apiLog) {
+        try {
+            thirdPartyApiLogMapper.insert(apiLog);
+        } catch (Exception e) {
+            log.error("保存第三方接口日志失败: {}", e.getMessage(), e);
+        }
+    }
+
+    @Async
+    @Override
+    public void saveLogAsync(ThirdPartyApiLog apiLog) {
+        saveLog(apiLog);
+    }
+}

+ 14 - 0
src/main/java/com/zsElectric/boot/charging/vo/ChargingPricePolicyVO.java

@@ -1,5 +1,7 @@
 package com.zsElectric.boot.charging.vo;
 
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.experimental.Accessors;
@@ -10,41 +12,53 @@ import java.util.List;
 @Data
 @Accessors(chain = true)
 @Schema(description = "充电桩价格策略响应VO")
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
 public class ChargingPricePolicyVO {
 
     @Schema(description = "设备业务序列号", example = "123456789123456789123456789")
+    @JsonProperty("EquipBizSeq")
     private String EquipBizSeq;
 
     @Schema(description = "充电桩ID", example = "TEST0000901_1")
+    @JsonProperty("ConnectorID")
     private String ConnectorID;
 
     @Schema(description = "成功状态(0-成功,1-失败)", example = "0")
+    @JsonProperty("SuccStat")
     private Integer SuccStat;
 
     @Schema(description = "失败原因(0-无错误)", example = "0")
+    @JsonProperty("FailReason")
     private Integer FailReason;
 
     @Schema(description = "总时段数", example = "3")
+    @JsonProperty("SumPeriod")
     private Integer SumPeriod;
 
     @Schema(description = "价格策略信息列表")
+    @JsonProperty("PolicyInfos")
     private List<PolicyInfo> PolicyInfos;
 
     @Data
     @Accessors(chain = true)
     @Schema(description = "价格策略信息")
+    @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
     public static class PolicyInfo {
 
         @Schema(description = "时段开始时间(HHmmss格式)", example = "000000")
+        @JsonProperty("StartTime")
         private String StartTime;
 
         @Schema(description = "电价(元/度)", example = "0.7")
+        @JsonProperty("ElecPrice")
         private BigDecimal ElecPrice;
 
         @Schema(description = "服务费(元/度)", example = "1.0")
+        @JsonProperty("ServicePrice")
         private BigDecimal ServicePrice;
 
         @Schema(description = "时段标志(平台扩展字段,1-尖,2-峰,3-平,4-谷)", example = "1")
+        @JsonProperty("PeriodFlag")
         private Integer PeriodFlag;
     }
 }

+ 20 - 0
src/main/java/com/zsElectric/boot/charging/vo/ChargingStatusQueryResponseVO.java

@@ -1,5 +1,7 @@
 package com.zsElectric.boot.charging.vo;
 
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.experimental.Accessors;
@@ -10,88 +12,106 @@ import java.util.List;
 @Data
 @Accessors(chain = true)
 @Schema(description = "充电状态查询响应VO")
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
 public class ChargingStatusQueryResponseVO {
 
     @Schema(description = "订单状态(1-启动中,2-启动成功,3-启动失败,4-结束)",
             example = "2",
             allowableValues = {"1", "2", "3", "4"},
             required = true)
+    @JsonProperty("StartChargeSeqStat")
     private Integer StartChargeSeqStat;
 
     @Schema(description = "充电订单号(格式:运营商ID+唯一编号)",
             example = "123456789201805071630123456",
             required = true)
+    @JsonProperty("StartChargeSeq")
     private String StartChargeSeq;
 
     @Schema(description = "充电设备接口编码",
             example = "3702120244102_1",
             required = true)
+    @JsonProperty("ConnectorID")
     private String ConnectorID;
 
     @Schema(description = "设备接口状态(0-离网,1-空闲,2-占用(未充电),3-占用(充电中),4-占用(预约锁定),255-故障)",
             example = "3",
             allowableValues = {"0", "1", "2", "3", "4", "255"},
             required = true)
+    @JsonProperty("ConnectorStatus")
     private Integer ConnectorStatus;
 
     @Schema(description = "累计充电电量,单位:kWh",
             example = "25.68",
             required = true)
+    @JsonProperty("TotalPower")
     private BigDecimal TotalPower;
 
     @Schema(description = "电费,单位:元",
             example = "15.80",
             required = true)
+    @JsonProperty("ElecMoney")
     private BigDecimal ElecMoney;
 
     @Schema(description = "服务费,单位:元",
             example = "3.20",
             required = true)
+    @JsonProperty("SeviceMoney")
     private BigDecimal SeviceMoney;
 
     @Schema(description = "总金额,单位:元",
             example = "19.00",
             required = true)
+    @JsonProperty("TotalMoney")
     private BigDecimal TotalMoney;
 
     @Schema(description = "充电明细信息体列表",
             required = true)
+    @JsonProperty("ChargeDetails")
     private List<ChargeDetail> ChargeDetails;
 
     @Data
     @Accessors(chain = true)
     @Schema(description = "充电明细信息体")
+    @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
     public static class ChargeDetail {
 
         @Schema(description = "开始时间,格式\"yyyy-MM-dd HH:mm:ss\"",
                 example = "2024-01-15 10:30:00",
                 required = true)
+        @JsonProperty("DetailStartTime")
         private String DetailStartTime;
 
         @Schema(description = "结束时间,格式\"yyyy-MM-dd HH:mm:ss\"",
                 example = "2024-01-15 11:30:00",
                 required = true)
+        @JsonProperty("DetailEndTime")
         private String DetailEndTime;
 
         @Schema(description = "时段电价,小数点后2位",
                 example = "0.65")
+        @JsonProperty("ElecPrice")
         private BigDecimal ElecPrice;
 
         @Schema(description = "时段服务费价格,小数点后2位",
                 example = "0.15")
+        @JsonProperty("SevicePrice")
         private BigDecimal SevicePrice;
 
         @Schema(description = "时段充电量,单位:度,小数点后2位",
                 example = "12.34",
                 required = true)
+        @JsonProperty("DetailPower")
         private BigDecimal DetailPower;
 
         @Schema(description = "时段电费,小数点后2位",
                 example = "8.02")
+        @JsonProperty("DetailElecMoney")
         private BigDecimal DetailElecMoney;
 
         @Schema(description = "时段服务费,小数点后2位",
                 example = "1.85")
+        @JsonProperty("DetailSeviceMoney")
         private BigDecimal DetailSeviceMoney;
     }
 }

+ 5 - 0
src/main/java/com/zsElectric/boot/charging/vo/QueryStationStatusVO.java

@@ -1,5 +1,7 @@
 package com.zsElectric.boot.charging.vo;
 
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.zsElectric.boot.charging.entity.StationStatusInfo;
 import lombok.Data;
 
@@ -8,12 +10,15 @@ import java.io.Serializable;
 import java.util.List;
 
 @Data
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
 public class QueryStationStatusVO implements Serializable {
 
     @Serial
     private static final long serialVersionUID = 1L;
 
+    @JsonProperty("StationStatusInfos")
     List<StationStatusInfo> StationStatusInfos;
 
+    @JsonProperty("Total")
     private Integer Total;
 }

+ 7 - 0
src/main/java/com/zsElectric/boot/charging/vo/QueryStationsInfoVO.java

@@ -1,5 +1,7 @@
 package com.zsElectric.boot.charging.vo;
 
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.zsElectric.boot.charging.entity.StationInfo;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.Min;
@@ -9,24 +11,29 @@ import lombok.Data;
 import java.util.List;
 
 @Data
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
 public class QueryStationsInfoVO {
 
     /** 当前页数 - 如果查询页码大于页码总数,返回查询页码数 */
     @NotNull(message = "当前页数不能为空")
     @Min(value = 1, message = "当前页数不能小于1")
+    @JsonProperty("PageNo")
     private Integer PageNo;
 
     /** 页码总数 - 总页数 */
     @NotNull(message = "总页数不能为空")
     @Min(value = 0, message = "总页数不能小于0")
+    @JsonProperty("PageCount")
     private Integer PageCount;
 
     /** 总记录条数 - 符合条件的电站总数 */
     @NotNull(message = "总记录条数不能为空")
     @Min(value = 0, message = "总记录条数不能小于0")
+    @JsonProperty("ItemSize")
     private Integer ItemSize;
 
     /** 充电站信息列表 - 包含充电站的基本信息、服务信息、支付信息、设备信息等 */
     @Valid
+    @JsonProperty("StationInfos")
     private List<StationInfo> StationInfos;
 }

+ 9 - 0
src/main/java/com/zsElectric/boot/charging/vo/StartChargingResponseVO.java

@@ -1,5 +1,7 @@
 package com.zsElectric.boot.charging.dto;
 
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.experimental.Accessors;
@@ -7,38 +9,45 @@ import lombok.experimental.Accessors;
 @Data
 @Accessors(chain = true)
 @Schema(description = "启动充电返回DTO")
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
 public class StartChargingResponseVO {
 
     @Schema(description = "充电订单号(格式:运营商ID+唯一编号)",
             example = "123456789201805071630123456",
             required = true)
+    @JsonProperty("StartChargeSeq")
     private String StartChargeSeq;
 
     @Schema(description = "充电订单状态(1-启动中,2-充电中,3-停止中,4-已结束,5-未知)",
             example = "1",
             allowableValues = {"1", "2", "3", "4", "5"},
             required = true)
+    @JsonProperty("StartChargeSeqStat")
     private Integer StartChargeSeqStat;
 
     @Schema(description = "充电设备接口编码(参见《电动汽车充换电服务信息交换 第2部分:公共信息交换规范》)",
             example = "3702120244102_1",
             required = true)
+    @JsonProperty("ConnectorID")
     private String ConnectorID;
 
     @Schema(description = "操作结果(0-成功,1-失败)",
             example = "0",
             allowableValues = {"0", "1"},
             required = true)
+    @JsonProperty("SuccStat")
     private Integer SuccStat;
 
     @Schema(description = "失败原因(0-无,1-此设备不存在,2-此设备离线,其它-自定义)",
             example = "0",
             allowableValues = {"0", "1", "2"},
             required = true)
+    @JsonProperty("FailReason")
     private Integer FailReason;
 
     @Schema(description = "失败原因描述",
             example = "无错误",
             required = true)
+    @JsonProperty("FailReasonMsg")
     private String FailReasonMsg;
 }

+ 8 - 0
src/main/java/com/zsElectric/boot/charging/vo/StopChargingOperationResponseVO.java

@@ -1,5 +1,7 @@
 package com.zsElectric.boot.charging.vo;
 
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.experimental.Accessors;
@@ -7,33 +9,39 @@ import lombok.experimental.Accessors;
 @Data
 @Accessors(chain = true)
 @Schema(description = "停止充电操作响应VO")
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
 public class StopChargingOperationResponseVO {
 
     @Schema(description = "充电订单号(格式:运营商ID+唯一编号)",
             example = "123456789201805071630123456",
             required = true)
+    @JsonProperty("StartChargeSeq")
     private String StartChargeSeq;
 
     @Schema(description = "充电订单状态(1:启动中;2:充电中;3:停止中;4:已结束;5:未知)",
             example = "4",
             allowableValues = {"1", "2", "3", "4", "5"},
             required = true)
+    @JsonProperty("StartChargeSeqStat")
     private Integer StartChargeSeqStat;
 
     @Schema(description = "操作结果(0:成功;1:失败)",
             example = "0",
             allowableValues = {"0", "1"},
             required = true)
+    @JsonProperty("SuccStat")
     private Integer SuccStat;
 
     @Schema(description = "失败原因(0:无;1:此设备不存在;2:此设备离线;3:设备已停止充电;其它:自定义)",
             example = "0",
             allowableValues = {"0", "1", "2", "3"},
             required = true)
+    @JsonProperty("FailReason")
     private Integer FailReason;
 
     @Schema(description = "失败原因描述",
             example = "停止成功",
             required = true)
+    @JsonProperty("FailReasonMsg")
     private String FailReasonMsg;
 }

+ 5 - 0
src/main/resources/mapper/charging/ThirdPartyApiLogMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zsElectric.boot.charging.mapper.ThirdPartyApiLogMapper">
+
+</mapper>