Procházet zdrojové kódy

fix(service): 优化充电订单补偿推送数据处理流程

- 补偿推送时优先使用third_party_api_log表获取原始推送数据
- 新增buildCompensationPushData方法构建推送数据,支持ChargeDetails多样结构
- 统一规范补偿推送数据字段及缺省值填充,兼容多种ChargeDetails格式
- 调整推送接口调用为doPostForm提高兼容性
- 修改充值订单补偿查询条件,补偿状态为null或0均匹配
- 定时任务补偿执行采用原子标志避免并发执行
- 删除定时任务中异步执行逻辑,改为同步调用并记录详细日志
- 添加chargeDetails非空判断,避免重复覆盖数据
wzq před 4 dny
rodič
revize
fa80b9e4c3

+ 13 - 24
src/main/java/com/zsElectric/boot/business/quartz/CompensateOrderJob.java

@@ -3,13 +3,10 @@ package com.zsElectric.boot.business.quartz;
 import com.zsElectric.boot.business.service.ChargeOrderInfoService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 
-import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * 订单补偿定时任务
@@ -25,37 +22,29 @@ public class CompensateOrderJob {
 
     private final ChargeOrderInfoService chargeOrderInfoService;
 
-    @Autowired
-    @Qualifier("businessTaskExecutor")
-    private ThreadPoolTaskExecutor businessTaskExecutor;
+    private final AtomicBoolean running = new AtomicBoolean(false);
 
     /**
      * 充电订单补偿定时任务 - 每10分钟执行一次
      * 查找状态为3(已完成)或5(未成功充电)且充电数据为0的订单
      * 优先通过third_party_api_log表获取推送数据,备选通过third_party_charge_status表查询
      */
-    @Scheduled(cron = "0 0/10 * * * ?")
+    @Scheduled(cron = "${job.compensate-order.cron:0 0/10 * * * ?}")
     public void compensateChargeOrder() {
+        if (!running.compareAndSet(false, true)) {
+            log.warn("充电订单补偿定时任务正在执行中,跳过本次调度");
+            return;
+        }
+
         log.info("开始执行充电订单补偿定时任务");
 
         try {
-            CompletableFuture<String> stringCompletableFuture = executeTaskWithResult();
-            log.info("充电订单补偿定时任务执行完成: {}", stringCompletableFuture.get());
+            String result = chargeOrderInfoService.compensateUnprocessedOrders();
+            log.info("充电订单补偿定时任务执行完成: {}", result);
         } catch (Exception e) {
             log.error("执行充电订单补偿任务失败", e);
+        } finally {
+            running.set(false);
         }
     }
-
-    /**
-     * 执行有返回值的异步任务
-     */
-    public CompletableFuture<String> executeTaskWithResult() {
-        // 使用 supplyAsync 并指定自定义线程池
-        return CompletableFuture.supplyAsync(() -> {
-            // 这里是你的异步任务逻辑
-            System.out.println("执行线程: " + Thread.currentThread().getName());
-            String result = chargeOrderInfoService.compensateUnprocessedOrders();
-            return "处理结果: " + result;
-        }, businessTaskExecutor); // 关键:传入线程池实例
-    }
-}
+}

+ 232 - 14
src/main/java/com/zsElectric/boot/business/service/impl/ChargeOrderInfoServiceImpl.java

@@ -629,6 +629,9 @@ public class ChargeOrderInfoServiceImpl extends ServiceImpl<ChargeOrderInfoMappe
                 order.setThirdPartyTotalCost(chargeStatus.getTotalMoney() != null ? chargeStatus.getTotalMoney() : BigDecimal.ZERO);
                 order.setThirdPartyServerfee(chargeStatus.getServiceMoney() != null ? chargeStatus.getServiceMoney() : BigDecimal.ZERO);
                 order.setThirdPartyElecfee(chargeStatus.getElecMoney() != null ? chargeStatus.getElecMoney() : BigDecimal.ZERO);
