From ec926afa467aaf8320d160855cea9c7e3710a82d Mon Sep 17 00:00:00 2001 From: weicw <594098497@qq.com> Date: Mon, 26 Oct 2026 03:14:05 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E6=96=B0=E5=A2=9E=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E5=AE=A1=E6=A0=B8=E8=83=BD=E5=8A=9B=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pojo/vo/CertAuditCapabilityVO.java | 55 +++++++++++++++++++ .../CertificationAuditController.java | 9 +++ .../transport/service/ICertAuditService.java | 13 +++++ .../service/impl/CertAuditServiceImpl.java | 15 +++++ .../support/CertAuditPermissions.java | 29 +++++++++- .../impl/CertAuditServiceImplTest.java | 45 ++++++++++++++- .../service/impl/PersonCertRoundTripTest.java | 4 +- 7 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CertAuditCapabilityVO.java diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CertAuditCapabilityVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CertAuditCapabilityVO.java new file mode 100644 index 00000000..67ba55fb --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CertAuditCapabilityVO.java @@ -0,0 +1,55 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 认证审核能力 + *

+ * 小程序端进入审核页前先取它决定空态与按钮禁用。没有权限时两个字段为 false, + * 而不是让接口返回 403——列表与详情各自仍挂权限表达式,这里只回答「能不能」。 + * + * @author Chill + */ +@Data +@Schema(description = "认证审核能力") +public class CertAuditCapabilityVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "能否查看审核列表与详情:无权限时为 false,不抛 403") + private Boolean canView; + + @Schema(description = "能否执行审核(通过/驳回):无权限时为 false,不抛 403") + private Boolean canAudit; + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CertificationAuditController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CertificationAuditController.java index a7b8f544..86dcf0da 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CertificationAuditController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CertificationAuditController.java @@ -36,6 +36,7 @@ import org.springblade.core.mp.support.Condition; import org.springblade.core.mp.support.Query; import org.springblade.core.secure.annotation.PreAuth; import org.springblade.core.tool.api.R; +import org.springblade.transport.pojo.vo.CertAuditCapabilityVO; import org.springblade.transport.pojo.vo.CertAuditDetailVO; import org.springblade.transport.pojo.vo.TransportCertRecordVO; import org.springblade.transport.service.ICertAuditService; @@ -100,4 +101,12 @@ public class CertificationAuditController extends BladeController { return R.status(certAuditService.audit(id, Boolean.TRUE.equals(pass), rejectReason)); } + // 不挂 @PreAuth:没有审核权限时也要能拿到 false,而不是被拦成 403 + @GetMapping("/capability") + @ApiOperationSupport(order = 4) + @Operation(summary = "审核能力判定", description = "无权限返回 false,不抛 403,供小程序决定空态与按钮禁用") + public R capability() { + return R.data(certAuditService.capability()); + } + } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICertAuditService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICertAuditService.java index 89af4d43..bdfd3327 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICertAuditService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICertAuditService.java @@ -27,6 +27,7 @@ package org.springblade.transport.service; import com.baomidou.mybatisplus.core.metadata.IPage; import org.springblade.transport.pojo.entity.TransportCertRecord; +import org.springblade.transport.pojo.vo.CertAuditCapabilityVO; import org.springblade.transport.pojo.vo.CertAuditDetailVO; import org.springblade.transport.pojo.vo.TransportCertRecordVO; @@ -80,4 +81,16 @@ public interface ICertAuditService { */ boolean audit(Long recordId, boolean pass, String rejectReason); + /** + * 当前登录用户的审核能力:能否查看、能否审核 + *

+ * 小程序端据此决定无权限空态与审核按钮禁用。两项都按菜单码判断, + * 没有权限时返回 false,**不抛异常**;未登录仍由全局鉴权链拦截,与其它接口一致。 + *

