♻️ 车牌软关联逻辑抽为 VehiclePlateSupport 并补测试
司机 driving_vehicle 与车辆表 plate_no 之间没有外键,只有文本关联,且库中存在 间隔符/横线/大小写差异,一个字段还可能并列多辆车。认证提交的越权校验 (只能操作本人绑定车辆)依赖这套匹配:漏判会让司机改不了自己的车, 误判会让司机改到别人的车,故先把规则集中并锁住行为。 - 新增 VehiclePlateSupport:splitPlates / normalize / isBound; isBound 对空值一律判不绑定(不能因为双方都为空就放行,那等于越权)。 - DriverAppServiceImpl 原先自己实现了一份 splitPlates/normalizePlate, 改为委托同一个实现,避免两处口径分叉。 - 6 个用例覆盖分隔符变体、去重、规范化、脏数据与绑定判定。 可离线执行:mvn -o test -pl blade-service/blade-transport -am
This commit is contained in:
+2
-29
@@ -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<String> plates = splitPlates(driver.getDrivingVehicle());
|
||||
List<String> plates = VehiclePlateSupport.splitPlates(driver.getDrivingVehicle());
|
||||
if (plates.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
@@ -119,30 +116,6 @@ public class DriverAppServiceImpl implements IDriverAppService {
|
||||
return driver;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private DriverVehicleCardVO toCard(TransportVehicle vehicle) {
|
||||
DriverVehicleCardVO card = new DriverVehicleCardVO();
|
||||
card.setId(vehicle.getId());
|
||||
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 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 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;
|
||||
|
||||
/**
|
||||
* 司机档案与车辆表之间的车牌软关联支持类
|
||||
* <p>
|
||||
* 司机「驾驶车辆」与车辆表「车牌号」之间**没有外键**,只有文本关联,
|
||||
* 且库中实际存在写法差异:间隔符(·、•、.)、横线、空格、大小写不一致,
|
||||
* 以及一个字段里用逗号/顿号/分号/斜杠/竖线并列多辆车。
|
||||
* 认证提交时的「只能操作本人绑定车辆」校验依赖本类,因此匹配规则集中在此处维护,
|
||||
* 避免各业务各自实现一套导致口径分叉。
|
||||
*
|
||||
* @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<String> splitPlates(String drivingVehicle) {
|
||||
if (Func.isEmpty(drivingVehicle)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
String normalized = drivingVehicle.replaceAll(SEPARATOR_PATTERN, ",");
|
||||
Set<String> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定某车牌是否属于该司机绑定的车辆
|
||||
* <p>
|
||||
* 空值一律判为「不绑定」——不能因为双方都为空就放行,那等于越权。
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
+91
@@ -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;
|
||||
|
||||
/**
|
||||
* 车牌匹配逻辑测试。
|
||||
* <p>
|
||||
* 司机档案的 {@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("", ""));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user