+                if (StrUtil.isNotBlank(chargeStatus.getChargeDetails())) {
+                    order.setChargeDetails(chargeStatus.getChargeDetails());
+                }
                 
                 // 6. 计算平台服务费
                 BigDecimal serviceFee = calculateServiceFee(order, chargeStatus, stationInfo);
@@ -1003,6 +1006,9 @@ public class ChargeOrderInfoServiceImpl extends ServiceImpl<ChargeOrderInfoMappe
             order.setThirdPartyTotalCost(chargeStatus.getTotalMoney() != null ? chargeStatus.getTotalMoney() : BigDecimal.ZERO);
             order.setThirdPartyServerfee(chargeStatus.getServiceMoney() != null ? chargeStatus.getServiceMoney() : BigDecimal.ZERO);
             order.setThirdPartyElecfee(chargeStatus.getElecMoney() != null ? chargeStatus.getElecMoney() : BigDecimal.ZERO);
+            if (StrUtil.isNotBlank(chargeStatus.getChargeDetails())) {
+                order.setChargeDetails(chargeStatus.getChargeDetails());
+            }
             
             // 7. 计算平台服务费
             BigDecimal serviceFee = calculateServiceFee(order, chargeStatus, stationInfo);
@@ -1193,7 +1199,9 @@ public class ChargeOrderInfoServiceImpl extends ServiceImpl<ChargeOrderInfoMappe
                         .eq(ChargeOrderInfo::getThirdPartyServerfee, BigDecimal.ZERO))
                 // 补偿状态为0或null
                 .and(wrapper -> wrapper
-                        .eq(ChargeOrderInfo::getCompensateStatus, BigDecimal.ZERO))
+                        .isNull(ChargeOrderInfo::getCompensateStatus)
+                        .or()
+                        .eq(ChargeOrderInfo::getCompensateStatus, 0))
         );
         
         if (unprocessedOrders.isEmpty()) {
@@ -1278,6 +1286,9 @@ public class ChargeOrderInfoServiceImpl extends ServiceImpl<ChargeOrderInfoMappe
                 order.setThirdPartyTotalCost(chargeStatus.getTotalMoney() != null ? chargeStatus.getTotalMoney() : BigDecimal.ZERO);
                 order.setThirdPartyServerfee(chargeStatus.getServiceMoney() != null ? chargeStatus.getServiceMoney() : BigDecimal.ZERO);
                 order.setThirdPartyElecfee(chargeStatus.getElecMoney() != null ? chargeStatus.getElecMoney() : BigDecimal.ZERO);
+                if (StrUtil.isNotBlank(chargeStatus.getChargeDetails())) {
+                    order.setChargeDetails(chargeStatus.getChargeDetails());
+                }
                 
                 // 6. 计算平台服务费
                 BigDecimal serviceFee = calculateServiceFee(order, chargeStatus, stationInfo);
@@ -1370,27 +1381,19 @@ public class ChargeOrderInfoServiceImpl extends ServiceImpl<ChargeOrderInfoMappe
                 pushData = objectMapper.readValue(apiLogData, Map.class);
             } else {
                 // 从order字段构建推送数据
-                pushData = new HashMap<>();
-                pushData.put("StartChargeSeq", order.getStartChargeSeq());
-                pushData.put("ConnectorID", order.getConnectorId());
-                pushData.put("StartTime", order.getStartTime());
-                pushData.put("EndTime", order.getEndTime());
-                pushData.put("TotalPower", order.getTotalCharge());
-                pushData.put("TotalElecMoney", order.getThirdPartyElecfee());
-                pushData.put("TotalSeviceMoney", order.getThirdPartyServerfee());
-                pushData.put("TotalMoney", order.getThirdPartyTotalCost());
-                pushData.put("StopReason", order.getStopReason());
+                pushData = buildCompensationPushData(order);
             }
+            normalizeCompensationPushData(pushData, order);
             pushData.put("chargeOrderNo", order.getChargeOrderNo());
 
             String url = firmInfo.getChannelUrl() + "/notification_charge_order_info";
