puzhibing
5 小时以前 976ff3fe092ee2f9515013dbfab77d25b2944b75
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -1936,13 +1936,7 @@
        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);
@@ -2082,6 +2076,33 @@
                && 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;
    }
@@ -2119,6 +2140,44 @@
        }
    }
    /**
     * 启动超时兜底:订单支付完成后长时间未真正开始充电(等待安全检测无进展、启动指令未生效),
     * 自动取消订单并释放锁定额度,避免订单永久停留在「启动中」导致该枪无法再次启动。
     */
    @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 {
@@ -2152,12 +2211,18 @@
            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())) {
@@ -2196,8 +2261,13 @@
            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);
                //启动失败
@@ -2248,6 +2318,11 @@
            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) {
                //启动成功
@@ -2279,8 +2354,8 @@
            } 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);
                    //启动失败
@@ -2332,25 +2407,43 @@
        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);
            }
        }
    }
@@ -2375,6 +2468,11 @@
            preChargeCheck1.setElectronicLockLock(true);
            preChargeCheck1.setInsulationTesting(true);
            preChargeCheck1.setStartupSuccess(1);
        } else {
            // 缓存缺失时用临时对象推进订单状态,避免设备已启动成功而订单因缓存为空无法流转
            preChargeCheck1 = new PreChargeCheck();
            preChargeCheck1.setElectronicLockLock(true);
            preChargeCheck1.setInsulationTesting(true);
        }
        if (0 == startup_result) {
            //启动失败
@@ -2769,19 +2867,22 @@
    @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"));
@@ -8240,13 +8341,7 @@
        //添加安全检测数据到缓存中,每步安全检测完成后需要更新缓存数据
        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);