✨ 审核列表新增司机姓名与手机号,并修正驾驶车辆绑定校验
审核页的司机姓名与手机号取司机档案的**当前值**,不是提交时的历史值: 手机号从未进入过认证快照(只有司机档案这一个来源),而队列的用途是 「找得到人、联系得上」。司机侧的认证对象就是记录本身;车辆侧记录不含任何 司机信息,取提交该记录的司机。存量订正记录里没有系统账号的那些司机解析不到, 两列为空属正常状态。 筛选必须发生在分页之前,故列表改为自定义 SQL;在 Java 层补字段再过滤会变成 「先分页再过滤」,每页条数与总数都不准。姓名搜索同时匹配档案当前姓名与提交时的 旧名,改名后按新名、旧名都搜得到。 顺带修掉两处既有缺陷: 1. 管理端保存绑定多辆车的司机会误报「驾驶车辆不存在」——小程序把车牌逗号并列 写入 driving_vehicle,管理端却拿整串去车牌表精确查,两边口径不一致。 2. 「一个司机最多 5 辆车」后端从未校验,且 driving_vehicle 原为 varchar(30), 5 个车牌连分隔符要 39 字符,超长写入会被静默截断成半个车牌(还会被当成一辆车)。 列扩到 varchar(100) 并在写入前校验数量与车牌格式。
This commit is contained in:
+7
-3
@@ -63,17 +63,21 @@ public class CertificationAuditController extends BladeController {
|
||||
|
||||
@GetMapping("/pending-page")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "认证记录列表", description = "按认证对象类型与审核状态分页查询,按提交时间倒序;状态不传时查审核中的记录")
|
||||
@Operation(summary = "认证记录列表", description = "按认证对象类型与审核状态分页查询,按提交时间倒序;状态不传时查审核中的记录。列表同时下发该记录司机的姓名与手机号(司机档案当前值)")
|
||||
@PreAuth(CertAuditPermissions.AUDIT_VIEW)
|
||||
public R<IPage<TransportCertRecordVO>> pendingPage(
|
||||
@Parameter(description = "认证对象类型:driver-司机,vehicle-车辆", required = true)
|
||||
@RequestParam String bizType,
|
||||
@Parameter(description = "展示键模糊匹配:司机姓名 / 车牌号")
|
||||
@Parameter(description = "展示键模糊匹配:提交时的司机姓名 / 车牌号")
|
||||
@RequestParam(required = false) String bizKey,
|
||||
@Parameter(description = "司机姓名模糊匹配:当前姓名,同时兼顾提交时的旧名")
|
||||
@RequestParam(required = false) String driverName,
|
||||
@Parameter(description = "司机手机号模糊匹配:支持只输后四位")
|
||||
@RequestParam(required = false) String driverMobile,
|
||||
@Parameter(description = "审核状态:0审核中 1已通过 2已驳回 3已作废;不传按0(审核中)处理")
|
||||
@RequestParam(required = false) Integer status,
|
||||
Query query) {
|
||||
return R.data(certAuditService.pendingPage(bizType, bizKey, status, Condition.getPage(query)));
|
||||
return R.data(certAuditService.pendingPage(bizType, bizKey, driverName, driverMobile, status, Condition.getPage(query)));
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
|
||||
+27
@@ -26,7 +26,12 @@
|
||||
package org.springblade.transport.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 认证记录 Mapper 接口
|
||||
@@ -35,4 +40,26 @@ import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||
*/
|
||||
public interface TransportCertRecordMapper extends BaseMapper<TransportCertRecord> {
|
||||
|
||||
/**
|
||||
* 审核列表分页:认证记录 + 该记录的司机姓名与手机号
|
||||
* <p>
|
||||
* 必须走自定义 SQL 而不是单表 Wrapper:司机姓名与手机号取的是司机档案的**当前**值,
|
||||
* 且「按司机姓名/手机号筛选」必须发生在**分页之前**——若在 Java 层补字段再过滤,
|
||||
* 就变成「先分页再过滤」,每页条数与总数都不准。
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param bizType 认证对象类型:driver-司机,vehicle-车辆
|
||||
* @param status 审核状态:0审核中 1已通过 2已驳回 3已作废
|
||||
* @param bizKey 展示键模糊匹配(提交时的司机姓名 / 车牌号),可为空
|
||||
* @param driverName 司机姓名模糊匹配(当前姓名,并兼顾提交时的旧名),可为空
|
||||
* @param driverMobile 司机手机号模糊匹配,可为空
|
||||
* @return 认证记录列表
|
||||
*/
|
||||
List<TransportCertRecordVO> selectAuditPage(IPage<TransportCertRecordVO> page,
|
||||
@Param("bizType") String bizType,
|
||||
@Param("status") Integer status,
|
||||
@Param("bizKey") String bizKey,
|
||||
@Param("driverName") String driverName,
|
||||
@Param("driverMobile") String driverMobile);
|
||||
|
||||
}
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.springblade.transport.mapper.TransportCertRecordMapper">
|
||||
|
||||
<resultMap id="certRecordAuditMap" type="org.springblade.transport.pojo.vo.TransportCertRecordVO">
|
||||
<result column="id" property="id"/>
|
||||
<result column="tenant_id" property="tenantId"/>
|
||||
<result column="biz_type" property="bizType"/>
|
||||
<result column="biz_id" property="bizId"/>
|
||||
<result column="biz_key" property="bizKey"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="reject_reason" property="rejectReason"/>
|
||||
<result column="submit_time" property="submitTime"/>
|
||||
<result column="submit_user" property="submitUser"/>
|
||||
<result column="audit_time" property="auditTime"/>
|
||||
<result column="audit_user" property="auditUser"/>
|
||||
<result column="create_user" property="createUser"/>
|
||||
<result column="create_dept" property="createDept"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="update_user" property="updateUser"/>
|
||||
<result column="update_time" property="updateTime"/>
|
||||
<result column="is_deleted" property="isDeleted"/>
|
||||
<result column="driver_name" property="driverName"/>
|
||||
<result column="driver_mobile" property="driverMobile"/>
|
||||
</resultMap>
|
||||
|
||||
<!--
|
||||
审核列表:认证记录 + 该记录的司机姓名与手机号(司机档案的当前值)。
|
||||
|
||||
司机是谁:司机侧就是认证对象本身(biz_id);车辆侧是这条记录的**提交司机**
|
||||
(车辆认证强制「只能操作本人绑定车辆」,故提交司机必然是这辆车的绑定司机)。
|
||||
提交人存的是用户 ID,而司机档案的 user_id 是同一个值,故按 user_id 定位。
|
||||
|
||||
子查询带 ORDER BY update_time DESC LIMIT 1 有实际作用,不是保险起见:
|
||||
user_id 上没有唯一索引,脏数据里同一用户可能对应多条司机档案,
|
||||
不收敛就会让一条记录在列表里出现多行(并让总数虚高)。
|
||||
按 update_time 最新取,与司机端解析当前司机档案的既有口径一致。
|
||||
-->
|
||||
<select id="selectAuditPage" resultMap="certRecordAuditMap">
|
||||
SELECT
|
||||
r.id,
|
||||
r.tenant_id,
|
||||
r.biz_type,
|
||||
r.biz_id,
|
||||
r.biz_key,
|
||||
r.status,
|
||||
r.reject_reason,
|
||||
r.submit_time,
|
||||
r.submit_user,
|
||||
r.audit_time,
|
||||
r.audit_user,
|
||||
r.create_user,
|
||||
r.create_dept,
|
||||
r.create_time,
|
||||
r.update_user,
|
||||
r.update_time,
|
||||
r.is_deleted,
|
||||
d.driver_name AS driver_name,
|
||||
d.mobile AS driver_mobile
|
||||
FROM
|
||||
blade_transport_cert_record r
|
||||
LEFT JOIN blade_transport_driver d
|
||||
ON d.is_deleted = 0
|
||||
AND d.id = CASE
|
||||
WHEN r.biz_type = 'driver' THEN r.biz_id
|
||||
ELSE (
|
||||
SELECT dr.id
|
||||
FROM blade_transport_driver dr
|
||||
WHERE dr.is_deleted = 0
|
||||
AND dr.user_id = r.submit_user
|
||||
ORDER BY dr.update_time DESC, dr.id DESC
|
||||
LIMIT 1
|
||||
)
|
||||
END
|
||||
WHERE
|
||||
r.is_deleted = 0
|
||||
AND r.biz_type = #{bizType}
|
||||
AND r.status = #{status}
|
||||
<if test="bizKey != null and bizKey != ''">
|
||||
AND r.biz_key LIKE CONCAT('%', #{bizKey}, '%')
|
||||
</if>
|
||||
<!--
|
||||
姓名同时匹配「档案当前姓名」与「提交时的姓名」:
|
||||
司机改名后,按新名与按旧名都应该搜得到他的记录,
|
||||
只认一个会让审核员两头试。手机号则只有档案当前值一个来源。
|
||||
-->
|
||||
<if test="driverName != null and driverName != ''">
|
||||
AND (
|
||||
d.driver_name LIKE CONCAT('%', #{driverName}, '%')
|
||||
OR r.biz_key LIKE CONCAT('%', #{driverName}, '%')
|
||||
)
|
||||
</if>
|
||||
<if test="driverMobile != null and driverMobile != ''">
|
||||
AND d.mobile LIKE CONCAT('%', #{driverMobile}, '%')
|
||||
</if>
|
||||
ORDER BY
|
||||
r.submit_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
+13
-6
@@ -45,15 +45,22 @@ public interface ICertAuditService {
|
||||
* <p>
|
||||
* 审核页按「待审核 / 已通过 / 已驳回」分页签展示,故状态可传;
|
||||
* 不传时按待审核处理,兼容只想要待审队列的调用方。
|
||||
* <p>
|
||||
* 列表同时下发该记录的司机姓名与手机号,取的是司机档案的**当前**值
|
||||
* (认证记录里的姓名是提交时的历史值,手机号则从来不在快照里)。
|
||||
* 解析不到司机档案的记录,两个字段为空,不回落成提交时的值——
|
||||
* 那会让「同一个人的多条记录显示成不同的人」。
|
||||
*
|
||||
* @param bizType 认证对象类型:driver-司机,vehicle-车辆
|
||||
* @param bizKey 展示键模糊匹配,可为空
|
||||
* @param status 审核状态:0审核中 1已通过 2已驳回 3已作废;为空时取 0(审核中)
|
||||
* @param page 分页参数(由 Controller 用 Condition.getPage 构造)
|
||||
* @param bizType 认证对象类型:driver-司机,vehicle-车辆
|
||||
* @param bizKey 展示键模糊匹配(提交时的司机姓名 / 车牌号),可为空
|
||||
* @param driverName 司机姓名模糊匹配(当前姓名,并兼顾提交时的旧名),可为空
|
||||
* @param driverMobile 司机手机号模糊匹配,可为空
|
||||
* @param status 审核状态:0审核中 1已通过 2已驳回 3已作废;为空时取 0(审核中)
|
||||
* @param page 分页参数(由 Controller 用 Condition.getPage 构造)
|
||||
* @return 认证记录分页
|
||||
*/
|
||||
IPage<TransportCertRecordVO> pendingPage(String bizType, String bizKey, Integer status,
|
||||
IPage<TransportCertRecord> page);
|
||||
IPage<TransportCertRecordVO> pendingPage(String bizType, String bizKey, String driverName, String driverMobile,
|
||||
Integer status, IPage<TransportCertRecordVO> page);
|
||||
|
||||
/**
|
||||
* 审核详情:同时返回待审快照与当前生效值,供前端做 diff
|
||||
|
||||
+29
-10
@@ -25,14 +25,13 @@
|
||||
*/
|
||||
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 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.mapper.TransportCertRecordMapper;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||
import org.springblade.transport.pojo.entity.TransportVehicle;
|
||||
@@ -48,6 +47,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 认证审核服务实现
|
||||
@@ -66,24 +66,43 @@ import java.time.LocalDateTime;
|
||||
public class CertAuditServiceImpl implements ICertAuditService {
|
||||
|
||||
private final ITransportCertRecordService certRecordService;
|
||||
private final TransportCertRecordMapper certRecordMapper;
|
||||
private final ITransportVehicleService transportVehicleService;
|
||||
private final IDriverService driverService;
|
||||
|
||||
@Override
|
||||
public IPage<TransportCertRecordVO> pendingPage(String bizType, String bizKey, Integer status,
|
||||
IPage<TransportCertRecord> page) {
|
||||
public IPage<TransportCertRecordVO> pendingPage(String bizType, String bizKey, String driverName, String driverMobile,
|
||||
Integer status, IPage<TransportCertRecordVO> page) {
|
||||
if (!CertificationRuleSupport.isValidBizType(bizType)) {
|
||||
throw new ServiceException("认证对象类型不正确");
|
||||
}
|
||||
// 未传状态时按「审核中」处理:审核页默认停在待审页签,
|
||||
// 只关心待审队列的调用方也不必额外传参
|
||||
Integer queryStatus = status == null ? TransportCertRecord.STATUS_PENDING : status;
|
||||
LambdaQueryWrapper<TransportCertRecord> queryWrapper = Wrappers.<TransportCertRecord>lambdaQuery()
|
||||
.eq(TransportCertRecord::getBizType, bizType)
|
||||
.eq(TransportCertRecord::getStatus, queryStatus)
|
||||
.like(Func.isNotEmpty(bizKey), TransportCertRecord::getBizKey, bizKey)
|
||||
.orderByDesc(TransportCertRecord::getSubmitTime);
|
||||
return TransportCertRecordWrapper.build().pageVO(certRecordService.page(page, queryWrapper));
|
||||
|
||||
// 司机姓名与手机号、以及按它们的筛选,都要在**分页之前**由 SQL 完成:
|
||||
// 在 Java 层补字段再过滤会变成「先分页再过滤」,条数与总数都不准。
|
||||
List<TransportCertRecordVO> records = certRecordMapper.selectAuditPage(page, bizType, queryStatus,
|
||||
blankToNull(bizKey), blankToNull(driverName), blankToNull(driverMobile));
|
||||
TransportCertRecordWrapper wrapper = TransportCertRecordWrapper.build();
|
||||
records.forEach(wrapper::fillDisplayFields);
|
||||
return page.setRecords(records);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索项的空串与纯空白一律当作「没填」
|
||||
* <p>
|
||||
* 否则 {@code " "} 会变成 {@code LIKE '% %'},把「没输入」当成一个真实筛选条件。
|
||||
*
|
||||
* @param value 原始搜索值
|
||||
* @return 去空白后的值;为空时返回 null
|
||||
*/
|
||||
private String blankToNull(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = value.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -352,6 +352,9 @@ public class DriverCertServiceImpl implements IDriverCertService {
|
||||
String drivingVehicle = Func.isEmpty(driver.getDrivingVehicle())
|
||||
? normalizedPlate
|
||||
: driver.getDrivingVehicle() + "," + normalizedPlate;
|
||||
// 「最多 5 辆 + 车牌格式」在写入前校验:字段只有 varchar(100),
|
||||
// 超限内容一旦写进去要么报错、要么被静默截断成半个车牌(还会被当成一辆车)
|
||||
VehiclePlateSupport.validateBoundVehicles(drivingVehicle);
|
||||
Driver patch = new Driver();
|
||||
patch.setId(driver.getId());
|
||||
patch.setDrivingVehicle(drivingVehicle);
|
||||
|
||||
+7
-12
@@ -43,12 +43,12 @@ import org.springblade.system.pojo.enums.UserSex;
|
||||
import org.springblade.system.pojo.enums.UserType;
|
||||
import org.springblade.transport.excel.DriverExcel;
|
||||
import org.springblade.transport.mapper.DriverMapper;
|
||||
import org.springblade.transport.mapper.TransportVehicleMapper;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
import org.springblade.transport.pojo.entity.TransportVehicle;
|
||||
import org.springblade.transport.pojo.vo.DriverExpiryStatVO;
|
||||
import org.springblade.transport.pojo.vo.DriverVO;
|
||||
import org.springblade.transport.service.IDriverService;
|
||||
import org.springblade.transport.support.VehiclePlateSupport;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -68,14 +68,13 @@ public class DriverServiceImpl extends BaseServiceImpl<DriverMapper, Driver> imp
|
||||
private static final int NAME_MAX_LENGTH = 10;
|
||||
private static final int ID_CARD_MAX_LENGTH = 18;
|
||||
private static final int MOBILE_MAX_LENGTH = 20;
|
||||
private static final int VEHICLE_NO_MAX_LENGTH = 30;
|
||||
private static final int VEHICLE_NO_MAX_LENGTH = 100;
|
||||
private static final int SHORT_TEXT_MAX_LENGTH = 50;
|
||||
private static final int ADDRESS_MAX_LENGTH = 200;
|
||||
private static final int REMARK_MAX_LENGTH = 200;
|
||||
private static final int DEFAULT_ENABLED_STATUS = 1;
|
||||
private static final int DEFAULT_FALSE = 0;
|
||||
|
||||
private final TransportVehicleMapper transportVehicleMapper;
|
||||
private final IUserClient userClient;
|
||||
private final ISysClient sysClient;
|
||||
|
||||
@@ -250,15 +249,11 @@ public class DriverServiceImpl extends BaseServiceImpl<DriverMapper, Driver> imp
|
||||
validateLength(driver.getIdCardNo(), ID_CARD_MAX_LENGTH, "身份证号不能超过18字");
|
||||
validateLength(driver.getMobile(), MOBILE_MAX_LENGTH, "手机号不能超过20字");
|
||||
validateLength(driver.getEmergencyContactMobile(), MOBILE_MAX_LENGTH, "紧急联系人手机号不能超过20字");
|
||||
validateLength(driver.getDrivingVehicle(), VEHICLE_NO_MAX_LENGTH, "驾驶车辆不能超过30字");
|
||||
if (Func.isNotEmpty(driver.getDrivingVehicle())) {
|
||||
Long vehicleCount = transportVehicleMapper.selectCount(Wrappers.<TransportVehicle>lambdaQuery()
|
||||
.eq(TransportVehicle::getIsDeleted, 0)
|
||||
.eq(TransportVehicle::getPlateNo, driver.getDrivingVehicle()));
|
||||
if (vehicleCount == null || vehicleCount <= 0) {
|
||||
throw new ServiceException("驾驶车辆不存在,请重新选择");
|
||||
}
|
||||
}
|
||||
validateLength(driver.getDrivingVehicle(), VEHICLE_NO_MAX_LENGTH, "驾驶车辆不能超过100字");
|
||||
// 「驾驶车辆」是**逗号并列的多辆车**(小程序每绑一辆就追加一个车牌),
|
||||
// 不是一个车牌。此前这里拿整串去车牌表精确查,绑了两辆车的司机
|
||||
// 在司机管理页一保存就会撞上「驾驶车辆不存在」——校验口径必须与写入方一致。
|
||||
VehiclePlateSupport.validateBoundVehicles(driver.getDrivingVehicle());
|
||||
validateLength(driver.getDrivingLicenseNo(), SHORT_TEXT_MAX_LENGTH, "驾驶证档案编号不能超过50字");
|
||||
validateLength(driver.getQualificationNo(), SHORT_TEXT_MAX_LENGTH, "资格证号不能超过50字");
|
||||
validateLength(driver.getOrganizationName(), SHORT_TEXT_MAX_LENGTH, "所属组织不能超过50字");
|
||||
|
||||
+2
-3
@@ -41,6 +41,7 @@ import org.springblade.transport.pojo.entity.TransportVehicle;
|
||||
import org.springblade.transport.pojo.vo.TransportVehicleExpiryStatVO;
|
||||
import org.springblade.transport.pojo.vo.TransportVehicleVO;
|
||||
import org.springblade.transport.service.ITransportVehicleService;
|
||||
import org.springblade.transport.support.VehiclePlateSupport;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -50,7 +51,6 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -62,7 +62,6 @@ import java.util.stream.Collectors;
|
||||
@RequiredArgsConstructor
|
||||
public class TransportVehicleServiceImpl extends BaseServiceImpl<TransportVehicleMapper, TransportVehicle> implements ITransportVehicleService {
|
||||
|
||||
private static final Pattern PLATE_NO_PATTERN = Pattern.compile("^[\\u4e00-\\u9fa5][A-Z][A-Z0-9挂学警港澳]{5,6}$");
|
||||
private static final String PLATE_COLOR_BLUE = "蓝牌";
|
||||
private static final String PLATE_COLOR_YELLOW = "黄牌";
|
||||
private static final String PLATE_COLOR_GREEN = "绿牌";
|
||||
@@ -262,7 +261,7 @@ public class TransportVehicleServiceImpl extends BaseServiceImpl<TransportVehicl
|
||||
if (Func.isEmpty(vehicle.getPlateNo())) {
|
||||
throw new ServiceException("车牌号不能为空");
|
||||
}
|
||||
if (!PLATE_NO_PATTERN.matcher(vehicle.getPlateNo()).matches()) {
|
||||
if (!VehiclePlateSupport.isValidPlateNo(vehicle.getPlateNo())) {
|
||||
throw new ServiceException("车牌号格式不正确");
|
||||
}
|
||||
if (Func.isEmpty(vehicle.getPlateColor())) {
|
||||
|
||||
+54
@@ -26,6 +26,7 @@
|
||||
package org.springblade.transport.support;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.transport.pojo.entity.TransportVehicle;
|
||||
|
||||
@@ -34,6 +35,7 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 司机档案与车辆表之间的车牌软关联支持类
|
||||
@@ -112,6 +114,58 @@ public final class VehiclePlateSupport {
|
||||
return splitPlates(drivingVehicle).contains(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个司机最多能绑定的车辆数
|
||||
* <p>
|
||||
* 与小程序端「一个司机最多添加 5 辆」的上限一致。后端此前完全没有这个约束,
|
||||
* 而 {@code driving_vehicle} 只有 varchar(30)——5 个车牌连分隔符要 39 字符,
|
||||
* 第 4 辆起就会写不进去或被静默截断(截断后剩下半个车牌,还会被当成一辆车)。
|
||||
*/
|
||||
public static final int MAX_BOUND_VEHICLES = 5;
|
||||
|
||||
/**
|
||||
* 车牌格式:中文省份简称 + 字母 + 5~6 位(含挂/学/警/港/澳等特殊字)
|
||||
* <p>
|
||||
* 与车辆建档时的车牌校验共用同一条规则:能绑到司机名下的车牌,
|
||||
* 必须先能作为一辆车建档,否则两处口径分叉,会出现「绑得上但建不了档」。
|
||||
* 传入值必须是 {@link #normalize} 之后的形态(已去排版字符并转大写)。
|
||||
*/
|
||||
private static final Pattern PLATE_NO_PATTERN = Pattern.compile("^[\\u4e00-\\u9fa5][A-Z][A-Z0-9挂学警港澳]{5,6}$");
|
||||
|
||||
/**
|
||||
* 车牌格式是否合法
|
||||
*
|
||||
* @param plateNo 车牌(会先做规范化)
|
||||
* @return 是否合法
|
||||
*/
|
||||
public static boolean isValidPlateNo(String plateNo) {
|
||||
String normalized = normalize(plateNo);
|
||||
return Func.isNotEmpty(normalized) && PLATE_NO_PATTERN.matcher(normalized).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验「驾驶车辆」字段:车辆数上限与逐个车牌的格式
|
||||
* <p>
|
||||
* 规则只在这里维护一处——小程序新增车辆、管理端新增/编辑司机都调它。
|
||||
* 此前管理端把这个字段当作**单个**车牌校验(拿整串去车牌表里精确查),
|
||||
* 而小程序写入的是逗号并列的多辆车,两边口径本就不一致:
|
||||
* 一个绑了两辆车的司机,管理员在司机管理页一保存就会撞上「驾驶车辆不存在」。
|
||||
*
|
||||
* @param drivingVehicle 司机档案里的驾驶车辆字段,可并列多辆
|
||||
* @throws ServiceException 车辆数超过上限,或存在格式非法的车牌
|
||||
*/
|
||||
public static void validateBoundVehicles(String drivingVehicle) {
|
||||
List<String> plates = splitPlates(drivingVehicle);
|
||||
if (plates.size() > MAX_BOUND_VEHICLES) {
|
||||
throw new ServiceException("一个司机最多绑定" + MAX_BOUND_VEHICLES + "辆车");
|
||||
}
|
||||
for (String plate : plates) {
|
||||
if (!PLATE_NO_PATTERN.matcher(plate).matches()) {
|
||||
throw new ServiceException("车牌号格式不正确:" + plate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按车牌匹配车辆的 SQL 条件:精确匹配 **或** 规范化后匹配
|
||||
* <p>
|
||||
|
||||
+19
-5
@@ -48,12 +48,26 @@ public class TransportCertRecordWrapper extends BaseEntityWrapper<TransportCertR
|
||||
@Override
|
||||
public TransportCertRecordVO entityVO(TransportCertRecord transportCertRecord) {
|
||||
TransportCertRecordVO transportCertRecordVO = Objects.requireNonNull(BeanUtil.copyProperties(transportCertRecord, TransportCertRecordVO.class));
|
||||
transportCertRecordVO.setCreateUserName(UserCache.getUserRealName(transportCertRecord.getCreateUser()));
|
||||
transportCertRecordVO.setUpdateUserName(UserCache.getUserRealName(transportCertRecord.getUpdateUser()));
|
||||
transportCertRecordVO.setSubmitUserName(UserCache.getUserRealName(transportCertRecord.getSubmitUser()));
|
||||
transportCertRecordVO.setAuditUserName(UserCache.getUserRealName(transportCertRecord.getAuditUser()));
|
||||
return fillDisplayFields(transportCertRecordVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充面向前端的展示字段:四个姓名与状态文案
|
||||
* <p>
|
||||
* 审核列表走的是自定义 SQL,查出来已经是 VO——它多带司机姓名与手机号两个字段,
|
||||
* 这两个字段实体上没有、无法由实体转换得到,所以那条路径不能走 {@link #entityVO}。
|
||||
* 把填充逻辑单独暴露一次,避免 Service 里再复制一份翻译规则,日后两处走偏。
|
||||
*
|
||||
* @param transportCertRecordVO 认证记录视图对象
|
||||
* @return 同一个对象,便于链式调用
|
||||
*/
|
||||
public TransportCertRecordVO fillDisplayFields(TransportCertRecordVO transportCertRecordVO) {
|
||||
transportCertRecordVO.setCreateUserName(UserCache.getUserRealName(transportCertRecordVO.getCreateUser()));
|
||||
transportCertRecordVO.setUpdateUserName(UserCache.getUserRealName(transportCertRecordVO.getUpdateUser()));
|
||||
transportCertRecordVO.setSubmitUserName(UserCache.getUserRealName(transportCertRecordVO.getSubmitUser()));
|
||||
transportCertRecordVO.setAuditUserName(UserCache.getUserRealName(transportCertRecordVO.getAuditUser()));
|
||||
// 状态文案统一在此翻译,避免小程序、Web、接口各处各造一套说法
|
||||
transportCertRecordVO.setStatusText(CertificationRuleSupport.statusText(transportCertRecord.getStatus()));
|
||||
transportCertRecordVO.setStatusText(CertificationRuleSupport.statusText(transportCertRecordVO.getStatus()));
|
||||
return transportCertRecordVO;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user