From 6c112bea652f863ec6079b260280c33eb5a3c3c3 Mon Sep 17 00:00:00 2001 From: weicw <594098497@qq.com> Date: Sun, 25 Oct 2026 19:33:42 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20=E8=BD=A6=E8=BE=86=E4=BE=A7=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E6=89=80=E5=B1=9E=E7=BB=84=E7=BB=87=E5=BF=85=E5=A1=AB?= =?UTF-8?q?=EF=BC=8C=E5=8F=B8=E6=9C=BA=E6=97=A0=E7=BB=84=E7=BB=87=E4=B9=9F?= =?UTF-8?q?=E8=83=BD=E5=BB=BA=E9=A6=96=E8=BE=86=E8=BD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/DriverCertServiceImpl.java | 31 ++-- .../impl/TransportVehicleServiceImpl.java | 3 - .../impl/DriverCertServiceImplTest.java | 17 +- .../impl/TransportVehicleServiceImplTest.java | 148 ++++++++++++++++++ 4 files changed, 167 insertions(+), 32 deletions(-) create mode 100644 blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TransportVehicleServiceImplTest.java 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 ca7ef54e..703119df 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 @@ -123,21 +123,6 @@ public class DriverCertServiceImpl implements IDriverCertService { } } - /** - * 建档前置条件:车辆行的 {@code organization_name} 是 NOT NULL 且无默认值,缺了这条 INSERT - * 直接报「Field 'organization_name' doesn't have a default value」,用户看到的是一串英文异常。 - *

- * 只在**新建车辆**时要求:重新认证不会写这一列(它由管理端维护,属于内部管理归属), - * 拿它拦住重新认证只会让司机无从下手。 - * - * @param driver 当前司机 - */ - private void requireOrganizationForNewVehicle(Driver driver) { - if (Func.isEmpty(driver.getOrganizationName())) { - throw new ServiceException("司机档案缺少所属组织,请联系管理员补充后再提交"); - } - } - /** * 文本必填 + 长度校验 * @@ -474,15 +459,13 @@ public class DriverCertServiceImpl implements IDriverCertService { */ private TransportVehicle createVehicleForDriver(Driver driver, VehicleCertSubmitRequest request, String normalizedPlate) { - // 内容校验已在 submitVehicleCert 里统一做过;这里只剩「这条 INSERT 需要什么」的前置条件 - requireOrganizationForNewVehicle(driver); - + // 内容校验已在 submitVehicleCert 里统一做过 TransportVehicle vehicle = new TransportVehicle(); vehicle.setPlateNo(normalizedPlate); - // 所属组织取司机档案:车辆表的该列 NOT NULL 且无默认值,不填这条 INSERT 直接报 - // 「Field 'organization_name' doesn't have a default value」(严格模式下必错)。 - // 车辆归到司机所属组织名下,管理端按组织查看时才看得到这台车。 - vehicle.setOrganizationName(driver.getOrganizationName()); + // 所属组织是建档时对司机档案取的一次**快照**,不是活关联:司机之后补上组织也不回填已建的车辆。 + // 司机档案没有组织时写空串——该列 NOT NULL 且无默认值,落 null 这条 INSERT 直接报 + // 「Field 'organization_name' doesn't have a default value」。 + vehicle.setOrganizationName(trimToEmpty(driver.getOrganizationName())); vehicle.setOwnerName(request.getOwnerName()); vehicle.setPlateColor(request.getPlateColor()); vehicle.setVehicleType(request.getVehicleType()); @@ -571,4 +554,8 @@ public class DriverCertServiceImpl implements IDriverCertService { return driver; } + private String trimToEmpty(String value) { + return value == null ? "" : value.trim(); + } + } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportVehicleServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportVehicleServiceImpl.java index d9e1c734..4e6e2c00 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportVehicleServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportVehicleServiceImpl.java @@ -264,9 +264,6 @@ public class TransportVehicleServiceImpl extends BaseServiceImpl service.submitVehicleCert(completeVehicleRequest("桂A32523"))); - assertTrue(exception.getMessage().contains("所属组织")); + service.submitVehicleCert(completeVehicleRequest("桂A32523")); + + ArgumentCaptor captor = ArgumentCaptor.forClass(TransportVehicle.class); + verify(transportVehicleService).save(captor.capture()); + assertEquals("", captor.getValue().getOrganizationName(), + "该列 NOT NULL 且无默认值:缺组织要落空串,落 null 这条 INSERT 就直接报错"); + verify(certRecordService).save(any(TransportCertRecord.class)); } @Test @@ -689,8 +693,7 @@ class DriverCertServiceImplTest { // 司机档案ID与用户ID是两个不同的ID:提交人字段要的是后者 driver.setUserId(USER_ID); driver.setDriverName("张三"); - // 车辆表的 organization_name 是 NOT NULL 且无默认值:司机缺组织时建车必然失败, - // 故建车链路要求它非空,fixture 也必须给 + // 车辆行的 organization_name 是建档时从司机档案抄下来的快照 driver.setOrganizationName("广西现代物流集团有限公司"); driver.setDrivingVehicle(drivingVehicle); return driver; diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TransportVehicleServiceImplTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TransportVehicleServiceImplTest.java new file mode 100644 index 00000000..393762bb --- /dev/null +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TransportVehicleServiceImplTest.java @@ -0,0 +1,148 @@ +package org.springblade.transport.service.impl; + +import com.baomidou.mybatisplus.core.MybatisConfiguration; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import org.apache.ibatis.builder.MapperBuilderAssistant; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.transport.mapper.DriverMapper; +import org.springblade.transport.mapper.TransportVehicleMapper; +import org.springblade.transport.pojo.entity.TransportVehicle; + +import java.lang.reflect.Field; +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * 车辆管理保存({@code submit} → {@code prepare} + {@code validate})的组织口径。 + *

+ * 所属组织是车辆的档案属性,不是保存的前置条件:司机端建出来的车组织可能是空串, + * 管理端若仍要求必填,这辆车在后台就成了只读——打开能看、一保存就报错。 + */ +class TransportVehicleServiceImplTest { + + private static final String PLATE = "桂A32523"; + + private TransportVehicleServiceImpl service; + private TransportVehicleMapper vehicleMapper; + private MockedStatic authUtil; + + @BeforeAll + static void initTableInfo() { + // saveOrUpdate / count 需要 MyBatis-Plus 的实体元数据缓存 + MybatisConfiguration configuration = new MybatisConfiguration(); + MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, ""); + assistant.setCurrentNamespace("test"); + TableInfoHelper.initTableInfo(assistant, TransportVehicle.class); + } + + @BeforeEach + void setUp() throws Exception { + vehicleMapper = mock(TransportVehicleMapper.class); + service = new TransportVehicleServiceImpl(mock(DriverMapper.class)); + inject("baseMapper", vehicleMapper); + authUtil = mockStatic(AuthUtil.class); + } + + @AfterEach + void tearDown() { + authUtil.close(); + } + + @Test + @DisplayName("保存车辆:所属组织为空也允许保存,落库是空串而不是 null(该列 NOT NULL)") + void submitAcceptsVehicleWithoutOrganization() { + when(vehicleMapper.selectCount(any(Wrapper.class))).thenReturn(0L); + + TransportVehicle vehicle = completeVehicle(); + vehicle.setOrganizationName(null); + + assertDoesNotThrow(() -> service.submit(vehicle)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(TransportVehicle.class); + verify(vehicleMapper).insert(captor.capture()); + assertEquals("", captor.getValue().getOrganizationName(), + "organization_name 是 NOT NULL 且无默认值,null 会让这条 INSERT 直接报错"); + } + + @Test + @DisplayName("保存车辆:所属组织是空白串时归一成空串,不落一片空格") + void submitTrimsBlankOrganizationToEmpty() { + when(vehicleMapper.selectCount(any(Wrapper.class))).thenReturn(0L); + + TransportVehicle vehicle = completeVehicle(); + vehicle.setOrganizationName(" "); + + assertDoesNotThrow(() -> service.submit(vehicle)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(TransportVehicle.class); + verify(vehicleMapper).insert(captor.capture()); + assertEquals("", captor.getValue().getOrganizationName()); + } + + @Test + @DisplayName("保存车辆:组织已不再必填,但其余必填项照旧拦住(证明校验本身还在跑)") + void submitStillValidatesOtherRequiredFields() { + TransportVehicle vehicle = completeVehicle(); + vehicle.setPlateNo(""); + + ServiceException exception = assertThrows(ServiceException.class, () -> service.submit(vehicle)); + assertEquals("车牌号不能为空", exception.getMessage()); + verify(vehicleMapper, never()).insert(any(TransportVehicle.class)); + } + + // ------------------------------------------------------------ 夹具 + + /** + * 一份能通过全部校验的车辆内容;只有组织一项按用例需要改动。 + */ + private TransportVehicle completeVehicle() { + TransportVehicle vehicle = new TransportVehicle(); + vehicle.setOrganizationName("测试组织"); + vehicle.setPlateNo(PLATE); + vehicle.setVehicleType("重型货车"); + vehicle.setBusinessRelation("管理挂靠"); + vehicle.setEnergyType("柴油"); + vehicle.setDrivingLicenseNo("450100123456"); + vehicle.setDrivingLicenseEndDate(LocalDate.of(2028, 3, 9)); + vehicle.setRoadTransportCertNo("桂交运管0001"); + vehicle.setRoadTransportCertEndDate(LocalDate.of(2030, 1, 1)); + vehicle.setAnnualReviewEndDate(LocalDate.of(2030, 1, 1)); + vehicle.setRegistrationNo("450100123456"); + vehicle.setApprovedLoadKg(18000); + return vehicle; + } + + private void inject(String field, Object value) throws Exception { + Class type = service.getClass(); + while (type != null) { + try { + Field declared = type.getDeclaredField(field); + declared.setAccessible(true); + declared.set(service, value); + return; + } catch (NoSuchFieldException ignored) { + type = type.getSuperclass(); + } + } + throw new IllegalStateException("未找到字段:" + field); + } + +}