+ * 只读,无副作用;不带租户或对象参数,答案与具体记录无关。 + * + * @return 审核能力,两个字段均不为 null + */ + CertAuditCapabilityVO capability(); + } 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 14fa3cbf..ccd59c44 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 @@ -30,6 +30,7 @@ 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.handler.IPermissionHandler; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.utils.Func; import org.springblade.system.cache.UserCache; @@ -40,12 +41,14 @@ import org.springblade.transport.pojo.dto.VehicleCertSubmitRequest; import org.springblade.transport.pojo.entity.Driver; import org.springblade.transport.pojo.entity.TransportCertRecord; import org.springblade.transport.pojo.entity.TransportVehicle; +import org.springblade.transport.pojo.vo.CertAuditCapabilityVO; import org.springblade.transport.pojo.vo.CertAuditDetailVO; import org.springblade.transport.pojo.vo.TransportCertRecordVO; import org.springblade.transport.service.ICertAuditService; import org.springblade.transport.service.IDriverService; 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.wrapper.TransportCertRecordWrapper; import org.springframework.stereotype.Service; @@ -75,6 +78,7 @@ public class CertAuditServiceImpl implements ICertAuditService { private final TransportCertRecordMapper certRecordMapper; private final ITransportVehicleService transportVehicleService; private final IDriverService driverService; + private final IPermissionHandler permissionHandler; @Override public IPage pendingPage(String bizType, String bizKey, String driverName, String driverMobile, @@ -178,6 +182,17 @@ public class CertAuditServiceImpl implements ICertAuditService { return true; } + @Override + public CertAuditCapabilityVO capability() { + // 菜单码与三个审核接口的 @PreAuth 表达式同源,不另写字面量,也不在这里求值表达式 + CertAuditCapabilityVO capability = new CertAuditCapabilityVO(); + capability.setCanView(permissionHandler.hasMenu(CertAuditPermissions.DRIVER_VIEW_MENU) + || permissionHandler.hasMenu(CertAuditPermissions.VEHICLE_VIEW_MENU)); + capability.setCanAudit(permissionHandler.hasMenu(CertAuditPermissions.DRIVER_AUDIT_MENU) + || permissionHandler.hasMenu(CertAuditPermissions.VEHICLE_AUDIT_MENU)); + return capability; + } + /** * 更新认证对象的展示缓存,并在通过时把生效快照指针切到这条记录 *

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 4817baaf..8a09c5b7 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 @@ -35,6 +35,9 @@ package org.springblade.transport.support; * 用表达式而不是 {@code @PreAuth(menu = "...")} 的单值形式,是因为同一组接口 * 由 {@code bizType} 参数区分、同时服务司机与车辆两侧,单值 menu 表达不了「二者之一」。 *

+ * 四个菜单码在本类集中定义,权限表达式与能力判定接口都只引用这些常量, + * 避免同一串菜单码在多处各写一遍、改了一处漏掉另一处。 + *

* 表达式最终由框架交给 SpEL 求值,root object 是 {@code AuthFun}, * 故这里用的函数名必须与它的公开方法一致({@code hasMenu})。 * {@code CertAuditPermissionsTest} 会实际解析并求值这两个表达式, @@ -44,17 +47,39 @@ package org.springblade.transport.support; */ public final class CertAuditPermissions { + /** + * 司机认证的查看菜单码 + */ + public static final String DRIVER_VIEW_MENU = "cert_certification_audit_driver"; + + /** + * 车辆认证的查看菜单码 + */ + public static final String VEHICLE_VIEW_MENU = "cert_certification_audit_vehicle"; + + /** + * 司机认证的「审核」按钮菜单码 + */ + public static final String DRIVER_AUDIT_MENU = "cert_certification_audit_driver_audit"; + + /** + * 车辆认证的「审核」按钮菜单码 + */ + public static final String VEHICLE_AUDIT_MENU = "cert_certification_audit_vehicle_audit"; + /** * 待审列表与审核详情:司机或车辆任一审核菜单即可 + *

+ * 由上面两个查看菜单码拼成,结果仍是编译期常量,故 {@code @PreAuth} 可以直接引用。 */ public static final String AUDIT_VIEW = - "hasMenu('cert_certification_audit_driver') or hasMenu('cert_certification_audit_vehicle')"; + "hasMenu('" + DRIVER_VIEW_MENU + "') or hasMenu('" + VEHICLE_VIEW_MENU + "')"; /** * 审核动作:司机或车辆任一「审核」按钮权限即可 */ public static final String AUDIT_ACTION = - "hasMenu('cert_certification_audit_driver_audit') or hasMenu('cert_certification_audit_vehicle_audit')"; + "hasMenu('" + DRIVER_AUDIT_MENU + "') or hasMenu('" + VEHICLE_AUDIT_MENU + "')"; 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 5de77bd9..56de6146 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 @@ -11,10 +11,12 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.secure.handler.IPermissionHandler; import org.springblade.transport.pojo.entity.Driver; import org.springblade.transport.mapper.TransportCertRecordMapper; import org.springblade.transport.pojo.entity.TransportCertRecord; import org.springblade.transport.pojo.entity.TransportVehicle; +import org.springblade.transport.pojo.vo.CertAuditCapabilityVO; import org.springblade.transport.pojo.vo.CertAuditDetailVO; import org.springblade.transport.service.IDriverService; import org.springblade.transport.service.ITransportCertRecordService; @@ -50,6 +52,7 @@ class CertAuditServiceImplTest { private TransportCertRecordMapper certRecordMapper; private ITransportVehicleService transportVehicleService; private IDriverService driverService; + private IPermissionHandler permissionHandler; private CertAuditServiceImpl service; @BeforeAll @@ -68,7 +71,9 @@ class CertAuditServiceImplTest { certRecordMapper = mock(TransportCertRecordMapper.class); transportVehicleService = mock(ITransportVehicleService.class); driverService = mock(IDriverService.class); - service = new CertAuditServiceImpl(certRecordService, certRecordMapper, transportVehicleService, driverService); + permissionHandler = mock(IPermissionHandler.class); + service = new CertAuditServiceImpl(certRecordService, certRecordMapper, transportVehicleService, driverService, + permissionHandler); // 目标对象的写入默认成功;「写入一行都没命中」由专门的用例覆盖 when(transportVehicleService.updateById(any(TransportVehicle.class))).thenReturn(true); when(driverService.updateById(any(Driver.class))).thenReturn(true); @@ -349,6 +354,44 @@ class CertAuditServiceImplTest { assertNull(detail.getEffectiveRecordId()); } + // ------------------------------------------------------------ 审核能力 + + @Test + @DisplayName("能力判定:只有司机查看菜单时能进审核页,但审核按钮必须禁用") + void capabilityViewWithoutAudit() { + when(permissionHandler.hasMenu("cert_certification_audit_driver")).thenReturn(true); + + CertAuditCapabilityVO capability = service.capability(); + + assertTrue(capability.getCanView(), "有查看菜单就应能查看列表与详情"); + assertFalse(capability.getCanAudit(), "只有查看权限时不得允许审核"); + } + + @Test + @DisplayName("能力判定:司机审核按钮菜单即可审核") + void capabilityAuditWithDriverMenu() { + when(permissionHandler.hasMenu("cert_certification_audit_driver_audit")).thenReturn(true); + + assertTrue(service.capability().getCanAudit(), "司机审核菜单应放行审核动作"); + } + + @Test + @DisplayName("能力判定:车辆审核按钮菜单即可审核") + void capabilityAuditWithVehicleMenu() { + when(permissionHandler.hasMenu("cert_certification_audit_vehicle_audit")).thenReturn(true); + + assertTrue(service.capability().getCanAudit(), "车辆审核菜单应放行审核动作"); + } + + @Test + @DisplayName("能力判定:一个菜单都没有时两项都是 false,且不抛异常") + void capabilityWithoutAnyMenu() { + CertAuditCapabilityVO capability = service.capability(); + + assertFalse(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 fa1671b7..b5b14c16 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 @@ -9,6 +9,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.secure.handler.IPermissionHandler; import org.springblade.transport.pojo.dto.PersonCertSubmitRequest; import org.springblade.transport.pojo.entity.Driver; import org.springblade.transport.mapper.TransportCertRecordMapper; @@ -97,7 +98,8 @@ class PersonCertRoundTripTest { driverCertService = new DriverCertServiceImpl(driverAppService, driverService, transportVehicleService, certRecordService); - certAuditService = new CertAuditServiceImpl(certRecordService, certRecordMapper, transportVehicleService, driverService); + certAuditService = new CertAuditServiceImpl(certRecordService, certRecordMapper, transportVehicleService, driverService, + mock(IPermissionHandler.class)); } @Test