refactor(系统设置): 组织至少保留一个管理员

This commit is contained in:
wxg0103 2024-02-07 15:08:01 +08:00 committed by wxg0103
parent 68bd55647d
commit 5662e5abe7
8 changed files with 75 additions and 45 deletions

View File

@ -9,6 +9,7 @@ import io.metersphere.project.request.ProjectMemberBatchDeleteRequest;
import io.metersphere.project.request.ProjectMemberEditRequest; import io.metersphere.project.request.ProjectMemberEditRequest;
import io.metersphere.project.request.ProjectMemberRequest; import io.metersphere.project.request.ProjectMemberRequest;
import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.constants.InternalUserRole;
import io.metersphere.sdk.constants.UserRoleEnum; import io.metersphere.sdk.constants.UserRoleEnum;
import io.metersphere.sdk.constants.UserRoleType; import io.metersphere.sdk.constants.UserRoleType;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
@ -223,6 +224,14 @@ public class ProjectMemberService {
List<LogDTO> logs = new ArrayList<>(); List<LogDTO> logs = new ArrayList<>();
// 项目不存在, 则不移除 // 项目不存在, 则不移除
checkProjectExist(projectId); checkProjectExist(projectId);
//判断用户是不是最后一个管理员 如果是 就报错
UserRoleRelationExample userRoleRelationExample = new UserRoleRelationExample();
userRoleRelationExample.createCriteria().andUserIdNotEqualTo(userId)
.andSourceIdEqualTo(projectId)
.andRoleIdEqualTo(InternalUserRole.PROJECT_ADMIN.getValue());
if (userRoleRelationMapper.countByExample(userRoleRelationExample) == 0) {
throw new MSException(Translator.get("keep_at_least_one_administrator"));
}
// 移除成员, 则移除该成员在该项目下的所有用户组 // 移除成员, 则移除该成员在该项目下的所有用户组
UserRoleRelationExample example = new UserRoleRelationExample(); UserRoleRelationExample example = new UserRoleRelationExample();
example.createCriteria().andSourceIdEqualTo(projectId).andUserIdEqualTo(userId); example.createCriteria().andSourceIdEqualTo(projectId).andUserIdEqualTo(userId);
@ -331,6 +340,7 @@ public class ProjectMemberService {
/** /**
* 获取项目评论下拉成员选项 * 获取项目评论下拉成员选项
*
* @param keyword 搜索关键字 * @param keyword 搜索关键字
* @param projectId 项目ID * @param projectId 项目ID
* @return 用户集合信息 * @return 用户集合信息

View File

@ -196,6 +196,7 @@ public class ProjectMemberControllerTests extends BaseTest {
@Order(12) @Order(12)
public void testRemoveMemberError() throws Exception { public void testRemoveMemberError() throws Exception {
this.requestGet(REMOVE_MEMBER + "/default-project-member-x/default-project-member-user-1", status().is5xxServerError()); this.requestGet(REMOVE_MEMBER + "/default-project-member-x/default-project-member-user-1", status().is5xxServerError());
this.requestGet(REMOVE_MEMBER + "/default-project-member-test-1/admin", status().is5xxServerError());
} }
@Test @Test

View File

@ -21,7 +21,8 @@ INSERT INTO user_role_relation (id, user_id, role_id, source_id, organization_id
INSERT INTO user_role_relation (id, user_id, role_id, source_id, organization_id, create_time, create_user) VALUES INSERT INTO user_role_relation (id, user_id, role_id, source_id, organization_id, create_time, create_user) VALUES
(UUID(), 'default-project-member-user-1', 'project_admin', 'default-project-member-test', 'default-organization-member-test', UNIX_TIMESTAMP() * 1000, 'admin'), (UUID(), 'default-project-member-user-1', 'project_admin', 'default-project-member-test', 'default-organization-member-test', UNIX_TIMESTAMP() * 1000, 'admin'),
(UUID(), 'default-project-member-user-2', 'project_admin', 'default-project-member-test', 'default-organization-member-test', UNIX_TIMESTAMP() * 1000, 'admin'), (UUID(), 'default-project-member-user-2', 'project_admin', 'default-project-member-test', 'default-organization-member-test', UNIX_TIMESTAMP() * 1000, 'admin'),
(UUID(), 'default-project-member-user-del', 'project_admin', 'default-project-member-test', 'default-organization-member-test', UNIX_TIMESTAMP() * 1000, 'admin'); (UUID(), 'default-project-member-user-del', 'project_admin', 'default-project-member-test', 'default-organization-member-test', UNIX_TIMESTAMP() * 1000, 'admin'),
(UUID(), 'admin', 'org_admin', 'default-project-member-test-1', 'default-organization-member-test', UNIX_TIMESTAMP() * 1000, 'admin');

View File

@ -4,6 +4,7 @@ import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated; import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -27,5 +28,6 @@ public class OrganizationEditRequest implements Serializable {
private String description; private String description;
@Schema(description = "成员ID集合") @Schema(description = "成员ID集合")
@NotEmpty(message = "{project.member_count.not_blank}", groups = {Created.class, Updated.class})
private List<String> userIds; private List<String> userIds;
} }

View File

@ -107,6 +107,7 @@ public class OrganizationService {
/** /**
* 更新组织名称 * 更新组织名称
*
* @param organizationDTO 组织请求参数 * @param organizationDTO 组织请求参数
*/ */
public void updateName(OrganizationDTO organizationDTO) { public void updateName(OrganizationDTO organizationDTO) {
@ -120,6 +121,7 @@ public class OrganizationService {
/** /**
* 更新组织 * 更新组织
*
* @param organizationDTO 组织请求参数 * @param organizationDTO 组织请求参数
*/ */
public void update(OrganizationDTO organizationDTO) { public void update(OrganizationDTO organizationDTO) {
@ -134,7 +136,6 @@ public class OrganizationService {
List<String> addOrgAdmins = organizationDTO.getUserIds(); List<String> addOrgAdmins = organizationDTO.getUserIds();
// 旧的组织管理员ID // 旧的组织管理员ID
List<String> oldOrgAdmins = getOrgAdminIds(organizationDTO.getId()); List<String> oldOrgAdmins = getOrgAdminIds(organizationDTO.getId());
if (CollectionUtils.isNotEmpty(addOrgAdmins)) {
// 需要新增组织管理员ID // 需要新增组织管理员ID
List<String> addIds = addOrgAdmins.stream().filter(addOrgAdmin -> !oldOrgAdmins.contains(addOrgAdmin)).toList(); List<String> addIds = addOrgAdmins.stream().filter(addOrgAdmin -> !oldOrgAdmins.contains(addOrgAdmin)).toList();
// 需要删除的组织管理员ID // 需要删除的组织管理员ID
@ -152,18 +153,11 @@ public class OrganizationService {
deleteExample.createCriteria().andSourceIdEqualTo(organizationDTO.getId()).andRoleIdEqualTo(InternalUserRole.ORG_ADMIN.getValue()).andUserIdIn(deleteIds); deleteExample.createCriteria().andSourceIdEqualTo(organizationDTO.getId()).andRoleIdEqualTo(InternalUserRole.ORG_ADMIN.getValue()).andUserIdIn(deleteIds);
userRoleRelationMapper.deleteByExample(deleteExample); userRoleRelationMapper.deleteByExample(deleteExample);
} }
} else {
// 前端传入的组织管理员ID为空删除所有组织管理员
if (CollectionUtils.isNotEmpty(oldOrgAdmins)) {
UserRoleRelationExample example = new UserRoleRelationExample();
example.createCriteria().andSourceIdEqualTo(organizationDTO.getId()).andRoleIdEqualTo(InternalUserRole.ORG_ADMIN.getValue());
userRoleRelationMapper.deleteByExample(example);
}
}
} }
/** /**
* 删除组织 * 删除组织
*
* @param organizationDeleteRequest 组织删除参数 * @param organizationDeleteRequest 组织删除参数
*/ */
public void delete(OrganizationDeleteRequest organizationDeleteRequest) { public void delete(OrganizationDeleteRequest organizationDeleteRequest) {
@ -176,6 +170,7 @@ public class OrganizationService {
/** /**
* 恢复组织 * 恢复组织
*
* @param id 组织ID * @param id 组织ID
*/ */
public void recover(String id) { public void recover(String id) {
@ -185,6 +180,7 @@ public class OrganizationService {
/** /**
* 开启组织 * 开启组织
*
* @param id 组织ID * @param id 组织ID
*/ */
public void enable(String id) { public void enable(String id) {
@ -194,6 +190,7 @@ public class OrganizationService {
/** /**
* 结束组织 * 结束组织
*
* @param id 组织ID * @param id 组织ID
*/ */
public void disable(String id) { public void disable(String id) {
@ -318,6 +315,14 @@ public class OrganizationService {
public void removeMember(String organizationId, String userId, String currentUser) { public void removeMember(String organizationId, String userId, String currentUser) {
List<LogDTO> logs = new ArrayList<>(); List<LogDTO> logs = new ArrayList<>();
checkOrgExistById(organizationId); checkOrgExistById(organizationId);
//检查用户是不是最后一个管理员
UserRoleRelationExample userRoleRelationExample = new UserRoleRelationExample();
userRoleRelationExample.createCriteria().andUserIdNotEqualTo(userId)
.andSourceIdEqualTo(organizationId)
.andRoleIdEqualTo(InternalUserRole.ORG_ADMIN.getValue());
if (userRoleRelationMapper.countByExample(userRoleRelationExample) == 0) {
throw new MSException(Translator.get("keep_at_least_one_administrator"));
}
//删除组织下项目与成员的关系 //删除组织下项目与成员的关系
List<String> projectIds = getProjectIds(organizationId); List<String> projectIds = getProjectIds(organizationId);
if (CollectionUtils.isNotEmpty(projectIds)) { if (CollectionUtils.isNotEmpty(projectIds)) {
@ -1015,6 +1020,7 @@ public class OrganizationService {
/** /**
* 校验组织是否存在 * 校验组织是否存在
* 这里使用静态方法避免需要注入导致循环依赖 * 这里使用静态方法避免需要注入导致循环依赖
*
* @param id * @param id
* @return * @return
*/ */
@ -1024,6 +1030,7 @@ public class OrganizationService {
/** /**
* 剩余天数 * 剩余天数
*
* @param deleteTime 删除时间 * @param deleteTime 删除时间
* @return 剩余天数 * @return 剩余天数
*/ */

View File

@ -282,7 +282,7 @@ public class OrganizationControllerTests extends BaseTest {
Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(pageData.getList())).size() <= organizationRequest.getPageSize()); Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(pageData.getList())).size() <= organizationRequest.getPageSize());
//判断是否为空 //判断是否为空
List<OrgUserExtend> orgUserExtends = JSON.parseArray(JSON.toJSONString(pageData.getList()), OrgUserExtend.class); List<OrgUserExtend> orgUserExtends = JSON.parseArray(JSON.toJSONString(pageData.getList()), OrgUserExtend.class);
Assertions.assertTrue(CollectionUtils.isEmpty(orgUserExtends)); Assertions.assertTrue(CollectionUtils.isNotEmpty(orgUserExtends));
} }
@ -309,13 +309,14 @@ public class OrganizationControllerTests extends BaseTest {
@Test @Test
@Order(14) @Order(14)
public void removeOrgMemberSuccess() throws Exception { public void removeOrgMemberSuccess() throws Exception {
this.requestGet(ORGANIZATION_REMOVE_MEMBER + "/sys_default_organization_6/sys_default_user", status().isOk()); this.requestGet(ORGANIZATION_REMOVE_MEMBER + "/sys_default_organization_6/sys_default_user4", status().isOk());
this.requestGet(ORGANIZATION_REMOVE_MEMBER + "/sys_default_organization_6/sys_default_user", status().is5xxServerError());
} }
@Test @Test
@Order(15) @Order(15)
public void removeOrgMemberSuccessWithNoProject() throws Exception { public void removeOrgMemberSuccessWithNoProject() throws Exception {
this.requestGet(ORGANIZATION_REMOVE_MEMBER + "/sys_default_organization_3/sys_default_user", status().isOk()); this.requestGet(ORGANIZATION_REMOVE_MEMBER + "/sys_default_organization_3/sys_default_user4", status().isOk());
} }
@Test @Test
@ -425,7 +426,6 @@ public class OrganizationControllerTests extends BaseTest {
} }
@Test @Test
@Order(23) @Order(23)
public void getNotExistUserListByOrgSuccess() throws Exception { public void getNotExistUserListByOrgSuccess() throws Exception {
@ -474,7 +474,6 @@ public class OrganizationControllerTests extends BaseTest {
} }
private void listByKeyWord(String keyWord, String orgId, boolean compare, String userRoleId, String projectId, boolean checkPart, String noUserRoleId, String noProjectId) throws Exception { private void listByKeyWord(String keyWord, String orgId, boolean compare, String userRoleId, String projectId, boolean checkPart, String noUserRoleId, String noProjectId) throws Exception {
OrganizationRequest organizationRequest = new OrganizationRequest(); OrganizationRequest organizationRequest = new OrganizationRequest();
organizationRequest.setCurrent(1); organizationRequest.setCurrent(1);

View File

@ -145,8 +145,8 @@ public class SystemOrganizationControllerTests extends BaseTest{
request.setUserIds(List.of("user-id1")); request.setUserIds(List.of("user-id1"));
this.requestPost(ORGANIZATION_UPDATE, request).andExpect(status().isOk()); this.requestPost(ORGANIZATION_UPDATE, request).andExpect(status().isOk());
request.setUserIds(List.of()); request.setUserIds(List.of());
this.requestPost(ORGANIZATION_UPDATE, request).andExpect(status().isOk()); this.requestPost(ORGANIZATION_UPDATE, request).andExpect(status().isBadRequest());
this.requestPost(ORGANIZATION_UPDATE, request).andExpect(status().isOk()); this.requestPost(ORGANIZATION_UPDATE, request).andExpect(status().isBadRequest());
} }
@Test @Test
@ -154,12 +154,15 @@ public class SystemOrganizationControllerTests extends BaseTest{
public void testUpdateOrganizationError() throws Exception { public void testUpdateOrganizationError() throws Exception {
OrganizationEditRequest request = new OrganizationEditRequest(); OrganizationEditRequest request = new OrganizationEditRequest();
request.setName("default-4"); request.setName("default-4");
request.setUserIds(List.of("user-id1", "user-id2"));
// 组织不存在 // 组织不存在
request.setId("default-organization-x"); request.setId("default-organization-x");
this.requestPost(ORGANIZATION_UPDATE, request).andExpect(status().is5xxServerError()); this.requestPost(ORGANIZATION_UPDATE, request).andExpect(status().is5xxServerError());
// 组织存在, 但是名称重复 // 组织存在, 但是名称重复
request.setId("default-organization-5"); request.setId("default-organization-5");
this.requestPost(ORGANIZATION_UPDATE, request).andExpect(status().is5xxServerError()); this.requestPost(ORGANIZATION_UPDATE, request).andExpect(status().is5xxServerError());
request.setUserIds(new ArrayList<>());
this.requestPost(ORGANIZATION_UPDATE, request).andExpect(status().isBadRequest());
} }
@Test @Test
@ -381,6 +384,8 @@ public class SystemOrganizationControllerTests extends BaseTest{
this.requestGet(ORGANIZATION_REMOVE_MEMBER + "/default-organization-3/admin", status().isOk()); this.requestGet(ORGANIZATION_REMOVE_MEMBER + "/default-organization-3/admin", status().isOk());
// 日志校验 // 日志校验
checkLog("default-organization-3", OperationLogType.DELETE); checkLog("default-organization-3", OperationLogType.DELETE);
this.requestGet(ORGANIZATION_REMOVE_MEMBER + "/default-organization-5/user-id1", status().is5xxServerError());
// 权限校验 // 权限校验
requestGetPermissionTest(PermissionConstants.SYSTEM_ORGANIZATION_PROJECT_MEMBER_DELETE, ORGANIZATION_REMOVE_MEMBER + "/default-organization-3/admin"); requestGetPermissionTest(PermissionConstants.SYSTEM_ORGANIZATION_PROJECT_MEMBER_DELETE, ORGANIZATION_REMOVE_MEMBER + "/default-organization-3/admin");
} }

View File

@ -50,4 +50,9 @@ INSERT INTO user_role(id, name, description, internal, type, create_time, update
INSERT INTO user_role_relation(id, user_id, role_id, source_id, organization_id, create_time, create_user) VALUE INSERT INTO user_role_relation(id, user_id, role_id, source_id, organization_id, create_time, create_user) VALUE
('gyq_user_role_relation_test', 'sys_default_user4', 'sys_default_org_role_id_5', 'sys_default_organization_6', 'sys_default_organization_6', UNIX_TIMESTAMP() * 1000, 'admin'); ('gyq_user_role_relation_test', 'sys_default_user4', 'sys_default_org_role_id_5', 'sys_default_organization_6', 'sys_default_organization_6', UNIX_TIMESTAMP() * 1000, 'admin');
INSERT INTO user_role_relation(id, user_id, role_id, source_id, organization_id, create_time, create_user) VALUE
('gyq_user_role_relation_test-1', 'sys_default_user', 'org_admin', 'sys_default_organization_6', 'sys_default_organization_6', UNIX_TIMESTAMP() * 1000, 'admin');
INSERT INTO user_role_relation(id, user_id, role_id, source_id, organization_id, create_time, create_user) VALUE
('gyq_user_role_relation_test-2', 'admin', 'org_admin', 'sys_default_organization_3', 'sys_default_organization_3', UNIX_TIMESTAMP() * 1000, 'admin');
INSERT INTO user_role_relation(id, user_id, role_id, source_id, organization_id, create_time, create_user) VALUE
('gyq_user_role_relation_test-3', 'sys_default_user4', 'sys_default_project_role_id_3', 'sys_org_projectId', 'sys_default_organization_3', UNIX_TIMESTAMP() * 1000, 'admin');