🐛 审核未命中目标档案时整体回滚,不再静默置为已通过

审核通过时对司机/车辆档案的 updateById 命中 0 行只返回 false、不抛异常,而这个返回值原先被丢弃,于是「认证记录已通过、档案一个字没变」能静默成立:生效快照指针没写进去,对象下次接单仍被判未认证,审核页却显示已通过。现在命中 0 行即抛异常并回滚整次审核,宁可报错也不留下状态与档案互相矛盾的数据。
This commit is contained in:
2026-10-07 18:21:29 +08:00
parent 2e5eae4d32
commit 7d8f2df56f
2 changed files with 47 additions and 2 deletions
@@ -206,7 +206,7 @@ public class CertAuditServiceImpl implements ICertAuditService {
// 与指针合并成同一次 UPDATE,不额外多写一次同一行。
applySnapshotToVehicle(vehicle, record.getSnapshotJson());
}
transportVehicleService.updateById(vehicle);
requireTargetUpdated(transportVehicleService.updateById(vehicle), "车辆", record.getBizId());
return;
}
Driver driver = new Driver();
@@ -225,7 +225,26 @@ public class CertAuditServiceImpl implements ICertAuditService {
// 与指针合并成同一次 UPDATE,不额外多写一次同一行。
applySnapshotToDriver(driver, record.getSnapshotJson());
}
driverService.updateById(driver);
requireTargetUpdated(driverService.updateById(driver), "司机", record.getBizId());
}
/**
* 目标对象的写入必须命中一行,否则整体失败
* <p>
* {@code updateById} 命中 0 行时**不抛异常、只返回 false**。原先丢弃这个返回值,
* 于是「审核记录已通过、司机/车辆档案一个字没变」能静默成立:生效快照指针没写进去,
* 对象下次接单仍被判未认证,而审核页显示的是已通过。此处让事务整体回滚,
* 宁可报错也不留下状态与档案互相矛盾的数据。
*
* @param updated 更新结果
* @param target 对象名称,用于提示
* @param id 对象主键
*/
private void requireTargetUpdated(boolean updated, String target, Long id) {
if (!updated) {
throw new ServiceException("未能更新" + target + "档案(ID:" + id
+ "),本次审核已回滚、未生效;请核对该对象是否存在、未被删除,且与认证记录属于同一租户");
}
}
/**
@@ -50,6 +50,9 @@ class CertAuditServiceImplTest {
transportVehicleService = mock(ITransportVehicleService.class);
driverService = mock(IDriverService.class);
service = new CertAuditServiceImpl(certRecordService, certRecordMapper, transportVehicleService, driverService);
// 目标对象的写入默认成功;「写入一行都没命中」由专门的用例覆盖
when(transportVehicleService.updateById(any(TransportVehicle.class))).thenReturn(true);
when(driverService.updateById(any(Driver.class))).thenReturn(true);
}
// ------------------------------------------------------------ 通过
@@ -174,6 +177,29 @@ class CertAuditServiceImplTest {
"审核通过不得把司机的车辆绑定清空");
}
@Test
@DisplayName("通过:目标对象一行都没命中时必须整体失败,不得「记录已通过、档案没变」")
void approveFailsWhenVehicleNotUpdated() {
when(certRecordService.getById(RECORD_ID)).thenReturn(vehicleRecord(TransportCertRecord.STATUS_PENDING));
when(transportVehicleService.updateById(any(TransportVehicle.class))).thenReturn(false);
ServiceException exception = assertThrows(ServiceException.class,
() -> service.audit(RECORD_ID, true, null));
assertTrue(exception.getMessage().contains("未生效"), exception.getMessage());
}
@Test
@DisplayName("通过司机认证:司机档案没命中时同样必须整体失败,不得静默放行")
void approveFailsWhenDriverNotUpdated() {
when(certRecordService.getById(RECORD_ID)).thenReturn(driverRecord(TransportCertRecord.STATUS_PENDING));
when(driverService.getById(DRIVER_ID)).thenReturn(driver(null));
when(driverService.updateById(any(Driver.class))).thenReturn(false);
ServiceException exception = assertThrows(ServiceException.class,
() -> service.audit(RECORD_ID, true, null));
assertTrue(exception.getMessage().contains("司机"), exception.getMessage());
}
// ------------------------------------------------------------ 驳回
@Test