diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CertificationAuditController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CertificationAuditController.java index d4528ea1..3d7990c1 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CertificationAuditController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CertificationAuditController.java @@ -32,6 +32,8 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import org.springblade.core.boot.ctrl.BladeController; +import org.springblade.core.mp.support.Condition; +import org.springblade.core.mp.support.Query; import org.springblade.core.secure.annotation.PreAuth; import org.springblade.core.tool.api.R; import org.springblade.transport.pojo.vo.CertAuditDetailVO; @@ -68,9 +70,8 @@ public class CertificationAuditController extends BladeController { @RequestParam String bizType, @Parameter(description = "展示键模糊匹配:司机姓名 / 车牌号") @RequestParam(required = false) String bizKey, - @Parameter(description = "页码") @RequestParam(required = false) Integer current, - @Parameter(description = "每页条数") @RequestParam(required = false) Integer size) { - return R.data(certAuditService.pendingPage(bizType, bizKey, current, size)); + Query query) { + return R.data(certAuditService.pendingPage(bizType, bizKey, Condition.getPage(query))); } @GetMapping("/detail") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICertAuditService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICertAuditService.java index 91df728e..4efaa9f8 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICertAuditService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICertAuditService.java @@ -44,12 +44,12 @@ public interface ICertAuditService { * 待审记录分页(按认证对象类型) * * @param bizType 认证对象类型:driver-司机,vehicle-车辆 - * @param bizKey 展示键模糊匹配,可为空 - * @param pageNum 页码 - * @param pageSize 每页条数 + * @param bizKey 展示键模糊匹配,可为空 + * @param page 分页参数(由 Controller 用 Condition.getPage 构造) * @return 待审记录分页 */ - IPage pendingPage(String bizType, String bizKey, Integer pageNum, Integer pageSize); + IPage pendingPage(String bizType, String bizKey, + IPage page); /** * 审核详情:同时返回待审快照与当前生效值,供前端做 diff @@ -69,12 +69,4 @@ public interface ICertAuditService { */ boolean audit(Long recordId, boolean pass, String rejectReason); - /** - * 按主键取记录,供写指针时复用 - * - * @param recordId 认证记录主键 - * @return 认证记录;不存在返回 null - */ - TransportCertRecord getRecordOrNull(Long recordId); - } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CertAuditServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CertAuditServiceImpl.java index 439bf5c6..2d088094 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CertAuditServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CertAuditServiceImpl.java @@ -28,11 +28,11 @@ package org.springblade.transport.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.utils.Func; +import org.springblade.system.cache.UserCache; import org.springblade.transport.pojo.entity.Driver; import org.springblade.transport.pojo.entity.TransportCertRecord; import org.springblade.transport.pojo.entity.TransportVehicle; @@ -71,7 +71,7 @@ public class CertAuditServiceImpl implements ICertAuditService { @Override public IPage pendingPage(String bizType, String bizKey, - Integer pageNum, Integer pageSize) { + IPage page) { if (!CertificationRuleSupport.isValidBizType(bizType)) { throw new ServiceException("认证对象类型不正确"); } @@ -80,9 +80,6 @@ public class CertAuditServiceImpl implements ICertAuditService { .eq(TransportCertRecord::getStatus, TransportCertRecord.STATUS_PENDING) .like(Func.isNotEmpty(bizKey), TransportCertRecord::getBizKey, bizKey) .orderByDesc(TransportCertRecord::getSubmitTime); - Page page = Page.of( - Func.isEmpty(pageNum) ? 1 : pageNum, - Func.isEmpty(pageSize) ? 10 : pageSize); return TransportCertRecordWrapper.build().pageVO(certRecordService.page(page, queryWrapper)); } @@ -100,6 +97,8 @@ public class CertAuditServiceImpl implements ICertAuditService { detail.setStatusText(CertificationRuleSupport.statusText(record.getStatus())); detail.setSubmitTime(record.getSubmitTime()); detail.setSubmitUser(record.getSubmitUser()); + // 面向前端的展示字段必须由服务端填好,否则审核页「提交人」一栏恒为空白 + detail.setSubmitUserName(UserCache.getUserRealName(record.getSubmitUser())); detail.setPendingSnapshotJson(record.getSnapshotJson()); // 生效值一侧:从未认证通过时为 null,前端按「无对比基准」处理 @@ -148,11 +147,6 @@ public class CertAuditServiceImpl implements ICertAuditService { return true; } - @Override - public TransportCertRecord getRecordOrNull(Long recordId) { - return Func.isEmpty(recordId) ? null : certRecordService.getById(recordId); - } - /** * 更新认证对象的展示缓存,并在通过时把生效快照指针切到这条记录 *

diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverAppServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverAppServiceImpl.java index 84c8047a..22c45d94 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverAppServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverAppServiceImpl.java @@ -22,6 +22,7 @@ */ package org.springblade.transport.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import org.springblade.core.log.exception.ServiceException; @@ -73,18 +74,11 @@ public class DriverAppServiceImpl implements IDriverAppService { if (plates.isEmpty()) { return List.of(); } - // 精确匹配 + 规范化匹配(兼容库中带间隔符/横线的车牌) - List vehicles = transportVehicleService.list(Wrappers.lambdaQuery() - .and(w -> { - w.in(TransportVehicle::getPlateNo, plates); - for (String plate : plates) { - w.or().apply( - "REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(UPPER(plate_no),'·',''),'•',''),'.',''),'-',''),' ','') = {0}", - plate - ); - } - }) - .orderByDesc(TransportVehicle::getUpdateTime)); + // 精确 + 规范化双条件匹配,统一走共享实现(避免各调用点各自实现而漏掉写法差异) + LambdaQueryWrapper plateQuery = Wrappers.lambdaQuery(); + VehiclePlateSupport.applyPlateMatch(plateQuery, plates); + List vehicles = transportVehicleService.list( + plateQuery.orderByDesc(TransportVehicle::getUpdateTime)); // 去重(精确与规范化可能命中同一条) Map uniq = new LinkedHashMap<>(); for (TransportVehicle vehicle : vehicles) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverCertServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverCertServiceImpl.java index bf25d29c..072eafc9 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverCertServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverCertServiceImpl.java @@ -25,6 +25,7 @@ */ package org.springblade.transport.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; @@ -263,11 +264,12 @@ public class DriverCertServiceImpl implements IDriverCertService { throw new ServiceException("车牌号不能为空"); } String normalized = VehiclePlateSupport.normalize(plateNo); - List bound = transportVehicleService.list( - Wrappers.lambdaQuery() - .in(TransportVehicle::getPlateNo, - VehiclePlateSupport.splitPlates(driver.getDrivingVehicle()))); - return bound.stream() + // 车牌软关联必须同时容忍写法差异:只精确 IN 会漏掉带间隔符/大小写不同的车牌, + // 后置 filter 无法把漏掉的候选找回来(它只能收窄,不能放宽) + LambdaQueryWrapper plateQuery = Wrappers.lambdaQuery(); + VehiclePlateSupport.applyPlateMatch(plateQuery, + VehiclePlateSupport.splitPlates(driver.getDrivingVehicle())); + return transportVehicleService.list(plateQuery).stream() .filter(item -> normalized.equals(VehiclePlateSupport.normalize(item.getPlateNo()))) .findFirst() .orElse(null); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java index ea4b53c6..d38413bd 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java @@ -391,9 +391,11 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { if (currentBoundPlates(driver).isEmpty()) { return; } - List boundVehicles = transportVehicleService.list( - Wrappers.lambdaQuery() - .in(TransportVehicle::getPlateNo, currentBoundPlates(driver))); + // 用共享的车牌匹配条件:只做精确 IN 会漏掉库中带间隔符/大小写差异的车牌, + // 那会把已认证的司机误判为未认证而拒绝接单 + LambdaQueryWrapper plateQuery = Wrappers.lambdaQuery(); + VehiclePlateSupport.applyPlateMatch(plateQuery, currentBoundPlates(driver)); + List boundVehicles = transportVehicleService.list(plateQuery); boolean anyCertified = boundVehicles.stream().anyMatch(vehicle -> certificationGate.isVehicleAvailable( vehicle.getStatus() != null && vehicle.getStatus() == BladeConstant.DB_STATUS_NORMAL, diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/VehiclePlateSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/VehiclePlateSupport.java index f8f85ec5..600efe8b 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/VehiclePlateSupport.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/VehiclePlateSupport.java @@ -25,7 +25,9 @@ */ package org.springblade.transport.support; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.springblade.core.tool.utils.Func; +import org.springblade.transport.pojo.entity.TransportVehicle; import java.util.ArrayList; import java.util.LinkedHashSet; @@ -110,4 +112,42 @@ public final class VehiclePlateSupport { return splitPlates(drivingVehicle).contains(target); } + /** + * 按车牌匹配车辆的 SQL 条件:精确匹配 **或** 规范化后匹配 + *

+ * ⚠️ 只做精确匹配是不够的。{@link #splitPlates} 返回的已是**规范化**车牌 + * (去空格/间隔符/横线并转大写),若直接拿它与 {@code plate_no} 原始列做 IN, + * 库中任何带间隔符或大小写差异的车牌都会**匹配不到**。 + * 后果不是「少查一条」,而是调用方据此判定「该司机名下没有可用车辆」—— + * 认证门槛会因此把**已认证的司机误判成未认证并拒绝接单**。 + *

+ * 库中已知存在这类写法差异({@code DriverAppServiceImpl.myVehicles()} 早就为此加了 + * 同样的兜底),故此处把规则收敛成一处,避免各调用点各自实现、各自漏掉。 + * + * @param wrapper 查询条件 + * @param normalizedPlates 已规范化的车牌列表 + */ + public static void applyPlateMatch(LambdaQueryWrapper wrapper, + List normalizedPlates) { + if (normalizedPlates == null || normalizedPlates.isEmpty()) { + return; + } + wrapper.and(w -> { + w.in(TransportVehicle::getPlateNo, normalizedPlates); + for (String plate : normalizedPlates) { + w.or().apply(NORMALIZED_PLATE_SQL + " = {0}", plate); + } + }); + } + + /** + * 与 {@link #normalize} 等价的 SQL 表达式(列名固定为 {@code plate_no}) + *

+ * 两者必须保持一致:Java 侧剔除的字符集变了,这里也要跟着变。 + * {@code VehiclePlateSupportTest} 会断言两边覆盖同一批字符,防止悄悄分叉。 + */ + public static final String NORMALIZED_PLATE_SQL = + "REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(UPPER(plate_no)" + + ",'·',''),'•',''),'.',''),'.',''),'-',''),' ','')"; + } diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/VehiclePlateSupportTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/VehiclePlateSupportTest.java index 3aa28221..388d848f 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/VehiclePlateSupportTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/VehiclePlateSupportTest.java @@ -1,5 +1,12 @@ package org.springblade.transport.support; +import org.junit.jupiter.api.BeforeAll; +import org.apache.ibatis.builder.MapperBuilderAssistant; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import com.baomidou.mybatisplus.core.MybatisConfiguration; +import org.springblade.transport.pojo.entity.TransportVehicle; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,6 +27,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue; */ class VehiclePlateSupportTest { + @BeforeAll + static void initTableInfo() { + // 构造 LambdaQueryWrapper 需要 MyBatis-Plus 的实体元数据缓存 + MybatisConfiguration configuration = new MybatisConfiguration(); + MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, ""); + assistant.setCurrentNamespace("test"); + TableInfoHelper.initTableInfo(assistant, TransportVehicle.class); + } + @Test @DisplayName("单个车牌:原样返回规范化结果") void singlePlate() { @@ -88,4 +104,39 @@ class VehiclePlateSupportTest { assertFalse(VehiclePlateSupport.isBound("", "")); } + @Test + @DisplayName("SQL 规范化表达式必须覆盖 Java 侧剔除的全部字符(否则两边会悄悄分叉)") + void sqlNormalizationMirrorsJava() { + String sql = VehiclePlateSupport.NORMALIZED_PLATE_SQL; + // Java 的 normalize 会剔除这些排版字符并转大写,SQL 必须一一对应 + for (String noise : List.of("·", "•", ".", ".", "-", " ")) { + assertTrue(sql.contains("'" + noise + "',''"), + "SQL 规范化漏掉了字符 [" + noise + "],会导致该写法差异的车牌匹配不到"); + } + assertTrue(sql.contains("UPPER(plate_no)"), "SQL 必须先转大写再比对"); + assertTrue(sql.contains("plate_no"), "表达式作用的列应为 plate_no"); + } + + @Test + @DisplayName("车牌匹配条件是「精确 IN 或 规范化相等」,不能只有精确 IN") + void plateMatchIncludesNormalizedFallback() { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + VehiclePlateSupport.applyPlateMatch(wrapper, List.of("桂A32523")); + + String sql = wrapper.getTargetSql(); + assertTrue(sql.contains("plate_no IN"), "必须保留精确匹配,避免全表函数扫描"); + assertTrue(sql.contains("REPLACE"), "必须带规范化兜底,否则脏车牌匹配不到"); + assertTrue(sql.contains("OR"), "两者应是 OR 关系"); + } + + @Test + @DisplayName("空车牌列表不产生任何条件(避免 IN () 语法错误)") + void emptyPlateListAddsNoCondition() { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + VehiclePlateSupport.applyPlateMatch(wrapper, List.of()); + assertEquals("", wrapper.getTargetSql().trim()); + VehiclePlateSupport.applyPlateMatch(wrapper, null); + assertEquals("", wrapper.getTargetSql().trim()); + } + }