From b7d922077de44cbe3248645842fee519c8168594 Mon Sep 17 00:00:00 2001 From: weicw <594098497@qq.com> Date: Mon, 26 Oct 2026 11:38:50 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20=E8=AE=A4=E8=AF=81=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E6=94=BE=E8=A1=8C=E5=B0=8F=E7=A8=8B=E5=BA=8F=E8=B0=83=E5=BA=A6?= =?UTF-8?q?=E7=AB=AF=E7=AE=A1=E7=90=86=E8=BA=AB=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/CertAuditServiceImpl.java | 10 ++++- .../support/CertAuditPermissions.java | 22 +++++++++-- .../impl/CertAuditServiceImplTest.java | 31 ++++++++++++++- .../service/impl/PersonCertRoundTripTest.java | 3 +- .../support/CertAuditPermissionsTest.java | 38 ++++++++++++++++++- 5 files changed, 96 insertions(+), 8 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CertAuditServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CertAuditServiceImpl.java index 112231de..a35ee3a3 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CertAuditServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CertAuditServiceImpl.java @@ -50,6 +50,7 @@ import org.springblade.transport.service.ITransportCertRecordService; import org.springblade.transport.service.ITransportVehicleService; import org.springblade.transport.support.CertAuditPermissions; import org.springblade.transport.support.CertificationRuleSupport; +import org.springblade.transport.support.MpUserDataScopeSupport; import org.springblade.transport.wrapper.TransportCertRecordWrapper; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -79,6 +80,7 @@ public class CertAuditServiceImpl implements ICertAuditService { private final ITransportVehicleService transportVehicleService; private final IDriverService driverService; private final IPermissionHandler permissionHandler; + private final MpUserDataScopeSupport mpUserDataScopeSupport; @Override public IPage pendingPage(String bizType, String bizKey, String driverName, String driverMobile, @@ -185,8 +187,14 @@ public class CertAuditServiceImpl implements ICertAuditService { @Override public CertAuditCapabilityVO capability() { - // 使用与三个审核接口 @PreAuth 相同的菜单码常量;表达式由框架求值,此处直接查菜单。 + // 与三个审核接口 @PreAuth 同一口径:小程序调度端管理身份(token roleName=admin)查不到菜单码,直接放行; + // 其余账号查菜单。 CertAuditCapabilityVO capability = new CertAuditCapabilityVO(); + if (mpUserDataScopeSupport.isMpDispatcher()) { + capability.setCanView(true); + capability.setCanAudit(true); + return capability; + } capability.setCanView(permissionHandler.hasMenu(CertAuditPermissions.DRIVER_VIEW_MENU) || permissionHandler.hasMenu(CertAuditPermissions.VEHICLE_VIEW_MENU)); capability.setCanAudit(permissionHandler.hasMenu(CertAuditPermissions.DRIVER_AUDIT_MENU) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/CertAuditPermissions.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/CertAuditPermissions.java index aa1d17a0..ec31f27d 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/CertAuditPermissions.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/CertAuditPermissions.java @@ -34,6 +34,10 @@ package org.springblade.transport.support; * {@code AUDIT_VIEW} 与 {@code AUDIT_ACTION} 由上述常量拼接,结果仍是编译期常量。 * 三个审核接口的 {@code @PreAuth} 与 {@code CertAuditServiceImpl#capability()} 使用同一组菜单码常量。 *

+ * 小程序调度端管理身份不走菜单码:小程序登录不经过 {@code blade_user},token 的 {@code roleId} + * 是占位值 {@code "0"}、{@code roleName} 取 {@code blade_mp_user.identities}(调度端管理员为 {@code admin}), + * 查不到 {@code blade_role_menu},因此两个表达式末尾各放行一个角色名 {@code admin}。 + *

* 表达式由框架交给 SpEL 求值,root object 是 {@code AuthFun},函数名须与它的公开方法一致({@code hasMenu})。 * {@code CertAuditPermissionsTest} 会解析并求值这两个表达式。 * @@ -62,18 +66,28 @@ public final class CertAuditPermissions { public static final String VEHICLE_AUDIT_MENU = "cert_certification_audit_vehicle_audit"; /** - * 待审列表与审核详情:司机或车辆任一查看菜单即可 + * 小程序调度端管理身份的角色名 + *

+ * 取值来自 {@code blade_mp_user.identities},由登录时拼进 token 的 {@code roleName}; + * 不是 {@code blade_role} 里的角色别名。 + */ + public static final String MP_DISPATCHER_ROLE = "admin"; + + /** + * 待审列表与审核详情:司机或车辆任一查看菜单,或小程序调度端管理身份 *

* 由上面两个查看菜单码拼成,结果仍是编译期常量,{@code @PreAuth} 可以直接引用。 */ public static final String AUDIT_VIEW = - "hasMenu('" + DRIVER_VIEW_MENU + "') or hasMenu('" + VEHICLE_VIEW_MENU + "')"; + "hasMenu('" + DRIVER_VIEW_MENU + "') or hasMenu('" + VEHICLE_VIEW_MENU + "')" + + " or hasRole('" + MP_DISPATCHER_ROLE + "')"; /** - * 审核动作:司机或车辆任一「审核」按钮权限即可 + * 审核动作:司机或车辆任一「审核」按钮权限,或小程序调度端管理身份 */ public static final String AUDIT_ACTION = - "hasMenu('" + DRIVER_AUDIT_MENU + "') or hasMenu('" + VEHICLE_AUDIT_MENU + "')"; + "hasMenu('" + DRIVER_AUDIT_MENU + "') or hasMenu('" + VEHICLE_AUDIT_MENU + "')" + + " or hasRole('" + MP_DISPATCHER_ROLE + "')"; private CertAuditPermissions() { } diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/CertAuditServiceImplTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/CertAuditServiceImplTest.java index df9bbd10..fa461766 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/CertAuditServiceImplTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/CertAuditServiceImplTest.java @@ -21,6 +21,7 @@ import org.springblade.transport.pojo.vo.CertAuditDetailVO; import org.springblade.transport.service.IDriverService; import org.springblade.transport.service.ITransportCertRecordService; import org.springblade.transport.service.ITransportVehicleService; +import org.springblade.transport.support.MpUserDataScopeSupport; import java.util.Collection; @@ -31,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -57,6 +59,7 @@ class CertAuditServiceImplTest { private ITransportVehicleService transportVehicleService; private IDriverService driverService; private IPermissionHandler permissionHandler; + private MpUserDataScopeSupport mpUserDataScopeSupport; private CertAuditServiceImpl service; @BeforeAll @@ -76,8 +79,9 @@ class CertAuditServiceImplTest { transportVehicleService = mock(ITransportVehicleService.class); driverService = mock(IDriverService.class); permissionHandler = mock(IPermissionHandler.class); + mpUserDataScopeSupport = mock(MpUserDataScopeSupport.class); service = new CertAuditServiceImpl(certRecordService, certRecordMapper, transportVehicleService, driverService, - permissionHandler); + permissionHandler, mpUserDataScopeSupport); // 目标对象的写入默认成功;「写入一行都没命中」由专门的用例覆盖 when(transportVehicleService.updateById(any(TransportVehicle.class))).thenReturn(true); when(driverService.updateById(any(Driver.class))).thenReturn(true); @@ -431,6 +435,31 @@ class CertAuditServiceImplTest { assertTrue(capability.getCanAudit()); } + @Test + @DisplayName("能力判定:小程序调度端管理身份查不到菜单码,查看与审核都放行") + void capabilityForMpDispatcher() { + // 小程序账号的 token roleId 是占位 "0",菜单表里查不到任何一条,只能按身份放行 + when(mpUserDataScopeSupport.isMpDispatcher()).thenReturn(true); + + CertAuditCapabilityVO capability = service.capability(); + + assertTrue(capability.getCanView(), "调度端管理员应能进审核页"); + assertTrue(capability.getCanAudit(), "调度端管理员应能执行审核"); + verify(permissionHandler, never()).hasMenu(anyString()); + } + + @Test + @DisplayName("能力判定:非小程序调度端仍只按菜单码判定") + void capabilityNonDispatcherUsesMenus() { + when(mpUserDataScopeSupport.isMpDispatcher()).thenReturn(false); + when(permissionHandler.hasMenu(DRIVER_VIEW_MENU)).thenReturn(true); + + CertAuditCapabilityVO capability = service.capability(); + + assertTrue(capability.getCanView()); + assertFalse(capability.getCanAudit(), "身份分支不放行时,仍要审核菜单码才能审核"); + } + // -------------------------------------------- 驳回原因的展示缓存 @Test diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/PersonCertRoundTripTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/PersonCertRoundTripTest.java index b5b14c16..0fee6807 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/PersonCertRoundTripTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/PersonCertRoundTripTest.java @@ -18,6 +18,7 @@ import org.springblade.transport.service.IDriverAppService; import org.springblade.transport.service.IDriverService; import org.springblade.transport.service.ITransportCertRecordService; import org.springblade.transport.service.ITransportVehicleService; +import org.springblade.transport.support.MpUserDataScopeSupport; import java.time.LocalDate; import java.util.HashMap; @@ -99,7 +100,7 @@ class PersonCertRoundTripTest { driverCertService = new DriverCertServiceImpl(driverAppService, driverService, transportVehicleService, certRecordService); certAuditService = new CertAuditServiceImpl(certRecordService, certRecordMapper, transportVehicleService, driverService, - mock(IPermissionHandler.class)); + mock(IPermissionHandler.class), mock(MpUserDataScopeSupport.class)); } @Test diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/CertAuditPermissionsTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/CertAuditPermissionsTest.java index 4422d9cd..833d0d3d 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/CertAuditPermissionsTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/CertAuditPermissionsTest.java @@ -82,11 +82,47 @@ class CertAuditPermissionsTest { assertTrue(CertAuditPermissions.AUDIT_VIEW.contains("cert_certification_audit_vehicle")); assertTrue(CertAuditPermissions.AUDIT_ACTION.contains("cert_certification_audit_driver_audit")); assertTrue(CertAuditPermissions.AUDIT_ACTION.contains("cert_certification_audit_vehicle_audit")); - // 不应再出现角色名判断:库里不存在 admin 角色,且会漏掉「运管」岗位 + // 角色放行只针对小程序调度端管理身份(blade_mp_user.identities 里的 admin,登录后写进 token 的 roleName), + // 不能改用 hasAnyRole 把 blade_role 的 administrator 也一并放行 + assertTrue(CertAuditPermissions.AUDIT_VIEW.contains("hasRole('" + CertAuditPermissions.MP_DISPATCHER_ROLE + "')")); + assertTrue(CertAuditPermissions.AUDIT_ACTION.contains("hasRole('" + CertAuditPermissions.MP_DISPATCHER_ROLE + "')")); assertFalse(CertAuditPermissions.AUDIT_VIEW.contains("hasAnyRole")); assertFalse(CertAuditPermissions.AUDIT_ACTION.contains("hasAnyRole")); } + @Test + @DisplayName("小程序调度端管理身份(token roleName=admin)无菜单码也可查看与审核") + void mpDispatcherRoleAllowed() { + AuthFun mpDispatcher = mock(AuthFun.class); + when(mpDispatcher.hasRole("admin")).thenReturn(true); + + assertTrue(evaluate(CertAuditPermissions.AUDIT_VIEW, mpDispatcher), + "小程序账号没有 roleId,查不到菜单码"); + assertTrue(evaluate(CertAuditPermissions.AUDIT_ACTION, mpDispatcher), + "小程序调度端管理员需要能执行审核"); + } + + @Test + @DisplayName("小程序司机身份(token roleName=driver)不得进入审核") + void mpDriverRoleDenied() { + AuthFun mpDriver = mock(AuthFun.class); + when(mpDriver.hasRole("driver")).thenReturn(true); + + assertFalse(evaluate(CertAuditPermissions.AUDIT_VIEW, mpDriver)); + assertFalse(evaluate(CertAuditPermissions.AUDIT_ACTION, mpDriver)); + } + + @Test + @DisplayName("超管角色名(administrator)不因角色放行,Web 侧仍须菜单码") + void administratorRoleAloneDoesNotPass() { + AuthFun administrator = mock(AuthFun.class); + when(administrator.hasRole("administrator")).thenReturn(true); + + assertFalse(evaluate(CertAuditPermissions.AUDIT_VIEW, administrator), + "表达式只放行 admin 角色名;超管走 hasMenu 由框架内部放行"); + assertFalse(evaluate(CertAuditPermissions.AUDIT_ACTION, administrator)); + } + /** * 用与框架相同的方式求值:AuthFun 作为 root object */