| | |
| | | chargingOrder.setPayTime(callbackPayTime); |
| | | |
| | | //添加安全检测数据到缓存中,每步安全检测完成后需要更新缓存数据 |
| | | PreChargeCheck preChargeCheck = new PreChargeCheck(); |
| | | preChargeCheck.setElectronicLockLock(false); |
| | | preChargeCheck.setInsulationTesting(false); |
| | | preChargeCheck.setSecureConnectionDetection(false); |
| | | preChargeCheck.setStartupSuccess(1); |
| | | preChargeCheck.setStartCommandSent(false); |
| | | preChargeCheck.setAddTime(chargingOrder.getReservationTime() != null ? chargingOrder.getReservationTime() : LocalDateTime.now()); |
| | | PreChargeCheck preChargeCheck = newPreChargeCheck(chargingOrder); |
| | | String key = "AQJC_" + chargingOrder.getChargingGunId(); |
| | | redisService.setCacheObject(key, preChargeCheck, 24L, TimeUnit.HOURS); |
| | | |
| | |
| | | && Boolean.TRUE.equals(check.getSecureConnectionDetection()); |
| | | } |
| | | |
| | | /** |
| | | * 查询充电枪进行中的待启动订单(支付完成、启动中) |
| | | */ |
| | | private TChargingOrder getPendingStartOrder(Integer chargingGunId) { |
| | | return this.getOne(new LambdaQueryWrapper<TChargingOrder>() |
| | | .eq(TChargingOrder::getChargingGunId, chargingGunId) |
| | | .eq(TChargingOrder::getRechargePaymentStatus, 2) |
| | | .eq(TChargingOrder::getStatus, 2) |
| | | .eq(TChargingOrder::getDelFlag, 0) |
| | | .orderByDesc(TChargingOrder::getCreateTime) |
| | | .last("limit 1")); |
| | | } |
| | | |
| | | /** |
| | | * 初始化安全检测等待对象(与建单/支付完成时的初始状态保持一致) |
| | | */ |
| | | private PreChargeCheck newPreChargeCheck(TChargingOrder chargingOrder) { |
| | | PreChargeCheck preChargeCheck = new PreChargeCheck(); |
| | | preChargeCheck.setElectronicLockLock(false); |
| | | preChargeCheck.setInsulationTesting(false); |
| | | preChargeCheck.setSecureConnectionDetection(false); |
| | | preChargeCheck.setStartupSuccess(1); |
| | | preChargeCheck.setStartCommandSent(false); |
| | | preChargeCheck.setAddTime(chargingOrder.getReservationTime() != null ? chargingOrder.getReservationTime() : LocalDateTime.now()); |
| | | return preChargeCheck; |
| | | } |
| | | |
| | | private String normalizeVin(String vin) { |
| | | return StringUtils.hasLength(vin) ? vin.replaceAll("\\s+", "").toUpperCase(Locale.ROOT) : null; |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 启动超时兜底:订单支付完成后长时间未真正开始充电(等待安全检测无进展、启动指令未生效), |
| | | * 自动取消订单并释放锁定额度,避免订单永久停留在「启动中」导致该枪无法再次启动。 |
| | | */ |
| | | @Scheduled(fixedDelay = 30000L) |
| | | public void cancelStartupTimeoutOrders() { |
| | | LocalDateTime deadline = LocalDateTime.now().minusMinutes(3); |
| | | List<TChargingOrder> timeoutOrders = this.list(new LambdaQueryWrapper<TChargingOrder>() |
| | | .eq(TChargingOrder::getRechargePaymentStatus, 2) |
| | | .eq(TChargingOrder::getStatus, 2) |
| | | .eq(TChargingOrder::getDelFlag, 0) |
| | | .isNull(TChargingOrder::getStartTime) |
| | | .isNull(TChargingOrder::getReservationTime) |
| | | .lt(TChargingOrder::getPayTime, deadline) |
| | | .last("limit 100")); |
| | | for (TChargingOrder order : timeoutOrders) { |
| | | try { |
| | | PreChargeCheck check = redisService.getCacheObject("AQJC_" + order.getChargingGunId()); |
| | | if (null != check && Boolean.TRUE.equals(check.getStartCommandSent())) { |
| | | // 已下发启动指令,交给既有启动结果检测(timingDetection)处理,避免误取消 |
| | | continue; |
| | | } |
| | | log.warn("订单编号={}:等待安全检测超时,自动取消订单并释放额度,充电枪id={}", order.getCode(), order.getChargingGunId()); |
| | | refund(order.getCode()); |
| | | TChargingOrder finish = new TChargingOrder(); |
| | | finish.setId(order.getId()); |
| | | finish.setAppUserId(order.getAppUserId()); |
| | | finish.setStatus(5); |
| | | finish.setEndMode(0); |
| | | finish.setEndTime(LocalDateTime.now()); |
| | | this.updateById(finish); |
| | | redisService.deleteObject("AQJC_" + order.getChargingGunId()); |
| | | } catch (Exception e) { |
| | | log.error("启动超时订单自动取消失败,orderCode={}", order.getCode(), e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void startChargingAfterSafety(TChargingOrder chargingOrder, PreChargeCheck check) { |
| | | RLock lock = redissonClient.getLock("START_AFTER_SAFETY:" + chargingOrder.getId()); |
| | | try { |
| | |
| | | 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); |
| | | try { |
| | | sendMessageClient.platformStartCharging(command); |
| | | } catch (Exception e) { |
| | | // 下发失败不置 startCommandSent:保留后续安全检测上报重试机会,超时订单由兜底任务取消 |
| | | log.error(latest.getCode() + ":启动充电命令下发失败,等待下次安全检测上报重试", e); |
| | | return; |
| | | } |
| | | check.setStartCommandSent(true); |
| | | redisService.setCacheObject("AQJC_" + latest.getChargingGunId(), check, 24L, TimeUnit.HOURS); |
| | | ScheduledExecutorService resultScheduler = Executors.newScheduledThreadPool(1); |
| | | resultScheduler.scheduleAtFixedRate(() -> { |
| | | if (timingDetection(latest.getId())) { |
| | |
| | | Integer failure_cause = platformStartChargingReply.getFailure_cause(); |
| | | Integer counter = counter_map.get(code); |
| | | PreChargeCheck preChargeCheck1 = redisService.getCacheObject(key); |
| | | //15分钟内还未插枪则取消充电,退回金额。 |
| | | if (failure_cause == 5 && (null == counter || counter < 60 * 15)) { |
| | | if (null == preChargeCheck1) { |
| | | // 缓存缺失时用临时对象推进,避免启动结果补偿因空缓存中断 |
| | | preChargeCheck1 = new PreChargeCheck(); |
| | | preChargeCheck1.setStartupSuccess(1); |
| | | } |
| | | //15分钟内还未插枪则取消充电,退回金额。按10秒探测一次,90次约15分钟 |
| | | if (failure_cause == 5 && (null == counter || counter < 90)) { |
| | | counter = (null == counter ? 0 : counter) + 1; |
| | | counter_map.put(code, counter); |
| | | //启动失败 |
| | |
| | | return true; |
| | | } else { |
| | | PreChargeCheck preChargeCheck1 = redisService.getCacheObject(key); |
| | | if (null == preChargeCheck1) { |
| | | // 缓存缺失时用临时对象推进,避免启动结果补偿因空缓存中断 |
| | | preChargeCheck1 = new PreChargeCheck(); |
| | | preChargeCheck1.setStartupSuccess(1); |
| | | } |
| | | UploadRealTimeMonitoringData uploadRealTimeMonitoringData = uploadRealTimeMonitoringDataService.getDataByOrderCode(code, "desc"); |
| | | if (null != uploadRealTimeMonitoringData) { |
| | | //启动成功 |
| | |
| | | } else { |
| | | Integer counter = boot_failed_map.get(code); |
| | | log.info(code + ":-------------------未上传开启充电结果-------------------" + counter); |
| | | //15分钟内未启动成功,退回金额。 |
| | | if (null == counter || counter < 60 * 15) { |
| | | //15分钟内未启动成功,退回金额。按10秒探测一次,90次约15分钟 |
| | | if (null == counter || counter < 90) { |
| | | counter = (null == counter ? 0 : counter) + 1; |
| | | boot_failed_map.put(code, counter); |
| | | //启动失败 |
| | |
| | | GetChargingGunByCode code = new GetChargingGunByCode(); |
| | | code.setCharging_pile_code(securityDetection.getCharging_pile_code()); |
| | | code.setCharging_gun_code(securityDetection.getCharging_gun_code()); |
| | | TChargingGun chargingGun = chargingGunClient.getChargingGunByCode(code).getData(); |
| | | PreChargeCheck preChargeCheck1 = redisService.getCacheObject("AQJC_" + chargingGun.getId()); |
| | | if (null != preChargeCheck1) { |
| | | 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); |
| | | } |
| | | TChargingGun chargingGun = null; |
| | | try { |
| | | chargingGun = chargingGunClient.getChargingGunByCode(code).getData(); |
| | | } catch (Exception e) { |
| | | log.error("安全检测上报-查询充电枪异常,charging_pile_code={},charging_gun_code={}", |
| | | securityDetection.getCharging_pile_code(), securityDetection.getCharging_gun_code(), e); |
| | | } |
| | | if (null == chargingGun) { |
| | | log.warn("安全检测上报-未匹配到充电枪,忽略,charging_pile_code={},charging_gun_code={}", |
| | | securityDetection.getCharging_pile_code(), securityDetection.getCharging_gun_code()); |
| | | return; |
| | | } |
| | | String key = "AQJC_" + chargingGun.getId(); |
| | | PreChargeCheck preChargeCheck1 = redisService.getCacheObject(key); |
| | | if (null == preChargeCheck1) { |
| | | // 缓存缺失(等待窗口内未收到上报、缓存被清理)时按该枪进行中的待启动订单重建, |
| | | // 保证设备后续安全检测上报仍能推进启动,避免订单永久卡在启动中 |
| | | TChargingOrder pendingOrder = getPendingStartOrder(chargingGun.getId()); |
| | | if (null == pendingOrder) { |
| | | log.info("安全检测上报-无待启动订单,忽略,充电枪id={},charging_pile_code={},charging_gun_code={}", |
| | | chargingGun.getId(), securityDetection.getCharging_pile_code(), securityDetection.getCharging_gun_code()); |
| | | return; |
| | | } |
| | | preChargeCheck1 = newPreChargeCheck(pendingOrder); |
| | | log.warn("安全检测上报-AQJC缓存缺失,已按待启动订单重建,订单编号={},充电枪id={}", pendingOrder.getCode(), chargingGun.getId()); |
| | | } |
| | | 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())); |
| | | // 刷新等待基准时间,避免前端等待窗口/超时判断误伤正在推进的安全检测 |
| | | preChargeCheck1.setAddTime(LocalDateTime.now()); |
| | | redisService.setCacheObject(key, preChargeCheck1, 24L, TimeUnit.HOURS); |
| | | if (isSafetyCheckPassed(preChargeCheck1)) { |
| | | TChargingOrder pendingOrder = getPendingStartOrder(chargingGun.getId()); |
| | | if (pendingOrder != null) { |
| | | startChargingAfterSafety(pendingOrder, preChargeCheck1); |
| | | } |
| | | } |
| | | } |
| | |
| | | preChargeCheck1.setElectronicLockLock(true); |
| | | preChargeCheck1.setInsulationTesting(true); |
| | | preChargeCheck1.setStartupSuccess(1); |
| | | } else { |
| | | // 缓存缺失时用临时对象推进订单状态,避免设备已启动成功而订单因缓存为空无法流转 |
| | | preChargeCheck1 = new PreChargeCheck(); |
| | | preChargeCheck1.setElectronicLockLock(true); |
| | | preChargeCheck1.setInsulationTesting(true); |
| | | } |
| | | if (0 == startup_result) { |
| | | //启动失败 |
| | |
| | | @Override |
| | | public ChargingDetails getChargingDetails(Integer id) { |
| | | Long userId = tokenService.getLoginUserApplet().getUserId(); |
| | | // 状态包含已结束(5):订单结束后小程序需继续拿到该笔订单,用于关闭「请勿拔枪,正在结束充电」 |
| | | // 并展示「充电完成,可拔枪」;仅取最近一笔,避免历史完成订单长期滞留结束态 |
| | | TChargingOrder one = this.getOne(new LambdaQueryWrapper<TChargingOrder>().eq(TChargingOrder::getAppUserId, userId).eq(TChargingOrder::getChargingGunId, id) |
| | | .eq(TChargingOrder::getRechargePaymentStatus, 2) |
| | | .in(TChargingOrder::getStatus, Arrays.asList(2, 3, 4)) |
| | | .in(TChargingOrder::getStatus, Arrays.asList(2, 3, 4, 5)) |
| | | .eq(TChargingOrder::getDelFlag, 0) |
| | | .orderByDesc(TChargingOrder::getCreateTime) |
| | | .last(" limit 0, 1")); |
| | | if (null == one || !one.getAppUserId().equals(userId)) { |
| | | // VIN 码充电订单兜底:VD 订单存于 t_vin_charging_order,且后付模式无支付动作, |
| | | // 不按 rechargePaymentStatus 过滤;TVinChargingOrder 继承 TChargingOrder,后续组装原样复用 |
| | | // 状态同样包含已结束(5),保证 VIN 订单结束后前端仍能拿到完成态 |
| | | TVinChargingOrder vinOrder = vinChargingOrderMapper.selectOne(new LambdaQueryWrapper<TVinChargingOrder>() |
| | | .eq(TVinChargingOrder::getAppUserId, userId) |
| | | .eq(TVinChargingOrder::getChargingGunId, id) |
| | | .in(TVinChargingOrder::getStatus, Arrays.asList(2, 3, 4)) |
| | | .in(TVinChargingOrder::getStatus, Arrays.asList(2, 3, 4, 5)) |
| | | .eq(TVinChargingOrder::getDelFlag, 0) |
| | | .orderByDesc(TVinChargingOrder::getCreateTime) |
| | | .last(" limit 0, 1")); |
| | |
| | | |
| | | |
| | | //添加安全检测数据到缓存中,每步安全检测完成后需要更新缓存数据 |
| | | PreChargeCheck preChargeCheck = new PreChargeCheck(); |
| | | preChargeCheck.setElectronicLockLock(false); |
| | | preChargeCheck.setInsulationTesting(false); |
| | | preChargeCheck.setSecureConnectionDetection(false); |
| | | preChargeCheck.setStartupSuccess(1); |
| | | preChargeCheck.setStartCommandSent(false); |
| | | preChargeCheck.setAddTime(chargingOrder.getReservationTime() != null ? chargingOrder.getReservationTime() : LocalDateTime.now()); |
| | | PreChargeCheck preChargeCheck = newPreChargeCheck(chargingOrder); |
| | | String key = "AQJC_" + chargingOrder.getChargingGunId(); |
| | | redisService.setCacheObject(key, preChargeCheck, 24L, TimeUnit.HOURS); |
| | | |