🐛 派单与导入解析不到司机档案时留脱敏 WARN

改派、主单派单、Excel 导入三条写路径在解析不到档案时只落空不阻断,补 log.warn(号码用 DesensitizedUtil.mobilePhone 脱敏)。同时去掉解析结果上多余的 id > 0 归一:解析入口只返回 null 或唯一命中的档案 ID。
This commit is contained in:
2026-10-27 22:15:16 +08:00
parent 7504ad38f4
commit 48acd6a91e
6 changed files with 67 additions and 20 deletions
@@ -4,8 +4,10 @@
*/ */
package org.springblade.transport.service.impl; 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.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException; import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl; import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.jackson.JsonUtil;
@@ -51,6 +53,7 @@ import java.util.Objects;
* @author Chill * @author Chill
*/ */
@Service @Service
@Slf4j
public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, MasterOrder> implements IMasterOrderService { public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, MasterOrder> implements IMasterOrderService {
private static final String CARRIER_SELF = "自运"; private static final String CARRIER_SELF = "自运";
@@ -266,11 +269,15 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
waybill.setTransportType(string(dispatch, "transportType")); waybill.setCarrierType(string(dispatch, "carrierType")); waybill.setTransportType(string(dispatch, "transportType")); waybill.setCarrierType(string(dispatch, "carrierType"));
waybill.setCarrierContractId(longValue(dispatch, "carrierContractId")); waybill.setCarrierName(string(dispatch, "carrierName")); waybill.setDriverName(string(dispatch, "driverName")); waybill.setCarrierContractId(longValue(dispatch, "carrierContractId")); waybill.setCarrierName(string(dispatch, "carrierName")); waybill.setDriverName(string(dispatch, "driverName"));
waybill.setDriverPhone(string(dispatch, "driverPhone")); waybill.setVehicleNo(string(dispatch, "vehicleNo")); waybill.setTrailerVehicleNo(string(dispatch, "trailerVehicleNo")); waybill.setDriverPhone(string(dispatch, "driverPhone")); waybill.setVehicleNo(string(dispatch, "vehicleNo")); waybill.setTrailerVehicleNo(string(dispatch, "trailerVehicleNo"));
// 归属主键是档案 ID:调度带了就用它,否则按手机号解析;承运商司机解析为空也照常派单 // 归属主键是档案 ID:调度带了就用它,否则按手机号解析;解析入口只返回 null 或唯一命中的
// 档案 ID,承运商司机解析为空也照常派单,落空时留 WARN
Long driverId = longValue(dispatch, "driverId"); Long driverId = longValue(dispatch, "driverId");
if (driverId == null || driverId <= 0) { if (driverId == null || driverId <= 0) {
Long resolvedDriverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), waybill.getDriverPhone()); driverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), waybill.getDriverPhone());
driverId = resolvedDriverId != null && resolvedDriverId > 0 ? resolvedDriverId : null; if (driverId == null) {
log.warn("总单 {} 派单的司机手机号未唯一匹配司机档案,driver_id 落空(不阻断派单)。租户={} 手机号={}",
masterOrder.getMasterNo(), waybill.getTenantId(), DesensitizedUtil.mobilePhone(waybill.getDriverPhone()));
}
} }
waybill.setDriverId(driverId); waybill.setDriverId(driverId);
waybill.setCaptainName(string(dispatch, "captainName")); waybill.setCabinNo(string(dispatch, "cabinNo")); waybill.setContainerNo(string(dispatch, "containerNo")); waybill.setCaptainName(string(dispatch, "captainName")); waybill.setCabinNo(string(dispatch, "cabinNo")); waybill.setContainerNo(string(dispatch, "containerNo"));
@@ -500,12 +500,12 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl<WaybillImport
if (driverId != null && driverId > 0) { if (driverId != null && driverId > 0) {
return; return;
} }
// 解析入口只返回 null 或唯一命中的档案 ID,不需要再按哨兵归一
Long resolvedDriverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), waybill.getDriverPhone()); Long resolvedDriverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), waybill.getDriverPhone());
if (resolvedDriverId != null && resolvedDriverId > 0) { if (resolvedDriverId != null) {
waybill.setDriverId(resolvedDriverId); waybill.setDriverId(resolvedDriverId);
return; return;
} }
// driver_id 只有 >0 才算有主,0 / -1 都按无主落空
waybill.setDriverId(null); waybill.setDriverId(null);
log.warn("第{}行 运单 {} 的司机手机号未唯一匹配司机档案,driver_id 留空(不阻断导入)", log.warn("第{}行 运单 {} 的司机手机号未唯一匹配司机档案,driver_id 留空(不阻断导入)",
rowNo, waybill.getWaybillNo()); rowNo, waybill.getWaybillNo());
@@ -22,6 +22,7 @@
*/ */
package org.springblade.transport.service.impl; 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.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -1615,12 +1616,15 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
TransportBusinessSupport.validateRequired(vehicleNo, "车牌号不能为空"); TransportBusinessSupport.validateRequired(vehicleNo, "车牌号不能为空");
// 归属主键是档案 ID,手机号只是派单时的号码快照:调用方带了档案 ID 就用它, // 归属主键是档案 ID,手机号只是派单时的号码快照:调用方带了档案 ID 就用它,
// 否则按手机号解析;承运商司机、未建档司机解析为空,派单照旧成功,只留 WARN // 否则按手机号解析;解析入口只返回 null 或唯一命中的档案 ID,承运商司机、未建档司机
// 解析为空,派单照旧成功,落空时留 WARN
Long driverId = request.getDriverId(); Long driverId = request.getDriverId();
if (driverId == null || driverId <= 0) { if (driverId == null || driverId <= 0) {
Long resolvedDriverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), driverPhone); driverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), driverPhone);
// driver_id 只有 >0 才算有主,0 / -1 都是「无主」的哨兵写法,落库统一为 NULL if (driverId == null) {
driverId = resolvedDriverId != null && resolvedDriverId > 0 ? resolvedDriverId : null; log.warn("运单 {} 的司机手机号未唯一匹配司机档案,driver_id 落空(不阻断派单)。租户={} 手机号={}",
waybill.getWaybillNo(), waybill.getTenantId(), DesensitizedUtil.mobilePhone(driverPhone));
}
} }
waybill.setDriverId(driverId); waybill.setDriverId(driverId);
waybill.setDriverName(driverName); waybill.setDriverName(driverName);
@@ -1,5 +1,7 @@
package org.springblade.transport.service.impl; package org.springblade.transport.service.impl;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -11,6 +13,7 @@ import org.springblade.transport.service.IDriverService;
import org.springblade.transport.service.IProjectApplyService; import org.springblade.transport.service.IProjectApplyService;
import org.springblade.transport.service.ITransportPlanService; import org.springblade.transport.service.ITransportPlanService;
import org.springblade.transport.service.IWaybillService; import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.LogCapture;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -18,6 +21,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@@ -84,6 +88,25 @@ class MasterOrderDriverIdTest {
verify(waybillService).submit(any(Waybill.class)); verify(waybillService).submit(any(Waybill.class));
} }
@Test
@DisplayName("解析不到档案:落空时留一条脱敏 WARN,派单照常成功")
void unresolvedProfileLogsMaskedWarning() throws Exception {
when(driverService.resolveDriverIdByMobile(any(), eq(DRIVER_PHONE))).thenReturn(null);
try (LogCapture logs = LogCapture.open(MasterOrderServiceImpl.class)) {
assertTrue(createWaybill(dispatch()));
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("ZD-20261027-001"), "WARN 要带业务标识(总单号),实际:" + warning);
}
}
// ------------------------------------------------------------ 夹具 // ------------------------------------------------------------ 夹具
private boolean createWaybill(Map<String, Object> dispatch) throws Exception { private boolean createWaybill(Map<String, Object> dispatch) throws Exception {
@@ -86,17 +86,6 @@ class WaybillImportDriverIdTest {
assertNull(waybill.getDriverId()); assertNull(waybill.getDriverId());
} }
@Test
@DisplayName("解析器返回 0(无主哨兵):按无主落空,不把 0 写进库")
void normalizesSentinelToNull() throws Exception {
when(driverService.resolveDriverIdByMobile(any(), eq(DRIVER_PHONE))).thenReturn(0L);
Waybill waybill = importedWaybill(0L);
fillDriverOwnership(waybill, 3);
assertNull(waybill.getDriverId());
}
// ------------------------------------------------------------ 夹具 // ------------------------------------------------------------ 夹具
private void fillDriverOwnership(Waybill waybill, int rowNo) throws Exception { private void fillDriverOwnership(Waybill waybill, int rowNo) throws Exception {
@@ -1,5 +1,7 @@
package org.springblade.transport.service.impl; 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.MybatisConfiguration;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.ibatis.builder.MapperBuilderAssistant; import org.apache.ibatis.builder.MapperBuilderAssistant;
@@ -12,11 +14,13 @@ import org.springblade.transport.mapper.WaybillMapper;
import org.springblade.transport.pojo.entity.Waybill; import org.springblade.transport.pojo.entity.Waybill;
import org.springblade.transport.service.IDriverService; import org.springblade.transport.service.IDriverService;
import org.springblade.transport.service.IProcessConfigService; import org.springblade.transport.service.IProcessConfigService;
import org.springblade.transport.support.LogCapture;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Collections; import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@@ -108,6 +112,25 @@ class WaybillReassignDriverIdTest {
assertNull(capturedWaybill().getDriverId()); assertNull(capturedWaybill().getDriverId());
} }
@Test
@DisplayName("解析不到档案:落空时留一条脱敏 WARN,派单照常成功")
void unresolvedProfileLogsMaskedWarning() {
when(driverService.resolveDriverIdByMobile(TENANT_ID, DRIVER_PHONE)).thenReturn(null);
try (LogCapture logs = LogCapture.open(WaybillServiceImpl.class)) {
assertTrue(service.reassignWithoutDeptCheck(reassignRequest(null)));
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("YD-20261027-001"), "WARN 要带业务标识(运单号),实际:" + warning);
}
}
// ------------------------------------------------------------ 夹具 // ------------------------------------------------------------ 夹具
private void setUpAgain() throws Exception { private void setUpAgain() throws Exception {
@@ -131,6 +154,7 @@ class WaybillReassignDriverIdTest {
waybill.setId(WAYBILL_ID); waybill.setId(WAYBILL_ID);
waybill.setIsDeleted(0); waybill.setIsDeleted(0);
waybill.setTenantId(TENANT_ID); waybill.setTenantId(TENANT_ID);
waybill.setWaybillNo("YD-20261027-001");
waybill.setBusinessStatus("pending"); waybill.setBusinessStatus("pending");
waybill.setTransportType("road"); waybill.setTransportType("road");
waybill.setDriverName("张三"); waybill.setDriverName("张三");