feat: 用户excel批量导入功能

用户excel批量导入功能
This commit is contained in:
song.tianyang 2021-03-02 18:22:22 +08:00
parent b7ddf4194a
commit 1c4d0c9012
29 changed files with 993 additions and 7 deletions

View File

@ -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);
});
}

View File

@ -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<OrganizationMemberDTO> findAllIdAndName();
}

View File

@ -10,4 +10,8 @@
and ur.role_id = #{roleId}
</select>
<select id="findAllIdAndName" resultType="io.metersphere.dto.OrganizationMemberDTO">
select id,name from Organization
</select>
</mapper>

View File

@ -23,4 +23,6 @@ public interface ExtUserMapper {
@MapKey("id")
Map<String, User> queryNameByIds(List<String> userIds);
List<String> selectAllId();
}

View File

@ -72,4 +72,8 @@
</foreach>
</select>
<select id="selectAllId" resultType="java.lang.String">
select id from `user`
</select>
</mapper>

View File

@ -10,4 +10,6 @@ public interface ExtWorkspaceMapper {
List<WorkspaceDTO> getWorkspaceWithOrg(@Param("request") WorkspaceRequest request);
List<String> getWorkspaceIdsByOrgId(@Param("orgId") String orgId);
List<WorkspaceDTO> findAllIdAndName();
}

View File

@ -18,4 +18,8 @@
where organization_id = #{orgId}
</select>
<select id="findAllIdAndName" resultType="io.metersphere.dto.WorkspaceDTO">
select id,name from workspace
</select>
</mapper>

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<T> extends AnalysisEventListener<T> {
public List<ExcelErrData<T>> getErrList() {
return errList;
}
}

View File

@ -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<UserExcelData> {
private UserService userService;
//key:workspace.name value:id
Map<String, String> workspaceNameMap;
//key:Organization.name value:id
Map<String, String> orgNameMap;
//已经保存的用户ID
List<String> savedUserId;
public UserDataListener(Class clazz,Map<String, String> workspaceNameMap,Map<String, String> 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<UserRequest> 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字典对象传入组织或者工作空间的<name:id>
* @return 转化后的id集合
*/
private List<String> getIdByExcelInfoAndIdDic(String userRoleInExcel, String nameStringInExce, Map<String, String> idDic) {
List<String> 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<Map<String, Object>> roleMapList = new ArrayList<>();
//判断是否是Admin
if (StringUtils.equalsIgnoreCase(Translator.get("options_yes"), data.getUserIsAdmin())) {
List<String> adminIdList = new ArrayList<>();
adminIdList.add("adminSourceId");
Map<String, Object> adminRoleMap = this.genRoleMap("admin", adminIdList);
roleMapList.add(adminRoleMap);
}
//判断组织管理员
List<String> orgManagerOrdIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsOrgAdmin(), data.getOrgAdminOrganization(), orgNameMap);
if (!orgManagerOrdIdList.isEmpty()) {
Map<String, Object> orgAdminRoleMap = this.genRoleMap("org_admin", orgManagerOrdIdList);
roleMapList.add(orgAdminRoleMap);
}
//判断组织成员
List<String> orgMemberOrdIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsOrgMember(), data.getOrgMemberOrganization(), orgNameMap);
if (!orgMemberOrdIdList.isEmpty()) {
Map<String, Object> orgMemberRoleMap = this.genRoleMap("org_member", orgMemberOrdIdList);
roleMapList.add(orgMemberRoleMap);
}
//判断测试经理
List<String> testManagerWorkspaceIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsTestManager(), data.getTestManagerWorkspace(), workspaceNameMap);
if (!testManagerWorkspaceIdList.isEmpty()) {
Map<String, Object> testManagerRoleMap = this.genRoleMap("test_manager", testManagerWorkspaceIdList);
roleMapList.add(testManagerRoleMap);
}
//判断测试人员
List<String> testgerWorkspaceIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsTester(), data.getTesterWorkspace(), workspaceNameMap);
if (!testgerWorkspaceIdList.isEmpty()) {
Map<String, Object> testerRoleMap = this.genRoleMap("test_user", testgerWorkspaceIdList);
roleMapList.add(testerRoleMap);
}
//判断只读用户
List<String> viewerWorkspaceIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsViewer(), data.getViewerWorkspace(), workspaceNameMap);
if (!viewerWorkspaceIdList.isEmpty()) {
Map<String, Object> testViewerRoleMap = this.genRoleMap("test_viewer", viewerWorkspaceIdList);
roleMapList.add(testViewerRoleMap);
}
request.setRoles(roleMapList);
return request;
}
/**
* 封装用户权限数据格式
* @param roleName 权限名称
* @param roleIdList 对应的权限ID
* @return 保存用户时对应的数据权限的数据格式
*/
private Map<String, Object> genRoleMap(String roleName, List<String> roleIdList) {
Map<String, Object> 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<UserExcelData> list){
String checkRepeatIdResult = new String();
List<String> allIdList = new ArrayList<>();
List<String> allEmailList = new ArrayList<>();
for (UserExcelData data: list) {
allIdList.add(data.getId());
allEmailList.add(data.getEmail());
}
List<String> repeatIdList = allIdList.stream()
.collect(Collectors.toMap(e -> e, e -> 1, (a, b) -> a + b)) // 获得元素出现频率的 Map键为元素值为元素出现的次数
.entrySet().stream() // Set<Entry>转换为Stream<Entry>
.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<String> repeatEmailList = allEmailList.stream()
.collect(Collectors.toMap(e -> e, e -> 1, (a, b) -> a + b)) // 获得元素出现频率的 Map键为元素值为元素出现的次数
.entrySet().stream() // Set<Entry>转换为Stream<Entry>
.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;
}
}

