✨ 补车辆认证删除接口(契约与小程序都已在调用,后端却缺失)
api-contract.md 承诺 DELETE /driver/auth/vehicle/{id},小程序
api/auth.ts 的 deleteVehicleAuth 也从 subpackages/auth/vehicle.vue 真实调用它,
但后端一直没实现——该操作此前必然 404。
实现要点:
- 对车辆做**逻辑删除**(deleteLogic,is_deleted=1),认证记录**一律保留**:
记录是不可变快照,也是「曾通过即永久可用」这一判定的依据,物理删除会让历史无从追溯;
- 越权校验复用同一套车牌绑定判定,写法差异(间隔符/大小写)不影响结果。
3 个用例:逻辑删除而非物理删除(显式断言从不调用 removeById/removeIds)、
越权拒绝、脏车牌仍可识别。全模块 85 个测试通过。
This commit is contained in:
+9
@@ -40,9 +40,11 @@ import org.springblade.transport.pojo.vo.BaiduOcrResultVO;
|
||||
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||
import org.springblade.transport.service.IDriverCertService;
|
||||
import org.springblade.transport.support.OcrTypeMapping;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
@@ -100,6 +102,13 @@ public class DriverAuthController extends BladeController {
|
||||
return R.data(driverCertService.submitVehicleCert(request));
|
||||
}
|
||||
|
||||
@DeleteMapping("/vehicle/{id}")
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "删除车辆认证", description = "对车辆做逻辑删除;认证记录保留,不物理删除")
|
||||
public R<Boolean> deleteVehicle(@PathVariable Long id) {
|
||||
return R.status(driverCertService.deleteVehicleCert(id));
|
||||
}
|
||||
|
||||
@GetMapping("/vehicle/effective")
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "车辆认证生效值", description = "返回该车当前生效快照的内容")
|
||||
|
||||
+12
@@ -71,6 +71,18 @@ public interface IDriverCertService {
|
||||
*/
|
||||
Long submitVehicleCert(VehicleCertSubmitRequest request);
|
||||
|
||||
/**
|
||||
* 删除车辆认证(按逻辑删除处理)
|
||||
* <p>
|
||||
* 只把车辆标记为已删除,**认证记录一律保留**——记录是不可变快照,
|
||||
* 也是「曾通过即永久可用」这一判定的依据,物理删除会让历史无从追溯。
|
||||
* 只能删除本人绑定的车辆,越权拒绝。
|
||||
*
|
||||
* @param vehicleId 车辆主键
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean deleteVehicleCert(Long vehicleId);
|
||||
|
||||
/**
|
||||
* 读取某辆车当前生效的认证内容
|
||||
*
|
||||
|
||||
+20
@@ -49,6 +49,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -127,6 +128,25 @@ public class DriverCertServiceImpl implements IDriverCertService {
|
||||
return record.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteVehicleCert(Long vehicleId) {
|
||||
if (Func.isEmpty(vehicleId)) {
|
||||
throw new ServiceException("车辆主键不能为空");
|
||||
}
|
||||
Driver driver = requireCurrentDriver();
|
||||
TransportVehicle vehicle = transportVehicleService.getById(vehicleId);
|
||||
if (vehicle == null) {
|
||||
throw new ServiceException("车辆不存在");
|
||||
}
|
||||
// 越权校验:与提交链路同一套绑定判定
|
||||
if (!VehiclePlateSupport.isBound(driver.getDrivingVehicle(), vehicle.getPlateNo())) {
|
||||
throw new ServiceException("无权删除该车辆的认证");
|
||||
}
|
||||
// 逻辑删除车辆;认证记录**不删**——它是不可变快照,且是「曾通过即永久可用」的依据
|
||||
return transportVehicleService.deleteLogic(Collections.singletonList(vehicleId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public VehicleCertSubmitRequest getEffectiveVehicleCert(Long vehicleId) {
|
||||
if (Func.isEmpty(vehicleId)) {
|
||||
|
||||
+39
@@ -330,6 +330,45 @@ class DriverCertServiceImplTest {
|
||||
.selectRecordList(TransportCertRecord.BIZ_TYPE_DRIVER, DRIVER_ID);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 删除车辆认证
|
||||
|
||||
@Test
|
||||
@DisplayName("删除车辆认证:逻辑删除车辆,且**不删**认证记录")
|
||||
void deleteVehicleCertIsLogicalOnly() {
|
||||
when(driverAppService.currentDriverEntity()).thenReturn(driver(PLATE));
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(VEHICLE_ID, PLATE, 777L));
|
||||
|
||||
service.deleteVehicleCert(VEHICLE_ID);
|
||||
|
||||
// 走的是 deleteLogic(is_deleted=1),不是物理删除
|
||||
verify(transportVehicleService).deleteLogic(java.util.Collections.singletonList(VEHICLE_ID));
|
||||
verify(transportVehicleService, never()).removeById(anyLong());
|
||||
// 认证记录是历史依据,任何情况下都不得被删
|
||||
verify(certRecordService, never()).removeById(anyLong());
|
||||
verify(certRecordService, never()).removeByIds(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("删除车辆认证:越权必须拒绝")
|
||||
void deleteVehicleCertRejectsUnbound() {
|
||||
when(driverAppService.currentDriverEntity()).thenReturn(driver(PLATE));
|
||||
when(transportVehicleService.getById(VEHICLE_ID))
|
||||
.thenReturn(vehicle(VEHICLE_ID, "桂B99999", null));
|
||||
|
||||
assertThrows(ServiceException.class, () -> service.deleteVehicleCert(VEHICLE_ID));
|
||||
verify(transportVehicleService, never()).deleteLogic(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("删除车辆认证:车牌写法差异不影响绑定判定")
|
||||
void deleteVehicleCertAcceptsMessyPlate() {
|
||||
when(driverAppService.currentDriverEntity()).thenReturn(driver("桂A·32523"));
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(VEHICLE_ID, "桂a32523", null));
|
||||
|
||||
service.deleteVehicleCert(VEHICLE_ID);
|
||||
verify(transportVehicleService).deleteLogic(java.util.Collections.singletonList(VEHICLE_ID));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 辅助
|
||||
|
||||
private Driver driver(String drivingVehicle) {
|
||||
|
||||
Reference in New Issue
Block a user