From 8690b9afd8ecc8e95663a8eb70d45fd91a4be9b6 Mon Sep 17 00:00:00 2001 From: wenyann <64353056+wenyann@users.noreply.github.com> Date: Tue, 2 Mar 2021 12:52:21 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E5=88=9B=E5=BB=BA=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E5=8A=A0=E6=89=A7=E8=A1=8C=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/components/TestCaseReviewEdit.vue | 31 +++++++++++++++++++ frontend/src/i18n/en-US.js | 1 + frontend/src/i18n/zh-CN.js | 1 + frontend/src/i18n/zh-TW.js | 1 + 4 files changed, 34 insertions(+) diff --git a/frontend/src/business/components/track/review/components/TestCaseReviewEdit.vue b/frontend/src/business/components/track/review/components/TestCaseReviewEdit.vue index b8301a29ac..904cd42a56 100644 --- a/frontend/src/business/components/track/review/components/TestCaseReviewEdit.vue +++ b/frontend/src/business/components/track/review/components/TestCaseReviewEdit.vue @@ -85,6 +85,9 @@ {{ $t('test_track.confirm') }} + + {{ $t('test_track.planning_execution') }} + @@ -134,6 +137,34 @@ export default { }; }, methods: { + reviewInfo(form) { + this.$refs['reviewForm'].validate((valid) => { + if (valid) { + let param = {}; + Object.assign(param, this.form); + param.name = param.name.trim(); + if (this.form.tags instanceof Array) { + this.form.tags = JSON.stringify(this.form.tags); + } + param.tags = this.form.tags; + if (param.name === '') { + this.$warning(this.$t('test_track.plan.input_plan_name')); + return; + } + + if (!this.compareTime(new Date().getTime(), this.form.endTime)) { + return false; + } + + this.result = this.$post('/test/case/review/' + this.operationType, param, response => { + this.dialogFormVisible = false; + this.$router.push('/track/review/view/' + response.data); + }); + } else { + return false; + } + }); + }, openCaseReviewEditDialog(caseReview) { this.resetForm(); this.setReviewerOptions(); diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index cc8af26412..7b15d6a7b1 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -1039,6 +1039,7 @@ export default { test_track: "Track", confirm: "Confirm", cancel: "Cancel", + planning_execution: "Planning&Execution", project: "Project", save: "Save", return: "Return", diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index de0e5423ff..c5bb583634 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -1041,6 +1041,7 @@ export default { }, test_track: { test_track: "测试跟踪", + planning_execution: "规划&执行", confirm: "确 定", cancel: "取 消", project: "项目", diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index b92acf43a4..7da7076b41 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -1041,6 +1041,7 @@ export default { test_track: "測試跟蹤", confirm: "確 定", cancel: "取 消", + planning_execution: "規劃&執行", project: "項目", save: "保 存", return: "返 回", From 140ce7c21104a32c965c821e1be1bb63b47fd9da Mon Sep 17 00:00:00 2001 From: shiziyuan9527 Date: Tue, 2 Mar 2021 14:06:10 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix(=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92):?= =?UTF-8?q?=20=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E8=A7=A6=E5=8F=91?= =?UTF-8?q?=E7=9A=84=E6=8E=A5=E5=8F=A3=E7=94=A8=E4=BE=8B=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/api/service/ApiTestCaseService.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java index 9c252f97a8..fbad14793a 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java @@ -486,6 +486,7 @@ public class ApiTestCaseService { mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); MsTestElement element = mapper.readValue(testCaseWithBLOBs.getRequest(), new TypeReference() { }); + element.setProjectId(testCaseWithBLOBs.getProjectId()); if (StringUtils.isBlank(request.getEnvironmentId())) { TestPlanApiCaseExample example = new TestPlanApiCaseExample(); example.createCriteria().andTestPlanIdEqualTo(request.getTestPlanId()).andApiCaseIdEqualTo(request.getCaseId()); @@ -513,9 +514,14 @@ public class ApiTestCaseService { ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class); ApiTestEnvironmentWithBLOBs environment = environmentService.get(request.getEnvironmentId()); ParameterConfig parameterConfig = new ParameterConfig(); -// if (environment != null && environment.getConfig() != null) { -// parameterConfig.setConfig(JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class)); -// } + + Map envConfig = new HashMap<>(16); + if (environment != null && environment.getConfig() != null) { + EnvironmentConfig environmentConfig = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class); + envConfig.put(testCaseWithBLOBs.getProjectId(), environmentConfig); + parameterConfig.setConfig(envConfig); + } + testPlan.toHashTree(jmeterHashTree, testPlan.getHashTree(), parameterConfig); return jmeterHashTree; } From 739d194854e5585142893782411a76c8e7b72147 Mon Sep 17 00:00:00 2001 From: shiziyuan9527 Date: Tue, 2 Mar 2021 14:10:29 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix(=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92):?= =?UTF-8?q?=20=E6=8B=BC=E5=86=99=E5=AF=BC=E8=87=B4=E7=9A=84=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E5=8F=B0=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/track/plan/components/TestPlanList.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/business/components/track/plan/components/TestPlanList.vue b/frontend/src/business/components/track/plan/components/TestPlanList.vue index d0905a2a10..7677da6ffd 100644 --- a/frontend/src/business/components/track/plan/components/TestPlanList.vue +++ b/frontend/src/business/components/track/plan/components/TestPlanList.vue @@ -83,13 +83,13 @@ - - + Date: Tue, 2 Mar 2021 18:01:09 +0800 Subject: [PATCH 4/5] =?UTF-8?q?refactor(=E5=9C=BA=E6=99=AF=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E8=B0=83=E6=95=B4=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../automation/scenario/EditApiScenario.vue | 33 +--- .../api/automation/scenario/EnvPopover.vue | 49 ++++++ .../api/automation/scenario/EnvSelect.vue | 149 ++++++++++++++++++ 3 files changed, 204 insertions(+), 27 deletions(-) create mode 100644 frontend/src/business/components/api/automation/scenario/EnvPopover.vue create mode 100644 frontend/src/business/components/api/automation/scenario/EnvSelect.vue diff --git a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue index 4f4ef858e5..ac270ddc2a 100644 --- a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue @@ -111,26 +111,9 @@ 共享cookie - - 环境配置 - - - - - - - - - - - - - - - - - - + + {{$t('api_test.request.debug')}} @@ -186,9 +169,6 @@ - - @@ -236,8 +216,7 @@ import MsComponentConfig from "./component/ComponentConfig"; import {handleCtrlSEvent} from "../../../../../common/js/utils"; import {getProject} from "@/business/components/api/automation/scenario/event"; - import ApiScenarioEnv from "@/business/components/api/automation/scenario/ApiScenarioEnv"; - + import EnvPopover from "@/business/components/api/automation/scenario/EnvPopover"; export default { name: "EditApiScenario", props: { @@ -245,7 +224,6 @@ currentScenario: {}, }, components: { - ApiScenarioEnv, MsVariableList, ScenarioRelevance, ScenarioApiRelevance, @@ -255,6 +233,7 @@ MsApiCustomize, ApiImport, MsComponentConfig, + EnvPopover }, data() { return { @@ -712,7 +691,7 @@ // this.$error(this.$t('api_test.environment.select_environment')); // return; // } - let sign = this.$refs.apiScenarioEnv.checkEnv(); + let sign = this.$refs.envPopover.checkEnv(); if (!sign) { return; } diff --git a/frontend/src/business/components/api/automation/scenario/EnvPopover.vue b/frontend/src/business/components/api/automation/scenario/EnvPopover.vue new file mode 100644 index 0000000000..e9807cddab --- /dev/null +++ b/frontend/src/business/components/api/automation/scenario/EnvPopover.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/frontend/src/business/components/api/automation/scenario/EnvSelect.vue b/frontend/src/business/components/api/automation/scenario/EnvSelect.vue new file mode 100644 index 0000000000..93d437382e --- /dev/null +++ b/frontend/src/business/components/api/automation/scenario/EnvSelect.vue @@ -0,0 +1,149 @@ + + + + + From 1c4d0c90122ab297887a3f096374920377f4faf9 Mon Sep 17 00:00:00 2001 From: "song.tianyang" Date: Tue, 2 Mar 2021 18:22:22 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7excel=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E5=AF=BC=E5=85=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户excel批量导入功能 --- .../dto/definition/request/MsTestElement.java | 2 +- .../mapper/ext/ExtOrganizationMapper.java | 6 + .../base/mapper/ext/ExtOrganizationMapper.xml | 4 + .../base/mapper/ext/ExtUserMapper.java | 2 + .../base/mapper/ext/ExtUserMapper.xml | 4 + .../base/mapper/ext/ExtWorkspaceMapper.java | 2 + .../base/mapper/ext/ExtWorkspaceMapper.xml | 4 + .../controller/UserController.java | 17 ++ .../excel/domain/UserExcelData.java | 44 +++ .../excel/domain/UserExcelDataCn.java | 91 ++++++ .../excel/domain/UserExcelDataFactory.java | 18 ++ .../excel/domain/UserExcelDataTw.java | 91 ++++++ .../excel/domain/UserExcelDataUs.java | 93 ++++++ .../excel/listener/EasyExcelListener.java | 2 +- .../excel/listener/UserDataListener.java | 277 ++++++++++++++++++ .../service/OrganizationService.java | 4 + .../io/metersphere/service/UserService.java | 106 +++++++ .../metersphere/service/WorkspaceService.java | 5 + .../main/resources/i18n/messages.properties | 13 + .../resources/i18n/messages_en_US.properties | 13 + .../resources/i18n/messages_zh_CN.properties | 13 + .../resources/i18n/messages_zh_TW.properties | 13 + .../common/components/MsTableHeader.vue | 15 + .../components/settings/system/User.vue | 14 +- .../settings/system/components/UserImport.vue | 136 +++++++++ frontend/src/i18n/en-US.js | 3 + frontend/src/i18n/zh-CN.js | 3 + frontend/src/i18n/zh-TW.js | 3 + frontend/vue.config.js | 2 +- 29 files changed, 993 insertions(+), 7 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/excel/domain/UserExcelData.java create mode 100644 backend/src/main/java/io/metersphere/excel/domain/UserExcelDataCn.java create mode 100644 backend/src/main/java/io/metersphere/excel/domain/UserExcelDataFactory.java create mode 100644 backend/src/main/java/io/metersphere/excel/domain/UserExcelDataTw.java create mode 100644 backend/src/main/java/io/metersphere/excel/domain/UserExcelDataUs.java create mode 100644 backend/src/main/java/io/metersphere/excel/listener/UserDataListener.java create mode 100644 frontend/src/business/components/settings/system/components/UserImport.vue diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java index 261b52098b..f3884336ab 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java @@ -256,7 +256,7 @@ public abstract class MsTestElement { randomVariableConfig.setProperty("outputFormat", item.getValue()); randomVariableConfig.setProperty("minimumValue", item.getMinNumber()); randomVariableConfig.setProperty("maximumValue", item.getMaxNumber()); - randomVariableConfig.setComment(item.getDescription()); + randomVariableConfig.setComment(StringUtils.isEmpty(item.getDescription()) ? "" : item.getDescription()); tree.add(randomVariableConfig); }); } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.java index 6d5209ad8a..aab424dadb 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.java @@ -1,8 +1,14 @@ package io.metersphere.base.mapper.ext; +import io.metersphere.base.domain.Organization; +import io.metersphere.dto.OrganizationMemberDTO; import org.apache.ibatis.annotations.Param; +import java.util.List; + public interface ExtOrganizationMapper { int checkSourceRole(@Param("sourceId") String sourceId,@Param("userId") String userId,@Param("roleId") String roleId); + + List findAllIdAndName(); } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.xml index 5dafb7ee75..c968753e01 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.xml @@ -10,4 +10,8 @@ and ur.role_id = #{roleId} + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.java index b6f09dbdd3..0fbc3044fc 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.java @@ -23,4 +23,6 @@ public interface ExtUserMapper { @MapKey("id") Map queryNameByIds(List userIds); + + List selectAllId(); } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.xml index d772524bf6..825e901dd4 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.xml @@ -72,4 +72,8 @@ + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.java index 5bff8104b1..dbdede5cf3 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.java @@ -10,4 +10,6 @@ public interface ExtWorkspaceMapper { List getWorkspaceWithOrg(@Param("request") WorkspaceRequest request); List getWorkspaceIdsByOrgId(@Param("orgId") String orgId); + + List findAllIdAndName(); } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.xml index 34013f1817..fc65c59d02 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.xml @@ -18,4 +18,8 @@ where organization_id = #{orgId} + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/controller/UserController.java b/backend/src/main/java/io/metersphere/controller/UserController.java index 7b7da40432..78507c2432 100644 --- a/backend/src/main/java/io/metersphere/controller/UserController.java +++ b/backend/src/main/java/io/metersphere/controller/UserController.java @@ -17,7 +17,9 @@ import io.metersphere.controller.request.organization.AddOrgMemberRequest; import io.metersphere.controller.request.organization.QueryOrgMemberRequest; import io.metersphere.dto.UserDTO; import io.metersphere.dto.UserRoleDTO; +import io.metersphere.excel.domain.ExcelResponse; import io.metersphere.i18n.Translator; +import io.metersphere.service.CheckPermissionService; import io.metersphere.service.OrganizationService; import io.metersphere.service.UserService; import io.metersphere.service.WorkspaceService; @@ -25,8 +27,10 @@ import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; import java.util.List; @RequestMapping("user") @@ -39,6 +43,8 @@ public class UserController { private OrganizationService organizationService; @Resource private WorkspaceService workspaceService; + @Resource + private CheckPermissionService checkPermissionService; @PostMapping("/special/add") @RequiresRoles(RoleConstants.ADMIN) @@ -298,4 +304,15 @@ public class UserController { return userService.searchUser(condition); } + @GetMapping("/export/template") + @RequiresRoles(value = {RoleConstants.ADMIN, RoleConstants.ORG_ADMIN, RoleConstants.TEST_MANAGER}, logical = Logical.OR) + public void testCaseTemplateExport(HttpServletResponse response) { + userService.userTemplateExport(response); + } + + @PostMapping("/import/{userId}") + @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) + public ExcelResponse testCaseImport(MultipartFile file, @PathVariable String userId) { + return userService.userImport(file, userId); + } } diff --git a/backend/src/main/java/io/metersphere/excel/domain/UserExcelData.java b/backend/src/main/java/io/metersphere/excel/domain/UserExcelData.java new file mode 100644 index 0000000000..5678ccbdde --- /dev/null +++ b/backend/src/main/java/io/metersphere/excel/domain/UserExcelData.java @@ -0,0 +1,44 @@ +package io.metersphere.excel.domain; + +import com.alibaba.excel.annotation.ExcelIgnore; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UserExcelData { + + @ExcelIgnore + private String id; + @ExcelIgnore + private String name; + @ExcelIgnore + private String email; + @ExcelIgnore + private String password; + @ExcelIgnore + private String phone; + @ExcelIgnore + private String userIsAdmin; + @ExcelIgnore + private String userIsOrgAdmin; + @ExcelIgnore + private String orgAdminOrganization; + @ExcelIgnore + private String userIsOrgMember; + @ExcelIgnore + private String orgMemberOrganization; + @ExcelIgnore + private String userIsTestManager; + @ExcelIgnore + private String testManagerWorkspace; + @ExcelIgnore + private String userIsTester; + @ExcelIgnore + private String testerWorkspace; + @ExcelIgnore + private String userIsViewer; + @ExcelIgnore + private String viewerWorkspace; + +} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/excel/domain/UserExcelDataCn.java b/backend/src/main/java/io/metersphere/excel/domain/UserExcelDataCn.java new file mode 100644 index 0000000000..e5e069ff0f --- /dev/null +++ b/backend/src/main/java/io/metersphere/excel/domain/UserExcelDataCn.java @@ -0,0 +1,91 @@ +package io.metersphere.excel.domain; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +@Data +@ColumnWidth(15) +public class UserExcelDataCn extends UserExcelData { + @NotBlank(message = "{cannot_be_null}") + @Length(max = 255) + @ExcelProperty("ID") + private String id; + + @NotBlank(message = "{cannot_be_null}") + @Length(max = 255) + @ExcelProperty("姓名") + private String name; + + @NotBlank(message = "{cannot_be_null}") + @Length(max = 255) + @ExcelProperty("电子邮箱") + @ColumnWidth(30) + @Pattern(regexp = "^[a-zA-Z0-9_._-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$", message = "{user_import_format_wrong}") + private String email; + + @NotBlank(message = "{cannot_be_null}") + @Length(max = 255) + @ExcelProperty("密码") + @ColumnWidth(30) + @Pattern(regexp = "^(?![0-9]+$)(?![^0-9]+$)(?![a-zA-Z]+$)(?![^a-zA-Z]+$)(?![a-zA-Z0-9]+$)[a-zA-Z0-9\\S]{8,30}$", message = "{user_import_format_wrong}") + private String password; + + @ExcelProperty("电话") + @Length(max = 11) + @Pattern(regexp = "^1(3|4|5|6|7|8|9)\\d{9}$", message = "{user_import_format_wrong}") + private String phone; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("是否是系统管理员(是/否)") + private String userIsAdmin; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("是否是组织管理员(是/否)") + private String userIsOrgAdmin; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("组织管理员工作空间") + private String orgAdminOrganization; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("是否是组织成员(是/否)") + private String userIsOrgMember; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("组织成员工作空间") + private String orgMemberOrganization; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("是否是测试经理(是/否)") + private String userIsTestManager; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("测试经理工作空间") + private String testManagerWorkspace; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("是否是测试成员(是/否)") + private String userIsTester; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("测试成员工作空间") + private String testerWorkspace; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("是否是只读用戶(是/否)") + private String userIsViewer; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("只读用户工作空间") + private String viewerWorkspace; +} diff --git a/backend/src/main/java/io/metersphere/excel/domain/UserExcelDataFactory.java b/backend/src/main/java/io/metersphere/excel/domain/UserExcelDataFactory.java new file mode 100644 index 0000000000..a3b5b8f20b --- /dev/null +++ b/backend/src/main/java/io/metersphere/excel/domain/UserExcelDataFactory.java @@ -0,0 +1,18 @@ +package io.metersphere.excel.domain; + +import org.springframework.context.i18n.LocaleContextHolder; + +import java.util.Locale; + +public class UserExcelDataFactory implements ExcelDataFactory { + @Override + public Class getExcelDataByLocal() { + Locale locale = LocaleContextHolder.getLocale(); + if (Locale.US.toString().equalsIgnoreCase(locale.toString())) { + return UserExcelDataUs.class; + } else if (Locale.TRADITIONAL_CHINESE.toString().equalsIgnoreCase(locale.toString())) { + return UserExcelDataTw.class; + } + return UserExcelDataCn.class; + } +} diff --git a/backend/src/main/java/io/metersphere/excel/domain/UserExcelDataTw.java b/backend/src/main/java/io/metersphere/excel/domain/UserExcelDataTw.java new file mode 100644 index 0000000000..757876a32d --- /dev/null +++ b/backend/src/main/java/io/metersphere/excel/domain/UserExcelDataTw.java @@ -0,0 +1,91 @@ +package io.metersphere.excel.domain; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +@Data +@ColumnWidth(15) +public class UserExcelDataTw extends TestCaseExcelData { + @NotBlank(message = "{cannot_be_null}") + @Length(max = 255) + @ExcelProperty("ID") + private String id; + + @NotBlank(message = "{cannot_be_null}") + @Length(max = 255) + @ExcelProperty("姓名") + private String name; + + @NotBlank(message = "{cannot_be_null}") + @Length(max = 255) + @ExcelProperty("電子郵箱") + @ColumnWidth(30) + @Pattern(regexp = "^[a-zA-Z0-9_._-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$", message = "{user_import_format_wrong}") + private String email; + + @NotBlank(message = "{cannot_be_null}") + @Length(max = 255) + @ExcelProperty("密碼") + @ColumnWidth(30) + @Pattern(regexp = "^(?![0-9]+$)(?![^0-9]+$)(?![a-zA-Z]+$)(?![^a-zA-Z]+$)(?![a-zA-Z0-9]+$)[a-zA-Z0-9\\S]{8,30}$", message = "{user_import_format_wrong}") + private String password; + + @ExcelProperty("電話") + @Length(max = 11) + @Pattern(regexp = "^1(3|4|5|6|7|8|9)\\d{9}$", message = "{user_import_format_wrong}") + private String phone; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("是否是系統管理員(是/否)") + private String userIsAdmin; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("是否是組織管理員(是/否)") + private String userIsOrgAdmin; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("組織管理員工作空間") + private String orgAdminOrganization; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("是否是組織成員(是/否)") + private String userIsOrgMember; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("組織成員工作空間") + private String orgMemberOrganization; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("是否是測試經理(是/否)") + private String userIsTestManager; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("測試經理工作空間") + private String testManagerWorkspace; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("是否是測試成員(是/否)") + private String userIsTester; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("測試成員工作空間") + private String testerWorkspace; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("是否是只讀用戶(是/否)") + private String userIsViewer; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("只讀用戶工作空間") + private String viewerWorkspace; +} diff --git a/backend/src/main/java/io/metersphere/excel/domain/UserExcelDataUs.java b/backend/src/main/java/io/metersphere/excel/domain/UserExcelDataUs.java new file mode 100644 index 0000000000..a64e92e63d --- /dev/null +++ b/backend/src/main/java/io/metersphere/excel/domain/UserExcelDataUs.java @@ -0,0 +1,93 @@ +package io.metersphere.excel.domain; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + + +@Data +@ColumnWidth(15) +public class UserExcelDataUs extends UserExcelData { + @NotBlank(message = "{cannot_be_null}") + @Length(max = 255) + @ExcelProperty("Id") + private String id; + + @NotBlank(message = "{cannot_be_null}") + @Length(max = 255) + @ExcelProperty("Name") + private String name; + + @NotBlank(message = "{cannot_be_null}") + @Length(max = 255) + @ExcelProperty("E-mail") + @ColumnWidth(30) + @Pattern(regexp = "^[a-zA-Z0-9_._-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$", message = "{user_import_format_wrong}") + private String email; + + @NotBlank(message = "{cannot_be_null}") + @Length(max = 255) + @ExcelProperty("Password") + @ColumnWidth(30) + @Pattern(regexp = "^(?![0-9]+$)(?![^0-9]+$)(?![a-zA-Z]+$)(?![^a-zA-Z]+$)(?![a-zA-Z0-9]+$)[a-zA-Z0-9\\S]{8,30}$", message = "{user_import_format_wrong}") + private String password; + + @ExcelProperty("Phone") + @Length(max = 11) + @Pattern(regexp = "^1(3|4|5|6|7|8|9)\\d{9}$", message = "{user_import_format_wrong}") + private String phone; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("User is administrator(Yes/No)") + private String userIsAdmin; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("User is organization manager(Yes/No)") + private String userIsOrgAdmin; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("Manager in witch organization") + private String orgAdminOrganization; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("User is organization member(Yes/No)") + private String userIsOrgMember; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("Member in witch organization") + private String orgMemberOrganization; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("User is test manager(Yes/No)") + private String userIsTestManager; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("Workspace of test manager") + private String testManagerWorkspace; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("User is tester(Yes/No)") + private String userIsTester; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("Workspace of tester") + private String testerWorkspace; + + @NotBlank(message = "{cannot_be_null}") + @ExcelProperty("User is read-only user(Yes/No)") + private String userIsViewer; + + @Length(max = 100) + @ColumnWidth(30) + @ExcelProperty("Workspace of read-only user") + private String viewerWorkspace; +} diff --git a/backend/src/main/java/io/metersphere/excel/listener/EasyExcelListener.java b/backend/src/main/java/io/metersphere/excel/listener/EasyExcelListener.java index 85f6dc31f6..3f2dbf0326 100644 --- a/backend/src/main/java/io/metersphere/excel/listener/EasyExcelListener.java +++ b/backend/src/main/java/io/metersphere/excel/listener/EasyExcelListener.java @@ -8,6 +8,7 @@ import com.alibaba.excel.util.StringUtils; import io.metersphere.commons.utils.LogUtil; import io.metersphere.excel.domain.ExcelErrData; import io.metersphere.excel.domain.TestCaseExcelData; +import io.metersphere.excel.domain.UserExcelData; import io.metersphere.excel.utils.ExcelValidateHelper; import io.metersphere.i18n.Translator; @@ -139,5 +140,4 @@ public abstract class EasyExcelListener extends AnalysisEventListener { public List> getErrList() { return errList; } - } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/excel/listener/UserDataListener.java b/backend/src/main/java/io/metersphere/excel/listener/UserDataListener.java new file mode 100644 index 0000000000..7f5f484c1c --- /dev/null +++ b/backend/src/main/java/io/metersphere/excel/listener/UserDataListener.java @@ -0,0 +1,277 @@ +package io.metersphere.excel.listener; + +import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.controller.request.member.UserRequest; +import io.metersphere.excel.domain.UserExcelData; +import io.metersphere.i18n.Translator; +import io.metersphere.service.UserService; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; +import java.util.stream.Collectors; + +public class UserDataListener extends EasyExcelListener { + + private UserService userService; + + //key:workspace.name value:id + Map workspaceNameMap; + //key:Organization.name value:id + Map orgNameMap; + //已经保存的用户ID + List savedUserId; + + public UserDataListener(Class clazz,Map workspaceNameMap,Map orgNameMap) { + this.clazz = clazz; + this.workspaceNameMap = workspaceNameMap; + this.orgNameMap = orgNameMap; + this.userService = (UserService) CommonBeanFactory.getBean("userService"); + savedUserId = userService.selectAllId(); + } + + @Override + public String validate(UserExcelData data, String errMsg) { + StringBuilder stringBuilder = new StringBuilder(errMsg); + + //判断组织管理员组织 + String orgManagerOrgCheck = this.checkOrganization(data.getUserIsOrgAdmin(), data.getOrgAdminOrganization()); + if (orgManagerOrgCheck != null) { + stringBuilder.append(orgManagerOrgCheck); + } + //判断组织成员组织 + String orgMemberOrgCheck = this.checkOrganization(data.getUserIsOrgMember(), data.getOrgMemberOrganization()); + if (orgMemberOrgCheck != null) { + stringBuilder.append(orgMemberOrgCheck); + } + //判断测试经理工作空间 + String testManagerWorkspaceCheck = this.checkWorkSpace(data.getUserIsTestManager(), data.getTestManagerWorkspace()); + if (testManagerWorkspaceCheck != null) { + stringBuilder.append(testManagerWorkspaceCheck); + } + //判断测试人员工作空间 + String testerWorkspaceCheck = this.checkWorkSpace(data.getUserIsTester(), data.getTesterWorkspace()); + if (testerWorkspaceCheck != null) { + stringBuilder.append(testerWorkspaceCheck); + } + //判断只读用户工作空间 + String viewerWorkspaceCheck = this.checkWorkSpace(data.getUserIsViewer(), data.getViewerWorkspace()); + if (viewerWorkspaceCheck != null) { + stringBuilder.append(viewerWorkspaceCheck); + } + return stringBuilder.toString(); + } + + @Override + public void saveData() { + //检查有无重复数据 + String checkRepeatDataResult = this.checkRepeatIdAndEmail(list); + if(!StringUtils.isEmpty(checkRepeatDataResult)){ + MSException.throwException(checkRepeatDataResult); + } + + //无错误数据才插入数据 + if (!errList.isEmpty()) { + return; + } + Collections.reverse(list); + List result = list.stream().map(item -> this.convert2UserRequest(item)).collect(Collectors.toList()); + + for (UserRequest userRequest : result) { + String id = userRequest.getId(); + if (savedUserId.contains(id)) { + //已经在数据库内的,走更新逻辑 + userService.updateUserRole(userRequest); + } else { + //不再数据库中的走新建逻辑 + userService.insert(userRequest); + } + } + } + + /** + * 检查工作空间 + * + * @param userRoleInExcel excel表里的用户权限填写信息 + * @param workspaceInfoInExcel excel表中用户的工作空间填写信息 + * @return 报错信息 + */ + private String checkWorkSpace(String userRoleInExcel, String workspaceInfoInExcel) { + String result = null; + if (StringUtils.equalsIgnoreCase(Translator.get("options_yes"), userRoleInExcel)) { + String[] workspaceArr = workspaceInfoInExcel.split("\n"); + for (String workspace : + workspaceArr) { + if (!workspaceNameMap.containsKey(workspace)) { + if (result == null) { + result = new String(Translator.get("user_import_workspace_not_fond") + ":" + workspace + "; "); + } else { + result += Translator.get("user_import_workspace_not_fond") + ":" + workspace + "; "; + } + } + } + } + return result; + } + + /** + * 检查组织 + * + * @param userRoleInExcel excel表里的用户权限填写信息 + * @param organizationInfoInExcel excel表中用户组织填写信息 + * @return 报错信息 + */ + private String checkOrganization(String userRoleInExcel, String organizationInfoInExcel) { + String result = null; + if (StringUtils.equalsIgnoreCase(Translator.get("options_yes"), userRoleInExcel)) { + String[] organizationArr = organizationInfoInExcel.split("\n"); + for (String organization : + organizationArr) { + if (!orgNameMap.containsKey(organization)) { + if (result == null) { + result = new String(Translator.get("user_import_organization_not_fond") + ":" + organization + "; "); + } else { + result += Translator.get("user_import_organization_not_fond") + ":" + organization + "; "; + } + } + } + } + return result; + } + + /** + * 通过excel的信息,以及id字典对象,获取相对应的ID + * + * @param userRoleInExcel excel中的信息,是否进行工作空间或者组织的id转化 + * @param nameStringInExce excel中的信息,组织或者工作空间的名称 + * @param idDic id字典对象,传入组织或者工作空间的类 + * @return 转化后的id集合 + */ + private List getIdByExcelInfoAndIdDic(String userRoleInExcel, String nameStringInExce, Map idDic) { + List resultList = new ArrayList<>(); + if (StringUtils.equalsIgnoreCase(Translator.get("options_yes"), userRoleInExcel)) { + String[] nameArr = nameStringInExce.split("\n"); + for (String name : nameArr) { + if (idDic.containsKey(name)) { + resultList.add(idDic.get(name)); + } + } + } + return resultList; + } + + private UserRequest convert2UserRequest(UserExcelData data) { + UserRequest request = new UserRequest(); + request.setId(data.getId()); + request.setStatus("1"); + request.setSource("LOCAL"); + request.setName(data.getName()); + request.setEmail(data.getEmail()); + request.setPhone(data.getPhone()); + //这里的password要加密 + request.setPassword(data.getPassword()); + + List> roleMapList = new ArrayList<>(); + //判断是否是Admin + if (StringUtils.equalsIgnoreCase(Translator.get("options_yes"), data.getUserIsAdmin())) { + List adminIdList = new ArrayList<>(); + adminIdList.add("adminSourceId"); + Map adminRoleMap = this.genRoleMap("admin", adminIdList); + roleMapList.add(adminRoleMap); + } + //判断组织管理员 + List orgManagerOrdIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsOrgAdmin(), data.getOrgAdminOrganization(), orgNameMap); + if (!orgManagerOrdIdList.isEmpty()) { + Map orgAdminRoleMap = this.genRoleMap("org_admin", orgManagerOrdIdList); + roleMapList.add(orgAdminRoleMap); + } + //判断组织成员 + List orgMemberOrdIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsOrgMember(), data.getOrgMemberOrganization(), orgNameMap); + if (!orgMemberOrdIdList.isEmpty()) { + Map orgMemberRoleMap = this.genRoleMap("org_member", orgMemberOrdIdList); + roleMapList.add(orgMemberRoleMap); + } + //判断测试经理 + List testManagerWorkspaceIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsTestManager(), data.getTestManagerWorkspace(), workspaceNameMap); + if (!testManagerWorkspaceIdList.isEmpty()) { + Map testManagerRoleMap = this.genRoleMap("test_manager", testManagerWorkspaceIdList); + roleMapList.add(testManagerRoleMap); + } + //判断测试人员 + List testgerWorkspaceIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsTester(), data.getTesterWorkspace(), workspaceNameMap); + if (!testgerWorkspaceIdList.isEmpty()) { + Map testerRoleMap = this.genRoleMap("test_user", testgerWorkspaceIdList); + roleMapList.add(testerRoleMap); + } + //判断只读用户 + List viewerWorkspaceIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsViewer(), data.getViewerWorkspace(), workspaceNameMap); + if (!viewerWorkspaceIdList.isEmpty()) { + Map testViewerRoleMap = this.genRoleMap("test_viewer", viewerWorkspaceIdList); + roleMapList.add(testViewerRoleMap); + } + request.setRoles(roleMapList); + return request; + } + + /** + * 封装用户权限数据格式 + * @param roleName 权限名称 + * @param roleIdList 对应的权限ID + * @return 保存用户时,对应的数据权限的数据格式 + */ + private Map genRoleMap(String roleName, List roleIdList) { + Map roleMap = new HashMap<>(); + if (roleName == null || roleIdList == null) { + return roleMap; + } + roleMap.put("id", roleName); + roleMap.put("ids", roleIdList); + return roleMap; + } + + /** + * 检查是否有重复的ID和Email + * @param list + * @return + */ + private String checkRepeatIdAndEmail(List list){ + String checkRepeatIdResult = new String(); + + List allIdList = new ArrayList<>(); + List allEmailList = new ArrayList<>(); + + for (UserExcelData data: list) { + allIdList.add(data.getId()); + allEmailList.add(data.getEmail()); + } + List repeatIdList = allIdList.stream() + .collect(Collectors.toMap(e -> e, e -> 1, (a, b) -> a + b)) // 获得元素出现频率的 Map,键为元素,值为元素出现的次数 + .entrySet().stream() // Set转换为Stream + .filter(entry -> entry.getValue() > 1) // 过滤出元素出现次数大于 1 的 entry + .map(entry -> entry.getKey()) // 获得 entry 的键(重复元素)对应的 Stream + .collect(Collectors.toList()); + if(!repeatIdList.isEmpty()){ + checkRepeatIdResult += Translator.get("user_import_id_is_repeat") + ":"; + for (String repeatID:repeatIdList) { + checkRepeatIdResult += repeatID+";"; + } + } + + List repeatEmailList = allEmailList.stream() + .collect(Collectors.toMap(e -> e, e -> 1, (a, b) -> a + b)) // 获得元素出现频率的 Map,键为元素,值为元素出现的次数 + .entrySet().stream() // Set转换为Stream + .filter(entry -> entry.getValue() > 1) // 过滤出元素出现次数大于 1 的 entry + .map(entry -> entry.getKey()) // 获得 entry 的键(重复元素)对应的 Stream + .collect(Collectors.toList()); + if(!repeatEmailList.isEmpty()){ + checkRepeatIdResult += Translator.get("user_import_email_is_repeat") + ":"; + for (String repeatEmail:repeatEmailList) { + checkRepeatIdResult += repeatEmail+";"; + } + } + + return checkRepeatIdResult; + } + + +} diff --git a/backend/src/main/java/io/metersphere/service/OrganizationService.java b/backend/src/main/java/io/metersphere/service/OrganizationService.java index 71f89263d4..5ce5fdcab2 100644 --- a/backend/src/main/java/io/metersphere/service/OrganizationService.java +++ b/backend/src/main/java/io/metersphere/service/OrganizationService.java @@ -178,4 +178,8 @@ public class OrganizationService { MSException.throwException(Translator.get("organization_does_not_belong_to_user")); } } + + public List findAllIdAndName(){ + return extOrganizationMapper.findAllIdAndName(); + } } diff --git a/backend/src/main/java/io/metersphere/service/UserService.java b/backend/src/main/java/io/metersphere/service/UserService.java index 5c59cffa03..cc09358f1e 100644 --- a/backend/src/main/java/io/metersphere/service/UserService.java +++ b/backend/src/main/java/io/metersphere/service/UserService.java @@ -1,16 +1,20 @@ package io.metersphere.service; +import com.alibaba.excel.EasyExcelFactory; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.*; +import io.metersphere.base.mapper.ext.ExtOrganizationMapper; import io.metersphere.base.mapper.ext.ExtUserMapper; import io.metersphere.base.mapper.ext.ExtUserRoleMapper; import io.metersphere.commons.constants.RoleConstants; +import io.metersphere.commons.constants.TestCaseConstants; import io.metersphere.commons.constants.UserSource; import io.metersphere.commons.constants.UserStatus; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.user.SessionUser; import io.metersphere.commons.utils.CodingUtil; import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.SessionUtils; import io.metersphere.controller.ResultHolder; import io.metersphere.controller.request.LoginRequest; @@ -20,11 +24,20 @@ import io.metersphere.controller.request.member.QueryMemberRequest; import io.metersphere.controller.request.member.UserRequest; import io.metersphere.controller.request.organization.AddOrgMemberRequest; import io.metersphere.controller.request.organization.QueryOrgMemberRequest; +import io.metersphere.dto.OrganizationMemberDTO; import io.metersphere.dto.UserDTO; import io.metersphere.dto.UserRoleDTO; +import io.metersphere.dto.WorkspaceDTO; +import io.metersphere.excel.domain.*; +import io.metersphere.excel.listener.EasyExcelListener; +import io.metersphere.excel.listener.TestCaseDataListener; +import io.metersphere.excel.listener.UserDataListener; +import io.metersphere.excel.utils.EasyExcelExporter; import io.metersphere.i18n.Translator; import io.metersphere.notice.domain.UserDetail; import io.metersphere.security.MsUserToken; +import io.metersphere.track.request.testcase.QueryTestCaseRequest; +import io.metersphere.xmind.XmindCaseParser; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; @@ -35,8 +48,10 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; import java.util.*; import java.util.stream.Collectors; @@ -63,6 +78,8 @@ public class UserService { @Lazy @Resource private WorkspaceService workspaceService; + @Resource + private ExtOrganizationMapper extOrganizationMapper; public List queryTypeByIds(List userIds) { return extUserMapper.queryTypeByIds(userIds); @@ -616,4 +633,93 @@ public class UserService { ssoService.logout(); } } + + public void userTemplateExport(HttpServletResponse response) { + try { + EasyExcelExporter easyExcelExporter = new EasyExcelExporter(new UserExcelDataFactory().getExcelDataByLocal()); + easyExcelExporter.export(response, generateExportTemplate(), + Translator.get("user_import_template_name"), Translator.get("user_import_template_sheet")); + } catch (Exception e) { + MSException.throwException(e); + } + } + + private List generateExportTemplate() { + List list = new ArrayList<>(); + List types = TestCaseConstants.Type.getValues(); + List methods = TestCaseConstants.Method.getValues(); + SessionUser user = SessionUtils.getUser(); + for (int i = 1; i <= 2; i++) { + UserExcelData data = new UserExcelData(); + data.setId("user_id_"+i); + data.setName(Translator.get("user") + i); + String workspace = ""; + for (int workspaceIndex = 1; workspaceIndex <= i; workspaceIndex++) { + if (workspaceIndex == 1) { + workspace = "workspace" + workspaceIndex; + } else { + workspace = workspace + "\n" + "workspace" + workspaceIndex; + } + } + data.setUserIsAdmin(Translator.get("options_no")); + data.setUserIsTester(Translator.get("options_no")); + data.setUserIsOrgMember(Translator.get("options_no")); + data.setUserIsViewer(Translator.get("options_no")); + data.setUserIsTestManager(Translator.get("options_no")); + data.setUserIsOrgAdmin(Translator.get("options_yes")); + data.setOrgAdminOrganization(workspace); + list.add(data); + } + + list.add(new UserExcelData()); + UserExcelData explain = new UserExcelData(); + explain.setName(Translator.get("do_not_modify_header_order")); + explain.setOrgAdminOrganization("多个工作空间请换行展示"); + list.add(explain); + return list; + } + + public ExcelResponse userImport(MultipartFile multipartFile, String userId) { + + ExcelResponse excelResponse = new ExcelResponse(); + String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId(); + List> errList = null; + if (multipartFile == null) { + MSException.throwException(Translator.get("upload_fail")); + } + try { + Class clazz = new UserExcelDataFactory().getExcelDataByLocal(); + + Map orgNameMap = new HashMap<>(); + Map workspaceNameMap = new HashMap<>(); + + List organizationList = extOrganizationMapper.findAllIdAndName(); + for (OrganizationMemberDTO model : organizationList) { + orgNameMap.put(model.getName(),model.getId()); + } + List workspaceList = workspaceService.findAllIdAndName(); + for (WorkspaceDTO model : workspaceList) { + workspaceNameMap.put(model.getName(),model.getId()); + } + EasyExcelListener easyExcelListener = new UserDataListener(clazz,workspaceNameMap,orgNameMap); + EasyExcelFactory.read(multipartFile.getInputStream(), clazz, easyExcelListener).sheet().doRead(); + errList = easyExcelListener.getErrList(); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException(e.getMessage()); + } + + //如果包含错误信息就导出错误信息 + if (!errList.isEmpty()) { + excelResponse.setSuccess(false); + excelResponse.setErrList(errList); + } else { + excelResponse.setSuccess(true); + } + return excelResponse; + } + + public List selectAllId() { + return extUserMapper.selectAllId(); + } } diff --git a/backend/src/main/java/io/metersphere/service/WorkspaceService.java b/backend/src/main/java/io/metersphere/service/WorkspaceService.java index 5868b270d2..4589c99929 100644 --- a/backend/src/main/java/io/metersphere/service/WorkspaceService.java +++ b/backend/src/main/java/io/metersphere/service/WorkspaceService.java @@ -1,5 +1,6 @@ package io.metersphere.service; +import com.sun.corba.se.spi.orbutil.threadpool.Work; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.ProjectMapper; import io.metersphere.base.mapper.UserMapper; @@ -284,4 +285,8 @@ public class WorkspaceService { projectExample.createCriteria().andWorkspaceIdEqualTo(workspaceId); return projectMapper.selectByExample(projectExample); } + + public List findAllIdAndName(){ + return extWorkspaceMapper.findAllIdAndName(); + } } diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 93f6073c99..108c7e15a7 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -29,6 +29,17 @@ test_case_remark= test_case_step_desc= test_case_step_result= test_case= +user= +user_import_template_name= +user_import_template_sheet= +user_import_format_wrong= +user_import_id_is_repeat= +user_import_email_is_repeat= +user_import_password_format_wrong= +user_import_phone_format_wrong= +user_import_email_format_wrong= +user_import_organization_not_fond= +user_import_workspace_not_fond= module= preconditions_optional= step_tip_separate= @@ -41,5 +52,7 @@ remark_optional= do_not_modify_header_order= module_created_automatically= options= +options_yes= +options_no= please_input_workspace_member= test_case_report_template_repeat= \ No newline at end of file diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties index 5ea0284f71..eed471852f 100644 --- a/backend/src/main/resources/i18n/messages_en_US.properties +++ b/backend/src/main/resources/i18n/messages_en_US.properties @@ -105,6 +105,17 @@ test_case_step_desc=Step description test_case_step_result=Step result test_case_module=Module test_case=Test case +user=User +user_import_template_name=User import templates +user_import_template_sheet=templates +user_import_format_wrong=input error +user_import_id_is_repeat=Id repeat +user_import_email_is_repeat=E-mail repeat +user_import_password_format_wrong=Wrong password format +user_import_phone_format_wrong=Wrong phone format +user_import_email_format_wrong=Wrong email format +user_import_organization_not_fond=Organization is not found +user_import_workspace_not_fond=Workspace is not found module=Module preconditions_optional=Preconditions optional step_tip_separate=Each step is separated by a new line @@ -117,6 +128,8 @@ remark_optional=Remark optional do_not_modify_header_order=Do not modify the header order module_created_automatically=If there is no such module, will be created automatically options=options +options_yes=Yes +options_no=No please_input_workspace_member=Please input workspace merber test_case_report_template_repeat=The workspace has the same name template plan_name_already_exists=Test plan name already exists diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties index 87c7dd1353..6935a01c0d 100644 --- a/backend/src/main/resources/i18n/messages_zh_CN.properties +++ b/backend/src/main/resources/i18n/messages_zh_CN.properties @@ -105,6 +105,17 @@ test_case_step_desc=步骤描述 test_case_step_result=预期结果 test_case_module=所属模块 test_case=测试用例 +user=用户 +user_import_template_name=用户导入模板 +user_import_template_sheet=模版 +user_import_format_wrong=格式错误 +user_import_id_is_repeat=ID重复 +user_import_email_is_repeat=E-mail重复 +user_import_password_format_wrong=密码格式错误 +user_import_phone_format_wrong=手机号码格式错误 +user_import_email_format_wrong=电子邮箱格式错误 +user_import_organization_not_fond=组织未找到 +user_import_workspace_not_fond=工作空间未找到 module=模块 preconditions_optional=前置条件选填 step_tip_separate=每个步骤以换行分隔 @@ -117,6 +128,8 @@ remark_optional=备注选填 do_not_modify_header_order=请勿修改表头顺序 module_created_automatically=若无该模块将自动创建 options=选项 +options_yes=是 +options_no=否 please_input_workspace_member=请填写该工作空间相关人员 test_case_report_template_repeat=同一工作空间下不能存在同名模版 plan_name_already_exists=测试计划名称已存在 diff --git a/backend/src/main/resources/i18n/messages_zh_TW.properties b/backend/src/main/resources/i18n/messages_zh_TW.properties index 485b69f6d6..3386da47a6 100644 --- a/backend/src/main/resources/i18n/messages_zh_TW.properties +++ b/backend/src/main/resources/i18n/messages_zh_TW.properties @@ -105,6 +105,17 @@ test_case_step_desc=步驟描述 test_case_step_result=預期結果 test_case_module=所屬模塊 test_case=測試用例 +user=用戶 +user_import_template_name=用戶導入模板 +user_import_template_sheet=模板 +user_import_format_wrong=格式錯誤 +user_import_id_is_repeat=ID重複 +user_import_email_is_repeat=E-mail重複 +user_import_password_format_wrong=密碼格式錯誤 +user_import_phone_format_wrong=手機號碼格式錯誤 +user_import_email_format_wrong=電子郵箱格式錯誤 +user_import_organization_not_fond=組織未找到 +user_import_workspace_not_fond=工作空間未找到 module=模塊 preconditions_optional=前置條件選填 step_tip_separate=每個步驟以換行分隔 @@ -117,6 +128,8 @@ remark_optional=備註選填 do_not_modify_header_order=請勿修改表頭順序 module_created_automatically=若無該模塊將自動創建 options=選項 +options_yes=是 +options_no=否 please_input_workspace_member=請填寫該工作空間相關人員 test_case_report_template_repeat=同壹工作空間下不能存在同名模版 plan_name_already_exists=測試計劃名稱已存在 diff --git a/frontend/src/business/components/common/components/MsTableHeader.vue b/frontend/src/business/components/common/components/MsTableHeader.vue index 463e5feadb..2a9918eae9 100644 --- a/frontend/src/business/components/common/components/MsTableHeader.vue +++ b/frontend/src/business/components/common/components/MsTableHeader.vue @@ -10,6 +10,8 @@ + @@ -47,6 +49,10 @@ type: Boolean, default: true }, + showImport: { + type: Boolean, + default: false + }, showRun: { type: Boolean, default: false @@ -60,6 +66,12 @@ return this.$t('commons.create'); } }, + importTip: { + type: String, + default() { + return this.$t('commons.import'); + } + }, runTip: { type: String, @@ -84,6 +96,9 @@ create() { this.$emit('create'); }, + importData() { + this.$emit('import'); + }, runTest() { this.$emit('runTest') }, diff --git a/frontend/src/business/components/settings/system/User.vue b/frontend/src/business/components/settings/system/User.vue index adf3841cb6..e7ce93f6a5 100644 --- a/frontend/src/business/components/settings/system/User.vue +++ b/frontend/src/business/components/settings/system/User.vue @@ -3,8 +3,9 @@ @@ -323,7 +324,7 @@ @confirm="editUserPassword('editPasswordForm')"/> - + @@ -339,6 +340,7 @@ import MsRolesTag from "../../common/components/MsRolesTag"; import {ROLE_ADMIN} from "@/common/js/constants"; import {getCurrentUser} from "../../../../common/js/utils"; import {PHONE_REGEX} from "@/common/js/regex"; +import UserImport from "@/business/components/settings/system/components/UserImport"; export default { name: "MsUser", @@ -349,7 +351,8 @@ export default { MsTableOperator, MsDialogFooter, MsTableOperatorButton, - MsRolesTag + MsRolesTag, + UserImport }, data() { return { @@ -579,6 +582,9 @@ export default { this.userRole = response.data; }) }, + importUserDialogOpen(){ + this.$refs.userImportDialog.open(); + }, addRole(validForm) { this.$refs[validForm].validate(valid => { if (valid) { diff --git a/frontend/src/business/components/settings/system/components/UserImport.vue b/frontend/src/business/components/settings/system/components/UserImport.vue new file mode 100644 index 0000000000..8be1ff9fa9 --- /dev/null +++ b/frontend/src/business/components/settings/system/components/UserImport.vue @@ -0,0 +1,136 @@ + + + + + + + diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index cc8af26412..a1eccd783d 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -2,8 +2,11 @@ export default { commons: { cover: 'Cover', not_cover: 'Not Cover', + import: 'Import', + import_success: 'Import success', import_mode: 'Import mode', import_module: 'Import module', + import_user: 'Import user', please_fill_in_the_template: 'Please fill in the template', cut_back_old_version: 'Cut back to old version', cut_back_new_version: 'Switch back to new version', diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index de0e5423ff..0895ea320e 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -2,8 +2,11 @@ export default { commons: { cover: '覆盖', not_cover: '不覆盖', + import: '导入', + import_success: '导入成功', import_mode: '导入模式', import_module: '导入模块', + import_user: '导入用户', please_fill_in_the_template: '请填写模版内容', cut_back_old_version: '切回旧版', cut_back_new_version: '切回新版', diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index b92acf43a4..e4d4993d19 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -2,8 +2,11 @@ export default { commons: { cover: '覆蓋', not_cover: '不覆蓋', + import: '導入', + import_success: '導入成功', import_mode: '導入模式', import_module: '導入模塊', + import_user: '導入用戶', please_fill_in_the_template: '請填寫模版內容', cut_back_old_version: '切回舊版', cut_back_new_version: '切回新版', diff --git a/frontend/vue.config.js b/frontend/vue.config.js index b1e45ef92d..50c7cbf1b9 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -10,7 +10,7 @@ module.exports = { port: 8080, proxy: { //1.8需求:增加分享功能,不登陆即可看到文档页面。所以代理设置增加了(?!/document)文档页面的相关信息 - // ['^((?!/login)']: { + // ['^(?!/login)']: { ['^((?!/login)(?!/document))']: { target: 'http://localhost:8081', ws: true,