refactor(系统设置): 优化用户组删除成员逻辑

This commit is contained in:
wxg0103 2024-03-14 12:34:06 +08:00 committed by 刘瑞斌
parent 5ceec82c42
commit 4c1e8ea363
25 changed files with 105 additions and 24 deletions

View File

@ -374,7 +374,7 @@ follow=关注
unfollow=取消关注
api_definition_exist=接口已存在
api_definition_mock_exist=接口 MOCK 已存在
execute_resource_pool_not_config_error=请在【项目管理-应用管理-接口测试】中选择资源池
execute_resource_pool_not_config_error=请在【项目管理-应用设置-接口测试】中选择资源池
resource_pool_execute_error=资源池调用失败
api_swagger_url_error=Swagger url无法连通
api_scenario_exist=场景已存在

View File

@ -354,7 +354,7 @@ follow=关注
unfollow=取消关注
api_definition_exist=接口已存在
api_definition_mock_exist=接口 MOCK 已存在
execute_resource_pool_not_config_error=请在【项目管理-应用管理-接口测试】中选择资源池
execute_resource_pool_not_config_error=请在【项目管理-应用设置-接口测试】中选择资源池
resource_pool_execute_error=资源池调用失败
api_swagger_url_error=Swagger url无法连通
api_scenario_exist=场景已存在

View File

@ -80,7 +80,7 @@ bug_relation_case.create_user.length_range=创建人长度必须在1-50之间
bug_not_exist=缺陷不存在
not_local_bug_error=非本地缺陷,无法操作
bug_tags_size_large_than=缺陷标签数量超过{0}个
third_party_not_config=项目应用管理的同步配置未启用, 或服务集成配置为空及未启用;
third_party_not_config=项目应用设置的同步配置未启用, 或服务集成配置为空及未启用;
bug_attachment_upload_error=缺陷附件上传失败
bug_attachment_link_error=缺陷附件关联失败
bug_attachment_delete_error=缺陷附件删除失败

View File

@ -80,7 +80,7 @@ bug_relation_case.create_user.length_range=创建人长度必须在1-50之间
bug_not_exist=缺陷不存在
not_local_bug_error=非本地缺陷,无法操作
bug_tags_size_large_than=缺陷标签数量超过{0}个
third_party_not_config=项目应用管理的同步配置未启用, 或服务集成配置为空及未启用;
third_party_not_config=项目应用设置的同步配置未启用, 或服务集成配置为空及未启用;
bug_attachment_upload_error=缺陷附件上传失败
bug_attachment_link_error=缺陷附件关联失败
bug_attachment_delete_error=缺陷附件删除失败

View File

@ -129,7 +129,7 @@ permission.project_template.name=模版管理
permission.project_message.name=消息管理
permission.project_version.name=版本管理
permission.project_fake_error.name=误报库
permission.project_application.name=应用管理
permission.project_application.name=应用设置
permission.project_application_test_plan.read=测试计划-查询
permission.project_application_test_plan.update=测试计划-编辑
permission.project_application_ui.read=UI测试-查询

View File

@ -143,7 +143,7 @@ permission.project_template.name=模版管理
permission.project_message.name=消息管理
permission.project_version.name=版本管理
permission.project_fake_error.name=误报库
permission.project_application.name=应用管理
permission.project_application.name=应用设置
permission.project_application_test_plan.read=测试计划-查询
permission.project_application_test_plan.update=测试计划-编辑
permission.project_application_ui.read=UI测试-查询

View File

@ -184,6 +184,8 @@ user_role_exist=用户组已存在
user_role_not_exist=用户组不存在
user_role_not_edit=用户组无法编辑
at_least_one_user_role_require=至少需要一个用户组
org_at_least_one_user_role_require=组织成员至少有一个用户组,如需从组织移除成员请在成员列表操作!
project_at_least_one_user_role_require=项目成员至少有一个用户组,如需从项目移除成员请在成员列表操作!
default_organization_not_allow_delete=默认组织无法删除
organization_template_permission_error=未开启组织模板
# plugin

View File

@ -186,6 +186,8 @@ user_role_exist=User role already exists
user_role_not_exist=User role not exist
user_role_not_edit=User role can not edit
at_least_one_user_role_require=At least one user role require
org_at_least_one_user_role_require=Organization members must have at least one user group. If you need to delete members from the organization, please operate in the member list!
project_at_least_one_user_role_require=Project members must have at least one user group. If you need to delete members from the project, please operate in the member list!
default_organization_not_allow_delete=Default organization not allow delete
organization_template_permission_error=The organization template is not turned on
# plugin