View File

@ -178,4 +178,8 @@ public class OrganizationService {
MSException.throwException(Translator.get("organization_does_not_belong_to_user"));
}
}
public List<OrganizationMemberDTO> findAllIdAndName(){
return extOrganizationMapper.findAllIdAndName();
}
}

View File

@ -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<UserDetail> queryTypeByIds(List<String> 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<UserExcelData> generateExportTemplate() {
List<UserExcelData> list = new ArrayList<>();
List<String> types = TestCaseConstants.Type.getValues();
List<String> 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<ExcelErrData<TestCaseExcelData>> errList = null;
if (multipartFile == null) {
MSException.throwException(Translator.get("upload_fail"));
}
try {
Class clazz = new UserExcelDataFactory().getExcelDataByLocal();
Map<String,String> orgNameMap = new HashMap<>();
Map<String,String> workspaceNameMap = new HashMap<>();
List<OrganizationMemberDTO> organizationList = extOrganizationMapper.findAllIdAndName();
for (OrganizationMemberDTO model : organizationList) {
orgNameMap.put(model.getName(),model.getId());
}
List<WorkspaceDTO> 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<String> selectAllId() {
return extUserMapper.selectAllId();
}
}

View File

@ -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<WorkspaceDTO> findAllIdAndName(){
return extWorkspaceMapper.findAllIdAndName();
}
}

View File

@ -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=

View File

@ -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

View File

@ -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=测试计划名称已存在

View File

@ -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=測試計劃名稱已存在

View File

@ -10,6 +10,8 @@
<span class="operate-button">
<ms-table-button :is-tester-permission="isTesterPermission" v-if="showCreate" icon="el-icon-circle-plus-outline"
:content="createTip" @click="create"/>
<ms-table-button :is-tester-permission="true" icon="el-icon-download" v-if="showImport"
:content="importTip" @click="importData"/>
<ms-table-button :is-tester-permission="isTesterPermission" v-if="showRun" icon="el-icon-video-play"
type="primary"
:content="runTip" @click="runTest"/>
@ -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')
},

View File

