From ee135cd15074f2710c5266bc84c0d9d8a05d3c04 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Sat, 3 Oct 2026 17:56:56 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20=E9=87=8D=E6=96=B0=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=E4=B9=9F=E8=A6=81=E8=BF=87=E5=86=85=E5=AE=B9=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E5=86=8D=E5=8F=AA=E6=A0=A1=E9=AA=8C=E6=96=B0?= =?UTF-8?q?=E5=BB=BA=E8=BD=A6=E8=BE=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 起先 validateCertContent 只在 createVehicleForDriver 里调用,走 request.id 的重新认证路径 一句都不校验:待审内容虽然要等审核通过才写进车辆档案,但它已经是一条不可变记录, 并会在通过时按字段写回车辆行,于是「内容校验」对重新认证形同虚设。 - 内容校验提到 submitVehicleCert 里、resolveBoundVehicle 之前,两条路径共用; - 「所属组织非空」拆成 requireOrganizationForNewVehicle,仍只在新车建档时要求: 该列只在 INSERT 时需要,它属于内部管理归属,拿它拦住重新认证只会让司机无从下手。 - 补 2 个用例:重新认证传空「所有人」必须被拒;司机档案没有所属组织时重新认证仍应放行。 - 顺带把 7 个此前只传车牌/ID 的用例改用字段齐全的请求:否则它们会因为「车牌颜色不能为空」 提前失败,看着还绿、其实已经不再验证原本要验证的越权与作废逻辑。 --- .../service/impl/DriverCertServiceImpl.java | 36 ++++++---- .../impl/DriverCertServiceImplTest.java | 66 ++++++++++++++----- 2 files changed, 74 insertions(+), 28 deletions(-) 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 d02d556e..524e81d4 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 @@ -90,19 +90,14 @@ public class DriverCertServiceImpl implements IDriverCertService { /** * 司机端提交的车辆认证内容校验 *

- * 服务端必须独立校验一遍,不能依赖小程序的前端校验:前端可绕过, - * 且这些字段里 {@code organization_name}、{@code approved_load_kg}、 - * {@code business_relation}、{@code energy_type}、{@code registration_no} - * 都是 NOT NULL 且无默认值——缺一个这条 INSERT 就直接报数据库错误, - * 用户看到的是一串英文异常,而不是「哪个字段没填」。 + * 服务端必须独立校验一遍,不能依赖小程序的前端校验:前端可绕过。 + * 校验发生在**落快照之前**——待审内容虽然要等审核通过才写进车辆档案,但它已经是一条 + * 不可变记录、并会在通过时按字段写回车辆行({@code applySnapshotToVehicle}), + * 校验只做在「新建车辆」那一条路上,重新认证就成了绕过校验的后门。 * - * @param driver 当前司机 * @param request 提交内容 */ - private void validateCertContent(Driver driver, VehicleCertSubmitRequest request) { - if (Func.isEmpty(driver.getOrganizationName())) { - throw new ServiceException("司机档案缺少所属组织,请联系管理员补充后再提交"); - } + private void validateCertContent(VehicleCertSubmitRequest request) { requireText(request.getPlateColor(), "车牌颜色不能为空", SHORT_TEXT_MAX_LENGTH); requireText(request.getVehicleType(), "车辆类型不能为空", SHORT_TEXT_MAX_LENGTH); requireText(request.getOwnerName(), "所有人不能为空", OWNER_NAME_MAX_LENGTH); @@ -128,6 +123,21 @@ 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("司机档案缺少所属组织,请联系管理员补充后再提交"); + } + } + /** * 文本必填 + 长度校验 * @@ -226,6 +236,9 @@ public class DriverCertServiceImpl implements IDriverCertService { throw new ServiceException("认证内容不能为空"); } Driver driver = requireCurrentDriver(); + // 校验在 resolveBoundVehicle 之前:它既覆盖「新建车辆」,也覆盖「重新认证」—— + // 后者原先一点校验都不做,内容全靠审核环节兜底 + validateCertContent(request); TransportVehicle vehicle = resolveBoundVehicle(driver, request); String snapshotJson = JSON.toJSONString(request); @@ -441,7 +454,8 @@ public class DriverCertServiceImpl implements IDriverCertService { */ private TransportVehicle createVehicleForDriver(Driver driver, VehicleCertSubmitRequest request, String normalizedPlate) { - validateCertContent(driver, request); + // 内容校验已在 submitVehicleCert 里统一做过;这里只剩「这条 INSERT 需要什么」的前置条件 + requireOrganizationForNewVehicle(driver); TransportVehicle vehicle = new TransportVehicle(); vehicle.setPlateNo(normalizedPlate); 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 61afbac0..43cac1ed 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 @@ -73,9 +73,8 @@ class DriverCertServiceImplTest { when(certRecordService.selectPendingRecord(TransportCertRecord.BIZ_TYPE_VEHICLE, VEHICLE_ID)) .thenReturn(null); - VehicleCertSubmitRequest request = new VehicleCertSubmitRequest(); + VehicleCertSubmitRequest request = completeVehicleRequest(PLATE); request.setId(VEHICLE_ID); - request.setPlateNo(PLATE); request.setVin("VIN123"); service.submitVehicleCert(request); @@ -99,9 +98,8 @@ class DriverCertServiceImplTest { when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(VEHICLE_ID, PLATE, null)); when(certRecordService.selectPendingRecord(any(), anyLong())).thenReturn(null); - VehicleCertSubmitRequest vehicleRequest = new VehicleCertSubmitRequest(); + VehicleCertSubmitRequest vehicleRequest = completeVehicleRequest(PLATE); vehicleRequest.setId(VEHICLE_ID); - vehicleRequest.setPlateNo(PLATE); service.submitVehicleCert(vehicleRequest); PersonCertSubmitRequest personRequest = new PersonCertSubmitRequest(); @@ -129,7 +127,7 @@ class DriverCertServiceImplTest { .thenReturn(vehicle(VEHICLE_ID, PLATE, effectiveId)); when(certRecordService.selectPendingRecord(any(), anyLong())).thenReturn(null); - VehicleCertSubmitRequest request = new VehicleCertSubmitRequest(); + VehicleCertSubmitRequest request = completeVehicleRequest(PLATE); request.setId(VEHICLE_ID); service.submitVehicleCert(request); @@ -151,7 +149,7 @@ class DriverCertServiceImplTest { when(certRecordService.selectPendingRecord(TransportCertRecord.BIZ_TYPE_VEHICLE, VEHICLE_ID)) .thenReturn(previous); - VehicleCertSubmitRequest request = new VehicleCertSubmitRequest(); + VehicleCertSubmitRequest request = completeVehicleRequest(PLATE); request.setId(VEHICLE_ID); service.submitVehicleCert(request); @@ -169,7 +167,7 @@ class DriverCertServiceImplTest { // selectPendingRecord 只返回待审;已通过的不该被它返回 when(certRecordService.selectPendingRecord(any(), anyLong())).thenReturn(null); - VehicleCertSubmitRequest request = new VehicleCertSubmitRequest(); + VehicleCertSubmitRequest request = completeVehicleRequest(PLATE); request.setId(VEHICLE_ID); service.submitVehicleCert(request); @@ -185,7 +183,7 @@ class DriverCertServiceImplTest { when(transportVehicleService.getById(VEHICLE_ID)) .thenReturn(vehicle(VEHICLE_ID, "桂B99999", null)); - VehicleCertSubmitRequest request = new VehicleCertSubmitRequest(); + VehicleCertSubmitRequest request = completeVehicleRequest(PLATE); request.setId(VEHICLE_ID); assertThrows(ServiceException.class, () -> service.submitVehicleCert(request)); @@ -200,7 +198,7 @@ class DriverCertServiceImplTest { when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(VEHICLE_ID, "桂a32523", null)); when(certRecordService.selectPendingRecord(any(), anyLong())).thenReturn(null); - VehicleCertSubmitRequest request = new VehicleCertSubmitRequest(); + VehicleCertSubmitRequest request = completeVehicleRequest("桂a32523"); request.setId(VEHICLE_ID); service.submitVehicleCert(request); @@ -212,7 +210,7 @@ class DriverCertServiceImplTest { void submitVehicleRejectsWhenNoBoundVehicle() { when(driverAppService.currentDriverEntity()).thenReturn(driver(null)); - VehicleCertSubmitRequest request = new VehicleCertSubmitRequest(); + VehicleCertSubmitRequest request = completeVehicleRequest(PLATE); request.setId(VEHICLE_ID); assertThrows(ServiceException.class, () -> service.submitVehicleCert(request)); @@ -448,8 +446,7 @@ class DriverCertServiceImplTest { when(transportVehicleService.list(any(LambdaQueryWrapper.class))) .thenReturn(List.of(vehicle(VEHICLE_ID, "桂A32523", null))); // 已登记,但不在本人名下 - VehicleCertSubmitRequest request = new VehicleCertSubmitRequest(); - request.setPlateNo("桂A32523"); + VehicleCertSubmitRequest request = completeVehicleRequest("桂A32523"); assertThrows(ServiceException.class, () -> service.submitVehicleCert(request)); verify(transportVehicleService, never()).save(any(TransportVehicle.class)); @@ -464,8 +461,7 @@ class DriverCertServiceImplTest { .thenReturn(List.of(vehicle(VEHICLE_ID, "桂A·32523", null))); // 写法不同但同一辆 when(certRecordService.selectPendingRecord(any(), anyLong())).thenReturn(null); - VehicleCertSubmitRequest request = new VehicleCertSubmitRequest(); - request.setPlateNo("桂a32523"); + VehicleCertSubmitRequest request = completeVehicleRequest("桂a32523"); service.submitVehicleCert(request); @@ -480,14 +476,50 @@ class DriverCertServiceImplTest { void submitVehicleRejectsBlankPlate() { when(driverAppService.currentDriverEntity()).thenReturn(driver("桂A32523")); - VehicleCertSubmitRequest request = new VehicleCertSubmitRequest(); - request.setPlateNo(" "); + // 内容给全,确保被拒的原因是车牌为空而不是别的字段(否则这条用例形同虚设) + VehicleCertSubmitRequest request = completeVehicleRequest(" "); - assertThrows(ServiceException.class, () -> service.submitVehicleCert(request)); + ServiceException exception = assertThrows(ServiceException.class, + () -> service.submitVehicleCert(request)); + assertTrue(exception.getMessage().contains("车牌"), exception.getMessage()); } // ---------------------------------------------------------------- 辅助 + @Test + @DisplayName("重新认证同样要过内容校验:只校验「新建车辆」等于给重新认证留了后门") + void submitVehicleValidatesContentOnResubmit() { + when(driverAppService.currentDriverEntity()).thenReturn(driver(PLATE)); + when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(VEHICLE_ID, PLATE, null)); + when(certRecordService.selectPendingRecord(any(), anyLong())).thenReturn(null); + + VehicleCertSubmitRequest request = completeVehicleRequest(PLATE); + request.setId(VEHICLE_ID); + request.setOwnerName(""); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.submitVehicleCert(request)); + assertEquals("所有人不能为空", exception.getMessage()); + verify(certRecordService, never()).save(any(TransportCertRecord.class)); + } + + @Test + @DisplayName("重新认证不要求司机档案有所属组织:那一列只在新建车辆时写,与管理归属无关") + void submitVehicleResubmitDoesNotRequireOrganization() { + Driver driver = driver(PLATE); + driver.setOrganizationName(null); + when(driverAppService.currentDriverEntity()).thenReturn(driver); + when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(VEHICLE_ID, PLATE, null)); + when(certRecordService.selectPendingRecord(any(), anyLong())).thenReturn(null); + + VehicleCertSubmitRequest request = completeVehicleRequest(PLATE); + request.setId(VEHICLE_ID); + + service.submitVehicleCert(request); + + verify(certRecordService).save(any(TransportCertRecord.class)); + } + @Test @DisplayName("司机建车:字段不齐时给可读提示,而不是让数据库报 NOT NULL") void submitVehicleRejectsIncompleteRequest() {