From 93779a655a310d5db646266b2abaa011a089d2c7 Mon Sep 17 00:00:00 2001 From: guoyuqi Date: Wed, 6 Nov 2024 10:28:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=B7=A5=E4=BD=9C=E5=8F=B0):=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E5=8F=B0=E9=A6=96=E9=A1=B5=E7=94=A8=E6=88=B7=E6=9D=83?= =?UTF-8?q?=E9=99=90=E5=BC=80=E9=80=9A=E6=A8=A1=E5=9D=97=E7=9A=84=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../constants/DashboardUserLayoutKeys.java | 40 ++++++ .../metersphere/dashboard/dto/LayoutDTO.java | 23 +++ .../request/DashboardBaseRequest.java | 59 ++++++++ .../request/DashboardFrontPageRequest.java | 16 +++ .../service/DashboardProjectService.java | 132 ++++++++++++++++++ .../service/PermissionCheckService.java | 108 ++++++++++---- 6 files changed, 350 insertions(+), 28 deletions(-) create mode 100644 backend/services/dashboard/src/main/java/io/metersphere/dashboard/constants/DashboardUserLayoutKeys.java create mode 100644 backend/services/dashboard/src/main/java/io/metersphere/dashboard/dto/LayoutDTO.java create mode 100644 backend/services/dashboard/src/main/java/io/metersphere/dashboard/request/DashboardBaseRequest.java create mode 100644 backend/services/dashboard/src/main/java/io/metersphere/dashboard/request/DashboardFrontPageRequest.java create mode 100644 backend/services/dashboard/src/main/java/io/metersphere/dashboard/service/DashboardProjectService.java diff --git a/backend/services/dashboard/src/main/java/io/metersphere/dashboard/constants/DashboardUserLayoutKeys.java b/backend/services/dashboard/src/main/java/io/metersphere/dashboard/constants/DashboardUserLayoutKeys.java new file mode 100644 index 0000000000..7b888c2acf --- /dev/null +++ b/backend/services/dashboard/src/main/java/io/metersphere/dashboard/constants/DashboardUserLayoutKeys.java @@ -0,0 +1,40 @@ +package io.metersphere.dashboard.constants; + +public enum DashboardUserLayoutKeys { + /** + * default + */ + CREATE_BY_ME, //我创建的 + PROJECT_VIEW,//项目概览 + PROJECT_MEMBER_VIEW,//项目成员概览 + /** + * functional + */ + CASE_COUNT, //用例数量统计 + ASSOCIATE_CASE_COUNT,//关联用例统计 + REVIEW_CASE_COUNT,//用例评审数量统计 + REVIEWING_BY_ME, //待我评审 + /** + * api + */ + API_COUNT,//接口数量统计 + API_CASE_COUNT,//接口用例数量统计 + SCENARIO_COUNT,//场景用例数量统计 + API_CHANGE,//接口变更统计 + /** + * test_plan + */ + TEST_PLAN_COUNT,//测试计划数量统计 + PLAN_LEGACY_BUG,//计划遗留bug统计 + /** + * bug + */ + BUG_COUNT,//缺陷数量统计 + CREATE_BUG_BY_ME, //我创建的缺陷 + HANDLE_BUG_BY_ME, //待我处理的缺陷 + BUG_HANDLE_USER // 缺陷处理人统计 + + + + +} diff --git a/backend/services/dashboard/src/main/java/io/metersphere/dashboard/dto/LayoutDTO.java b/backend/services/dashboard/src/main/java/io/metersphere/dashboard/dto/LayoutDTO.java new file mode 100644 index 0000000000..3cbdd8062d --- /dev/null +++ b/backend/services/dashboard/src/main/java/io/metersphere/dashboard/dto/LayoutDTO.java @@ -0,0 +1,23 @@ +package io.metersphere.dashboard.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class LayoutDTO { + @Schema(description = "布局卡片key") + private String id; + @Schema(description = "排序") + private Integer pos; + @Schema(description = "全屏/半屏") + private Boolean fullScreen; + @Schema(description = "选中的项目ID") + private List projectIds; + +} diff --git a/backend/services/dashboard/src/main/java/io/metersphere/dashboard/request/DashboardBaseRequest.java b/backend/services/dashboard/src/main/java/io/metersphere/dashboard/request/DashboardBaseRequest.java new file mode 100644 index 0000000000..f126e7682a --- /dev/null +++ b/backend/services/dashboard/src/main/java/io/metersphere/dashboard/request/DashboardBaseRequest.java @@ -0,0 +1,59 @@ +package io.metersphere.dashboard.request; + +import com.google.common.base.CaseFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; + +/** + * @author guoyuqi + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class DashboardBaseRequest { + + @Schema(description = "组织ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{organization.id.not_blank}") + private String organizationId; + + @Min(value = 1, message = "当前页码必须大于0") + @Schema(description = "当前页码") + private int current; + + @Min(value = 5, message = "每页显示条数必须不小于5") + @Max(value = 500, message = "每页显示条数不能大于500") + @Schema(description = "每页显示条数") + private int pageSize; + + @Schema(description = "过滤字段") + private Map> filter; + + @Schema(description = "排序字段(model中的字段 : asc/desc)") + private Map<@Valid @Pattern(regexp = "^[A-Za-z]+$") String, @Valid @NotBlank String> sort; + + + public String getSortString() { + if (sort == null || sort.isEmpty()) { + return null; + } + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : sort.entrySet()) { + String column = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, entry.getKey()); + sb.append(column) + .append(StringUtils.SPACE) + .append(StringUtils.equalsIgnoreCase(entry.getValue(), "DESC") ? "DESC" : "ASC") + .append(","); + } + return sb.substring(0, sb.length() - 1); + } + +} diff --git a/backend/services/dashboard/src/main/java/io/metersphere/dashboard/request/DashboardFrontPageRequest.java b/backend/services/dashboard/src/main/java/io/metersphere/dashboard/request/DashboardFrontPageRequest.java new file mode 100644 index 0000000000..2dbef56c04 --- /dev/null +++ b/backend/services/dashboard/src/main/java/io/metersphere/dashboard/request/DashboardFrontPageRequest.java @@ -0,0 +1,16 @@ +package io.metersphere.dashboard.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +public class DashboardFrontPageRequest extends DashboardBaseRequest{ + + @Schema(description = "项目ID集合") + private List projectIds; + + @Schema(description = "人员集合") + private List handleUsers; +} diff --git a/backend/services/dashboard/src/main/java/io/metersphere/dashboard/service/DashboardProjectService.java b/backend/services/dashboard/src/main/java/io/metersphere/dashboard/service/DashboardProjectService.java new file mode 100644 index 0000000000..1d42ff3395 --- /dev/null +++ b/backend/services/dashboard/src/main/java/io/metersphere/dashboard/service/DashboardProjectService.java @@ -0,0 +1,132 @@ +package io.metersphere.dashboard.service; + +import io.metersphere.project.domain.Project; +import io.metersphere.project.domain.ProjectExample; +import io.metersphere.project.mapper.ProjectMapper; +import io.metersphere.project.service.PermissionCheckService; +import io.metersphere.sdk.constants.PermissionConstants; +import io.metersphere.system.dto.user.UserDTO; +import jakarta.annotation.Resource; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +@Transactional(rollbackFor = Exception.class) +public class DashboardProjectService { + + @Resource + private PermissionCheckService permissionCheckService; + @Resource + private ProjectMapper projectMapper; + + public static final String API_TEST = "apiTest"; + public static final String TEST_PLAN = "testPlan"; + public static final String FUNCTIONAL_CASE = "caseManagement"; + public static final String BUG = "bugManagement"; + + public Map> getPermissionModuleProjectIds(String organizationId, List projectIds, String userId) { + boolean isAdmin = isAdmin(userId); + Set projectSet; + Map moduleMap; + if (CollectionUtils.isNotEmpty(projectIds)) { + projectSet = new HashSet<>(projectIds); + ProjectExample projectExample = new ProjectExample(); + projectExample.createCriteria().andIdIn(projectIds); + List projects = projectMapper.selectByExample(projectExample); + moduleMap = projects.stream().collect(Collectors.toMap(Project::getId, Project::getModuleSetting)); + } else { + ProjectExample projectExample = new ProjectExample(); + projectExample.createCriteria().andOrganizationIdEqualTo(organizationId); + List projects = projectMapper.selectByExample(projectExample); + projectSet = projects.stream().map(Project::getId).collect(Collectors.toSet()); + moduleMap = projects.stream().collect(Collectors.toMap(Project::getId, Project::getModuleSetting)); + } + + Map> hasModuleProjectIds = new HashMap<>(); + Map finalModuleMap = moduleMap; + Set searchCaseProjectIds = new HashSet<>(); + Set searchReviewProjectIds = new HashSet<>(); + Set searchApiProjectIds = new HashSet<>(); + Set searchApiCaseProjectIds = new HashSet<>(); + Set searchScenarioProjectIds = new HashSet<>(); + Set searchPlanProjectIds = new HashSet<>(); + Set searchBugProjectIds = new HashSet<>(); + //查出用户在选中的项目中有读取权限的, admin所有项目都有权限 + if (!isAdmin) { + Set permissionSet = getPermissionSet(); + Map> hasUserPermissionProjectIds = permissionCheckService.getHasUserPermissionProjectIds(userId, projectSet, permissionSet); + //查出这些项目分别有模块的 + Set functionalProjectIds = hasUserPermissionProjectIds.get(PermissionConstants.FUNCTIONAL_CASE_READ); + //检查是否开启功能用例模块 + if (CollectionUtils.isNotEmpty(functionalProjectIds)) { + searchCaseProjectIds = functionalProjectIds.stream().filter(t -> finalModuleMap.get(t).contains(FUNCTIONAL_CASE)).collect(Collectors.toSet()); + } + Set reviewProjectIds = hasUserPermissionProjectIds.get(PermissionConstants.CASE_REVIEW_READ); + if (CollectionUtils.isNotEmpty(reviewProjectIds)) { + searchReviewProjectIds = reviewProjectIds.stream().filter(t -> finalModuleMap.get(t).contains(FUNCTIONAL_CASE)).collect(Collectors.toSet()); + } + //检查是否开启接口模块 + Set apiProjectIds = hasUserPermissionProjectIds.get(PermissionConstants.PROJECT_API_DEFINITION_READ); + if (CollectionUtils.isNotEmpty(apiProjectIds)) { + searchApiProjectIds = apiProjectIds.stream().filter(t -> finalModuleMap.get(t).contains(API_TEST)).collect(Collectors.toSet()); + } + Set apiCaseProjectIds = hasUserPermissionProjectIds.get(PermissionConstants.PROJECT_API_DEFINITION_CASE_READ); + if (CollectionUtils.isNotEmpty(apiCaseProjectIds)) { + searchApiCaseProjectIds = apiCaseProjectIds.stream().filter(t -> finalModuleMap.get(t).contains(API_TEST)).collect(Collectors.toSet()); + } + Set scenarioProjectIds = hasUserPermissionProjectIds.get(PermissionConstants.PROJECT_API_SCENARIO_READ); + if (CollectionUtils.isNotEmpty(scenarioProjectIds)) { + searchScenarioProjectIds = scenarioProjectIds.stream().filter(t -> finalModuleMap.get(t).contains(API_TEST)).collect(Collectors.toSet()); + } + //检查是否开启测试计划模块 + Set planProjectIds = hasUserPermissionProjectIds.get(PermissionConstants.TEST_PLAN_READ); + if (CollectionUtils.isNotEmpty(scenarioProjectIds)) { + searchPlanProjectIds = planProjectIds.stream().filter(t -> finalModuleMap.get(t).contains(TEST_PLAN)).collect(Collectors.toSet()); + } + //检查是否开启缺陷模块 + Set bugProjectIds = hasUserPermissionProjectIds.get(PermissionConstants.PROJECT_BUG_READ); + if (CollectionUtils.isNotEmpty(bugProjectIds)) { + searchBugProjectIds = bugProjectIds.stream().filter(t -> finalModuleMap.get(t).contains(BUG)).collect(Collectors.toSet()); + } + } else { + //查出这些项目分别有模块的 + searchCaseProjectIds = projectSet.stream().filter(t -> finalModuleMap.get(t).contains(FUNCTIONAL_CASE)).collect(Collectors.toSet()); + searchReviewProjectIds = projectSet.stream().filter(t -> finalModuleMap.get(t).contains(FUNCTIONAL_CASE)).collect(Collectors.toSet()); + searchApiProjectIds = projectSet.stream().filter(t -> finalModuleMap.get(t).contains(API_TEST)).collect(Collectors.toSet()); + searchApiCaseProjectIds = projectSet.stream().filter(t -> finalModuleMap.get(t).contains(API_TEST)).collect(Collectors.toSet()); + searchScenarioProjectIds = projectSet.stream().filter(t -> finalModuleMap.get(t).contains(API_TEST)).collect(Collectors.toSet()); + searchPlanProjectIds = projectSet.stream().filter(t -> finalModuleMap.get(t).contains(TEST_PLAN)).collect(Collectors.toSet()); + searchBugProjectIds = projectSet.stream().filter(t -> finalModuleMap.get(t).contains(BUG)).collect(Collectors.toSet()); + } + hasModuleProjectIds.put(PermissionConstants.FUNCTIONAL_CASE_READ, searchCaseProjectIds); + hasModuleProjectIds.put(PermissionConstants.CASE_REVIEW_READ, searchReviewProjectIds); + hasModuleProjectIds.put(PermissionConstants.PROJECT_API_DEFINITION_READ, searchApiProjectIds); + hasModuleProjectIds.put(PermissionConstants.PROJECT_API_DEFINITION_CASE_READ, searchApiCaseProjectIds); + hasModuleProjectIds.put(PermissionConstants.PROJECT_API_SCENARIO_READ, searchScenarioProjectIds); + hasModuleProjectIds.put(PermissionConstants.TEST_PLAN_READ, searchPlanProjectIds); + hasModuleProjectIds.put(PermissionConstants.PROJECT_BUG_READ, searchBugProjectIds); + + return hasModuleProjectIds; + } + + private static Set getPermissionSet() { + Set permissionSet = new HashSet<>(); + permissionSet.add(PermissionConstants.FUNCTIONAL_CASE_READ); + permissionSet.add(PermissionConstants.CASE_REVIEW_READ); + permissionSet.add(PermissionConstants.PROJECT_API_DEFINITION_READ); + permissionSet.add(PermissionConstants.PROJECT_API_DEFINITION_CASE_READ); + permissionSet.add(PermissionConstants.PROJECT_API_SCENARIO_READ); + permissionSet.add(PermissionConstants.TEST_PLAN_READ); + permissionSet.add(PermissionConstants.PROJECT_BUG_READ); + return permissionSet; + } + + private boolean isAdmin(String userId) { + UserDTO userDTO = permissionCheckService.getUserDTO(userId); + return permissionCheckService.checkAdmin(userDTO); + } +} diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/service/PermissionCheckService.java b/backend/services/project-management/src/main/java/io/metersphere/project/service/PermissionCheckService.java index ceec60894b..a31caa726c 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/service/PermissionCheckService.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/service/PermissionCheckService.java @@ -8,14 +8,12 @@ import io.metersphere.system.domain.UserRolePermission; import io.metersphere.system.dto.user.UserDTO; import io.metersphere.system.service.UserLoginService; import jakarta.annotation.Resource; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; @Service @@ -25,34 +23,88 @@ public class PermissionCheckService { private UserLoginService userLoginService; public boolean userHasProjectPermission(String userId, String projectId, String permission) { + UserDTO user = getUserDTO(userId); + if (user == null) return false; + // 判断是否是超级管理员 + if (checkAdmin(user)) return true; + return checkHasPermission(projectId, permission, user); + } + + private static boolean checkHasPermission(String projectId, String permission, UserDTO user) { Map> userRolePermissions = new HashMap<>(); Map role = new HashMap<>(); - UserDTO user = userLoginService.getUserDTO(userId); - if (user != null) { - user.getUserRoleRelations().forEach(ug -> user.getUserRolePermissions().forEach(gp -> { - if (StringUtils.equalsIgnoreCase(gp.getUserRole().getId(), ug.getRoleId())) { - userRolePermissions.put(ug.getId(), gp.getUserRolePermissions()); - role.put(ug.getId(), gp.getUserRole()); - } - })); - // 判断是否是超级管理员 - long count = user.getUserRoles() - .stream() - .filter(g -> StringUtils.equalsIgnoreCase(g.getId(), InternalUserRole.ADMIN.getValue())) - .count(); - if (count > 0) { - return true; + getUserAllPermissions(user, userRolePermissions, role); + Set currentProjectPermissions = user.getUserRoleRelations().stream() + .filter(ug -> role.get(ug.getId()) != null && StringUtils.equalsIgnoreCase(role.get(ug.getId()).getType(), UserRoleType.PROJECT.name())) + .filter(ug -> StringUtils.equalsIgnoreCase(ug.getSourceId(), projectId)) + .flatMap(ug -> userRolePermissions.get(ug.getId()).stream()) + .map(UserRolePermission::getPermissionId) + .collect(Collectors.toSet()); + return currentProjectPermissions.contains(permission); + } + + public UserDTO getUserDTO(String userId) { + return userLoginService.getUserDTO(userId); + } + + public boolean checkAdmin(UserDTO user) { + long count = user.getUserRoles() + .stream() + .filter(g -> StringUtils.equalsIgnoreCase(g.getId(), InternalUserRole.ADMIN.getValue())) + .count(); + return count > 0; + } + + private static void getUserAllPermissions(UserDTO user, Map> userRolePermissions, Map role) { + user.getUserRoleRelations().forEach(ug -> user.getUserRolePermissions().forEach(gp -> { + if (StringUtils.equalsIgnoreCase(gp.getUserRole().getId(), ug.getRoleId())) { + userRolePermissions.put(ug.getId(), gp.getUserRolePermissions()); + role.put(ug.getId(), gp.getUserRole()); } - Set currentProjectPermissions = user.getUserRoleRelations().stream() - .filter(ug -> role.get(ug.getId()) != null && StringUtils.equalsIgnoreCase(role.get(ug.getId()).getType(), UserRoleType.PROJECT.name())) - .filter(ug -> StringUtils.equalsIgnoreCase(ug.getSourceId(), projectId)) - .flatMap(ug -> userRolePermissions.get(ug.getId()).stream()) - .map(UserRolePermission::getPermissionId) - .collect(Collectors.toSet()); - return currentProjectPermissions.contains(permission); - } - return false; + })); + } + + /** + * 获取用户某些权限所占据的项目集合 + * + * @param userId 用户ID + * @param projectIds 项目ids + * @param permissions 需要判断的权限集合 + * @return 有该类型权限的项目ids的map + */ + public Map> getHasUserPermissionProjectIds(String userId, SetprojectIds, Set permissions) { + UserDTO user = getUserDTO(userId); + if (user == null) return new HashMap<>(); + // 注意超级管理员包含所有权限,这里不予返回,请在方法外自行判断 + Map> permissionProjectIdMap = new HashMap<>(); + Map> projectPermissionMap = new HashMap<>(); + Map>rolePermissionMap = new HashMap<>(); + + user.getUserRolePermissions().forEach(t->{ + if (StringUtils.equalsIgnoreCase(t.getUserRole().getType(), UserRoleType.PROJECT.name())) { + rolePermissionMap.put(t.getUserRole().getId(),t.getUserRolePermissions()); + } + }); + user.getUserRoleRelations().forEach(ug -> { + List userRolePermissions = rolePermissionMap.get(ug.getRoleId()); + if (CollectionUtils.isNotEmpty(userRolePermissions) && projectIds.contains(ug.getSourceId())) { + projectPermissionMap.put(ug.getSourceId(),userRolePermissions); + } + }); + projectPermissionMap.forEach((projectId, userRolePermissions)->{ + for (UserRolePermission userRolePermission : userRolePermissions) { + if (permissions.contains(userRolePermission.getPermissionId())) { + permissionProjectIdMap.computeIfAbsent(userRolePermission.getPermissionId(), key -> new HashSet<>()).add(projectId); + } + } + }); + return permissionProjectIdMap; + } + + + + }