# 车辆管理 - 逻辑删除功能说明 ## 功能概述 车辆管理已实现**逻辑删除**(软删除)功能,删除操作不会真正删除数据库中的记录,而是将 `is_delete` 字段标记为 `1`,保留历史数据用于审计和追溯。 ## 逻辑删除 vs 物理删除 ### 逻辑删除(Soft Delete)✅ 当前实现 - **操作**: 将 `is_delete` 字段设置为 `1` - **优点**: - 数据可恢复 - 保留历史记录 - 符合审计要求 - 避免数据丢失 - **缺点**: - 数据库空间占用较大 - 查询时需要过滤已删除记录 ### 物理删除(Hard Delete)❌ 已弃用 - **操作**: 直接从数据库中删除记录 - **优点**: - 节省数据库空间 - 查询速度快 - **缺点**: - 数据无法恢复 - 丢失历史记录 - 不符合审计要求 ## 数据库表结构 ### vehicle 表(车辆表) ```sql CREATE TABLE `vehicle` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `license_plate` varchar(20) NOT NULL COMMENT '车牌号', `car_front_photo` varchar(500) DEFAULT NULL COMMENT '车头照片', `car_rear_photo` varchar(500) DEFAULT NULL COMMENT '车尾照片', `driving_license_photo` varchar(500) DEFAULT NULL COMMENT '行驶证照片', `record_code` varchar(500) DEFAULT NULL COMMENT '牧运通备案码照片', `remark` varchar(500) DEFAULT NULL COMMENT '备注', `is_delete` tinyint(1) DEFAULT 0 COMMENT '是否删除(0-未删除,1-已删除)', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `created_by` int(11) DEFAULT NULL COMMENT '创建人ID', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `updated_by` int(11) DEFAULT NULL COMMENT '更新人ID', PRIMARY KEY (`id`), UNIQUE KEY `uk_license_plate` (`license_plate`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆管理表'; ``` ### 关键字段 - `is_delete`: 删除标记 - `0` 或 `NULL`: 未删除(正常状态) - `1`: 已删除(逻辑删除) ## 后端实现 ### 1. VehicleServiceImpl.java - 逻辑删除方法 ```java /** * 删除车辆(逻辑删除,只标记为已删除,不真正删除数据) */ @Override @Transactional public AjaxResult deleteVehicle(Integer id) { System.out.println("[VEHICLE-DELETE] 开始逻辑删除车辆,ID: " + id); if (id == null) { System.out.println("[VEHICLE-DELETE] 删除失败:车辆ID为空"); return AjaxResult.error("车辆ID不能为空"); } // 查询车辆是否存在 Vehicle vehicle = vehicleMapper.selectById(id); if (vehicle == null) { System.out.println("[VEHICLE-DELETE] 删除失败:车辆不存在,ID: " + id); return AjaxResult.error("车辆不存在"); } // ✅ 检查是否已经被删除(避免重复删除) if (vehicle.getIsDelete() != null && vehicle.getIsDelete() == 1) { System.out.println("[VEHICLE-DELETE] 删除失败:车辆已经被删除,车牌号: " + vehicle.getLicensePlate()); return AjaxResult.error("车辆已经被删除"); } System.out.println("[VEHICLE-DELETE] 车辆信息 - 车牌号: " + vehicle.getLicensePlate()); // ✅ 逻辑删除:将 is_delete 设置为 1 vehicle.setIsDelete(1); // ✅ 记录删除操作者和时间 Integer userId = SecurityUtil.getCurrentUserId(); vehicle.setUpdatedBy(userId); vehicle.setUpdateTime(new Date()); int result = vehicleMapper.updateById(vehicle); if (result > 0) { System.out.println("[VEHICLE-DELETE] ✅ 逻辑删除成功,车牌号: " + vehicle.getLicensePlate() + ", 操作人ID: " + userId); return AjaxResult.success("删除车辆成功"); } else { System.out.println("[VEHICLE-DELETE] ❌ 逻辑删除失败,车牌号: " + vehicle.getLicensePlate()); return AjaxResult.error("删除车辆失败"); } } ``` ### 2. VehicleServiceImpl.java - 查询时过滤已删除记录 ```java /** * 分页查询车辆列表(只查询未删除的记录) */ @Override public PageResultResponse pageQuery(Map params) { // ... 省略其他代码 ... // 构建查询条件 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); // ✅ 只查询未删除的记录(is_delete=0 或 is_delete 为 null) queryWrapper.and(wrapper -> wrapper.eq(Vehicle::getIsDelete, 0).or().isNull(Vehicle::getIsDelete)); queryWrapper.like(licensePlate != null && !licensePlate.isEmpty(), Vehicle::getLicensePlate, licensePlate); queryWrapper.orderByDesc(Vehicle::getCreateTime); // 执行查询 List list = vehicleMapper.selectList(queryWrapper); // ... 省略其他代码 ... } ``` ### 3. VehicleMapper.java - 自定义查询过滤已删除记录 ```java /** * 根据车牌号查询车辆(唯一性校验) * ✅ 只查询未删除的记录 */ @Select("SELECT * FROM vehicle WHERE license_plate = #{licensePlate} AND is_delete = 0") Vehicle selectByLicensePlate(@Param("licensePlate") String licensePlate); /** * 根据车牌号模糊查询车辆列表 * ✅ 只查询未删除的记录 */ @Select("") List selectVehicleList(@Param("licensePlate") String licensePlate); ``` ## 前端实现 ### vehicle.vue - 删除按钮 ```vue ``` ```javascript const delClick = (row) => { ElMessageBox.confirm('请确认是否删除该车辆数据?', '删除', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }) .then(() => { vehicleDel(row.id) .then(() => { ElMessage.success('删除成功'); getDataList(); // 刷新列表 }) .catch((error) => { ElMessage.error('删除失败'); console.error('删除失败:', error); }); }) .catch(() => {}); }; ``` ## 操作流程 ### 删除车辆流程 1. **用户点击"删除"按钮** - 前端弹出确认对话框:`请确认是否删除该车辆数据?` 2. **用户确认删除** - 前端调用 `vehicleDel(id)` API - 请求: `GET /vehicle/delete?id=123` 3. **后端逻辑删除** ```sql UPDATE vehicle SET is_delete = 1, updated_by = {当前用户ID}, update_time = NOW() WHERE id = 123 ``` 4. **前端刷新列表** - 调用 `getDataList()` 重新加载列表 - 已删除的车辆不再显示(被 `is_delete = 0` 过滤掉) ### 后端日志输出示例 ``` [VEHICLE-DELETE] 开始逻辑删除车辆,ID: 1 [VEHICLE-DELETE] 车辆信息 - 车牌号: 鄂A 66662 [VEHICLE-DELETE] ✅ 逻辑删除成功,车牌号: 鄂A 66662, 操作人ID: 11 ``` ## 删除后的数据状态 ### 删除前 ```sql SELECT * FROM vehicle WHERE id = 1; ``` | id | license_plate | is_delete | created_by | updated_by | update_time | |----|---------------|-----------|------------|------------|-------------| | 1 | 鄂A 66662 | 0 | 11 | NULL | NULL | ### 删除后 ```sql SELECT * FROM vehicle WHERE id = 1; ``` | id | license_plate | is_delete | created_by | updated_by | update_time | |----|---------------|-----------|------------|------------|----------------------| | 1 | 鄂A 66662 | **1** | 11 | **11** | **2025-10-29 16:50** | ### 前端查询(已过滤) ```sql -- 前端列表查询会自动过滤 is_delete = 1 的记录 SELECT * FROM vehicle WHERE is_delete = 0; ``` 结果:**不包含** ID=1 的记录(已被逻辑删除) ## 关键改进点 ### ✅ 改进1: 查询时过滤已删除记录 ```java // 使用 LambdaQueryWrapper 过滤 queryWrapper.and(wrapper -> wrapper.eq(Vehicle::getIsDelete, 0).or().isNull(Vehicle::getIsDelete)); ``` **原因**: - 兼容 `is_delete` 为 `0` 和 `NULL` 的情况 - 确保列表中不显示已删除的记录 ### ✅ 改进2: 防止重复删除 ```java if (vehicle.getIsDelete() != null && vehicle.getIsDelete() == 1) { return AjaxResult.error("车辆已经被删除"); } ``` **原因**: - 避免对已删除的记录再次执行删除操作 - 提供明确的错误提示 ### ✅ 改进3: 记录删除操作者和时间 ```java vehicle.setIsDelete(1); vehicle.setUpdatedBy(userId); vehicle.setUpdateTime(new Date()); ``` **原因**: - 记录谁在什么时间删除了这条数据 - 符合审计要求 ### ✅ 改进4: 详细的日志输出 ```java System.out.println("[VEHICLE-DELETE] 开始逻辑删除车辆,ID: " + id); System.out.println("[VEHICLE-DELETE] ✅ 逻辑删除成功,车牌号: " + vehicle.getLicensePlate()); ``` **原因**: - 便于排查问题 - 追踪删除操作 ## 数据恢复 如果需要恢复已删除的车辆数据,可以执行以下 SQL: ```sql -- 恢复单条记录 UPDATE vehicle SET is_delete = 0, updated_by = {恢复操作人ID}, update_time = NOW() WHERE id = 1; -- 批量恢复 UPDATE vehicle SET is_delete = 0, updated_by = {恢复操作人ID}, update_time = NOW() WHERE is_delete = 1 AND license_plate IN ('鄂A 66662', '京B 12345'); ``` ## 测试步骤 ### 1. 重启后端服务 确保新的逻辑删除代码生效。 ### 2. 测试删除功能 1. 进入"车辆管理"页面 2. 点击任意车辆的"删除"按钮 3. 确认删除对话框 4. 观察后端日志: ``` [VEHICLE-DELETE] 开始逻辑删除车辆,ID: 1 [VEHICLE-DELETE] 车辆信息 - 车牌号: 鄂A 66662 [VEHICLE-DELETE] ✅ 逻辑删除成功,车牌号: 鄂A 66662, 操作人ID: 11 ``` 5. 刷新页面,确认该车辆不再显示 ### 3. 验证数据库 ```sql -- 查看所有记录(包括已删除) SELECT id, license_plate, is_delete, updated_by, update_time FROM vehicle ORDER BY update_time DESC; -- 查看未删除的记录 SELECT id, license_plate FROM vehicle WHERE is_delete = 0; -- 查看已删除的记录 SELECT id, license_plate, updated_by, update_time FROM vehicle WHERE is_delete = 1; ``` ### 4. 测试重复删除 1. 尝试再次删除同一车辆(通过直接调用 API) 2. 应该返回错误:`车辆已经被删除` ## 常见问题 ### Q1: 删除后为什么还能在数据库中看到? **答**: 这是逻辑删除,数据不会真正删除,只是标记为 `is_delete = 1`。前端列表查询时会自动过滤掉这些记录。 ### Q2: 如何查看所有已删除的车辆? **答**: 执行 SQL: ```sql SELECT * FROM vehicle WHERE is_delete = 1; ``` ### Q3: 删除的数据可以恢复吗? **答**: 可以,只需要将 `is_delete` 改回 `0` 即可。参考"数据恢复"章节。 ### Q4: 为什么要记录 updated_by 和 update_time? **答**: 用于审计,记录谁在什么时间删除了这条数据。 ## 总结 ✅ **逻辑删除的优势**: - 数据安全,可恢复 - 符合审计要求 - 保留历史记录 - 避免数据丢失 ✅ **实现完整性**: - 删除操作:标记 `is_delete = 1` - 查询操作:过滤 `is_delete = 0` - 唯一性校验:过滤 `is_delete = 0` - 审计记录:记录操作者和时间 ✅ **用户体验**: - 删除确认对话框 - 成功/失败提示 - 自动刷新列表 - 防止重复删除