🐛 派单与导入解析不到司机档案时留脱敏 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;
import cn.hutool.core.util.DesensitizedUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.tool.jackson.JsonUtil;
@@ -51,6 +53,7 @@ import java.util.Objects;
* @author Chill
*/
@Service
@Slf4j
public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, MasterOrder> implements IMasterOrderService {
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.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"));
// 归属主键是档案 ID:调度带了就用它,否则按手机号解析;承运商司机解析为空也照常派单
// 归属主键是档案 ID:调度带了就用它,否则按手机号解析;解析入口只返回 null 或唯一命中的
// 档案 ID,承运商司机解析为空也照常派单,落空时留 WARN
Long driverId = longValue(dispatch, "driverId");
if (driverId == null || driverId <= 0) {
Long resolvedDriverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), waybill.getDriverPhone());
driverId = resolvedDriverId != null && resolvedDriverId > 0 ? resolvedDriverId : null;
driverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), waybill.getDriverPhone());
if (driverId == null) {
log.warn("总单 {} 派单的司机手机号未唯一匹配司机档案,driver_id 落空(不阻断派单)。租户={} 手机号={}",
masterOrder.getMasterNo(), waybill.getTenantId(), DesensitizedUtil.mobilePhone(waybill.getDriverPhone()));
}
}
waybill.setDriverId(driverId);
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) {
return;
}
// 解析入口只返回 null 或唯一命中的档案 ID,不需要再按哨兵归一
Long resolvedDriverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), waybill.getDriverPhone());
if (resolvedDriverId != null && resolvedDriverId > 0) {
if (resolvedDriverId != null) {
waybill.setDriverId(resolvedDriverId);
return;
}
// driver_id 只有 >0 才算有主,0 / -1 都按无主落空
waybill.setDriverId(null);
log.warn("第{}行 运单 {} 的司机手机号未唯一匹配司机档案,driver_id 留空(不阻断导入)",
rowNo, waybill.getWaybillNo());
@@ -22,6 +22,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;
@@ -1615,12 +1616,15 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
TransportBusinessSupport.validateRequired(vehicleNo, "车牌号不能为空");
// 归属主键是档案 ID,手机号只是派单时的号码快照:调用方带了档案 ID 就用它,
// 否则按手机号解析;承运商司机、未建档司机解析为空,派单照旧成功,只留 WARN
// 否则按手机号解析;解析入口只返回 null 或唯一命中的档案 ID,承运商司机、未建档司机
// 解析为空,派单照旧成功,落空时留 WARN
Long driverId = request.getDriverId();
if (driverId == null || driverId <= 0) {
Long resolvedDriverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), driverPhone);
// driver_id 只有 >0 才算有主,0 / -1 都是「无主」的哨兵写法,落库统一为 NULL
driverId = resolvedDriverId != null && resolvedDriverId > 0 ? resolvedDriverId : null;
driverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), driverPhone);
if (driverId == null) {
log.warn("运单 {} 的司机手机号未唯一匹配司机档案,driver_id 落空(不阻断派单)。租户={} 手机号={}",
waybill.getWaybillNo(), waybill.getTenantId(), DesensitizedUtil.mobilePhone(driverPhone));
}
}
waybill.setDriverId(driverId);
waybill.setDriverName(driverName);
@@ -1,5 +1,7 @@
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.DisplayName;
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.ITransportPlanService;
import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.LogCapture;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
@@ -18,6 +21,7 @@ import java.util.List;
import java.util.Map;
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;
@@ -84,6 +88,25 @@ class MasterOrderDriverIdTest {
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 {
@@ -86,17 +86,6 @@ class WaybillImportDriverIdTest {
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 {
@@ -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.metadata.TableInfoHelper;
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.service.IDriverService;
import org.springblade.transport.service.IProcessConfigService;
import org.springblade.transport.support.LogCapture;
import java.lang.reflect.Field;
import java.util.Collections;
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;
@@ -108,6 +112,25 @@ class WaybillReassignDriverIdTest {
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 {
@@ -131,6 +154,7 @@ class WaybillReassignDriverIdTest {
waybill.setId(WAYBILL_ID);
waybill.setIsDeleted(0);
waybill.setTenantId(TENANT_ID);
waybill.setWaybillNo("YD-20261027-001");
waybill.setBusinessStatus("pending");
waybill.setTransportType("road");
waybill.setDriverName("张三");