🐛 修复启用/停用司机与关联账号时清空驾驶车辆绑定
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 driver = new Driver();
|
||||||
driver.setId(id);
|
driver.setId(id);
|
||||||
driver.setStatus(status);
|
driver.setStatus(status);
|
||||||
|
// 只改 status 也必须带上 driving_vehicle 原值,理由见 carryDrivingVehicle
|
||||||
|
carryDrivingVehicle(id, driver);
|
||||||
return updateById(driver);
|
return updateById(driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,9 +448,26 @@ public class DriverServiceImpl extends BaseServiceImpl<DriverMapper, Driver> imp
|
|||||||
Driver patch = new Driver();
|
Driver patch = new Driver();
|
||||||
patch.setId(driver.getId());
|
patch.setId(driver.getId());
|
||||||
patch.setUserId(userId);
|
patch.setUserId(userId);
|
||||||
|
// 同上:这次只改 user_id,不带 driving_vehicle 原值同样会把车辆绑定清掉
|
||||||
|
carryDrivingVehicle(driver.getId(), patch);
|
||||||
updateById(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) {
|
private Long defaultZero(Long value) {
|
||||||
return value == null ? 0L : 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