🐛 审核通过与重新提交时清空档案上的旧驳回原因
司机/车辆档案上的 certification_reject_reason 是展示缓存,没有驳回原因时应为 NULL(建表即 DEFAULT NULL,数据订正脚本也写 NULL)。但框架的 not_null 更新策略会跳过 patch 里的 null 字段,把 null 交给 updateById 到不了 SQL,于是两处都留下了陈旧文案:审核通过后状态已是「已通过」,档案上却还挂着上一次的驳回原因;被驳回后重新提交,旧原因会一直挂到审核通过,而提交侧本就写明「新提交不能继承上一次的驳回文案」。现在这两条路径各显式发一条只改该列的语句,带主键条件,不碰生效快照指针、不碰车辆绑定,驳回时仍照常写入原因。改用 ALWAYS 更新策略会让改手机号、绑定车辆这类无关操作也清掉该列,故不做。
This commit is contained in:
+42
-5
@@ -191,7 +191,6 @@ public class CertAuditServiceImpl implements ICertAuditService {
|
||||
private void updateCertTarget(TransportCertRecord record, boolean pass, String rejectReason) {
|
||||
boolean vehicleTarget = TransportCertRecord.BIZ_TYPE_VEHICLE.equals(record.getBizType());
|
||||
Integer status = pass ? TransportCertRecord.STATUS_APPROVED : TransportCertRecord.STATUS_REJECTED;
|
||||
String reason = pass ? null : CertificationRuleSupport.reasonForReject(rejectReason);
|
||||
// 只有通过才切指针;驳回时保持 null,框架的 not_null 更新策略会跳过该列
|
||||
Long newEffectiveId = pass ? record.getId() : null;
|
||||
|
||||
@@ -199,22 +198,25 @@ public class CertAuditServiceImpl implements ICertAuditService {
|
||||
TransportVehicle vehicle = new TransportVehicle();
|
||||
vehicle.setId(record.getBizId());
|
||||
vehicle.setCertificationStatus(status);
|
||||
vehicle.setCertificationRejectReason(reason);
|
||||
if (pass) {
|
||||
vehicle.setEffectiveCertRecordId(newEffectiveId);
|
||||
// 通过即生效:把这次快照的证件内容一并写回车辆档案,让「车辆档案 = 生效值」成立。
|
||||
// 不写回的话,审核通过之后管理端与司机端看到的仍是提交之前的旧值——
|
||||
// 状态显示「已通过」、内容却还是旧的,是最容易让人误判的一种不一致。
|
||||
// 与指针合并成同一次 UPDATE,不额外多写一次同一行。
|
||||
// 与指针合并成同一次 UPDATE。
|
||||
applySnapshotToVehicle(vehicle, record.getSnapshotJson());
|
||||
} else {
|
||||
vehicle.setCertificationRejectReason(CertificationRuleSupport.reasonForReject(rejectReason));
|
||||
}
|
||||
requireTargetUpdated(transportVehicleService.updateById(vehicle), "车辆", record.getBizId());
|
||||
if (pass) {
|
||||
clearVehicleRejectReason(record.getBizId());
|
||||
}
|
||||
return;
|
||||
}
|
||||
Driver driver = new Driver();
|
||||
driver.setId(record.getBizId());
|
||||
driver.setCertificationStatus(status);
|
||||
driver.setCertificationRejectReason(reason);
|
||||
// 必须带上 driving_vehicle 原值:该列是 FieldStrategy.ALWAYS,
|
||||
// 更新时忽略 null 直接写,patch 里不带它就会把司机的车辆绑定清成 NULL,
|
||||
// 而车辆列表正是按这一列匹配的(清空即「暂无绑定车辆」)。
|
||||
@@ -224,10 +226,45 @@ public class CertAuditServiceImpl implements ICertAuditService {
|
||||
// 通过即生效:与车辆侧同理,把这次快照的证件内容一并写回司机档案。
|
||||
// 不写回的话,司机档案里始终是管理端当初录入的旧值(甚至为空),
|
||||
// 而认证状态已是「已通过」——状态与内容不一致最容易让人误判。
|
||||
// 与指针合并成同一次 UPDATE,不额外多写一次同一行。
|
||||
// 与指针合并成同一次 UPDATE。
|
||||
applySnapshotToDriver(driver, record.getSnapshotJson());
|
||||
} else {
|
||||
driver.setCertificationRejectReason(CertificationRuleSupport.reasonForReject(rejectReason));
|
||||
}
|
||||
requireTargetUpdated(driverService.updateById(driver), "司机", record.getBizId());
|
||||
if (pass) {
|
||||
clearDriverRejectReason(record.getBizId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空车辆档案上的驳回原因展示缓存
|
||||
* <p>
|
||||
* 该列没有驳回原因时必须是 NULL(建表即 DEFAULT NULL)。但框架的 not_null 更新策略
|
||||
* 会跳过 patch 里的 null 字段,交给 updateById 的 null 到不了 SQL,
|
||||
* 「已通过」的车辆会一直挂着上一次的驳回文案,故单独发一条只改该列的语句。
|
||||
* 它不碰生效快照指针。
|
||||
*
|
||||
* @param vehicleId 车辆主键
|
||||
*/
|
||||
private void clearVehicleRejectReason(Long vehicleId) {
|
||||
transportVehicleService.update(Wrappers.<TransportVehicle>lambdaUpdate()
|
||||
.eq(TransportVehicle::getId, vehicleId)
|
||||
.set(TransportVehicle::getCertificationRejectReason, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空司机档案上的驳回原因展示缓存
|
||||
* <p>
|
||||
* 与车辆侧同理:null 交给 updateById 到不了 SQL,只能单独发一条只改该列的语句。
|
||||
* 它不碰生效快照指针,也不碰车辆绑定。
|
||||
*
|
||||
* @param driverId 司机主键
|
||||
*/
|
||||
private void clearDriverRejectReason(Long driverId) {
|
||||
driverService.update(Wrappers.<Driver>lambdaUpdate()
|
||||
.eq(Driver::getId, driverId)
|
||||
.set(Driver::getCertificationRejectReason, null));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+13
@@ -376,6 +376,13 @@ public class DriverCertServiceImpl implements IDriverCertService {
|
||||
patch.setDrivingVehicle(current.getDrivingVehicle());
|
||||
}
|
||||
driverService.updateById(patch);
|
||||
if (Func.isBlank(rejectReason)) {
|
||||
// 重新提交时驳回原因应为空,但框架的 not_null 更新策略会跳过 patch 里的 null,
|
||||
// 只写 null 到不了 SQL,旧驳回文案会一直挂到审核通过,故单独清一次
|
||||
driverService.update(Wrappers.<Driver>lambdaUpdate()
|
||||
.eq(Driver::getId, driverId)
|
||||
.set(Driver::getCertificationRejectReason, null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,6 +396,12 @@ public class DriverCertServiceImpl implements IDriverCertService {
|
||||
patch.setCertificationStatus(status);
|
||||
patch.setCertificationRejectReason(rejectReason);
|
||||
transportVehicleService.updateById(patch);
|
||||
if (Func.isBlank(rejectReason)) {
|
||||
// 与司机侧同理:null 交给 updateById 到不了 SQL,旧驳回文案会一直挂着
|
||||
transportVehicleService.update(Wrappers.<TransportVehicle>lambdaUpdate()
|
||||
.eq(TransportVehicle::getId, vehicleId)
|
||||
.set(TransportVehicle::getCertificationRejectReason, null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+86
@@ -1,5 +1,11 @@
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -14,7 +20,10 @@ import org.springblade.transport.service.IDriverService;
|
||||
import org.springblade.transport.service.ITransportCertRecordService;
|
||||
import org.springblade.transport.service.ITransportVehicleService;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
@@ -43,6 +52,16 @@ class CertAuditServiceImplTest {
|
||||
private IDriverService driverService;
|
||||
private CertAuditServiceImpl service;
|
||||
|
||||
@BeforeAll
|
||||
static void initTableInfo() {
|
||||
// 构造 LambdaUpdateWrapper 需要 MyBatis-Plus 的实体元数据缓存
|
||||
MybatisConfiguration configuration = new MybatisConfiguration();
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, "");
|
||||
assistant.setCurrentNamespace("test");
|
||||
TableInfoHelper.initTableInfo(assistant, Driver.class);
|
||||
TableInfoHelper.initTableInfo(assistant, TransportVehicle.class);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
certRecordService = mock(ITransportCertRecordService.class);
|
||||
@@ -330,6 +349,73 @@ class CertAuditServiceImplTest {
|
||||
assertNull(detail.getEffectiveRecordId());
|
||||
}
|
||||
|
||||
// -------------------------------------------- 驳回原因的展示缓存
|
||||
|
||||
@Test
|
||||
@DisplayName("通过车辆认证:显式清空档案上的驳回原因(not_null 策略会让 null 到不了 SQL)")
|
||||
@SuppressWarnings("unchecked")
|
||||
void approveClearsStaleVehicleRejectReason() {
|
||||
TransportCertRecord record = vehicleRecord(TransportCertRecord.STATUS_PENDING);
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(null));
|
||||
|
||||
service.audit(RECORD_ID, true, null);
|
||||
|
||||
ArgumentCaptor<Wrapper<TransportVehicle>> captor = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(transportVehicleService).update(captor.capture());
|
||||
LambdaUpdateWrapper<TransportVehicle> wrapper =
|
||||
(LambdaUpdateWrapper<TransportVehicle>) captor.getValue();
|
||||
assertTrue(wrapper.getSqlSet().contains("certification_reject_reason"),
|
||||
"通过时必须显式清空驳回原因,实际: " + wrapper.getSqlSet());
|
||||
Collection<Object> values = wrapper.getParamNameValuePairs().values();
|
||||
assertTrue(values.contains(null), "清空必须显式写 null(该列无原因时的取值)");
|
||||
assertTrue(wrapper.getSqlSegment().contains("id =")
|
||||
&& values.stream().anyMatch(v -> v instanceof Number
|
||||
&& ((Number) v).longValue() == VEHICLE_ID),
|
||||
"必须带主键条件且落在被审核的那一行,否则会更新整张表,实际: " + wrapper.getSqlSegment());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("通过司机认证:同样显式清空驳回原因,且不顺带动车辆绑定")
|
||||
@SuppressWarnings("unchecked")
|
||||
void approveClearsStaleDriverRejectReason() {
|
||||
TransportCertRecord record = driverRecord(TransportCertRecord.STATUS_PENDING);
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
when(driverService.getById(DRIVER_ID)).thenReturn(driver(null));
|
||||
|
||||
service.audit(RECORD_ID, true, null);
|
||||
|
||||
ArgumentCaptor<Wrapper<Driver>> captor = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(driverService).update(captor.capture());
|
||||
LambdaUpdateWrapper<Driver> wrapper = (LambdaUpdateWrapper<Driver>) captor.getValue();
|
||||
assertTrue(wrapper.getSqlSet().contains("certification_reject_reason"),
|
||||
"通过时必须显式清空驳回原因,实际: " + wrapper.getSqlSet());
|
||||
Collection<Object> values = wrapper.getParamNameValuePairs().values();
|
||||
assertTrue(values.contains(null));
|
||||
assertTrue(wrapper.getSqlSegment().contains("id =")
|
||||
&& values.stream().anyMatch(v -> v instanceof Number
|
||||
&& ((Number) v).longValue() == DRIVER_ID),
|
||||
"必须带主键条件且落在被审核的那一行,否则会更新整张表,实际: " + wrapper.getSqlSegment());
|
||||
assertFalse(wrapper.getSqlSet().contains("driving_vehicle"),
|
||||
"清空驳回原因不得顺带动车辆绑定");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("驳回:原因去空白后写入档案,且不发清空语句")
|
||||
void rejectWritesReasonAndDoesNotClear() {
|
||||
TransportCertRecord record = vehicleRecord(TransportCertRecord.STATUS_PENDING);
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(777L));
|
||||
|
||||
service.audit(RECORD_ID, false, " 行驶证模糊 ");
|
||||
|
||||
ArgumentCaptor<TransportVehicle> captor = ArgumentCaptor.forClass(TransportVehicle.class);
|
||||
verify(transportVehicleService).updateById(captor.capture());
|
||||
assertEquals("行驶证模糊", captor.getValue().getCertificationRejectReason(),
|
||||
"驳回原因去首尾空白后落库");
|
||||
verify(transportVehicleService, never()).update(any());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 辅助
|
||||
|
||||
private TransportCertRecord vehicleRecord(Integer status) {
|
||||
|
||||
+43
@@ -1,7 +1,13 @@
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -18,6 +24,7 @@ import org.springblade.transport.service.ITransportCertRecordService;
|
||||
import org.springblade.transport.service.ITransportVehicleService;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -53,6 +60,16 @@ class DriverCertServiceImplTest {
|
||||
private ITransportCertRecordService certRecordService;
|
||||
private DriverCertServiceImpl service;
|
||||
|
||||
@BeforeAll
|
||||
static void initTableInfo() {
|
||||
// 构造 LambdaUpdateWrapper 需要 MyBatis-Plus 的实体元数据缓存
|
||||
MybatisConfiguration configuration = new MybatisConfiguration();
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, "");
|
||||
assistant.setCurrentNamespace("test");
|
||||
TableInfoHelper.initTableInfo(assistant, Driver.class);
|
||||
TableInfoHelper.initTableInfo(assistant, TransportVehicle.class);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
driverAppService = mock(IDriverAppService.class);
|
||||
@@ -279,6 +296,32 @@ class DriverCertServiceImplTest {
|
||||
"提交链路不得写生效快照指针");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("个人提交:清空上一次的驳回原因(null 交给 updateById 到不了 SQL)")
|
||||
@SuppressWarnings("unchecked")
|
||||
void submitPersonCertClearsStaleRejectReason() {
|
||||
when(driverAppService.currentDriverEntity()).thenReturn(driver(PLATE));
|
||||
when(certRecordService.selectPendingRecord(any(), anyLong())).thenReturn(null);
|
||||
|
||||
PersonCertSubmitRequest request = new PersonCertSubmitRequest();
|
||||
request.setRealName("张三");
|
||||
request.setIdCardNo("110101199001011234");
|
||||
|
||||
service.submitPersonCert(request);
|
||||
|
||||
ArgumentCaptor<Wrapper<Driver>> captor = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(driverService).update(captor.capture());
|
||||
LambdaUpdateWrapper<Driver> wrapper = (LambdaUpdateWrapper<Driver>) captor.getValue();
|
||||
assertTrue(wrapper.getSqlSet().contains("certification_reject_reason"),
|
||||
"重新提交时旧驳回原因必须清掉,实际: " + wrapper.getSqlSet());
|
||||
Collection<Object> values = wrapper.getParamNameValuePairs().values();
|
||||
assertTrue(values.contains(null), "清空必须显式写 null");
|
||||
assertTrue(wrapper.getSqlSegment().contains("id =")
|
||||
&& values.stream().anyMatch(v -> v instanceof Number
|
||||
&& ((Number) v).longValue() == DRIVER_ID),
|
||||
"必须带主键条件且落在被审核的那一行,否则会更新整张表,实际: " + wrapper.getSqlSegment());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("个人提交:内容为空必须拒绝")
|
||||
void submitPersonCertRejectsEmpty() {
|
||||
|
||||
+13
@@ -1,6 +1,10 @@
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -53,6 +57,15 @@ class PersonCertRoundTripTest {
|
||||
private Driver driver;
|
||||
private long idSequence = 100L;
|
||||
|
||||
@BeforeAll
|
||||
static void initTableInfo() {
|
||||
// 构造 LambdaUpdateWrapper 需要 MyBatis-Plus 的实体元数据缓存
|
||||
MybatisConfiguration configuration = new MybatisConfiguration();
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, "");
|
||||
assistant.setCurrentNamespace("test");
|
||||
TableInfoHelper.initTableInfo(assistant, Driver.class);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
recordStore.clear();
|
||||
|
||||
Reference in New Issue
Block a user