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 551479d3..759c86a4 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 @@ -37,16 +37,13 @@ import org.springblade.transport.pojo.vo.DriverVO; import org.springblade.transport.service.IDriverAppService; import org.springblade.transport.service.IDriverService; import org.springblade.transport.service.ITransportVehicleService; +import org.springblade.transport.support.VehiclePlateSupport; import org.springblade.transport.wrapper.DriverWrapper; import org.springframework.stereotype.Service; -import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; /** @@ -72,7 +69,7 @@ public class DriverAppServiceImpl implements IDriverAppService { if (driver == null || Func.isEmpty(driver.getDrivingVehicle())) { return List.of(); } - List plates = splitPlates(driver.getDrivingVehicle()); + List plates = VehiclePlateSupport.splitPlates(driver.getDrivingVehicle()); if (plates.isEmpty()) { return List.of(); } @@ -119,30 +116,6 @@ public class DriverAppServiceImpl implements IDriverAppService { return driver; } - private List splitPlates(String drivingVehicle) { - String normalized = drivingVehicle.replace(",", ",").replace("、", ",").replace(";", ",") - .replace(";", ",").replace("/", ",").replace("|", ","); - Set 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); - } - private DriverVehicleCardVO toCard(TransportVehicle vehicle) { DriverVehicleCardVO card = new DriverVehicleCardVO(); card.setId(vehicle.getId()); 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 new file mode 100644 index 00000000..f8f85ec5 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/VehiclePlateSupport.java @@ -0,0 +1,113 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.support; + +import org.springblade.core.tool.utils.Func; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +/** + * 司机档案与车辆表之间的车牌软关联支持类 + *

+ * 司机「驾驶车辆」与车辆表「车牌号」之间**没有外键**,只有文本关联, + * 且库中实际存在写法差异:间隔符(·、•、.)、横线、空格、大小写不一致, + * 以及一个字段里用逗号/顿号/分号/斜杠/竖线并列多辆车。 + * 认证提交时的「只能操作本人绑定车辆」校验依赖本类,因此匹配规则集中在此处维护, + * 避免各业务各自实现一套导致口径分叉。 + * + * @author Chill + */ +public final class VehiclePlateSupport { + + /** + * 并列车牌的常见分隔符(中英文顿号、逗号、分号、斜杠、竖线) + */ + private static final String SEPARATOR_PATTERN = "[,,、;;/|]"; + + /** + * 车牌中需要剔除的排版字符(各类空白、横线与间隔符) + */ + private static final String NOISE_PATTERN = "[\\s\\-·•..]"; + + private VehiclePlateSupport() { + } + + /** + * 把「驾驶车辆」字段拆成规范化后的车牌列表(已去重、保持原顺序) + * + * @param drivingVehicle 司机档案里的驾驶车辆字段,可能并列多辆 + * @return 规范化车牌列表;空值返回空列表 + */ + public static List splitPlates(String drivingVehicle) { + if (Func.isEmpty(drivingVehicle)) { + return new ArrayList<>(); + } + String normalized = drivingVehicle.replaceAll(SEPARATOR_PATTERN, ","); + Set plates = new LinkedHashSet<>(); + for (String part : Func.toStrList(",", normalized)) { + String plate = normalize(part); + if (Func.isNotEmpty(plate)) { + plates.add(plate); + } + } + return new ArrayList<>(plates); + } + + /** + * 车牌规范化:去掉排版字符并转大写,使不同写法能比对 + * + * @param plateNo 原始车牌 + * @return 规范化结果;空值返回空串 + */ + public static String normalize(String plateNo) { + if (Func.isEmpty(plateNo)) { + return ""; + } + return plateNo.trim().replaceAll(NOISE_PATTERN, "").toUpperCase(Locale.ROOT); + } + + /** + * 判定某车牌是否属于该司机绑定的车辆 + *

+ * 空值一律判为「不绑定」——不能因为双方都为空就放行,那等于越权。 + * + * @param drivingVehicle 司机档案里的驾驶车辆字段 + * @param plateNo 待判定的车牌 + * @return 是否绑定 + */ + public static boolean isBound(String drivingVehicle, String plateNo) { + String target = normalize(plateNo); + if (Func.isEmpty(target)) { + return false; + } + return splitPlates(drivingVehicle).contains(target); + } + +} 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 new file mode 100644 index 00000000..3aa28221 --- /dev/null +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/VehiclePlateSupportTest.java @@ -0,0 +1,91 @@ +package org.springblade.transport.support; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * 车牌匹配逻辑测试。 + *