@ -3,8 +3,9 @@
<el-card class="table-card">
<template v-slot:header>
<ms-table-header :condition.sync="condition" @search="search" @create="create"
:create-tip="$t('user.create')" :title="$t('commons.user')"/>
<ms-table-header :condition.sync="condition" @search="search" @create="create" @import="importUserDialogOpen"
:create-tip="$t('user.create')" :show-import="true" :import-tip="$t('commons.import_user')" :title="$t('commons.user')"/>
</template>
<el-table border class="adjust-table" :data="tableData" style="width: 100%">
@ -323,7 +324,7 @@
@confirm="editUserPassword('editPasswordForm')"/>
</span>
</el-dialog>
<user-import ref="userImportDialog" @refreshAll="search"></user-import>
</div>
</template>
@ -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) {

View File

@ -0,0 +1,136 @@
<template>
<el-dialog class="user-import" :title="$t('commons.import_user')" :visible.sync="dialogVisible"
@close="close">
<el-row>
<el-link type="primary" class="download-template"
@click="downloadTemplate"
>{{$t('test_track.case.import.download_template')}}
</el-link>
</el-row>
<el-row>
<el-upload
style="width: 100%"
v-loading="result.loading"
:element-loading-text="$t('test_track.case.import.importing')"
element-loading-spinner="el-icon-loading"
class="upload-demo"
multiple
drag
:limit="1"
action=""
:on-exceed="handleExceed"
:beforeUpload="uploadValidate"
:on-error="handleError"
:show-file-list="false"
:http-request="upload"
:file-list="fileList">
<i class="el-icon-upload"></i>
<div class="el-upload__text" v-html="$t('load_test.upload_tips')"></div>
<div class="el-upload__tip" slot="tip">{{ $t('api_test.api_import.file_size_limit') }}</div>
</el-upload>
</el-row>
<el-row>
<ul>
<li v-for="errFile in errList" :key="errFile.rowNum">
{{errFile.errMsg}}
</li>
</ul>
</el-row>
</el-dialog>
</template>
<script>
import ElUploadList from "element-ui/packages/upload/src/upload-list";
import MsTableButton from '../../../../components/common/components/MsTableButton';
import {getCurrentProjectID, listenGoBack, removeGoBackListener} from "../../../../../common/js/utils";
import {TokenKey} from '../../../../../common/js/constants';
import axios from "axios";
export default {
name: "UserImport",
components: {ElUploadList, MsTableButton},
data() {
return {
result: {},
activeName: 'excelImport',
dialogVisible: false,
fileList: [],
errList: [],
isLoading: false
}
},
methods: {
handleExceed(files, fileList) {
this.$warning(this.$t('test_track.case.import.upload_limit_count'));
},
uploadValidate(file) {
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
if (suffix != 'xls' && suffix != 'xlsx') {
this.$warning(this.$t('test_track.case.import.upload_limit_format'));
return false;
}
if (file.size / 1024 / 1024 > 20) {
this.$warning(this.$t('test_track.case.import.upload_limit_size'));
return false;
}
this.isLoading = true;
this.errList = [];
return true;
},
handleError(err, file, fileList) {
this.isLoading = false;
this.$error(err.message);
},
open() {
listenGoBack(this.close);
this.projectId = getCurrentProjectID();
this.dialogVisible = true;
},
close() {
removeGoBackListener(this.close);
this.dialogVisible = false;
this.fileList = [];
this.errList = [];
},
downloadTemplate() {
this.$fileDownload('/user/export/template');
},
upload(file) {
this.isLoading = false;
this.fileList.push(file.file);
let user = JSON.parse(localStorage.getItem(TokenKey));
this.result = this.$fileUpload('/user/import/' + user.id, file.file, null, {}, response => {
let res = response.data;
if (res.success) {
this.$success(this.$t('commons.import_success'));
this.dialogVisible = false;
this.$emit("refreshAll");
} else {
this.errList = res.errList;
}
this.fileList = [];
}, erro => {
this.fileList = [];
});
},
}
}
</script>
<style>
</style>
<style scoped>
.download-template {
padding-top: 0px;
padding-bottom: 10px;
}
.user-import >>> .el-dialog {
width: 400px;
}
</style>

View File

@ -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',

View File

@ -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: '切回新版',

View File

@ -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: '切回新版',

View File

@ -10,7 +10,7 @@ module.exports = {
port: 8080,
proxy: {
//1.8需求:增加分享功能,不登陆即可看到文档页面。所以代理设置增加了(?!/document)文档页面的相关信息
// ['^((?!/login)']: {
// ['^(?!/login)']: {
['^((?!/login)(?!/document))']: {
target: 'http://localhost:8081',
ws: true,