puzhibing
2026-09-09 aa4b21bea0e3278872801183a434527ebd25c09c
补充VIN码充电和刷卡充电的状态流转补充
2个文件已添加
6个文件已修改
448 ■■■■■ 已修改文件
ruoyi-service/ruoyi-integration/src/main/java/com/ruoyi/integration/iotda/utils/tools/ChargingMessageUtil.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TCardChargingOrderService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TVinChargingOrderService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TCardChargingOrderServiceImpl.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TVinChargingOrderServiceImpl.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TCardChargingOrderHandshakeTest.java 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TVinChargingOrderHandshakeTest.java 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-integration/src/main/java/com/ruoyi/integration/iotda/utils/tools/ChargingMessageUtil.java
@@ -434,16 +434,21 @@
    private void updateHandshakeVin(ChargingHandshakeMessage message) {
        String orderCode = message.getTransaction_serial_number();
        String vin = message.getVin();
        if (!StringUtils.hasLength(orderCode) || !StringUtils.hasLength(vin)) {
        if (!StringUtils.hasLength(orderCode)) {
            return;
        }
        vin = vin.trim().toUpperCase(Locale.ROOT);
        if (!vin.matches("[A-HJ-NPR-Z0-9]{17}")) {
            log.warn("握手VIN不合规,订单编号:{},VIN:{}", orderCode, vin);
            return;
        // VIN 规范化:为空/不合规时传空串(SD/VD 订单侧仍执行握手状态推进,仅跳过 VIN 回写)
        String normalizedVin = "";
        if (StringUtils.hasLength(vin)) {
            vin = vin.trim().toUpperCase(Locale.ROOT);
            if (vin.matches("[A-HJ-NPR-Z0-9]{17}")) {
                normalizedVin = vin;
            } else {
                log.warn("握手VIN不合规,订单编号:{},VIN:{}", orderCode, vin);
            }
        }
        try {
            chargingOrderClient.updateHandshakeVin(orderCode, vin);
            chargingOrderClient.updateHandshakeVin(orderCode, normalizedVin);
        } catch (Exception e) {
            log.error("握手VIN回写订单失败,订单编号:{}", orderCode, e);
        }
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java
@@ -2367,22 +2367,19 @@
    @PostMapping("/updateHandshakeVin")
    public R<Boolean> updateHandshakeVin(@RequestParam("orderCode") String orderCode,
                                         @RequestParam("vin") String vin) {
        if (StringUtils.isEmpty(orderCode) || StringUtils.isEmpty(vin)) {
        if (StringUtils.isEmpty(orderCode)) {
            return R.ok(false);
        }
        boolean updated;
        if (orderCode.startsWith("CD")) {
            updated = chargingOrderService.update(Wrappers.<TChargingOrder>lambdaUpdate()
            updated = StringUtils.isNotEmpty(vin) && chargingOrderService.update(Wrappers.<TChargingOrder>lambdaUpdate()
                    .eq(TChargingOrder::getCode, orderCode)
                    .set(TChargingOrder::getActualVin, vin));
        } else if (orderCode.startsWith("SD")) {
            updated = cardChargingOrderService.update(Wrappers.<TCardChargingOrder>lambdaUpdate()
                    .eq(TCardChargingOrder::getCode, orderCode)
                    .set(TCardChargingOrder::getActualVin, vin));
            // SD/VD:VIN 为空也进入握手处理(状态推进必达);服务内部仅非空回写 VIN,且仅状态=启动中(2)才推进
            updated = cardChargingOrderService.handleHandshakeVin(orderCode, vin);
        } else if (orderCode.startsWith("VD")) {
            updated = vinChargingOrderService.update(Wrappers.<TVinChargingOrder>lambdaUpdate()
                    .eq(TVinChargingOrder::getCode, orderCode)
                    .set(TVinChargingOrder::getActualVin, vin));
            updated = vinChargingOrderService.handleHandshakeVin(orderCode, vin);
        } else {
            log.warn("握手VIN回写失败,未知订单类型:{}", orderCode);
            return R.ok(false);
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TCardChargingOrderService.java
@@ -59,6 +59,12 @@
    void cardStartChargeSuccessfully(PlatformStartChargingReplyMessageVO vo);
    /**
     * 刷卡订单充电握手处理:回写实际VIN;仅订单状态为启动中(2)时推进为充电中(3)、
     * 补开始时间、置枪充电中并推监管(其余状态幂等跳过,不复活已关单)
     */
    boolean handleHandshakeVin(String orderCode, String vin);
    /**
     * 刷卡订单实时监控数据更新(首次数据将订单置为充电中)
     */
    void cardChargeMonitoring(UploadRealTimeMonitoringDataQuery query);
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/TVinChargingOrderService.java
@@ -57,6 +57,12 @@
    void vinStartChargeSuccessfully(PlatformStartChargingReplyMessageVO vo);
    /**
     * VIN订单充电握手处理:回写实际VIN;仅订单状态为启动中(2)时推进为充电中(3)、
     * 补开始时间、置枪充电中并推监管(其余状态幂等跳过,不复活已关单)
     */
    boolean handleHandshakeVin(String orderCode, String vin);
    /**
     * VIN订单实时监控数据更新(首次数据将订单置为充电中)
     */
    void vinChargeMonitoring(UploadRealTimeMonitoringDataQuery query);
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TCardChargingOrderServiceImpl.java
@@ -693,6 +693,47 @@
    }
    @Override
    public boolean handleHandshakeVin(String orderCode, String vin) {
        if (StringUtils.isEmpty(orderCode)) {
            return false;
        }
        TCardChargingOrder order = getCardOrderByCode(orderCode);
        if (order == null) {
            log.warn("[SD握手] 订单不存在 code={}", orderCode);
            return false;
        }
        TCardChargingOrder update = new TCardChargingOrder();
        update.setId(order.getId());
        boolean needUpdate = false;
        if (StringUtils.isNotEmpty(vin)) {
            update.setActualVin(vin);
            needUpdate = true;
        }
        boolean advanced = order.getStatus() != null && order.getStatus() == ORDER_STATUS_STARTING;
        if (advanced) {
            update.setStatus(ORDER_STATUS_CHARGING);
            if (order.getStartTime() == null) {
                update.setStartTime(LocalDateTime.now());
            }
            needUpdate = true;
        }
        if (!needUpdate) {
            return true;
        }
        updateById(update);
        if (advanced) {
            TChargingGun gun = new TChargingGun();
            gun.setId(order.getChargingGunId());
            gun.setStatus(GUN_STATUS_CHARGING);
            chargingGunClient.updateChargingGunById(gun);
            order.setStatus(ORDER_STATUS_CHARGING);
            pushRegulatoryAsync(order, true, false);
            log.info("[SD握手] 握手推进订单进入充电中 code={}", orderCode);
        }
        return true;
    }
    @Override
    public void cardChargeMonitoring(UploadRealTimeMonitoringDataQuery query) {
        if (query == null || StringUtils.isEmpty(query.getTransaction_serial_number())) {
            return;
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TVinChargingOrderServiceImpl.java
@@ -715,6 +715,47 @@
    }
    @Override
    public boolean handleHandshakeVin(String orderCode, String vin) {
        if (StringUtils.isEmpty(orderCode)) {
            return false;
        }
        TVinChargingOrder order = getVinOrderByCode(orderCode);
        if (order == null) {
            log.warn("[VD握手] 订单不存在 code={}", orderCode);
            return false;
        }
        TVinChargingOrder update = new TVinChargingOrder();
        update.setId(order.getId());
        boolean needUpdate = false;
        if (StringUtils.isNotEmpty(vin)) {
            update.setActualVin(vin);
            needUpdate = true;
        }
        boolean advanced = order.getStatus() != null && order.getStatus() == ORDER_STATUS_STARTING;
        if (advanced) {
            update.setStatus(ORDER_STATUS_CHARGING);
            if (order.getStartTime() == null) {
                update.setStartTime(LocalDateTime.now());
            }
            needUpdate = true;
        }
        if (!needUpdate) {
            return true;
        }
        updateById(update);
        if (advanced) {
            TChargingGun gun = new TChargingGun();
            gun.setId(order.getChargingGunId());
            gun.setStatus(GUN_STATUS_CHARGING);
            chargingGunClient.updateChargingGunById(gun);
            order.setStatus(ORDER_STATUS_CHARGING);
            pushRegulatoryAsync(order, true, false);
            log.info("[VD握手] 握手推进订单进入充电中 code={}", orderCode);
        }
        return true;
    }
    @Override
    public void vinChargeMonitoring(UploadRealTimeMonitoringDataQuery query) {
        if (query == null || StringUtils.isEmpty(query.getTransaction_serial_number())) {
            return;
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TCardChargingOrderHandshakeTest.java
New file
@@ -0,0 +1,162 @@
package com.ruoyi.order.service.impl;
import com.ruoyi.chargingPile.api.feignClient.ChargingGunClient;
import com.ruoyi.chargingPile.api.model.TChargingGun;
import com.ruoyi.order.api.model.TCardChargingOrder;
import com.ruoyi.order.mapper.TCardChargingOrderMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import java.time.LocalDateTime;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
 * SD 握手处理单测:VIN 回写 + 仅状态=启动中(2)推进为充电中(3)。
 */
@ExtendWith(MockitoExtension.class)
class TCardChargingOrderHandshakeTest {
    private static final String VIN = "LFV3A23C8K3123456";
    @InjectMocks
    private TCardChargingOrderServiceImpl service;
    @Mock
    private TCardChargingOrderMapper mapper;
    @Mock
    private ChargingGunClient chargingGunClient;
    @BeforeEach
    void setUp() {
        ReflectionTestUtils.setField(service, "baseMapper", mapper);
    }
    private void mockOrder(TCardChargingOrder order) {
        lenient().when(mapper.selectOne(any())).thenReturn(order);
        lenient().when(mapper.selectList(any()))
                .thenReturn(order == null ? Collections.emptyList() : Collections.singletonList(order));
    }
    private TCardChargingOrder order(Integer status) {
        TCardChargingOrder order = new TCardChargingOrder();
        order.setId(1001L);
        order.setCode("SD20260909001");
        order.setStatus(status);
        order.setChargingGunId(5);
        return order;
    }
    @Test
    void advancesStartingOrderToChargingAndWritesVin() {
        TCardChargingOrder order = order(2);
        mockOrder(order);
        assertTrue(service.handleHandshakeVin("SD20260909001", VIN));
        ArgumentCaptor<TCardChargingOrder> orderCaptor = ArgumentCaptor.forClass(TCardChargingOrder.class);
        verify(mapper).updateById(orderCaptor.capture());
        TCardChargingOrder update = orderCaptor.getValue();
        assertEquals(3, update.getStatus());
        assertEquals(VIN, update.getActualVin());
        assertNotNull(update.getStartTime());
        ArgumentCaptor<TChargingGun> gunCaptor = ArgumentCaptor.forClass(TChargingGun.class);
        verify(chargingGunClient).updateChargingGunById(gunCaptor.capture());
        TChargingGun gun = gunCaptor.getValue();
        assertEquals(Integer.valueOf(5), gun.getId());
        assertEquals(Integer.valueOf(4), gun.getStatus());
    }
    @Test
    void keepsExistingStartTimeWhenAdvancing() {
        TCardChargingOrder order = order(2);
        LocalDateTime existed = LocalDateTime.now().minusMinutes(10);
        order.setStartTime(existed);
        mockOrder(order);
        assertTrue(service.handleHandshakeVin("SD20260909001", VIN));
        ArgumentCaptor<TCardChargingOrder> captor = ArgumentCaptor.forClass(TCardChargingOrder.class);
        verify(mapper).updateById(captor.capture());
        assertNull(captor.getValue().getStartTime());
    }
    @Test
    void advancesStartingOrderEvenWhenVinBlank() {
        TCardChargingOrder order = order(2);
        mockOrder(order);
        assertTrue(service.handleHandshakeVin("SD20260909001", ""));
        ArgumentCaptor<TCardChargingOrder> captor = ArgumentCaptor.forClass(TCardChargingOrder.class);
        verify(mapper).updateById(captor.capture());
        assertEquals(3, captor.getValue().getStatus());
        assertNull(captor.getValue().getActualVin());
        verify(chargingGunClient).updateChargingGunById(any(TChargingGun.class));
    }
    @Test
    void doesNotReviveStartFailedOrder() {
        TCardChargingOrder order = order(-1);
        mockOrder(order);
        assertTrue(service.handleHandshakeVin("SD20260909001", VIN));
        ArgumentCaptor<TCardChargingOrder> captor = ArgumentCaptor.forClass(TCardChargingOrder.class);
        verify(mapper).updateById(captor.capture());
        assertNull(captor.getValue().getStatus());
        assertEquals(VIN, captor.getValue().getActualVin());
        verify(chargingGunClient, never()).updateChargingGunById(any(TChargingGun.class));
    }
    @Test
    void skipsAdvancementForChargingOrder() {
        TCardChargingOrder order = order(3);
        mockOrder(order);
        assertTrue(service.handleHandshakeVin("SD20260909001", VIN));
        ArgumentCaptor<TCardChargingOrder> captor = ArgumentCaptor.forClass(TCardChargingOrder.class);
        verify(mapper).updateById(captor.capture());
        assertNull(captor.getValue().getStatus());
        assertEquals(VIN, captor.getValue().getActualVin());
        verify(chargingGunClient, never()).updateChargingGunById(any(TChargingGun.class));
    }
    @Test
    void skipsUpdateEntirelyForFinishedOrderWithoutVin() {
        TCardChargingOrder order = order(5);
        mockOrder(order);
        assertTrue(service.handleHandshakeVin("SD20260909001", ""));
        verify(mapper, never()).updateById(any(TCardChargingOrder.class));
        verify(chargingGunClient, never()).updateChargingGunById(any(TChargingGun.class));
    }
    @Test
    void returnsFalseWhenOrderMissing() {
        mockOrder(null);
        assertFalse(service.handleHandshakeVin("SD20260909001", VIN));
        verify(mapper, never()).updateById(any(TCardChargingOrder.class));
        verify(chargingGunClient, never()).updateChargingGunById(any(TChargingGun.class));
    }
}
ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TVinChargingOrderHandshakeTest.java
New file
@@ -0,0 +1,162 @@
package com.ruoyi.order.service.impl;
import com.ruoyi.chargingPile.api.feignClient.ChargingGunClient;
import com.ruoyi.chargingPile.api.model.TChargingGun;
import com.ruoyi.order.api.model.TVinChargingOrder;
import com.ruoyi.order.mapper.TVinChargingOrderMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import java.time.LocalDateTime;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
 * VD 握手处理单测:VIN 回写 + 仅状态=启动中(2)推进为充电中(3)。
 */
@ExtendWith(MockitoExtension.class)
class TVinChargingOrderHandshakeTest {
    private static final String VIN = "LFV3A23C8K3123456";
    @InjectMocks
    private TVinChargingOrderServiceImpl service;
    @Mock
    private TVinChargingOrderMapper mapper;
    @Mock
    private ChargingGunClient chargingGunClient;
    @BeforeEach
    void setUp() {
        ReflectionTestUtils.setField(service, "baseMapper", mapper);
    }
    private void mockOrder(TVinChargingOrder order) {
        lenient().when(mapper.selectOne(any())).thenReturn(order);
        lenient().when(mapper.selectList(any()))
                .thenReturn(order == null ? Collections.emptyList() : Collections.singletonList(order));
    }
    private TVinChargingOrder order(Integer status) {
        TVinChargingOrder order = new TVinChargingOrder();
        order.setId(2001L);
        order.setCode("VD20260909001");
        order.setStatus(status);
        order.setChargingGunId(6);
        return order;
    }
    @Test
    void advancesStartingOrderToChargingAndWritesVin() {
        TVinChargingOrder order = order(2);
        mockOrder(order);
        assertTrue(service.handleHandshakeVin("VD20260909001", VIN));
        ArgumentCaptor<TVinChargingOrder> orderCaptor = ArgumentCaptor.forClass(TVinChargingOrder.class);
        verify(mapper).updateById(orderCaptor.capture());
        TVinChargingOrder update = orderCaptor.getValue();
        assertEquals(3, update.getStatus());
        assertEquals(VIN, update.getActualVin());
        assertNotNull(update.getStartTime());
        ArgumentCaptor<TChargingGun> gunCaptor = ArgumentCaptor.forClass(TChargingGun.class);
        verify(chargingGunClient).updateChargingGunById(gunCaptor.capture());
        TChargingGun gun = gunCaptor.getValue();
        assertEquals(Integer.valueOf(6), gun.getId());
        assertEquals(Integer.valueOf(4), gun.getStatus());
    }
    @Test
    void keepsExistingStartTimeWhenAdvancing() {
        TVinChargingOrder order = order(2);
        LocalDateTime existed = LocalDateTime.now().minusMinutes(10);
        order.setStartTime(existed);
        mockOrder(order);
        assertTrue(service.handleHandshakeVin("VD20260909001", VIN));
        ArgumentCaptor<TVinChargingOrder> captor = ArgumentCaptor.forClass(TVinChargingOrder.class);
        verify(mapper).updateById(captor.capture());
        assertNull(captor.getValue().getStartTime());
    }
    @Test
    void advancesStartingOrderEvenWhenVinBlank() {
        TVinChargingOrder order = order(2);
        mockOrder(order);
        assertTrue(service.handleHandshakeVin("VD20260909001", ""));
        ArgumentCaptor<TVinChargingOrder> captor = ArgumentCaptor.forClass(TVinChargingOrder.class);
        verify(mapper).updateById(captor.capture());
        assertEquals(3, captor.getValue().getStatus());
        assertNull(captor.getValue().getActualVin());
        verify(chargingGunClient).updateChargingGunById(any(TChargingGun.class));
    }
    @Test
    void doesNotReviveStartFailedOrder() {
        TVinChargingOrder order = order(-1);
        mockOrder(order);
        assertTrue(service.handleHandshakeVin("VD20260909001", VIN));
        ArgumentCaptor<TVinChargingOrder> captor = ArgumentCaptor.forClass(TVinChargingOrder.class);
        verify(mapper).updateById(captor.capture());
        assertNull(captor.getValue().getStatus());
        assertEquals(VIN, captor.getValue().getActualVin());
        verify(chargingGunClient, never()).updateChargingGunById(any(TChargingGun.class));
    }
    @Test
    void skipsAdvancementForChargingOrder() {
        TVinChargingOrder order = order(3);
        mockOrder(order);
        assertTrue(service.handleHandshakeVin("VD20260909001", VIN));
        ArgumentCaptor<TVinChargingOrder> captor = ArgumentCaptor.forClass(TVinChargingOrder.class);
        verify(mapper).updateById(captor.capture());
        assertNull(captor.getValue().getStatus());
        assertEquals(VIN, captor.getValue().getActualVin());
        verify(chargingGunClient, never()).updateChargingGunById(any(TChargingGun.class));
    }
    @Test
    void skipsUpdateEntirelyForFinishedOrderWithoutVin() {
        TVinChargingOrder order = order(5);
        mockOrder(order);
        assertTrue(service.handleHandshakeVin("VD20260909001", ""));
        verify(mapper, never()).updateById(any(TVinChargingOrder.class));
        verify(chargingGunClient, never()).updateChargingGunById(any(TChargingGun.class));
    }
    @Test
    void returnsFalseWhenOrderMissing() {
        mockOrder(null);
        assertFalse(service.handleHandshakeVin("VD20260909001", VIN));
        verify(mapper, never()).updateById(any(TVinChargingOrder.class));
        verify(chargingGunClient, never()).updateChargingGunById(any(TChargingGun.class));
    }
}