🐛 修复启用/停用司机与关联账号时清空驾驶车辆绑定
blade_transport_driver.driving_vehicle 标了 FieldStrategy.ALWAYS,更新时忽略 null 直接写。changeStatus 与同步小程序账号的 linkDriverUserId 都是只带少数 几列的 patch,没带这一列就会把司机的车辆绑定清成 NULL,司机端车辆列表随即 变成「暂无绑定车辆」,司机在界面上无法自行恢复。 两处改为按库中原值一并带上(与认证缓存、审核写回两处的既有做法一致), 并补 DriverServiceImplTest 四个用例:撤下修复后两条断言即变红。
This commit is contained in:
+19
@@ -113,6 +113,8 @@ public class DriverServiceImpl extends BaseServiceImpl<DriverMapper, Driver> imp
|
||||
Driver driver = new Driver();
|
||||
driver.setId(id);
|
||||
driver.setStatus(status);
|
||||
// 只改 status 也必须带上 driving_vehicle 原值,理由见 carryDrivingVehicle
|
||||
carryDrivingVehicle(id, driver);
|
||||
return updateById(driver);
|
||||
}
|
||||
|
||||
@@ -446,9 +448,26 @@ public class DriverServiceImpl extends BaseServiceImpl<DriverMapper, Driver> imp
|
||||
Driver patch = new Driver();
|
||||
patch.setId(driver.getId());
|
||||
patch.setUserId(userId);
|
||||
// 同上:这次只改 user_id,不带 driving_vehicle 原值同样会把车辆绑定清掉
|
||||
carryDrivingVehicle(driver.getId(), patch);
|
||||
updateById(patch);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把库里的 driving_vehicle 原值带进 patch。
|
||||
* <p>
|
||||
* 该列标了 {@code FieldStrategy.ALWAYS},更新时忽略 null 直接写:patch 里不带这一列,
|
||||
* 一次只改别的列的更新就会把司机的车辆绑定清成 NULL,而司机端车辆列表正是按这一列
|
||||
* 的车牌匹配出来的(清空即「暂无绑定车辆」)。读不到该司机时保持原样,
|
||||
* 让更新自己去失败。
|
||||
*/
|
||||
private void carryDrivingVehicle(Long driverId, Driver patch) {
|
||||
Driver current = getById(driverId);
|
||||
if (current != null) {
|
||||
patch.setDrivingVehicle(current.getDrivingVehicle());
|
||||
}
|
||||
}
|
||||
|
||||
private Long defaultZero(Long value) {
|
||||
return value == null ? 0L : value;
|
||||
}
|
||||
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
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.core.tool.api.R;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.feign.ISysClient;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.transport.mapper.DriverMapper;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
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.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 司机管理保存与状态变更测试。
|
||||
* <p>
|
||||
* 守住的是一条不变式:司机档案的行更新一律不得把「驾驶车辆」绑定写掉。
|
||||
* 该列标了 {@code FieldStrategy.ALWAYS},patch 里不带它就写成 NULL,
|
||||
* 司机端车辆列表随即变成「暂无绑定车辆」,而司机在界面上无法自行恢复。
|
||||
*/
|
||||
class DriverServiceImplTest {
|
||||
|
||||
private static final Long DRIVER_ID = 9001L;
|
||||
private static final Long USER_ID = 7001L;
|
||||
private static final String TENANT_ID = "000000";
|
||||
private static final String PLATE = "桂A32523";
|
||||
private static final String MOBILE = "15975348524";
|
||||
|
||||
private DriverMapper driverMapper;
|
||||
private IMpUserClient mpUserClient;
|
||||
private ISysClient sysClient;
|
||||
private DriverServiceImpl service;
|
||||
|
||||
@BeforeAll
|
||||
static void initTableInfo() {
|
||||
// saveOrUpdate 需要 MyBatis-Plus 的实体元数据缓存
|
||||
MybatisConfiguration configuration = new MybatisConfiguration();
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, "");
|
||||
assistant.setCurrentNamespace("test");
|
||||
TableInfoHelper.initTableInfo(assistant, Driver.class);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
driverMapper = mock(DriverMapper.class);
|
||||
mpUserClient = mock(IMpUserClient.class);
|
||||
sysClient = mock(ISysClient.class);
|
||||
service = new DriverServiceImpl(mpUserClient, sysClient);
|
||||
inject("baseMapper", driverMapper);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 启用 / 停用
|
||||
|
||||
@Test
|
||||
@DisplayName("启用/停用司机:patch 必须带上驾驶车辆原值,否则这次更新就把车辆绑定清成 NULL")
|
||||
void changeStatusKeepsDrivingVehicle() {
|
||||
when(driverMapper.selectById(DRIVER_ID)).thenReturn(driver(PLATE));
|
||||
when(driverMapper.updateById(any(Driver.class))).thenReturn(1);
|
||||
|
||||
assertTrue(service.changeStatus(DRIVER_ID, 2));
|
||||
|
||||
ArgumentCaptor<Driver> captor = ArgumentCaptor.forClass(Driver.class);
|
||||
verify(driverMapper).updateById(captor.capture());
|
||||
assertEquals(2, captor.getValue().getStatus());
|
||||
assertEquals(PLATE, captor.getValue().getDrivingVehicle(),
|
||||
"只改 status 的 patch 不带 driving_vehicle 会把司机的车辆绑定写掉");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("启用/停用司机:状态取值不合法时拒绝,且不写库")
|
||||
void changeStatusRejectsInvalidStatus() {
|
||||
assertThrows(ServiceException.class, () -> service.changeStatus(DRIVER_ID, 9));
|
||||
verify(driverMapper, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 保存司机
|
||||
|
||||
@Test
|
||||
@DisplayName("保存司机:关联小程序账号的那次 patch 也必须带上驾驶车辆原值")
|
||||
void submitKeepsDrivingVehicleWhenLinkingMpUser() {
|
||||
Driver driver = driver(PLATE);
|
||||
// 提交前校验按身份证号与主键各查一次
|
||||
when(driverMapper.selectByIdIncludingDeleted(DRIVER_ID)).thenReturn(driver);
|
||||
when(driverMapper.selectByIdCardNoIncludingDeleted(driver.getIdCardNo())).thenReturn(null);
|
||||
// 完整对象更新:saveOrUpdate 判存在走更新
|
||||
when(driverMapper.selectById(DRIVER_ID)).thenReturn(driver);
|
||||
when(driverMapper.updateById(any(Driver.class))).thenReturn(1);
|
||||
// 先查不到小程序用户 → 建号 → 回查拿到新建用户
|
||||
MpUser created = new MpUser();
|
||||
created.setId(USER_ID);
|
||||
when(mpUserClient.userByPhone(TENANT_ID, MOBILE)).thenReturn(null, R.data(created));
|
||||
when(mpUserClient.saveUser(any(MpUser.class))).thenReturn(R.data(true));
|
||||
|
||||
try (MockedStatic<AuthUtil> authUtil = mockStatic(AuthUtil.class)) {
|
||||
authUtil.when(AuthUtil::getTenantId).thenReturn(TENANT_ID);
|
||||
assertTrue(service.submit(driver));
|
||||
}
|
||||
|
||||
ArgumentCaptor<Driver> captor = ArgumentCaptor.forClass(Driver.class);
|
||||
verify(driverMapper, times(2)).updateById(captor.capture());
|
||||
// captor 存的是引用:完整对象更新带走的是 driver 本身,而它随后会被就地写上 userId,
|
||||
// 所以只能用对象身份把「只带少数几列的 patch」摘出来,不能按 userId 认
|
||||
Driver linkPatch = captor.getAllValues().stream()
|
||||
.filter(patch -> patch != driver)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("没有发出关联小程序账号的那次 patch"));
|
||||
assertEquals(USER_ID, linkPatch.getUserId());
|
||||
assertEquals(PLATE, linkPatch.getDrivingVehicle(),
|
||||
"关联账号的 patch 不带 driving_vehicle 会把司机的车辆绑定写掉");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("保存司机:司机不存在时拒绝,不写库")
|
||||
void submitRejectsUnknownDriver() {
|
||||
Driver driver = driver(PLATE);
|
||||
when(driverMapper.selectByIdIncludingDeleted(DRIVER_ID)).thenReturn(null);
|
||||
|
||||
assertThrows(ServiceException.class, () -> service.submit(driver));
|
||||
verify(driverMapper, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 夹具
|
||||
|
||||
private Driver driver(String drivingVehicle) {
|
||||
Driver driver = new Driver();
|
||||
driver.setId(DRIVER_ID);
|
||||
driver.setIsDeleted(0);
|
||||
driver.setDriverName("张三");
|
||||
driver.setIdCardNo("110101199001011234");
|
||||
driver.setGender("男");
|
||||
driver.setPosts("司机");
|
||||
driver.setDrivingType("A2");
|
||||
driver.setDrivingLicenseNo("110101199001011234");
|
||||
driver.setDrivingLicenseStartDate(LocalDate.of(2020, 1, 1));
|
||||
driver.setDrivingLicenseLongTerm(1);
|
||||
driver.setQualificationType("道路运输");
|
||||
driver.setQualificationNo("Q123456");
|
||||
driver.setQualificationLongTerm(1);
|
||||
driver.setDriverType("自有");
|
||||
driver.setMobile(MOBILE);
|
||||
driver.setOrganizationName("测试组织");
|
||||
driver.setEmergencyContactName("李四");
|
||||
driver.setEmergencyContactMobile("13800000000");
|
||||
driver.setIdCardFront("https://oss/id-front.jpg");
|
||||
driver.setIdCardBack("https://oss/id-back.jpg");
|
||||
driver.setDrivingLicenseFront("https://oss/dl-front.jpg");
|
||||
driver.setDrivingLicenseBack("https://oss/dl-back.jpg");
|
||||
driver.setDrivingVehicle(drivingVehicle);
|
||||
return driver;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user