View File

@ -186,6 +186,8 @@ user_role_exist=用户组已存在
user_role_not_exist=用户组不存在
user_role_not_edit=用户组无法编辑
at_least_one_user_role_require=至少需要一个用户组
org_at_least_one_user_role_require=组织成员至少有一个用户组,如需从组织移除成员请在成员列表操作!
project_at_least_one_user_role_require=项目成员至少有一个用户组,如需从项目移除成员请在成员列表操作!
default_organization_not_allow_delete=默认组织无法删除
organization_template_permission_error=未开启组织模板
# plugin

View File

@ -185,6 +185,8 @@ user_role_exist=用戶組已存在
user_role_not_exist=用戶組不存在
user_role_not_edit=用戶組無法編輯
at_least_one_user_role_require=至少需要一個用戶組
org_at_least_one_user_role_require=組織成員至少有一個使用者群組,如需從組織中刪除成員請在成員清單操作!
project_at_least_one_user_role_require=项目成員至少有一個使用者群組,如需從项目中刪除成員請在成員清單操作!
default_organization_not_allow_delete=默認組織無法刪除
organization_template_permission_error=未開啟組織模板
# plugin

View File

@ -1,5 +1,6 @@
package io.metersphere.project.service;
import com.alibaba.excel.util.StringUtils;
import io.metersphere.project.dto.ProjectUserRoleDTO;
import io.metersphere.project.mapper.ExtProjectUserRoleMapper;
import io.metersphere.project.request.ProjectUserRoleMemberEditRequest;
@ -15,7 +16,6 @@ import io.metersphere.system.domain.UserRoleRelation;
import io.metersphere.system.domain.UserRoleRelationExample;
import io.metersphere.system.dto.permission.PermissionDefinitionItem;
import io.metersphere.system.dto.sdk.request.PermissionSettingUpdateRequest;
import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.mapper.UserRoleMapper;
import io.metersphere.system.mapper.UserRoleRelationMapper;
import io.metersphere.system.service.BaseUserRoleService;
@ -42,8 +42,6 @@ import static io.metersphere.system.controller.result.SystemResultCode.NO_PROJEC
@Transactional(rollbackFor = Exception.class)
public class ProjectUserRoleService extends BaseUserRoleService {
@Resource
UserMapper userMapper;
@Resource
UserRoleMapper userRoleMapper;
@Resource
@ -125,13 +123,23 @@ public class ProjectUserRoleService extends BaseUserRoleService {
public void removeMember(ProjectUserRoleMemberEditRequest request) {
String removeUserId = request.getUserIds().get(0);
checkMemberParam(removeUserId, request.getUserRoleId());
//检查移除的是不是管理员
if (StringUtils.equals(request.getUserRoleId(),InternalUserRole.PROJECT_ADMIN.getValue())) {
UserRoleRelationExample userRoleRelationExample = new UserRoleRelationExample();
userRoleRelationExample.createCriteria().andUserIdNotEqualTo(removeUserId)
.andSourceIdEqualTo(request.getProjectId())
.andRoleIdEqualTo(InternalUserRole.PROJECT_ADMIN.getValue());
if (userRoleRelationMapper.countByExample(userRoleRelationExample) == 0) {
throw new MSException(Translator.get("keep_at_least_one_administrator"));
}
}
// 移除项目-用户组的成员, 若成员只存在该项目下唯一用户组, 则提示不能移除
UserRoleRelationExample example = new UserRoleRelationExample();
example.createCriteria().andUserIdEqualTo(removeUserId)
.andRoleIdNotEqualTo(request.getUserRoleId())
.andSourceIdEqualTo(request.getProjectId());
if (userRoleRelationMapper.countByExample(example) == 0) {
throw new MSException(Translator.get("at_least_one_user_role_require"));
throw new MSException(Translator.get("project_at_least_one_user_role_require"));
}
example.clear();
example.createCriteria().andUserIdEqualTo(removeUserId)

View File

@ -12,9 +12,12 @@ import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.domain.User;
import io.metersphere.system.domain.UserRoleRelation;
import io.metersphere.system.dto.request.OrganizationUserRoleEditRequest;
import io.metersphere.system.dto.sdk.request.PermissionSettingUpdateRequest;
import io.metersphere.system.mapper.UserRoleRelationMapper;
import io.metersphere.system.service.BaseUserRolePermissionService;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.utils.Pager;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
@ -60,6 +63,8 @@ public class ProjectUserRoleControllerTests extends BaseTest {
public static final String PROJECT_USER_ROLE_LIST_MEMBER = "/user/role/project/list-member";
public static final String PROJECT_USER_ROLE_ADD_MEMBER = "/user/role/project/add-member";
public static final String PROJECT_USER_ROLE_REMOVE_MEMBER = "/user/role/project/remove-member";
@Resource
UserRoleRelationMapper userRoleRelationMapper;
@Test
@Order(0)
@ -414,6 +419,23 @@ public class ProjectUserRoleControllerTests extends BaseTest {
request.setUserIds(List.of("admin"));
// 成员用户组只有一个, 移除失败
this.requestPost(PROJECT_USER_ROLE_REMOVE_MEMBER, request, status().is5xxServerError());
//移除最后一个管理员移除失败
UserRoleRelation userRoleRelation = new UserRoleRelation();
userRoleRelation.setId(IDGenerator.nextStr());
userRoleRelation.setCreateUser("admin");
userRoleRelation.setRoleId(InternalUserRole.PROJECT_ADMIN.getValue());
userRoleRelation.setUserId("admin");
userRoleRelation.setSourceId("default-project-2");
userRoleRelation.setCreateTime(System.currentTimeMillis());
userRoleRelation.setOrganizationId("default-project-2");
userRoleRelationMapper.insert(userRoleRelation);
request = new ProjectUserRoleMemberEditRequest();
request.setProjectId("default-project-2");
request.setUserRoleId(InternalUserRole.PROJECT_ADMIN.getValue());
request.setUserIds(List.of("admin"));
// 成员用户组只有一个, 移除失败
this.requestPost(PROJECT_USER_ROLE_REMOVE_MEMBER, request, status().is5xxServerError());
}
@Test

View File

@ -119,7 +119,7 @@ public class SystemOrganizationController {
@PostMapping("/option/all")
@Operation(summary = "系统设置-系统-组织与项目-组织-获取系统所有组织下拉选项")
@RequiresPermissions(PermissionConstants.SYSTEM_ORGANIZATION_PROJECT_READ)
@RequiresPermissions(value = {PermissionConstants.SYSTEM_ORGANIZATION_PROJECT_READ, PermissionConstants.ORGANIZATION_PROJECT_READ}, logical = Logical.OR)
public List<OptionDTO> listAll() {
return organizationService.listAll();
}

View File

@ -105,7 +105,7 @@ public class OperationLogModule {
//项目管理-消息设置
public static final String PROJECT_MANAGEMENT_MESSAGE_MANAGEMENT_CONFIG = "PROJECT_MANAGEMENT_MESSAGE_MANAGEMENT_CONFIG";
public static final String PROJECT_MANAGEMENT_MESSAGE_MANAGEMENT_ROBOT = "PROJECT_MANAGEMENT_MESSAGE_MANAGEMENT_ROBOT";
//项目管理-应用管理
//项目管理-应用设置
public static final String PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT = "PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT";
// 项目模板管理-模板
public static final String PROJECT_MANAGEMENT_TEMPLATE_FUNCTIONAL_TEMPLATE = "PROJECT_MANAGEMENT_TEMPLATE_FUNCTIONAL_TEMPLATE";

View File

@ -1,5 +1,6 @@
package io.metersphere.system.service;
import com.alibaba.excel.util.StringUtils;
import io.metersphere.sdk.constants.InternalUserRole;
import io.metersphere.sdk.constants.UserRoleEnum;
import io.metersphere.sdk.constants.UserRoleType;
@ -101,13 +102,23 @@ public class OrganizationUserRoleService extends BaseUserRoleService {
public void removeMember(OrganizationUserRoleMemberEditRequest request) {
String removeUserId = request.getUserIds().get(0);
checkMemberParam(removeUserId, request.getUserRoleId());
//检查移除的是不是管理员
if (StringUtils.equals(request.getUserRoleId(),InternalUserRole.ORG_ADMIN.getValue())) {
UserRoleRelationExample userRoleRelationExample = new UserRoleRelationExample();
userRoleRelationExample.createCriteria().andUserIdNotEqualTo(removeUserId)
.andSourceIdEqualTo(request.getOrganizationId())
.andRoleIdEqualTo(InternalUserRole.ORG_ADMIN.getValue());
if (userRoleRelationMapper.countByExample(userRoleRelationExample) == 0) {
throw new MSException(Translator.get("keep_at_least_one_administrator"));
}
}
// 移除组织-用户组的成员, 若成员只存在该组织下唯一用户组, 则提示不能移除
UserRoleRelationExample example = new UserRoleRelationExample();
example.createCriteria().andUserIdEqualTo(removeUserId)
.andRoleIdNotEqualTo(request.getUserRoleId())
.andSourceIdEqualTo(request.getOrganizationId());
if (userRoleRelationMapper.countByExample(example) == 0) {
throw new MSException(Translator.get("at_least_one_user_role_require"));
throw new MSException(Translator.get("org_at_least_one_user_role_require"));
}
example.clear();
example.createCriteria().andUserIdEqualTo(removeUserId)

View File

@ -8,13 +8,16 @@ import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.domain.User;
import io.metersphere.system.domain.UserRole;
import io.metersphere.system.domain.UserRoleRelation;
import io.metersphere.system.dto.OrganizationDTO;
import io.metersphere.system.dto.request.OrganizationUserRoleEditRequest;
import io.metersphere.system.dto.request.OrganizationUserRoleMemberEditRequest;
import io.metersphere.system.dto.request.OrganizationUserRoleMemberRequest;
import io.metersphere.system.dto.sdk.request.PermissionSettingUpdateRequest;
import io.metersphere.system.mapper.UserRoleRelationMapper;
import io.metersphere.system.service.BaseUserRolePermissionService;
import io.metersphere.system.service.OrganizationService;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.utils.Pager;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
@ -50,6 +53,8 @@ public class OrganizationUserRoleControllerTests extends BaseTest {
private BaseUserRolePermissionService baseUserRolePermissionService;
@Resource
private OrganizationService organizationService;
@Resource
UserRoleRelationMapper userRoleRelationMapper;
public static final String ORGANIZATION_USER_ROLE_LIST = "/user/role/organization/list";
public static final String ORGANIZATION_USER_ROLE_ADD = "/user/role/organization/add";
@ -376,6 +381,22 @@ public class OrganizationUserRoleControllerTests extends BaseTest {
request.setUserIds(List.of("admin"));
// 成员用户组只有一个, 移除失败
this.requestPost(ORGANIZATION_USER_ROLE_REMOVE_MEMBER, request, status().is5xxServerError());
//移除最后一个管理员移除失败
UserRoleRelation userRoleRelation = new UserRoleRelation();
userRoleRelation.setId(IDGenerator.nextStr());
userRoleRelation.setCreateUser("admin");
userRoleRelation.setRoleId(InternalUserRole.ORG_ADMIN.getValue());
userRoleRelation.setUserId("admin");
userRoleRelation.setSourceId("default-organization-2");
userRoleRelation.setCreateTime(System.currentTimeMillis());
userRoleRelation.setOrganizationId("default-organization-2");
userRoleRelationMapper.insert(userRoleRelation);
request = new OrganizationUserRoleMemberEditRequest();
request.setOrganizationId("default-organization-2");
request.setUserRoleId(InternalUserRole.ORG_ADMIN.getValue());
request.setUserIds(List.of("admin"));
// 成员用户组只有一个, 移除失败
this.requestPost(ORGANIZATION_USER_ROLE_REMOVE_MEMBER, request, status().is5xxServerError());
}
@Test

View File

@ -189,9 +189,16 @@ export function getUserByProjectByOrg(organizationId: string, projectId: string,
}
// 系统或组织-获取项目下的资源池options
export function getPoolOptionsByOrgOrSystem(modulesIds: string[], rganizationId?: string) {
export function getPoolOptionsByOrgOrSystem(modulesIds: string[], organizationId?: string) {
return MSR.post({
url: orgUrl.getProjectPoolByOrgOrSystemUrl,
data: { rganizationId, modulesIds },
data: { organizationId, modulesIds },
});
}
// 组织-获取项目下的资源池options
export function getPoolOptionsByOrg(modulesIds: string[], organizationId?: string) {
return MSR.post({
url: orgUrl.getProjectPoolByOrg,
data: { organizationId, modulesIds },
});
}

View File

@ -94,3 +94,5 @@ export const getUserByOrganizationOrProjectUrl = '/organization/project/user-mem
export const getAdminByOrganizationOrProjectUrl = '/organization/project/user-admin-list/';
// 系统或组织-获取项目资源池下拉选项
export const getProjectPoolByOrgOrSystemUrl = '/system/project/pool-options';
// 组织-获取项目资源池下拉选项
export const getProjectPoolByOrg = '/organization/project/pool-options';

View File

@ -5,11 +5,11 @@
<script setup lang="ts">
import { computed, ref, watchEffect } from 'vue';
import { getPoolOptionsByOrgOrSystem } from '@/api/modules/setting/organizationAndProject';
import { getPoolOptionsByOrg, getPoolOptionsByOrgOrSystem } from '@/api/modules/setting/organizationAndProject';
const options = ref([]);
const fieldNames = { value: 'id', label: 'name' };
const props = defineProps<{ organizationId?: string; modelValue: string[]; moduleIds?: string[] }>();
const props = defineProps<{ organizationId?: string; modelValue: string[]; moduleIds?: string[]; isOrg?: boolean }>();
const loading = ref(false);
const emit = defineEmits<{
(e: 'update:modelValue', value: string[]): void;
@ -22,10 +22,10 @@
emit('update:modelValue', v);
},
});
const loadList = async (arr: string[], id?: string) => {
const loadList = async (arr: string[], id?: string, isOrg?: boolean) => {
try {
loading.value = true;
options.value = await getPoolOptionsByOrgOrSystem(arr, id);
options.value = !isOrg ? await getPoolOptionsByOrgOrSystem(arr, id) : await getPoolOptionsByOrg(arr, id);
} catch (error) {
options.value = [];
// eslint-disable-next-line no-console
@ -35,6 +35,6 @@
}
};
watchEffect(() => {
loadList(props.moduleIds || [], props.organizationId);
loadList(props.moduleIds || [], props.organizationId, props.isOrg || false);
});
</script>

View File

@ -329,6 +329,7 @@
class: 'mention',
},
// TODO userMap
// @ts-ignore
renderHTML({ options, node }) {
return [
'span',

View File

@ -2,7 +2,7 @@ export default {
'project.permission.projectAndPermission': 'Project & Permission',
'project.permission.project': 'Project',
'project.permission.basicInfo': 'Basic Info',
'project.permission.menuManagement': 'Menu Management',
'project.permission.menuManagement': 'Menu Setting',
'project.permission.templateManager': 'Template Manager',
'project.permission.projectVersion': 'Project Version',
'project.permission.memberPermission': 'Member Permission',

View File

@ -2,7 +2,7 @@ export default {
'project.permission.projectAndPermission': '项目与权限',
'project.permission.project': '项目',
'project.permission.basicInfo': '基本信息',
'project.permission.menuManagement': '应用管理',
'project.permission.menuManagement': '应用设置',
'project.permission.templateManager': '模板管理',
'project.permission.projectVersion': '项目版本',
'project.permission.memberPermission': '成员权限',

View File

@ -1,5 +1,5 @@
export default {
'project.menu.management': 'Application Management',
'project.menu.management': 'Application Setting',
'project.menu.manageTip':
'You can configure the switch of each function according to the usage scenario. After closing, the function entry will be hidden, and members cannot access this function and data; the data already generated will not be affected by this rule; when it is turned on again, it will be restored to the state before closing',
'project.menu.name': 'Menu Name',

View File

@ -1,5 +1,5 @@
export default {
'project.menu.management': '应用管理',
'project.menu.management': '应用设置',
'project.menu.manageTip':
'可根据使用场景配置各功能开关关闭后,将隐藏功能入口,成员无法访问该功能和数据;已产生的数据不够此规则影响;再次开启时,即恢复至关闭前状态',
'project.menu.name': '菜单名称',

View File

@ -76,6 +76,7 @@
v-model:modelValue="form.resourcePoolIds"
:module-ids="form.moduleIds"
:organization-id="currentOrgId"
:is-org="true"
/>
</a-form-item>
<a-form-item field="description" :label="t('system.organization.description')">