From 751b246d8ebcdc5a5837cd8bb74dc905efde4ef3 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Sat, 17 Oct 2026 18:34:30 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20=E8=BF=90=E6=AC=A1=E7=BA=A7=E6=8E=A5?= =?UTF-8?q?=E5=8D=95=E4=B8=8E=E6=8B=92=E5=8D=95=E8=A1=A5=E4=B8=8A=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=97=95=E8=BF=B9=EF=BC=8C=E4=B8=8E=E5=8D=95=E7=A5=A8?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 单票接单/拒单走 updateById,写前会把 update_time 刷成提交时刻、 update_user 填成当前用户(无登录态不填);运次级原来用 wrapper 一次写完, 这两列保持旧值,同一动作的两条路径在库里留下不同痕迹。 现显式补上同样两列,时刻取与接单/拒单时刻同一个 Date。 --- .../impl/DriverWaybillServiceImpl.java | 20 ++++- .../impl/DriverTripAcceptRejectTest.java | 75 ++++++++++++++++++- 2 files changed, 92 insertions(+), 3 deletions(-) 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 3c598aa2..29ea160f 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 @@ -28,6 +28,7 @@ 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.BladeUser; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.utils.DateUtil; @@ -651,7 +652,11 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { List children = tripChildren(tripId); assertTripAcceptable(children); List pendingIds = writeCandidateIds(children, "当前运单状态不允许接单"); + // 更新痕迹与单票接单一致:单票走 updateById,写前会把 update_time 刷成提交时刻、 + // 把 update_user 填成当前用户(无登录态则不填);这里一次写完,显式补上同样两列, + // 时刻取与接单时刻同一个 now,操作人取同一个登录用户 Date now = new Date(); + Long operatorId = currentOperatorId(); return waybillService.update(Wrappers.lambdaUpdate() .set(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_ACCEPTED) .set(Waybill::getDriverAcceptTime, now) @@ -659,6 +664,8 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { .set(Waybill::getDriverRejectTime, null) .set(Waybill::getDriverRejectReason, null) .set(Waybill::getBusinessStatus, STATUS_RUNNING) + .set(Waybill::getUpdateTime, now) + .set(operatorId != null, Waybill::getUpdateUser, operatorId) .in(Waybill::getId, pendingIds)); } @@ -673,8 +680,9 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { throw new ServiceException("拒绝原因不能超过200字"); } List pendingIds = writeCandidateIds(children, "当前运单状态不允许拒绝接单"); + // 整单拒绝落在每张未终态子运单上,一次一条语句写完,拒单时刻与更新痕迹全趟车一致 Date now = new Date(); - // 整单拒绝落在每张未终态子运单上,一次一条语句写完,接单时刻与拒单时刻全趟车一致 + Long operatorId = currentOperatorId(); return waybillService.update(Wrappers.lambdaUpdate() .set(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED) .set(Waybill::getDriverAcceptTime, null) @@ -682,9 +690,19 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { .set(Waybill::getDriverRejectTime, now) .set(Waybill::getDriverRejectReason, rejectReason) .set(Waybill::getBusinessStatus, STATUS_PENDING) + .set(Waybill::getUpdateTime, now) + .set(operatorId != null, Waybill::getUpdateUser, operatorId) .in(Waybill::getId, pendingIds)); } + /** + * 当前登录用户主键:与框架写入时填充更新人的口径一致——没有登录态就不填 + */ + private Long currentOperatorId() { + BladeUser operator = AuthUtil.getUser(); + return operator == null ? null : operator.getUserId(); + } + /** * 运次 → 子运单集合:配载的运次走运次读服务解析(配载单主键命中后按配载单号取子运单), * 未配载的就是那张运单本身。收口与列表一致——别人的运次在这里已经被挡住。 diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripAcceptRejectTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripAcceptRejectTest.java index 588e05fe..24d1d635 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripAcceptRejectTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripAcceptRejectTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.MockedStatic; import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.secure.BladeUser; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.system.feign.IMpUserClient; import org.springblade.transport.mapper.ExceptionDisposalMapper; @@ -29,11 +30,13 @@ import org.springblade.transport.service.IWaybillService; import org.springblade.transport.support.CertificationGate; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -110,7 +113,9 @@ class DriverTripAcceptRejectTest { "driver_accept_driver_id", "driver_reject_time", "driver_reject_reason", - "business_status"), updatedColumns(), "只写接单相关的列,司机字段一概不动"); + "business_status", + "update_time", + "update_user"), updatedColumns(), "只写接单相关的列与刷新痕迹,司机字段一概不动"); assertTrue(capturedUpdateParams().containsAll(List.of(PENDING_ID, SECOND_ID, DRIVER_ID)), "两张子运单与接单司机一起下发:" + capturedUpdateParams()); assertTrue(capturedWhereSql().contains("(id IN ("), capturedWhereSql()); @@ -118,6 +123,49 @@ class DriverTripAcceptRejectTest { assertEquals(PHONE, capturedQuery().getDriverPhone(), "归属仍按司机手机号收口"); } + @Test + @DisplayName("运次接单的更新时间与接单时刻是同一个 now、更新人是当前操作人") + void tripAcceptRefreshesUpdateTrace() { + stubTrip(requireAcceptChild(PENDING_ID, "pending", "pending")); + + runAsDriver(() -> service.tripAccept(TRIP_ID)); + + List dates = capturedUpdateDates(); + assertEquals(2, dates.size(), "接单时刻与更新时间各占一列"); + assertSame(dates.get(0), dates.get(1), "update_time 与接单时刻必须是同一个 now"); + assertTrue(capturedUpdateParams().contains(USER_ID), "update_user 就是当前操作人:" + capturedUpdateParams()); + } + + @Test + @DisplayName("没有登录态时不写 update_user,但 update_time 仍随操作刷新") + void tripAcceptWithoutLoginSkipsUpdateUser() { + stubTrip(requireAcceptChild(PENDING_ID, "pending", "pending")); + + runWithoutLoginUser(() -> service.tripAccept(TRIP_ID)); + + assertEquals(List.of( + "driver_accept_status", + "driver_accept_time", + "driver_accept_driver_id", + "driver_reject_time", + "driver_reject_reason", + "business_status", + "update_time"), updatedColumns(), "无登录态时与框架填充口径一致:只刷 update_time"); + } + + @Test + @DisplayName("运次拒单的更新时间与拒单时刻是同一个 now、更新人是当前操作人") + void tripRejectRefreshesUpdateTrace() { + stubTrip(requireAcceptChild(PENDING_ID, "pending", "pending")); + + runAsDriver(() -> service.tripReject(TRIP_ID, "不要了")); + + List dates = capturedUpdateDates(); + assertEquals(2, dates.size(), "拒单时刻与更新时间各占一列"); + assertSame(dates.get(0), dates.get(1), "update_time 与拒单时刻必须是同一个 now"); + assertTrue(capturedUpdateParams().contains(USER_ID), "update_user 就是当前操作人:" + capturedUpdateParams()); + } + @Test @DisplayName("任一子单已接单 → 整单已接单,不再重复写") void tripAcceptRefusesSecondCall() { @@ -204,7 +252,9 @@ class DriverTripAcceptRejectTest { "driver_accept_driver_id", "driver_reject_time", "driver_reject_reason", - "business_status"), updatedColumns(), "只写拒单相关的列,司机字段一概不动"); + "business_status", + "update_time", + "update_user"), updatedColumns(), "只写拒单相关的列与刷新痕迹,司机字段一概不动"); assertTrue(capturedUpdateParams().contains("车坏了"), "拒单原因原样下发(去首尾空格):" + capturedUpdateParams()); assertTrue(capturedUpdateParams().containsAll(List.of(PENDING_ID, SECOND_ID))); } @@ -294,10 +344,27 @@ class DriverTripAcceptRejectTest { private T runAsDriver(Supplier action) { try (MockedStatic authUtil = mockStatic(AuthUtil.class)) { authUtil.when(AuthUtil::getUserId).thenReturn(USER_ID); + authUtil.when(AuthUtil::getUser).thenReturn(bladeUser()); return action.get(); } } + /** 框架填充更新人看的是 getUserId() 背后的登录用户:为空就不填,这里把这一口径钉住 */ + private T runWithoutLoginUser(Supplier action) { + try (MockedStatic authUtil = mockStatic(AuthUtil.class)) { + authUtil.when(AuthUtil::getUserId).thenReturn(USER_ID); + authUtil.when(AuthUtil::getUser).thenReturn(null); + return action.get(); + } + } + + private BladeUser bladeUser() { + BladeUser user = new BladeUser(); + user.setUserId(USER_ID); + user.setUserName("黄宇富"); + return user; + } + private void stubTrip(TripVO.TripWaybillVO... children) { stubTrip(new TripVO.TripWaybillVO[0], children); } @@ -360,6 +427,10 @@ class DriverTripAcceptRejectTest { .toList(); } + private List capturedUpdateDates() { + return capturedUpdateParams().stream().filter(Date.class::isInstance).map(Date.class::cast).toList(); + } + /** SET 与 WHERE 的参数都是渲染时才进参数表的,取值前先把两段都渲染一遍 */ private Collection capturedUpdateParams() { LambdaUpdateWrapper update = capturedUpdate();