From 128a0e3bd3847d11b7f2f9e169dd36fce3b0b581 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 23 Oct 2026 22:58:18 +0800 Subject: [PATCH] fix(report): 补齐积木报表超级管理员权限 --- yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImpl.java | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- yudao-module-report/src/test/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImplTest.java | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 yudao-module-report/src/test/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImplTest.java diff --git a/yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImpl.java b/yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImpl.java index 50673b5..b25e7d0 100644 --- a/yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImpl.java +++ b/yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImpl.java @@ -167,20 +167,65 @@ public class JmReportTokenServiceImpl implements JmReportTokenServiceI { TenantContextHolder.setTenantId(loginUser.getTenantId()); // 参见文档 https://help.jimureport.com/prodSafe/ 文档 - // 适配:如果是本系统的管理员,则返回积木报表(仪表盘/大屏设计器)的所有权限指令 + // 适配:如果是本系统的管理员,则返回积木报表(仪表盘/大屏设计器、报表设计器)的所有权限指令 // 如果不处理,会碰到 https://t.zsxq.com/yzlkA 反馈的问题 + // 权限清单参考 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1600 Long userId = SecurityFrameworkUtils.getLoginUserId(); if (permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode())) { return new String[]{ - "drag:datasource:testConnection", // 数据库连接测试 - "drag:datasource:saveOrUpate", // 数据源保存 - "drag:datasource:delete", // 数据源删除 - "drag:analysis:sql", // SQL解析 - "drag:design:getTotalData", // 展示Online表单数据 - "drag:dataset:save", // 数据集保存 - "drag:dataset:delete", // 数据集删除 - "onl:drag:clear:recovery", // 清空回收站 - "onl:drag:page:delete" // 数据删除 + // ========== 第一块:大屏(仪表盘 BI)按钮权限 ========== + "drag:datasource:testConnection", // 数据源连接测试 + "drag:datasource:saveOrUpate", // 数据源新增/编辑 + "drag:datasource:delete", // 数据源删除 + "drag:datasource:deleteBatch", // 数据源批量删除 + "drag:datasource:queryById", // 数据源详情查询 + "drag:dataset:save", // 数据集保存 + "drag:dataset:delete", // 数据集删除 + "drag:analysis:sql", // SQL解析 + "drag:design:getTotalData", // 仪表盘对Online表单展示数据 + "drag:iconlib:config", // 图标库配置 + "onl:drag:clear:recovery", // 清空回收站 + "onl:drag:page:delete", // 仪表盘页面删除 + "onl:drag:category:delete", // 仪表盘分类删除 + "onl:drag:comp:add", // 组件新增 + "onl:drag:comp:edit", // 组件编辑 + "onl:drag:comp:delete", // 组件删除 + "onl:drag:comp:deleteBatch", // 组件批量删除 + "onl:drag:getRawTableData", // 获取原始表数据 + // ========== 第二块:积木报表按钮权限 ========== + "jmreport:exportJob:plugin", // 导出任务-插件 + "jmreport:exportJob:list", // 导出任务-列表 + "jmreport:exportJob:save", // 导出任务-保存 + "jmreport:exportJob:detail", // 导出任务-详情 + "jmreport:exportJob:delete", // 导出任务-删除 + "jmreport:exportJob:status", // 导出任务-状态变更 + "jmreport:exportJob:run", // 导出任务-执行 + "jmreport:exportJob:export", // 导出任务-导出 + "jmreport:map:add", // 地图-新增 + "jmreport:map:delete", // 地图-删除 + "jmreport:reportGroup:list", // 报表分组-列表 + "jmreport:reportGroup:detail", // 报表分组-详情 + "jmreport:reportGroup:save", // 报表分组-保存 + "jmreport:reportGroup:delete", // 报表分组-删除 + "jmreport:category:add", // 报表分类-新增 + "jmreport:category:edit", // 报表分类-编辑 + "jmreport:category:delete", // 报表分类-删除 + "jmreport:category:reduction", // 报表分类-还原 + "jmreport:category:completelyDelete", // 报表分类-彻底删除 + "jmreport:category:batchMove", // 报表分类-批量移动 + "jmreport:category:folderSort", // 报表分类-文件夹排序 + "jmreport:datasource:testConnection", // 数据源-连接测试 + "jmreport:datasource:saveOrUpate", // 数据源-新增/编辑 + "jmreport:datasource:delete", // 数据源-删除 + "jmreport:datasource:query", // 数据源-查询(列表/详情) + "jmreport:design:loadTableData", // 设计器-加载表数据 + "jmreport:design:parseField", // 设计器-解析字段(SQL/API/JavaBean/填报处理器) + "jmreport:design:loadTable", // 设计器-库表/字段元数据查询 + "jmreport:design:saveDb", // 设计器-数据集保存 + "jmreport:design:filesDataset", // 设计器-文件数据集 + "jmreport:design:editEnhance", // 设计器-增强代码(JS/CSS/Python) + "jmreport:ai:tableMeta", // AI-表元数据管理 + "jimu:chat2bi:permEdit" // Chat2BI-权限编辑 }; } return null; diff --git a/yudao-module-report/src/test/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImplTest.java b/yudao-module-report/src/test/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImplTest.java new file mode 100644 index 0000000..6e8910c --- /dev/null +++ b/yudao-module-report/src/test/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImplTest.java @@ -0,0 +1,140 @@ +package cn.iocoder.yudao.module.report.framework.jmreport.core.service; + +import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi; +import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi; +import cn.iocoder.yudao.framework.security.config.SecurityProperties; +import cn.iocoder.yudao.framework.security.core.LoginUser; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.core.context.SecurityContextHolder; + +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +/** + * {@link JmReportTokenServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +public class JmReportTokenServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private JmReportTokenServiceImpl jmReportTokenService; + + @Mock + private OAuth2TokenCommonApi oauth2TokenApi; + @Mock + private PermissionCommonApi permissionApi; + @Mock + private SecurityProperties securityProperties; + + @AfterEach + public void tearDown() { + SecurityContextHolder.clearContext(); + TenantContextHolder.clear(); + } + + @Test + public void testGetPermissions_superAdmin() { + // 准备参数 + Long userId = randomLongId(); + Long tenantId = randomLongId(); + SecurityFrameworkUtils.setLoginUser(new LoginUser().setId(userId).setTenantId(tenantId), + new MockHttpServletRequest()); + // mock 方法 + when(permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode())).thenReturn(true); + + // 调用 + String[] permissions = jmReportTokenService.getPermissions("token"); + // 断言:原有的大屏权限仍保留 + assertTrue(ArrayUtil.containsAll(permissions, "drag:datasource:testConnection", "drag:analysis:sql", + "onl:drag:page:delete")); + // 断言:大屏只校验权限、未配置时超级管理员会被拒绝的三处 + assertTrue(ArrayUtil.containsAll(permissions, "drag:datasource:queryById", "drag:datasource:deleteBatch", + "onl:drag:getRawTableData")); + // 断言:报表设计器的扩展权限 + assertTrue(ArrayUtil.containsAll(permissions, "jmreport:datasource:query", "jmreport:exportJob:export", + "jmreport:ai:tableMeta")); + assertEquals(permissions.length, ArrayUtil.distinct(permissions).length); + assertEquals(tenantId, TenantContextHolder.getTenantId()); + } + + @Test + public void testGetPermissions_notSuperAdmin() { + // 准备参数 + Long userId = randomLongId(); + Long tenantId = randomLongId(); + SecurityFrameworkUtils.setLoginUser(new LoginUser().setId(userId).setTenantId(tenantId), + new MockHttpServletRequest()); + // mock 方法 + when(permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode())).thenReturn(false); + + // 调用 + String[] permissions = jmReportTokenService.getPermissions("token"); + // 断言 + assertNull(permissions); + assertEquals(tenantId, TenantContextHolder.getTenantId()); + } + + @Test + public void testGetPermissions_notLogin() { + // 调用 + String[] permissions = jmReportTokenService.getPermissions("token"); + // 断言 + assertNull(permissions); + assertNull(TenantContextHolder.getTenantId()); + verifyNoInteractions(permissionApi); + } + + @Test + public void testGetRoles_superAdmin() { + // 准备参数 + Long userId = randomLongId(); + Long tenantId = randomLongId(); + SecurityFrameworkUtils.setLoginUser(new LoginUser().setId(userId).setTenantId(tenantId), + new MockHttpServletRequest()); + // mock 方法 + when(permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode())).thenReturn(true); + + // 调用 + String[] roles = jmReportTokenService.getRoles("token"); + // 断言 + assertArrayEquals(new String[]{"admin"}, roles); + assertEquals(tenantId, TenantContextHolder.getTenantId()); + } + + @Test + public void testGetRoles_notSuperAdmin() { + // 准备参数 + Long userId = randomLongId(); + SecurityFrameworkUtils.setLoginUser(new LoginUser().setId(userId).setTenantId(randomLongId()), + new MockHttpServletRequest()); + // mock 方法 + when(permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode())).thenReturn(false); + + // 调用 + String[] roles = jmReportTokenService.getRoles("token"); + // 断言 + assertNull(roles); + } + + @Test + public void testGetRoles_notLogin() { + // 调用 + String[] roles = jmReportTokenService.getRoles("token"); + // 断言 + assertNull(roles); + verifyNoInteractions(permissionApi); + } + +} -- libgit2 0.21.4