shinnosuke
2026-07-13 193f08a3a815ed0cf4567bdbc38ba317dd9ec57c
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -268,6 +268,7 @@
      for (TChargingOrder tChargingOrder : list) {
         MyChargingOrderList myChargingOrderList = new MyChargingOrderList();
         myChargingOrderList.setId(tChargingOrder.getId().toString());
         myChargingOrderList.setSiteId(tChargingOrder.getSiteId());
         myChargingOrderList.setStatus(tChargingOrder.getStatus());
         Site site = siteClient.getSiteByIds(Arrays.asList(tChargingOrder.getSiteId())).getData().get(0);
         myChargingOrderList.setTitle(site.getName());
@@ -405,6 +406,7 @@
      for (TChargingOrder tChargingOrder : orderList) {
         MyChargingOrderList myChargingOrderList = new MyChargingOrderList();
         myChargingOrderList.setId(tChargingOrder.getId().toString());
         myChargingOrderList.setSiteId(tChargingOrder.getSiteId());
         myChargingOrderList.setStatus(tChargingOrder.getStatus());
         Site site = siteClient.getSiteByIds(Arrays.asList(tChargingOrder.getSiteId())).getData().get(0);
         myChargingOrderList.setTitle(site.getName());
@@ -652,6 +654,15 @@
      BigDecimal rechargeAmount = money(addChargingOrder.getPaymentAmount());
      TChargingGun tChargingGun = chargingGunClient.getChargingGunById(addChargingOrder.getId()).getData();
      TChargingPile chargingPile = chargingPileClient.getChargingPileById(tChargingGun.getChargingPileId()).getData();
      TAppUserCar selectedCar = null;
      if (addChargingOrder.getAppUserCarId() != null) {
         selectedCar = appUserCarClient.getCarById(addChargingOrder.getAppUserCarId().toString()).getData();
      }
      String selectedVin = selectedCar == null ? null : normalizeVin(selectedCar.getVin());
      if (Integer.valueOf(1).equals(chargingPile.getVinFlag())
            && (!StringUtils.hasLength(selectedVin) || !selectedVin.matches("[A-HJ-NPR-Z0-9]{17}"))) {
         return AjaxResult.error("该充电桩需要17位有效车辆VIN,请先完善所选车辆VIN");
      }
      AppUserWhitelistMatchVO whitelist = appUserWhitelistClient.matchWhitelist(userId, tChargingGun.getSiteId()).getData();
      boolean whitelistMatched = whitelist != null && Boolean.TRUE.equals(whitelist.getMatched());
      if (whitelistMatched) {
@@ -784,12 +795,11 @@
      chargingOrder.setOrderClassification(1);
      chargingOrder.setAppUserId(userId);
      chargingOrder.setAppUserCarId(addChargingOrder.getAppUserCarId());
      if (null != addChargingOrder.getAppUserCarId()) {
         TAppUserCar userCar = appUserCarClient.getCarById(addChargingOrder.getAppUserCarId().toString()).getData();
         if (null != userCar) {
            chargingOrder.setPlateNum(userCar.getLicensePlate());
         }
      if (selectedCar != null) {
         chargingOrder.setPlateNum(selectedCar.getLicensePlate());
         chargingOrder.setExpectedVin(selectedVin);
      }
      chargingOrder.setReservationTime(addChargingOrder.getReservationTime());
      chargingOrder.setSiteId(tChargingGun.getSiteId());
      chargingOrder.setChargingPileId(tChargingGun.getChargingPileId());
      chargingOrder.setChargingGunId(addChargingOrder.getId());
@@ -813,6 +823,10 @@
      chargingOrder.setTitle("【充电桩充电】" + chargingPile.getNumber() + "号桩/" + tChargingGun.getCode() + "号枪");
      
      Site site = siteClient.getSiteByIds(Arrays.asList(tChargingGun.getSiteId())).getData().get(0);
      AjaxResult reservationValidation = validateReservation(site, addChargingOrder.getReservationTime());
      if (reservationValidation != null) {
         return reservationValidation;
      }
      Integer accountingStrategyId = tChargingGun.getAccountingStrategyId();
      if (null == accountingStrategyId) {
         //查询站点上面的计费策略
@@ -948,7 +962,8 @@
      preChargeCheck.setInsulationTesting(false);
      preChargeCheck.setSecureConnectionDetection(false);
      preChargeCheck.setStartupSuccess(1);
        preChargeCheck.setAddTime(LocalDateTime.now());
      preChargeCheck.setStartCommandSent(false);
        preChargeCheck.setAddTime(chargingOrder.getReservationTime() != null ? chargingOrder.getReservationTime() : LocalDateTime.now());
      String key = "AQJC_" + chargingOrder.getChargingGunId();
      redisService.setCacheObject(key, preChargeCheck, 24L, TimeUnit.HOURS);
      
@@ -1048,42 +1063,140 @@
      this.updateById(chargingOrder);
      
      
      TChargingPile chargingPile = chargingPileClient.getChargingPileById(tChargingGun.getChargingPileId()).getData();
      //调用远程启动充电消息
      PlatformStartCharging platformStartCharging = new PlatformStartCharging();
      platformStartCharging.setTransaction_serial_number(chargingOrder.getCode());
      platformStartCharging.setCharging_pile_code(chargingPile.getCode());
      platformStartCharging.setCharging_gun_code(tChargingGun.getCode());
      //使用订单id作为逻辑卡号
      platformStartCharging.setCard_number(chargingOrder.getId().toString());
      platformStartCharging.setAccount_balance(account_balance);
      log.info(chargingOrder.getCode() + ":-------------------远程调起开始充电请求-------------------" + platformStartCharging.toString());
      sendMessageClient.platformStartCharging(platformStartCharging);
      ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
      threadPoolExecutor.execute(()->{
         //推送成都监管
         NotificationConnectorChargeStatusVo vo = new NotificationConnectorChargeStatusVo();
         vo.setOrderCode(chargingOrder.getCode());
         vo.setChargingGunId(chargingOrder.getChargingGunId());
         chengDuJianGuanClient.notificationConnectorChargeStatus(vo);
      });
      //异步线程检测远程启动的应答结果。如果失败,则需要全额退款
      Long id = chargingOrder.getId();
      //执行10分钟的定时任务检测
      ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
      scheduler.scheduleAtFixedRate(()->{
         if(timingDetection(id)){
            scheduler.shutdown();
         }
      }, 10, 1, TimeUnit.SECONDS);
      scheduleReservationStart(chargingOrder);
      return AjaxResult.success();
   }
   private AjaxResult validateReservation(Site site, LocalDateTime reservationTime) {
      if (reservationTime == null) {
         return null;
      }
      if (!Integer.valueOf(1).equals(site.getSupportOrder())) {
         return AjaxResult.error("该电站暂不支持预约充电");
      }
      if (reservationTime.isBefore(LocalDateTime.now().minusMinutes(1))) {
         return AjaxResult.error("预约时间不能早于当前时间");
      }
      if (!StringUtils.hasLength(site.getReservationStartTime())
            || !StringUtils.hasLength(site.getReservationEndTime())) {
         return AjaxResult.error("电站尚未配置预约时段");
      }
      LocalTime start = LocalTime.parse(site.getReservationStartTime(), DateTimeFormatter.ofPattern("HH:mm"));
      LocalTime end = LocalTime.parse(site.getReservationEndTime(), DateTimeFormatter.ofPattern("HH:mm"));
      if (!isTimeInWindow(LocalTime.now(), start, end)
            || !isTimeInWindow(reservationTime.toLocalTime(), start, end)) {
         return AjaxResult.error("当前时间或预约时间不在允许预约时段内");
      }
      return null;
   }
   private boolean isTimeInWindow(LocalTime value, LocalTime start, LocalTime end) {
      if (start.equals(end)) {
         return true;
      }
      if (start.isBefore(end)) {
         return !value.isBefore(start) && !value.isAfter(end);
      }
      return !value.isBefore(start) || !value.isAfter(end);
   }
   private boolean isSafetyCheckPassed(PreChargeCheck check) {
      return check != null
            && Boolean.TRUE.equals(check.getElectronicLockLock())
            && Boolean.TRUE.equals(check.getInsulationTesting())
            && Boolean.TRUE.equals(check.getSecureConnectionDetection());
   }
   private String normalizeVin(String vin) {
      return StringUtils.hasLength(vin) ? vin.replaceAll("\\s+", "").toUpperCase(Locale.ROOT) : null;
   }
   private boolean vinMatches(String expectedVin, String actualVin) {
      if (!StringUtils.hasLength(expectedVin) || !StringUtils.hasLength(actualVin)) {
         return false;
      }
      return expectedVin.equals(actualVin)
            || expectedVin.equals(new StringBuilder(actualVin).reverse().toString());
   }
   private void scheduleReservationStart(TChargingOrder chargingOrder) {
      if (chargingOrder.getReservationTime() == null || !chargingOrder.getReservationTime().isAfter(LocalDateTime.now())) {
         return;
      }
      long delay = Math.max(0L, Duration.between(LocalDateTime.now(), chargingOrder.getReservationTime()).toMillis());
      ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
      scheduler.schedule(() -> {
         try {
            TChargingOrder latest = this.getById(chargingOrder.getId());
            if (latest == null || latest.getStatus() != 2) {
               return;
            }
            PreChargeCheck check = redisService.getCacheObject("AQJC_" + latest.getChargingGunId());
            if (isSafetyCheckPassed(check)) {
               startChargingAfterSafety(latest, check);
            }
         } finally {
            scheduler.shutdown();
         }
      }, delay, TimeUnit.MILLISECONDS);
   }
   private void startChargingAfterSafety(TChargingOrder chargingOrder, PreChargeCheck check) {
      RLock lock = redissonClient.getLock("START_AFTER_SAFETY:" + chargingOrder.getId());
      try {
         if (!lock.tryLock(1, 10, TimeUnit.SECONDS)) {
            return;
         }
         TChargingOrder latest = this.getById(chargingOrder.getId());
         if (latest == null || latest.getStatus() != 2 || Boolean.TRUE.equals(check.getStartCommandSent())) {
            return;
         }
         if (latest.getReservationTime() != null && latest.getReservationTime().isAfter(LocalDateTime.now())) {
            return;
         }
         TChargingGun gun = chargingGunClient.getChargingGunById(latest.getChargingGunId()).getData();
         TChargingPile pile = chargingPileClient.getChargingPileById(gun.getChargingPileId()).getData();
         String actualVin = normalizeVin(check.getActualVin());
         String expectedVin = normalizeVin(latest.getExpectedVin());
         if (Integer.valueOf(1).equals(pile.getVinFlag())
               && !vinMatches(expectedVin, actualVin)) {
            check.setStartupSuccess(3);
            check.setFailureCause(9);
            latest.setActualVin(actualVin);
            this.updateById(latest);
            redisService.setCacheObject("AQJC_" + latest.getChargingGunId(), check, 24L, TimeUnit.HOURS);
            refund(latest.getCode());
            return;
         }
         PlatformStartCharging command = new PlatformStartCharging();
         command.setTransaction_serial_number(latest.getCode());
         command.setCharging_pile_code(pile.getCode());
         command.setCharging_gun_code(gun.getCode());
         command.setCard_number(latest.getId().toString());
         command.setAccount_balance(latest.getChargeAmount());
         check.setStartCommandSent(true);
         latest.setActualVin(actualVin);
         this.updateById(latest);
         redisService.setCacheObject("AQJC_" + latest.getChargingGunId(), check, 24L, TimeUnit.HOURS);
         log.info(latest.getCode() + ":安全检测及VIN校验通过,下发启动充电命令:" + command);
         sendMessageClient.platformStartCharging(command);
         ScheduledExecutorService resultScheduler = Executors.newScheduledThreadPool(1);
         resultScheduler.scheduleAtFixedRate(() -> {
            if (timingDetection(latest.getId())) {
               resultScheduler.shutdown();
            }
         }, 10, 1, TimeUnit.SECONDS);
      } catch (InterruptedException e) {
         Thread.currentThread().interrupt();
         throw new ServiceException("启动充电锁等待被中断");
      } finally {
         if (lock.isHeldByCurrentThread()) {
            lock.unlock();
         }
      }
   }
   /**
    * 定时检测mongodb数据库数据
    *
@@ -1245,10 +1358,23 @@
      TChargingGun chargingGun = chargingGunClient.getChargingGunByCode(code).getData();
      PreChargeCheck preChargeCheck1 = redisService.getCacheObject("AQJC_" + chargingGun.getId());
      if (null != preChargeCheck1) {
         preChargeCheck1.setElectronicLockLock(true);
         preChargeCheck1.setInsulationTesting(true);
         preChargeCheck1.setSecureConnectionDetection(securityDetection.getSecure_connection() == 1);
         preChargeCheck1.setElectronicLockLock(Integer.valueOf(1).equals(securityDetection.getElectronic_lock_lock()));
         preChargeCheck1.setInsulationTesting(Integer.valueOf(0).equals(securityDetection.getNsulation_detection()));
         preChargeCheck1.setSecureConnectionDetection(Integer.valueOf(1).equals(securityDetection.getSecure_connection()));
         preChargeCheck1.setActualVin(normalizeVin(securityDetection.getVin()));
         redisService.setCacheObject("AQJC_" + chargingGun.getId(), preChargeCheck1, 24L, TimeUnit.HOURS);
         if (isSafetyCheckPassed(preChargeCheck1)) {
            TChargingOrder pendingOrder = this.getOne(new LambdaQueryWrapper<TChargingOrder>()
                  .eq(TChargingOrder::getChargingGunId, chargingGun.getId())
                  .eq(TChargingOrder::getRechargePaymentStatus, 2)
                  .eq(TChargingOrder::getStatus, 2)
                  .eq(TChargingOrder::getDelFlag, 0)
                  .orderByDesc(TChargingOrder::getCreateTime)
                  .last("limit 1"));
            if (pendingOrder != null) {
               startChargingAfterSafety(pendingOrder, preChargeCheck1);
            }
         }
      }
   }
   
@@ -5074,7 +5200,8 @@
      preChargeCheck.setInsulationTesting(false);
      preChargeCheck.setSecureConnectionDetection(false);
      preChargeCheck.setStartupSuccess(1);
        preChargeCheck.setAddTime(LocalDateTime.now());
      preChargeCheck.setStartCommandSent(false);
        preChargeCheck.setAddTime(chargingOrder.getReservationTime() != null ? chargingOrder.getReservationTime() : LocalDateTime.now());
      String key = "AQJC_" + chargingOrder.getChargingGunId();
      redisService.setCacheObject(key, preChargeCheck, 24L, TimeUnit.HOURS);