+ * 司机档案的 {@code driving_vehicle} 与车辆表的 {@code plate_no} 是**文本软关联**, + * 无外键,且库里已知存在间隔符/横线/全角等写法差异(例如「沪AE23411」在车辆表中不存在, + * 而「桂A·32523」这类带间隔符的写法要靠规范化才能匹配上)。 + * 认证提交的越权校验依赖这套匹配,匹配漏判会让司机操作不了自己的车, + * 误判则会让司机改到别人的车,故用测试锁住行为。 + */ +class VehiclePlateSupportTest { + + @Test + @DisplayName("单个车牌:原样返回规范化结果") + void singlePlate() { + assertEquals(List.of("桂A32523"), VehiclePlateSupport.splitPlates("桂A32523")); + } + + @Test + @DisplayName("多个车牌:中英文逗号、顿号、分号、斜杠、竖线都算分隔符") + void multipleSeparators() { + assertEquals( + List.of("桂A32523", "桂B11111", "桂C22222"), + VehiclePlateSupport.splitPlates("桂A32523,桂B11111、桂C22222")); + assertEquals( + List.of("桂A32523", "桂B11111"), + VehiclePlateSupport.splitPlates("桂A32523;桂B11111")); + assertEquals( + List.of("桂A32523", "桂B11111"), + VehiclePlateSupport.splitPlates("桂A32523/桂B11111")); + assertEquals( + List.of("桂A32523", "桂B11111"), + VehiclePlateSupport.splitPlates("桂A32523|桂B11111")); + } + + @Test + @DisplayName("去重:同一车牌写两次只保留一条") + void dedupe() { + assertEquals(List.of("桂A32523"), VehiclePlateSupport.splitPlates("桂A32523,桂A32523")); + } + + @Test + @DisplayName("规范化:去空格/横线/间隔符并转大写") + void normalize() { + assertEquals("桂A32523", VehiclePlateSupport.normalize(" 桂a32523 ")); + assertEquals("桂A32523", VehiclePlateSupport.normalize("桂A-32523")); + assertEquals("桂A32523", VehiclePlateSupport.normalize("桂A·32523")); + assertEquals("桂A32523", VehiclePlateSupport.normalize("桂A•32523")); + assertEquals("桂A32523", VehiclePlateSupport.normalize("桂A.32523")); + assertEquals("桂A32523", VehiclePlateSupport.normalize("桂A 32523")); + } + + @Test + @DisplayName("空值与脏数据:不抛异常,返回空串或空列表") + void emptyAndDirty() { + assertEquals("", VehiclePlateSupport.normalize(null)); + assertEquals("", VehiclePlateSupport.normalize(" ")); + assertTrue(VehiclePlateSupport.splitPlates(null).isEmpty()); + assertTrue(VehiclePlateSupport.splitPlates("").isEmpty()); + assertTrue(VehiclePlateSupport.splitPlates(",,、;;").isEmpty()); + } + + @Test + @DisplayName("绑定判定:写法差异不影响匹配(越权校验的关键)") + void isBound() { + // 司机档案里带间隔符,车辆表里是干净写法 + assertTrue(VehiclePlateSupport.isBound("桂A·32523", "桂A32523")); + // 大小写差异 + assertTrue(VehiclePlateSupport.isBound("桂a32523", "桂A32523")); + // 多车:命中其中一辆即可 + assertTrue(VehiclePlateSupport.isBound("桂A32523,桂B11111", "桂B11111")); + // 未绑定 + assertFalse(VehiclePlateSupport.isBound("桂A32523", "桂B11111")); + // 空值一律不绑定(不能因为双方都为空就放行——那等于越权) + assertFalse(VehiclePlateSupport.isBound(null, "桂A32523")); + assertFalse(VehiclePlateSupport.isBound("桂A32523", null)); + assertFalse(VehiclePlateSupport.isBound(null, null)); + assertFalse(VehiclePlateSupport.isBound("", "")); + } + +}