-            String requestBody = com.alibaba.fastjson2.JSONObject.toJSONString(pushData);
+            String pushJson = objectMapper.writeValueAsString(pushData);
 
             int maxRetries = 3;
             int retryIntervalMs = 5000;
             for (int attempt = 1; attempt <= maxRetries; attempt++) {
                 try {
-                    JsonNode response = okHttpUtil.doPostJson(url, requestBody, null);
+                    JsonNode response = okHttpUtil.doPostForm(url, pushJson, null);
                     log.info("补偿任务: 渠道方推送充电订单信息成功 - chargeOrderNo: {}, firmId: {}, response: {}",
                             order.getChargeOrderNo(), order.getFirmId(), response);
                     return;
@@ -1415,6 +1418,208 @@ public class ChargeOrderInfoServiceImpl extends ServiceImpl<ChargeOrderInfoMappe
     /**
      * 渠道方账户余额扣减 + 记录资金流水
      */
+    private Map<String, Object> buildCompensationPushData(ChargeOrderInfo order) throws JsonProcessingException {
+        Map<String, Object> pushData = new LinkedHashMap<>();
+
+        if (StrUtil.isNotBlank(order.getChargeDetails())) {
+            JsonNode chargeDetailsNode = objectMapper.readTree(order.getChargeDetails());
+            if (chargeDetailsNode.isObject()) {
+                pushData.putAll(objectMapper.convertValue(chargeDetailsNode, LinkedHashMap.class));
+            } else if (chargeDetailsNode.isArray()) {
+                pushData.put("ChargeDetails", objectMapper.convertValue(chargeDetailsNode, List.class));
+            }
+        }
+
+        normalizeCompensationPushData(pushData, order);
+
+        return pushData;
+    }
+
+    private void normalizeCompensationPushData(Map<String, Object> pushData, ChargeOrderInfo order) throws JsonProcessingException {
+        List<Map<String, Object>> chargeDetails = resolveChargeDetails(pushData, order);
+
+        putIfBlank(pushData, "ArrearsAmt", BigDecimal.ZERO);
+        pushData.put("ChargeDetails", chargeDetails);
+        pushData.put("SumPeriod", chargeDetails.size());
+        putIfBlank(pushData, "ConnectorID", order.getConnectorId());
+        putIfBlank(pushData, "EndTime", order.getEndTime());
+        putIfBlank(pushData, "OriginElecMoney", defaultDecimal(order.getThirdPartyElecfee()));
+        putIfBlank(pushData, "OriginMoney", defaultDecimal(order.getThirdPartyTotalCost()));
+        putIfBlank(pushData, "OriginServiceMoney", defaultDecimal(order.getThirdPartyServerfee()));
+        putIfBlank(pushData, "PlatOrderId", order.getId());
+        putIfBlank(pushData, "ReceiptsAmt", defaultDecimal(order.getThirdPartyTotalCost()));
+        putIfBlank(pushData, "StartChargeSeq", order.getStartChargeSeq());
+        putIfBlank(pushData, "StartTime", order.getStartTime());
+        putIfBlank(pushData, "StopReason", parseIntegerOrDefault(order.getStopReason(), 0));
+        putIfBlank(pushData, "TotalElecMoney", defaultDecimal(order.getThirdPartyElecfee()));
+        putIfBlank(pushData, "TotalMoney", defaultDecimal(order.getThirdPartyTotalCost()));
+        putIfBlank(pushData, "TotalPower", defaultDecimal(order.getTotalCharge()));
+        putIfBlank(pushData, "TotalSeviceMoney", defaultDecimal(order.getThirdPartyServerfee()));
+    }
+
+    private List<Map<String, Object>> resolveChargeDetails(Map<String, Object> pushData, ChargeOrderInfo order) throws JsonProcessingException {
+        List<Map<String, Object>> chargeDetails = convertChargeDetails(pushData.get("ChargeDetails"));
+        if (!chargeDetails.isEmpty()) {
+            return chargeDetails;
+        }
+
+        if (StrUtil.isNotBlank(order.getChargeDetails())) {
+            JsonNode chargeDetailsNode = objectMapper.readTree(order.getChargeDetails());
+            if (chargeDetailsNode.isArray()) {
+                chargeDetails = convertChargeDetails(chargeDetailsNode);
+            } else if (chargeDetailsNode.isObject() && chargeDetailsNode.has("ChargeDetails")) {
+                chargeDetails = convertChargeDetails(chargeDetailsNode.get("ChargeDetails"));
+            }
+        }
+
+        if (!chargeDetails.isEmpty()) {
+            return chargeDetails;
+        }
+
+        return buildFallbackChargeDetails(order);
+    }
+
+    private List<Map<String, Object>> convertChargeDetails(Object rawChargeDetails) {
+        if (rawChargeDetails == null) {
+            return new ArrayList<>();
+        }
+
+        List<Map<String, Object>> chargeDetails = new ArrayList<>();
+        if (rawChargeDetails instanceof JsonNode jsonNode && jsonNode.isArray()) {
+            for (JsonNode detailNode : jsonNode) {
+                chargeDetails.add(objectMapper.convertValue(detailNode, LinkedHashMap.class));
+            }
+            return chargeDetails;
+        }
+
+        if (rawChargeDetails instanceof List<?> detailList) {
+            for (Object detail : detailList) {
+                chargeDetails.add(objectMapper.convertValue(detail, LinkedHashMap.class));
+            }
+        }
+        return chargeDetails;
+    }
+
+    private List<Map<String, Object>> buildFallbackChargeDetails(ChargeOrderInfo order) {
+        List<Map<String, Object>> chargeDetails = new ArrayList<>();
+        if (order == null) {
+            return chargeDetails;
+        }
+
+        Map<String, Object> detail = new LinkedHashMap<>();
+        BigDecimal totalPower = defaultDecimal(order.getTotalCharge());
+        BigDecimal totalElecMoney = defaultDecimal(order.getThirdPartyElecfee());
+        BigDecimal totalServiceMoney = defaultDecimal(order.getThirdPartyServerfee());
+
+        detail.put("DetailStartTime", order.getStartTime());
+        detail.put("DetailEndTime", order.getEndTime());
+        detail.put("ItemFlag", resolvePeriodFlag(order));
+        detail.put("ElecPrice", calculateUnitPrice(totalElecMoney, totalPower));
+        detail.put("SevicePrice", calculateUnitPrice(totalServiceMoney, totalPower));
+        detail.put("DetailPower", totalPower);
+        detail.put("DetailElecMoney", totalElecMoney);
+        detail.put("DetailSeviceMoney", totalServiceMoney);
+
+        chargeDetails.add(detail);
+        return chargeDetails;
+    }
+
+    private Integer resolvePeriodFlag(ChargeOrderInfo order) {
+        if (order == null || StrUtil.isBlank(order.getConnectorId())) {
+            return 3;
+        }
+
+        ThirdPartyEquipmentPricePolicy pricePolicy = thirdPartyEquipmentPricePolicyMapper.selectOne(
+                Wrappers.<ThirdPartyEquipmentPricePolicy>lambdaQuery()
+                        .eq(ThirdPartyEquipmentPricePolicy::getConnectorId, order.getConnectorId())
+                        .eq(ThirdPartyEquipmentPricePolicy::getIsDeleted, 0)
+                        .last("LIMIT 1"));
+        if (pricePolicy == null) {
+            return 3;
+        }
+
+        List<ThirdPartyPolicyInfo> policyInfos = thirdPartyPolicyInfoMapper.selectList(
+                Wrappers.<ThirdPartyPolicyInfo>lambdaQuery()
+                        .eq(ThirdPartyPolicyInfo::getPricePolicyId, pricePolicy.getId())
+                        .eq(ThirdPartyPolicyInfo::getIsDeleted, 0)
+                        .orderByAsc(ThirdPartyPolicyInfo::getStartTime));
+        if (policyInfos == null || policyInfos.isEmpty()) {
+            return 3;
+        }
+
+        String chargeStartTime = extractTimePart(order.getStartTime());
+        if (StrUtil.isBlank(chargeStartTime)) {
+            return policyInfos.get(0).getPeriodFlag() != null ? policyInfos.get(0).getPeriodFlag() : 3;
+        }
+
+        ThirdPartyPolicyInfo matchedPolicy = null;
+        for (ThirdPartyPolicyInfo policyInfo : policyInfos) {
+            if (policyInfo.getStartTime() == null) {
+                continue;
+            }
+            if (chargeStartTime.compareTo(policyInfo.getStartTime()) >= 0) {
+                matchedPolicy = policyInfo;
+            } else {
+                break;
+            }
+        }
+
+        if (matchedPolicy == null) {
+            matchedPolicy = policyInfos.get(policyInfos.size() - 1);
+        }
+        return matchedPolicy.getPeriodFlag() != null ? matchedPolicy.getPeriodFlag() : 3;
+    }
+
+    private String extractTimePart(String dateTime) {
+        if (StrUtil.isBlank(dateTime)) {
+            return null;
+        }
+        try {
+            return LocalDateTime.parse(dateTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
+                    .format(DateTimeFormatter.ofPattern("HHmmss"));
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+    private BigDecimal calculateUnitPrice(BigDecimal amount, BigDecimal power) {
+        if (power == null || power.compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO;
+        }
+        return amount.divide(power, 4, RoundingMode.HALF_UP);
+    }
+
+    private BigDecimal defaultDecimal(BigDecimal value) {
+        return value != null ? value : BigDecimal.ZERO;
+    }
+
+    private void putIfBlank(Map<String, Object> data, String key, Object value) {
+        if (!data.containsKey(key) || isNullLikeValue(data.get(key))) {
+            data.put(key, value);
+        }
+    }
+
+    private boolean isNullLikeValue(Object value) {
+        if (value == null) {
+            return true;
+        }
+        if (value instanceof String str) {
+            return StrUtil.isBlank(str) || "null".equalsIgnoreCase(str.trim());
+        }
+        return false;
+    }
+
+    private Integer parseIntegerOrDefault(String value, Integer defaultValue) {
+        if (StrUtil.isBlank(value) || "null".equalsIgnoreCase(value.trim())) {
+            return defaultValue;
+        }
+        try {
+            return Integer.parseInt(value.trim());
+        } catch (NumberFormatException ex) {
+            return defaultValue;
+        }
+    }
+
     private void deductChannelFirmBalance(ChargeOrderInfo order, FirmInfo firmInfo) {
         BigDecimal cost = order.getRealCost();
         if (cost == null || cost.compareTo(BigDecimal.ZERO) <= 0) {
@@ -1467,7 +1672,20 @@ public class ChargeOrderInfoServiceImpl extends ServiceImpl<ChargeOrderInfoMappe
         for (ChargeOrderInfo order : channelOrders) {
             totalCount++;
             try {
-                compensateChannelOrder(order, null);
+                // 优先从third_party_api_log获取原始推送数据
+                String apiLogData = null;
+                if (order.getStartChargeSeq() != null) {
+                    ThirdPartyApiLog apiLog = thirdPartyApiLogMapper.selectOne(
+                            Wrappers.<ThirdPartyApiLog>lambdaQuery()
+                                    .eq(ThirdPartyApiLog::getInterfaceDescription, "推送充电订单信息")
+                                    .like(ThirdPartyApiLog::getDecryptedRequestData, order.getStartChargeSeq())
+                                    .orderByDesc(ThirdPartyApiLog::getCreatedTime)
+                                    .last("LIMIT 1"));
+                    if (apiLog != null && apiLog.getDecryptedRequestData() != null) {
+                        apiLogData = apiLog.getDecryptedRequestData();
+                    }
+                }
+                compensateChannelOrder(order, apiLogData);
                 successCount++;
                 log.info("渠道方推送补偿: 订单{}补偿成功", order.getChargeOrderNo());
             } catch (Exception e) {