From 11e62c172c1a2e34d08c28f025ff90a17e63c4c7 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Sat, 3 Oct 2026 17:46:53 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20=E4=BF=AE=E8=BD=A6=E8=BE=86=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E8=AE=A4=E8=AF=81=E9=95=BF=E6=9C=9F=E6=9C=89=E6=95=88?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=9B=9E=E6=98=BE=EF=BC=8C=E6=9C=89=E6=95=88?= =?UTF-8?q?=E6=9C=9F=E4=B8=8D=E5=86=8D=E8=A6=81=E6=B1=82=E8=B5=B7=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 卡片接口不再把「长期」当日期下发,也不在长期有效时下发上一版证件残留的止期: 两者都会被「重新认证」表单原样回填再原样提交,前者进日期框后按 LocalDate 反序列化直接报错, 后者会让一辆长期有效的车带着一个早已作废的止期一路写回车辆行。 - 车辆有效期校验收敛为「止期必填、起期可空」,和管理端车辆表单同一口径: 起期只在两端都有值时才比较先后。司机端表单本来也不采集起期。 - 补 6 个测试:3 个盯卡片回显(含长期有效 + 残留止期、无止期两种), 3 个盯有效期校验(起期可空、止期必填、长期有效时止期可空)。 --- .../service/impl/DriverAppServiceImpl.java | 21 ++- .../service/impl/DriverCertServiceImpl.java | 16 ++- .../impl/DriverAppServiceImplTest.java | 125 ++++++++++++++++++ .../impl/DriverCertServiceImplTest.java | 59 +++++++++ 4 files changed, 208 insertions(+), 13 deletions(-) create mode 100644 blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverAppServiceImplTest.java diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverAppServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverAppServiceImpl.java index 9acc9d76..ee8feb27 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverAppServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverAppServiceImpl.java @@ -155,20 +155,27 @@ public class DriverAppServiceImpl implements IDriverAppService { } /** - * 日期字段的展示值:长期有效显示「长期」,没填显示空串,其余显示日期 + * 日期字段的下发值:只下发 {@code yyyy-MM-dd} 或空串,**从不下发「长期」这类文字** *

- * 卡片是「重新认证」表单的回填来源,空值必须是空串而不是「null」这类字符串, - * 否则用户会在日期框里看到一个字面量 null。 + * 卡片是「重新认证」表单的回填来源,值会直接进日期框,所以两条约束必须守住: + *

* * @param date 日期 * @param longTerm 长期有效标志:1 表示长期 - * @return 展示文本 + * @return {@code yyyy-MM-dd} 或空串 */ private String dateText(LocalDate date, Integer longTerm) { - if (date != null) { - return date.toString(); + if (Integer.valueOf(1).equals(longTerm)) { + return ""; } - return Integer.valueOf(1).equals(longTerm) ? "长期" : ""; + return date == null ? "" : date.toString(); } /** diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverCertServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverCertServiceImpl.java index 81699684..d02d556e 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverCertServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverCertServiceImpl.java @@ -161,23 +161,27 @@ public class DriverCertServiceImpl implements IDriverCertService { } /** - * 有效期校验:未勾「长期有效」时起止必填,且止不得早于起 + * 有效期校验:未勾「长期有效」时**止期**必填;勾了长期有效则止期也不要求 + *

+ * 起期不再是必填:司机端表单不采集它(管理端车辆表单同样只填止期), + * 存量车辆档案里起期列全是空的,强制必填会让每一次重新认证都卡在提交前。 + * 起止先后只在**两端都有值**时才比较——只有止期时没有可比的对象。 * - * @param start 有效期起 + * @param start 有效期起,可为空 * @param end 有效期止 * @param longTerm 长期有效标志:1 表示长期 - * @param emptyText 缺值提示 - * @param rangeText 区间颠倒提示 + * @param emptyText 缺止期时的提示 + * @param rangeText 区间颠倒时的提示 */ private void requireDateRange(LocalDate start, LocalDate end, Integer longTerm, String emptyText, String rangeText) { if (Integer.valueOf(1).equals(longTerm)) { return; } - if (start == null || end == null) { + if (end == null) { throw new ServiceException(emptyText); } - if (end.isBefore(start)) { + if (start != null && end.isBefore(start)) { throw new ServiceException(rangeText); } } diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverAppServiceImplTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverAppServiceImplTest.java new file mode 100644 index 00000000..0894e214 --- /dev/null +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverAppServiceImplTest.java @@ -0,0 +1,125 @@ +package org.springblade.transport.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.transport.pojo.entity.Driver; +import org.springblade.transport.pojo.entity.TransportVehicle; +import org.springblade.transport.pojo.vo.DriverVehicleCardVO; +import org.springblade.transport.service.IDriverService; +import org.springblade.transport.service.ITransportVehicleService; +import org.springblade.system.feign.IUserClient; + +import java.time.LocalDate; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +/** + * 司机端车辆卡片(重新认证表单的数据来源)测试。 + *

+ * 卡片字段会**直接进表单**,所以这里盯的不是「显示得好不好看」,而是两条会让回显出错的契约: + * 日期字段只能是 {@code yyyy-MM-dd} 或空串(不能是「长期」这类文字); + * 标了长期有效的字段不能再下发一个作废的止期。 + */ +class DriverAppServiceImplTest { + + private static final String MOBILE = "15577122721"; + private static final Long USER_ID = 7001L; + + private IDriverService driverService; + private ITransportVehicleService transportVehicleService; + private DriverAppServiceImpl service; + + @BeforeEach + void setUp() { + driverService = mock(IDriverService.class); + transportVehicleService = mock(ITransportVehicleService.class); + service = new DriverAppServiceImpl(driverService, transportVehicleService, mock(IUserClient.class)); + } + + @Test + @DisplayName("长期有效且没有止期时下发空串,不能下发「长期」——否则它会被当日期填进日期框") + void longTermWithoutEndDateSendsEmptyText() { + DriverVehicleCardVO card = firstCard(vehicle(LocalDate.of(2022, 8, 17), null, 1)); + + assertEquals("", card.getDrivingLicenseEndDate(), "长期有效时日期字段只能是空串"); + assertEquals(1, card.getDrivingLicenseLongTerm(), "长期有效标志本身要照常下发"); + assertEquals("2022-08-17", card.getDrivingLicenseStartDate(), "起期有值时照常下发"); + } + + @Test + @DisplayName("长期有效时即使库里还留着止期也不下发:那个止期是上一版证件作废的值") + void longTermWithStaleEndDateSendsEmptyText() { + DriverVehicleCardVO card = firstCard(vehicle(null, LocalDate.of(2040, 8, 14), 1)); + + assertEquals("", card.getDrivingLicenseEndDate()); + assertEquals("", card.getRoadTransportCertEndDate()); + assertEquals("", card.getCompulsoryScrapDate()); + } + + @Test + @DisplayName("未标长期有效时照常下发止期,空值一律是空串而不是 null 字面量") + void normalVehicleSendsRealDates() { + TransportVehicle vehicle = vehicle(null, LocalDate.of(2030, 8, 14), 0); + vehicle.setCompulsoryScrapDate(LocalDate.of(2035, 1, 1)); + vehicle.setRoadTransportCertEndDate(null); + vehicle.setRoadTransportCertLongTerm(0); + vehicle.setRoadTransportCertStartDate(LocalDate.of(2028, 12, 4)); + DriverVehicleCardVO card = firstCard(vehicle); + + assertEquals("2030-08-14", card.getDrivingLicenseEndDate()); + assertEquals("2035-01-01", card.getCompulsoryScrapDate()); + assertEquals("", card.getRoadTransportCertEndDate(), "没填的止期是空串"); + assertEquals("2028-12-04", card.getRoadTransportCertStartDate()); + } + + // ---------------------------------------------------------------- 辅助 + + /** 造一辆只关心有效期三个字段的车 */ + private TransportVehicle vehicle(LocalDate licenseStart, LocalDate licenseEnd, Integer licenseLongTerm) { + TransportVehicle vehicle = new TransportVehicle(); + vehicle.setId(8001L); + vehicle.setPlateNo("桂AAW3255"); + vehicle.setDrivingLicenseStartDate(licenseStart); + vehicle.setDrivingLicenseEndDate(licenseEnd); + vehicle.setDrivingLicenseLongTerm(licenseLongTerm); + // 三组有效期都按同一个长期有效标志造,便于一次断言三处 + vehicle.setCompulsoryScrapDate(null); + vehicle.setCompulsoryScrapLongTerm(licenseLongTerm); + vehicle.setRoadTransportCertStartDate(LocalDate.of(2028, 12, 3)); + vehicle.setRoadTransportCertEndDate(LocalDate.of(2043, 8, 18)); + vehicle.setRoadTransportCertLongTerm(licenseLongTerm); + return vehicle; + } + + /** 按当前登录司机的手机号跑一次「我的车辆」,取第一张卡片 */ + private DriverVehicleCardVO firstCard(TransportVehicle vehicle) { + Driver driver = new Driver(); + driver.setId(9001L); + driver.setUserId(USER_ID); + driver.setDriverName("韦司机"); + driver.setMobile(MOBILE); + driver.setDrivingVehicle(vehicle.getPlateNo()); + + when(driverService.getOne(any(LambdaQueryWrapper.class))).thenReturn(driver); + when(transportVehicleService.list(any(LambdaQueryWrapper.class))).thenReturn(List.of(vehicle)); + + try (MockedStatic authUtil = mockStatic(AuthUtil.class)) { + authUtil.when(AuthUtil::getUserId).thenReturn(USER_ID); + // 账号本身就是手机号,resolvePhone 走这条短路分支,不再回调用户中心 + authUtil.when(AuthUtil::getUserAccount).thenReturn(MOBILE); + List cards = service.myVehicles(); + assertEquals(1, cards.size(), "应当只返回绑定到本人的这一辆车"); + return cards.get(0); + } + } + +} diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverCertServiceImplTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverCertServiceImplTest.java index ea3dc404..61afbac0 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverCertServiceImplTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverCertServiceImplTest.java @@ -519,6 +519,65 @@ class DriverCertServiceImplTest { assertTrue(exception.getMessage().contains("所属组织")); } + @Test + @DisplayName("有效期只填止期:起期为空也允许提交(司机端不采集起期)") + void submitVehicleAcceptsBlankStartDate() { + Driver driver = driver(null); + when(driverAppService.currentDriverEntity()).thenReturn(driver); + when(transportVehicleService.list(any(LambdaQueryWrapper.class))).thenReturn(List.of()); + when(certRecordService.selectPendingRecord(any(), anyLong())).thenReturn(null); + + VehicleCertSubmitRequest request = completeVehicleRequest(PLATE); + request.setDrivingLicenseStartDate(null); + request.setRoadTransportCertStartDate(null); + + service.submitVehicleCert(request); + + ArgumentCaptor vehicleCaptor = ArgumentCaptor.forClass(TransportVehicle.class); + verify(transportVehicleService).save(vehicleCaptor.capture()); + assertNotNull(vehicleCaptor.getValue().getDrivingLicenseEndDate(), "止期照常写入车辆行"); + assertNull(vehicleCaptor.getValue().getDrivingLicenseStartDate(), "起期为空就是空,不该编一个值出来"); + } + + @Test + @DisplayName("有效期未勾长期有效时止期必填:起期可以空,止期不能") + void submitVehicleRejectsBlankEndDate() { + Driver driver = driver(null); + when(driverAppService.currentDriverEntity()).thenReturn(driver); + when(transportVehicleService.list(any(LambdaQueryWrapper.class))).thenReturn(List.of()); + when(certRecordService.selectPendingRecord(any(), anyLong())).thenReturn(null); + + VehicleCertSubmitRequest request = completeVehicleRequest(PLATE); + request.setDrivingLicenseStartDate(null); + request.setDrivingLicenseEndDate(null); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.submitVehicleCert(request)); + assertEquals("请选择行驶证有效期", exception.getMessage()); + verify(transportVehicleService, never()).save(any(TransportVehicle.class)); + } + + @Test + @DisplayName("长期有效时止期可以为空,此时不再比较起止先后") + void submitVehicleAllowsLongTermWithoutEndDate() { + Driver driver = driver(null); + when(driverAppService.currentDriverEntity()).thenReturn(driver); + when(transportVehicleService.list(any(LambdaQueryWrapper.class))).thenReturn(List.of()); + when(certRecordService.selectPendingRecord(any(), anyLong())).thenReturn(null); + + VehicleCertSubmitRequest request = completeVehicleRequest(PLATE); + request.setDrivingLicenseStartDate(LocalDate.of(2020, 1, 1)); + request.setDrivingLicenseEndDate(null); + request.setDrivingLicenseLongTerm(1); + + service.submitVehicleCert(request); + + ArgumentCaptor captor = ArgumentCaptor.forClass(TransportCertRecord.class); + verify(certRecordService).save(captor.capture()); + assertTrue(captor.getValue().getSnapshotJson().contains("\"drivingLicenseLongTerm\":1"), + "长期有效标志要落进快照"); + } + /** * 一份字段齐全的车辆认证提交内容 *