From 7504ad38f442035f084607f9b4b6a5d8cf11396a Mon Sep 17 00:00:00 2001 From: weicw <594098497@qq.com> Date: Tue, 27 Oct 2026 22:15:09 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20=E9=85=8D=E8=BD=BD=E5=8D=95=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E6=8C=89=E9=9C=80=E8=A7=A3=E6=9E=90=E5=8F=B8=E6=9C=BA?= =?UTF-8?q?=E5=BD=92=E5=B1=9E=EF=BC=8C=E4=B8=8D=E5=86=8D=E6=8A=B9=E6=8E=89?= =?UTF-8?q?=E5=B7=B2=E6=9C=89=E5=BD=92=E5=B1=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原实现每次保存都按手机号重解析 driver_id,与司机无关的保存会把归属清空并同步到子运单。改为:单据带正数 driver_id 直接沿用;更新时号码与库中一致且库中已有归属则保留;其余情况(新建、换号、原本无主)才按手机号在租户内唯一匹配,匹配不到落 null 并留脱敏 WARN,不阻断保存。复制配载单直接带源单归属。 --- .../impl/LoadingManageServiceImpl.java | 57 ++++++--- .../LoadingManageChildStatusWriteTest.java | 119 ++++++++++++++++++ .../transport/support/LogCapture.java | 48 +++++++ 3 files changed, 208 insertions(+), 16 deletions(-) create mode 100644 blade-service/blade-transport/src/test/java/org/springblade/transport/support/LogCapture.java diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java index 13b6252e..63a9cbf4 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java @@ -4,6 +4,7 @@ */ package org.springblade.transport.service.impl; +import cn.hutool.core.util.DesensitizedUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -11,6 +12,7 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.base.BaseServiceImpl; +import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.Func; @@ -143,16 +145,17 @@ public class LoadingManageServiceImpl extends BaseServiceImpl oldWaybillIds = new ArrayList<>(); // 新建暂存为草稿;编辑已有单据时保持原业务状态(如待执行),避免被改回草稿 String businessStatus = STATUS_DRAFT; - if (Func.isNotEmpty(loadingManage.getId())) { - LoadingManage oldRecord = loadEditable(loadingManage.getId(), true); - oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson()); - loadingManage.setLoadingNo(oldRecord.getLoadingNo()); - loadingManage.setDeptId(oldRecord.getDeptId()); - loadingManage.setDeptName(oldRecord.getDeptName()); - if (Func.isNotEmpty(oldRecord.getBusinessStatus())) { - businessStatus = oldRecord.getBusinessStatus(); + LoadingManage stored = Func.isEmpty(loadingManage.getId()) ? null : loadEditable(loadingManage.getId(), true); + if (stored != null) { + oldWaybillIds = waybillIds(stored.getWaybillIdsJson()); + loadingManage.setLoadingNo(stored.getLoadingNo()); + loadingManage.setDeptId(stored.getDeptId()); + loadingManage.setDeptName(stored.getDeptName()); + if (Func.isNotEmpty(stored.getBusinessStatus())) { + businessStatus = stored.getBusinessStatus(); } } + applyDriverOwnership(loadingManage, stored); prepareCreateOrUpdate(loadingManage); loadingManage.setBusinessStatus(businessStatus); if (Func.isEmpty(loadingManage.getLoadingNo())) { @@ -204,6 +207,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson()); loadingManage.setLoadingNo(oldRecord.getLoadingNo()); @@ -756,12 +761,6 @@ public class LoadingManageServiceImpl extends BaseServiceImpl0 才算有主,0 / -1 都是「无主」的哨兵写法,落库统一为 NULL - loadingManage.setDriverId(resolvedDriverId != null && resolvedDriverId > 0 ? resolvedDriverId : null); loadingManage.setEscortName(TransportBusinessSupport.trimToNull(loadingManage.getEscortName())); loadingManage.setEscortPhone(TransportBusinessSupport.trimToNull(loadingManage.getEscortPhone())); loadingManage.setCarrierType(TransportBusinessSupport.trimToNull(loadingManage.getCarrierType())); @@ -794,6 +793,32 @@ public class LoadingManageServiceImpl extends BaseServiceImpl + * 判定顺序:调用方带了正数档案 ID 就用它;更新时 driver_phone 与库中一致且库中 driver_id 非空就保留库中值; + * 其余情况(新建、换号、原本无主)按手机号在租户内唯一匹配司机档案,匹配不到落 null 并留 WARN,不阻断保存。 + */ + private void applyDriverOwnership(LoadingManage loadingManage, LoadingManage stored) { + Long driverId = loadingManage.getDriverId(); + if (driverId != null && driverId > 0) { + return; + } + if (stored != null && Func.isNotEmpty(stored.getDriverId()) + && Objects.equals(TransportBusinessSupport.trimToNull(stored.getDriverPhone()), loadingManage.getDriverPhone())) { + loadingManage.setDriverId(stored.getDriverId()); + return; + } + String tenantId = Func.isNotEmpty(loadingManage.getTenantId()) ? loadingManage.getTenantId() : AuthUtil.getTenantId(); + Long resolvedDriverId = driverService.resolveDriverIdByMobile(tenantId, loadingManage.getDriverPhone()); + loadingManage.setDriverId(resolvedDriverId); + if (resolvedDriverId == null) { + log.warn("配载单 {} 的司机手机号未唯一匹配司机档案,driver_id 落空。租户={} 手机号={}", + Func.isEmpty(loadingManage.getLoadingNo()) ? "(未编号)" : loadingManage.getLoadingNo(), + tenantId, DesensitizedUtil.mobilePhone(loadingManage.getDriverPhone())); + } + } + private void validate(LoadingManage loadingManage, boolean draftMode) { TransportBusinessSupport.validateRequired(loadingManage.getLoadingSubNos(), "配载子单号不能为空"); TransportBusinessSupport.validateRequired(loadingManage.getTransportType(), "运输类型不能为空"); diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/LoadingManageChildStatusWriteTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/LoadingManageChildStatusWriteTest.java index 6087f36b..3213ac95 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/LoadingManageChildStatusWriteTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/LoadingManageChildStatusWriteTest.java @@ -1,5 +1,7 @@ package org.springblade.transport.service.impl; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; @@ -24,6 +26,7 @@ import org.springblade.transport.service.IContractManageService; import org.springblade.transport.service.IDriverService; import org.springblade.transport.service.IProcessConfigService; import org.springblade.transport.service.IReceivablePayableDetailService; +import org.springblade.transport.support.LogCapture; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -35,6 +38,7 @@ import java.util.Map; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -66,6 +70,9 @@ class LoadingManageChildStatusWriteTest { private static final String DRIVER_PHONE = "13800138000"; private static final String TENANT_ID = "000000"; private static final Long RESOLVED_DRIVER_ID = 9321L; + private static final Long MANUAL_DRIVER_ID = 7001L; + private static final Long OTHER_PROFILE_ID = 8888L; + private static final String OTHER_DRIVER_PHONE = "13900139000"; private static final Long SOURCE_LOADING_ID = 1800000000000000002L; private static final Long NEED_ACCEPT_WAYBILL_ID = 3101L; @@ -394,6 +401,110 @@ class LoadingManageChildStatusWriteTest { assertEquals(RESOLVED_DRIVER_ID, capturedSaved().getDriverId()); } + @Test + @DisplayName("已归属的单子号码没变:无关字段的保存不重解析,归属原样保留") + void unrelatedEditKeepsStoredDriverId() { + LoadingManage record = runningRecord(); + record.setDriverId(RESOLVED_DRIVER_ID); + stubLoadingRecord(record); + stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID)); + // 这一次谁都解析不到:只要实现还按手机号重解析,归属就会被清空 + when(driverService.resolveDriverIdByMobile(any(), any())).thenReturn(null); + + assertTrue(service.submit(editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID))); + + assertEquals(RESOLVED_DRIVER_ID, capturedLoading().getDriverId()); + assertEquals(RESOLVED_DRIVER_ID, statusSyncUpdates().get(0).setValue("driver_id")); + verify(driverService, never()).resolveDriverIdByMobile(any(), any()); + } + + @Test + @DisplayName("人工补过归属的单子:号码恰好命中别人的档案,保存也不改归属") + void unrelatedEditKeepsManualOwnership() { + LoadingManage record = runningRecord(); + record.setDriverId(MANUAL_DRIVER_ID); + stubLoadingRecord(record); + stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID)); + when(driverService.resolveDriverIdByMobile(any(), any())).thenReturn(OTHER_PROFILE_ID); + + assertTrue(service.submit(editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID))); + + assertEquals(MANUAL_DRIVER_ID, capturedLoading().getDriverId(), + "人工归属不能被一次无关保存改到同号的另一个档案上"); + assertEquals(MANUAL_DRIVER_ID, statusSyncUpdates().get(0).setValue("driver_id")); + } + + @Test + @DisplayName("-1 哨兵的归属:号码没变的保存原样保留,不抹成 NULL") + void sentinelDriverIdSurvivesUnrelatedEdit() { + LoadingManage record = runningRecord(); + record.setDriverId(-1L); + stubLoadingRecord(record); + stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID)); + when(driverService.resolveDriverIdByMobile(any(), any())).thenReturn(null); + + assertTrue(service.submit(editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID))); + + assertEquals(-1L, capturedLoading().getDriverId()); + } + + @Test + @DisplayName("换司机:号码变了就按新号码重新解析,归属跟着换") + void changingPhoneResolvesNewDriverId() { + LoadingManage record = pendingRecord(); + record.setDriverId(RESOLVED_DRIVER_ID); + stubLoadingRecord(record); + stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID)); + stubConfigs(config(NEED_ACCEPT_PROJECT_ID, NEED_ACCEPT_JSON)); + when(driverService.resolveDriverIdByMobile(any(), eq(OTHER_DRIVER_PHONE))).thenReturn(OTHER_PROFILE_ID); + LoadingManage form = editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID); + form.setDriverPhone(OTHER_DRIVER_PHONE); + + assertTrue(service.submit(form)); + + assertEquals(OTHER_PROFILE_ID, capturedLoading().getDriverId()); + assertEquals(OTHER_PROFILE_ID, statusSyncUpdates().get(0).setValue("driver_id")); + } + + @Test + @DisplayName("号码改成一个解析不到的号:归属落空,子单同步落空") + void changingPhoneToUnresolvedNumberClearsDriverId() { + LoadingManage record = pendingRecord(); + record.setDriverId(RESOLVED_DRIVER_ID); + stubLoadingRecord(record); + stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID)); + stubConfigs(config(NEED_ACCEPT_PROJECT_ID, NEED_ACCEPT_JSON)); + when(driverService.resolveDriverIdByMobile(any(), eq(OTHER_DRIVER_PHONE))).thenReturn(null); + LoadingManage form = editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID); + form.setDriverPhone(OTHER_DRIVER_PHONE); + + assertTrue(service.submit(form)); + + assertNull(capturedLoading().getDriverId()); + assertNull(statusSyncUpdates().get(0).setValue("driver_id")); + } + + @Test + @DisplayName("解析不到档案:留一条脱敏 WARN,保存照常成功") + void unresolvedPhoneLogsMaskedWarning() { + stubWaybills(waybill(NO_ACCEPT_WAYBILL_ID, NO_ACCEPT_PROJECT_ID)); + stubConfigs(config(NO_ACCEPT_PROJECT_ID, NO_ACCEPT_JSON)); + when(driverService.resolveDriverIdByMobile(any(), eq(DRIVER_PHONE))).thenReturn(null); + try (LogCapture logs = LogCapture.open(LoadingManageServiceImpl.class)) { + assertTrue(service.submit(loadingForm(NO_ACCEPT_WAYBILL_ID))); + + String warning = logs.events().stream() + .filter(event -> event.getLevel() == Level.WARN) + .map(ILoggingEvent::getFormattedMessage) + .filter(message -> message.contains("司机档案")) + .findFirst() + .orElseThrow(() -> new AssertionError("解析失败必须留 WARN,实际日志:" + logs.messages())); + assertTrue(warning.contains("138****8000"), "WARN 里的号码必须脱敏,实际:" + warning); + assertFalse(warning.contains(DRIVER_PHONE), "WARN 里不能出现完整号码,实际:" + warning); + assertTrue(warning.contains(LOADING_NO), "WARN 要带业务标识(配载单号),实际:" + warning); + } + } + // ------------------------------------------------------------ 夹具 private LoadingManage loadingForm(Long... waybillIds) { @@ -468,6 +579,14 @@ class LoadingManageChildStatusWriteTest { return record; } + private LoadingManage pendingRecord() { + LoadingManage record = loadingRecord("pending", NEED_ACCEPT_WAYBILL_ID); + record.setVehicleNo("桂A12345"); + record.setDriverName("张三"); + record.setDriverPhone(DRIVER_PHONE); + return record; + } + private LoadingManage copySource() { LoadingManage source = loadingRecord("pending", NEED_ACCEPT_WAYBILL_ID); source.setLoadingSubNos("YD-" + NEED_ACCEPT_WAYBILL_ID); diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/LogCapture.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/LogCapture.java new file mode 100644 index 00000000..7fa8d76b --- /dev/null +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/LogCapture.java @@ -0,0 +1,48 @@ +package org.springblade.transport.support; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * 抓取某个类的日志输出(logback ListAppender)。 + *

+ * 「解析失败时留一条脱敏 WARN」这类要求只看返回值验不出来,必须断言真实日志正文。 + */ +public final class LogCapture implements AutoCloseable { + + private final Logger logger; + private final ListAppender appender; + + private LogCapture(Logger logger, ListAppender appender) { + this.logger = logger; + this.appender = appender; + } + + public static LogCapture open(Class loggerType) { + Logger logger = (Logger) LoggerFactory.getLogger(loggerType); + ListAppender appender = new ListAppender<>(); + appender.start(); + logger.addAppender(appender); + return new LogCapture(logger, appender); + } + + /** 已按占位符填充好的日志正文,按发生顺序 */ + public List messages() { + return appender.list.stream().map(ILoggingEvent::getFormattedMessage).toList(); + } + + public List events() { + return List.copyOf(appender.list); + } + + @Override + public void close() { + logger.detachAppender(appender); + appender.stop(); + } + +}