✨ 接单叠加认证可用性门槛,带开关可快速回退
在原有运营状态(启用/停用)之上叠加一层「是否已认证」: - 车辆:启用 且 存在生效快照;司机:存在生效快照。 判据是**生效快照指针**而非「最新提交状态」展示缓存,这一区别正是本改造的核心: 司机重新提交认证会把展示缓存变成「审核中」,若照它判定,司机一提交就接不了单 ——那恰恰是本次改造要消除的问题。生效快照只在审核通过时切换,故曾通过即持续可用。 两个开关默认开启,可通过配置关闭以便线上快速回退到改造前行为: transport.certification.vehicle-gate-enabled / driver-gate-enabled 门槛落在 DriverWaybillServiceImpl.accept():司机本人与名下**任一**车辆满足即可。 运营状态优先于认证——停用的车即便已认证也不可用(认证只叠加不覆盖)。 顺带消除第三份重复的车牌匹配实现:DriverWaybillServiceImpl 里的 splitPlates/normalizePlate 与 VehiclePlateSupport 逐行相同,改为委托同一实现。 10 个门槛用例(含「重新提交后仍可用」「停用车即便已认证也不可用」) 与全模块 73 个测试通过:mvn -o test -pl blade-service/blade-transport -am
This commit is contained in:
+36
-22
@@ -37,6 +37,10 @@ import org.springblade.transport.mapper.WaybillNodePunchMapper;
|
||||
import org.springblade.transport.pojo.dto.EnrouteSubmitDTO;
|
||||
import org.springblade.transport.pojo.dto.NodeSubmitDTO;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
import org.springblade.transport.pojo.entity.TransportVehicle;
|
||||
import org.springblade.transport.service.ITransportVehicleService;
|
||||
import org.springblade.transport.support.CertificationGate;
|
||||
import org.springblade.transport.support.VehiclePlateSupport;
|
||||
import org.springblade.transport.pojo.entity.ProcessConfig;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.entity.WaybillEnroutePunch;
|
||||
@@ -68,12 +72,10 @@ import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -110,6 +112,8 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
private final IWaybillService waybillService;
|
||||
private final IDriverService driverService;
|
||||
private final IProcessConfigService processConfigService;
|
||||
private final ITransportVehicleService transportVehicleService;
|
||||
private final CertificationGate certificationGate;
|
||||
private final WaybillEnroutePunchMapper enroutePunchMapper;
|
||||
private final WaybillNodePunchMapper nodePunchMapper;
|
||||
|
||||
@@ -355,6 +359,9 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean accept(Long id) {
|
||||
Driver driver = requireCurrentDriver();
|
||||
// 认证门槛:司机本人须已认证,且承运车辆须已认证。
|
||||
// 判据是各自持有的生效快照指针,故「已认证的车重新提交认证」不会拦下接单。
|
||||
assertCertified(driver);
|
||||
Waybill waybill = loadDriverWaybill(id, currentBoundPlates(driver));
|
||||
waybillService.syncDriverAcceptState(waybill);
|
||||
assertAcceptable(waybill);
|
||||
@@ -368,6 +375,32 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
return waybillService.updateById(waybill);
|
||||
}
|
||||
|
||||
/**
|
||||
* 接单前的认证门槛校验
|
||||
* <p>
|
||||
* 司机的生效快照由其档案持有;车辆按司机绑定的车牌取。
|
||||
* 任一不满足即拒绝接单,并给出可读原因。
|
||||
*
|
||||
* @param driver 当前司机
|
||||
*/
|
||||
private void assertCertified(Driver driver) {
|
||||
if (!certificationGate.isDriverAvailable(driver.getEffectiveCertRecordId())) {
|
||||
throw new ServiceException(certificationGate.driverUnavailableReason());
|
||||
}
|
||||
if (currentBoundPlates(driver).isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<TransportVehicle> boundVehicles = transportVehicleService.list(
|
||||
Wrappers.<TransportVehicle>lambdaQuery()
|
||||
.in(TransportVehicle::getPlateNo, currentBoundPlates(driver)));
|
||||
boolean anyCertified = boundVehicles.stream().anyMatch(vehicle ->
|
||||
certificationGate.isVehicleAvailable(
|
||||
Integer.valueOf(1).equals(vehicle.getStatus()), vehicle.getEffectiveCertRecordId()));
|
||||
if (!anyCertified) {
|
||||
throw new ServiceException(certificationGate.vehicleUnavailableReason());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean reject(Long id, String reason) {
|
||||
@@ -563,26 +596,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
}
|
||||
|
||||
private List<String> splitPlates(String drivingVehicle) {
|
||||
String normalized = drivingVehicle.replace(",", ",").replace("、", ",").replace(";", ",")
|
||||
.replace(";", ",").replace("/", ",").replace("|", ",");
|
||||
Set<String> plates = new LinkedHashSet<>();
|
||||
for (String part : Func.toStrList(",", normalized)) {
|
||||
if (Func.isEmpty(part)) {
|
||||
continue;
|
||||
}
|
||||
String plate = normalizePlate(part);
|
||||
if (Func.isNotEmpty(plate)) {
|
||||
plates.add(plate);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(plates);
|
||||
}
|
||||
|
||||
private String normalizePlate(String plateNo) {
|
||||
if (Func.isEmpty(plateNo)) {
|
||||
return "";
|
||||
}
|
||||
return plateNo.trim().replaceAll("[\\s\\-·•..]", "").toUpperCase(Locale.ROOT);
|
||||
return VehiclePlateSupport.splitPlates(drivingVehicle);
|
||||
}
|
||||
|
||||
private int normalizePreviewSize(Integer size) {
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 认证可用性门槛
|
||||
* <p>
|
||||
* 在原有的运营状态(启用 / 停用)之上**叠加**一层「是否已认证」:
|
||||
* <ul>
|
||||
* <li>车辆:运营状态为启用,且存在生效快照;</li>
|
||||
* <li>司机:存在生效快照。</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* ⚠️ 判据是**生效快照指针**,不是「最新提交状态」展示缓存。
|
||||
* 这一区别是本改造的核心:司机重新提交认证会把展示缓存变成「审核中」,
|
||||
* 若照它判定,司机一提交认证就立刻接不了单——正是要避免的事。
|
||||
* 生效快照只在审核通过时才切换,故「曾认证通过即持续可用」。
|
||||
* <p>
|
||||
* 两个开关默认开启,可通过配置关闭以便线上快速回退到改造前的行为:
|
||||
* <pre>
|
||||
* transport:
|
||||
* certification:
|
||||
* vehicle-gate-enabled: false
|
||||
* driver-gate-enabled: false
|
||||
* </pre>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Component
|
||||
@Getter
|
||||
@Setter
|
||||
public class CertificationGate {
|
||||
|
||||
/**
|
||||
* 车辆认证门槛开关
|
||||
*/
|
||||
@Value("${transport.certification.vehicle-gate-enabled:true}")
|
||||
private boolean vehicleGateEnabled = true;
|
||||
|
||||
/**
|
||||
* 司机认证门槛开关
|
||||
*/
|
||||
@Value("${transport.certification.driver-gate-enabled:true}")
|
||||
private boolean driverGateEnabled = true;
|
||||
|
||||
/**
|
||||
* 判定车辆是否可用
|
||||
* <p>
|
||||
* 运营状态优先:停用的车即便已认证也不可用(认证门槛只叠加,不覆盖运营状态)。
|
||||
*
|
||||
* @param enabledState 运营状态:TRUE 启用
|
||||
* @param effectiveRecordId 生效快照指针
|
||||
* @return 是否可用
|
||||
*/
|
||||
public boolean isVehicleAvailable(Boolean enabledState, Long effectiveRecordId) {
|
||||
if (!Boolean.TRUE.equals(enabledState)) {
|
||||
return false;
|
||||
}
|
||||
if (!vehicleGateEnabled) {
|
||||
return true;
|
||||
}
|
||||
return CertificationRuleSupport.isCertified(effectiveRecordId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定司机是否可接单
|
||||
*
|
||||
* @param effectiveRecordId 生效快照指针
|
||||
* @return 是否可用
|
||||
*/
|
||||
public boolean isDriverAvailable(Long effectiveRecordId) {
|
||||
if (!driverGateEnabled) {
|
||||
return true;
|
||||
}
|
||||
return CertificationRuleSupport.isCertified(effectiveRecordId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 车辆不可用的提示文案
|
||||
*
|
||||
* @return 面向司机的中文提示
|
||||
*/
|
||||
public String vehicleUnavailableReason() {
|
||||
return "该车辆尚未通过认证审核,暂不可接单,请先提交认证";
|
||||
}
|
||||
|
||||
/**
|
||||
* 司机不可用的提示文案
|
||||
*
|
||||
* @return 面向司机的中文提示
|
||||
*/
|
||||
public String driverUnavailableReason() {
|
||||
return "您尚未通过认证审核,暂不可接单,请先完成认证";
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user