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)); } }