diff --git a/test-track/backend/src/main/java/io/metersphere/excel/handler/IssueTemplateHeadWriteHandler.java b/test-track/backend/src/main/java/io/metersphere/excel/handler/IssueTemplateHeadWriteHandler.java index f9126ade8b..d43af3465d 100644 --- a/test-track/backend/src/main/java/io/metersphere/excel/handler/IssueTemplateHeadWriteHandler.java +++ b/test-track/backend/src/main/java/io/metersphere/excel/handler/IssueTemplateHeadWriteHandler.java @@ -74,6 +74,7 @@ public class IssueTemplateHeadWriteHandler implements RowWriteHandler, SheetWrit if (BooleanUtils.isTrue(context.getHead())) { sheet = context.getWriteSheetHolder().getSheet(); drawingPatriarch = sheet.createDrawingPatriarch(); + // 设置表头内容 headCommentIndexMap.forEach(this::setComment); } } diff --git a/test-track/backend/src/main/java/io/metersphere/excel/listener/IssueExcelListener.java b/test-track/backend/src/main/java/io/metersphere/excel/listener/IssueExcelListener.java index b3101b47e9..c739ed0db9 100644 --- a/test-track/backend/src/main/java/io/metersphere/excel/listener/IssueExcelListener.java +++ b/test-track/backend/src/main/java/io/metersphere/excel/listener/IssueExcelListener.java @@ -31,6 +31,7 @@ import org.apache.commons.lang3.StringUtils; import java.lang.reflect.Field; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; /** @@ -42,10 +43,11 @@ public class IssueExcelListener extends AnalysisEventListener headMap; - private List customFields = new ArrayList<>(); + private List customFields; private IssuesService issuesService; + private Map memberMap; /** * excel表头字段字典值 */ @@ -66,12 +68,13 @@ public class IssueExcelListener extends AnalysisEventListener updateList = new ArrayList<>(); protected List> errList = new ArrayList<>(); - public IssueExcelListener(IssueImportRequest request, Class clazz, Boolean isThirdPlatform, List customFields) { + public IssueExcelListener(IssueImportRequest request, Class clazz, Boolean isThirdPlatform, List customFields, Map memberMap) { this.request = request; this.dataClass = clazz; this.isThirdPlatform = isThirdPlatform; this.customFields = customFields; this.issuesService = CommonBeanFactory.getBean(IssuesService.class); + this.memberMap = memberMap; } @Override @@ -83,9 +86,9 @@ public class IssueExcelListener extends AnalysisEventListener> customFieldMap = customFields.stream().collect(Collectors.groupingBy(CustomFieldDao::getName)); + data.getCustomData().forEach((k, v) -> { + List customFieldDaos = customFieldMap.get(k); + if (CollectionUtils.isNotEmpty(customFieldDaos) && customFieldDaos.size() > 0) { + CustomFieldDao customFieldDao = customFieldDaos.get(0); + String type = customFieldDao.getType(); + Boolean required = customFieldDao.getRequired(); + String options = StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.MEMBER.getValue(), CustomFieldType.MULTIPLE_MEMBER.getValue()) ? + this.memberMap.toString() : customFieldDao.getOptions(); + if (required && StringUtils.isEmpty(v.toString())) { + errMsg.append(k).append(Translator.get("can_not_be_null")).append(";"); + } else if (StringUtils.isNotEmpty(v.toString()) && isSelect(type) && !isOptionInclude(v, options)) { + errMsg.append(k).append(Translator.get("options_not_exist")).append(";"); + } else if (StringUtils.isNotEmpty(v.toString()) && isIllegalFormat(type, v.toString())) { + errMsg.append(k).append(Translator.get("format_error")).append(";"); + } + } else { + if (!exportFieldsContains(k)) { + errMsg.append(k).append(Translator.get("excel_field_not_exist")).append(";"); + } + } + }); } private IssueExcelData parseDataToModel(Map rowData) { @@ -309,7 +333,7 @@ public class IssueExcelListener extends AnalysisEventListener isInclude = new AtomicReference<>(Boolean.TRUE); + if (value instanceof List) { + ((List) value).forEach(item -> { + String s = item.toString().replaceAll("\"", StringUtils.EMPTY); + if (!StringUtils.contains(options, s)) { + isInclude.set(Boolean.FALSE); + } + }); + } else { + isInclude.set(StringUtils.contains(options, value.toString())); + } + return isInclude.get(); + } + + public Boolean exportFieldsContains(String name) { + for (IssueExportHeadField issueExportHeadField : IssueExportHeadField.values()) { + if (StringUtils.equals(name, issueExportHeadField.getName())) { + return Boolean.TRUE; + } + } + return Boolean.FALSE; + } } diff --git a/test-track/backend/src/main/java/io/metersphere/service/IssuesService.java b/test-track/backend/src/main/java/io/metersphere/service/IssuesService.java index cd04646ef2..9586dbe641 100644 --- a/test-track/backend/src/main/java/io/metersphere/service/IssuesService.java +++ b/test-track/backend/src/main/java/io/metersphere/service/IssuesService.java @@ -495,13 +495,13 @@ public class IssuesService { issuesRequest.setProjectId(SessionUtils.getCurrentProjectId()); List issuesDaos = listByWorkspaceId(issuesRequest); if (CollectionUtils.isNotEmpty(issuesDaos)) { - issuesDaos.forEach(issuesDao -> { + issuesDaos.parallelStream().forEach(issuesDao -> { delete(issuesDao.getId()); }); } } else { if (CollectionUtils.isNotEmpty(request.getBatchDeleteIds())) { - request.getBatchDeleteIds().forEach(id -> delete(id)); + request.getBatchDeleteIds().parallelStream().forEach(id -> delete(id)); } } } @@ -610,17 +610,19 @@ public class IssuesService { if (StringUtils.equalsAnyIgnoreCase(customField.getType(), CustomFieldType.RICH_TEXT.getValue(), CustomFieldType.TEXTAREA.getValue())) { fieldDao.setValue(fieldDao.getTextValue()); } - if (StringUtils.equalsAnyIgnoreCase(customField.getType(), CustomFieldType.DATE.getValue()) && StringUtils.isNotEmpty(fieldDao.getValue())) { + if (StringUtils.equalsAnyIgnoreCase(customField.getType(), CustomFieldType.DATE.getValue()) && StringUtils.isNotEmpty(fieldDao.getValue()) && !StringUtils.equals(fieldDao.getValue(), "null")) { Date date = DateUtils.parseDate(fieldDao.getValue().replaceAll("\"", StringUtils.EMPTY), "yyyy-MM-dd"); String format = DateUtils.format(date, "yyyy/MM/dd"); fieldDao.setValue("\"" + format + "\""); } - if (StringUtils.equalsAnyIgnoreCase(customField.getType(), CustomFieldType.DATETIME.getValue()) && StringUtils.isNotEmpty(fieldDao.getValue())) { + if (StringUtils.equalsAnyIgnoreCase(customField.getType(), CustomFieldType.DATETIME.getValue()) && StringUtils.isNotEmpty(fieldDao.getValue()) && !StringUtils.equals(fieldDao.getValue(), "null")) { Date date = null; if (fieldDao.getValue().contains("T") && fieldDao.getValue().length() == 18) { date = DateUtils.parseDate(fieldDao.getValue().replaceAll("\"", StringUtils.EMPTY), "yyyy-MM-dd'T'HH:mm"); } else if (fieldDao.getValue().contains("T") && fieldDao.getValue().length() == 21) { date = DateUtils.parseDate(fieldDao.getValue().replaceAll("\"", StringUtils.EMPTY), "yyyy-MM-dd'T'HH:mm:ss"); + } else if (fieldDao.getValue().contains("T") && fieldDao.getValue().length() > 21) { + date = DateUtils.parseDate(fieldDao.getValue().replaceAll("\"", StringUtils.EMPTY).substring(0, 19), "yyyy-MM-dd'T'HH:mm:ss"); } else { date = DateUtils.parseDate(fieldDao.getValue().replaceAll("\"", StringUtils.EMPTY)); } @@ -1194,9 +1196,12 @@ public class IssuesService { public void issueImportTemplate(String projectId, HttpServletResponse response) { Map userMap = baseUserService.getProjectMemberOption(projectId).stream().collect(Collectors.toMap(User::getId, User::getName)); + // 获取缺陷模板及自定义字段 IssueTemplateDao issueTemplate = getIssueTemplateByProjectId(projectId); List customFields = Optional.ofNullable(issueTemplate.getCustomFields()).orElse(new ArrayList<>()); + // 根据自定义字段获取表头 List> heads = new IssueExcelDataFactory().getIssueExcelDataLocal().getHead(issueTemplate.getIsThirdTemplate(), customFields, null); + // 导出空模板, heads->表头, headHandler->表头处理 IssueTemplateHeadWriteHandler headHandler = new IssueTemplateHeadWriteHandler(userMap, heads, issueTemplate.getCustomFields()); new EasyExcelExporter(new IssueExcelDataFactory().getExcelDataByLocal()) .exportByCustomWriteHandler(response, heads, null, Translator.get("issue_import_template_name"), @@ -1207,16 +1212,21 @@ public class IssuesService { if (importFile == null) { MSException.throwException(Translator.get("upload_fail")); } + Map userMap = baseUserService.getProjectMemberOption(request.getProjectId()).stream().collect(Collectors.toMap(User::getId, User::getName)); + // 获取缺陷模板及自定义字段 IssueTemplateDao issueTemplate = getIssueTemplateByProjectId(request.getProjectId()); List customFields = Optional.ofNullable(issueTemplate.getCustomFields()).orElse(new ArrayList<>()); + // 获取本地EXCEL数据对象 Class clazz = new IssueExcelDataFactory().getExcelDataByLocal(); - IssueExcelListener issueExcelListener = new IssueExcelListener(request, clazz, issueTemplate.getIsThirdTemplate(), customFields); + // IssueExcelListener读取file内容 + IssueExcelListener issueExcelListener = new IssueExcelListener(request, clazz, issueTemplate.getIsThirdTemplate(), customFields, userMap); try { EasyExcelFactory.read(importFile.getInputStream(), issueExcelListener).sheet().doRead(); } catch (IOException e) { LogUtil.error(e.getMessage(), e); e.printStackTrace(); } + // 获取错误信息并返回 List> errList = issueExcelListener.getErrList(); ExcelResponse excelResponse = new ExcelResponse(); if (CollectionUtils.isNotEmpty(errList)) { @@ -1230,24 +1240,33 @@ public class IssuesService { public void issueExport(IssueExportRequest request, HttpServletResponse response) { Map userMap = baseUserService.getProjectMemberOption(request.getProjectId()).stream().collect(Collectors.toMap(User::getId, User::getName)); + // 获取缺陷模板及自定义字段 IssueTemplateDao issueTemplate = getIssueTemplateByProjectId(request.getProjectId()); List customFields = Optional.ofNullable(issueTemplate.getCustomFields()).orElse(new ArrayList<>()); + // 根据自定义字段获取表头内容 List> heads = new IssueExcelDataFactory().getIssueExcelDataLocal().getHead(issueTemplate.getIsThirdTemplate(), customFields, request); + // 获取导出缺陷列表 List exportIssues = getExportIssues(request, issueTemplate.getIsThirdTemplate(), customFields); + // 解析issue对象数据->excel对象数据 List excelDataList = parseIssueDataToExcelData(exportIssues); + // 解析excel对象数据->excel列表数据 List> data = parseExcelDataToList(heads, excelDataList); + // 导出EXCEL IssueTemplateHeadWriteHandler headHandler = new IssueTemplateHeadWriteHandler(userMap, heads, issueTemplate.getCustomFields()); + // heads-> 表头内容, data -> 导出EXCEL列表数据, headHandler -> 表头处理 new EasyExcelExporter(new IssueExcelDataFactory().getExcelDataByLocal()) .exportByCustomWriteHandler(response, heads, data, Translator.get("issue_list_export_excel"), Translator.get("issue_list_export_excel_sheet"), headHandler); } public List getExportIssues(IssueExportRequest exportRequest, Boolean isThirdTemplate, List customFields) { + // 根据列表条件获取符合缺陷集合 IssuesRequest request = new IssuesRequest(); request.setProjectId(exportRequest.getProjectId()); request.setWorkspaceId(exportRequest.getWorkspaceId()); request.setSelectAll(exportRequest.getIsSelectAll()); request.setExportIds(exportRequest.getExportIds()); + // 列表排序 request.setOrders(exportRequest.getOrders()); request.setOrders(ServiceUtils.getDefaultOrderByField(request.getOrders(), "create_time")); request.getOrders().forEach(order -> { @@ -1266,6 +1285,7 @@ public class IssuesService { Map planMap = getPlanMap(issues); Map> commentMap = getCommentMap(issues); + // 设置creator, caseCount, commnet issues.forEach(item -> { User createUser = userMap.get(item.getCreator()); if (createUser != null) { @@ -1288,6 +1308,7 @@ public class IssuesService { item.setComment(StringUtils.join(comments, ";")); } }); + // 解析自定义字段 buildCustomField(issues, isThirdTemplate, customFields); return issues; } @@ -1424,13 +1445,13 @@ public class IssuesService { } public void saveImportData(List issues) { - issues.forEach(issue -> { + issues.parallelStream().forEach(issue -> { addIssues(issue, null); }); } public void updateImportData(List issues) { - issues.forEach(issue -> { + issues.parallelStream().forEach(issue -> { updateIssues(issue); }); } diff --git a/test-track/backend/src/main/resources/i18n/commons_en_US.properties b/test-track/backend/src/main/resources/i18n/commons_en_US.properties index 33f5c5d4d3..421eba19a0 100644 --- a/test-track/backend/src/main/resources/i18n/commons_en_US.properties +++ b/test-track/backend/src/main/resources/i18n/commons_en_US.properties @@ -51,7 +51,7 @@ int_import_cell_format_comment=cell format: 100001 float_import_cell_format_comment=cell format: 24 multiple_input_import_cell_format_comment=This field has multiple values. Separate multiple values with commas or semicolons options_tips=(format{key:value}, please fill in the corresponding value)Option value: -# issue export +# issue import and issue export title==Title description=Description case_count=Case count @@ -59,6 +59,10 @@ comment=Comment issue_resource=Issue resource issue_platform=Issue platform create_time=CreateTime +can_not_be_null=Can not be null +excel_field_not_exist=Not exist +options_not_exist=Incorrect option value +format_error=Format error #project project_name_is_null=Project name cannot be null project_name_already_exists=The project name already exists diff --git a/test-track/backend/src/main/resources/i18n/commons_zh_CN.properties b/test-track/backend/src/main/resources/i18n/commons_zh_CN.properties index ba0a87432b..40bf7041f2 100644 --- a/test-track/backend/src/main/resources/i18n/commons_zh_CN.properties +++ b/test-track/backend/src/main/resources/i18n/commons_zh_CN.properties @@ -28,7 +28,7 @@ int_import_cell_format_comment=整型单元格格式为: 100001 float_import_cell_format_comment=浮点单元格格式为: 24 multiple_input_import_cell_format_comment=该单元格可输入多个值,多个值请用逗号或分号隔开(v1;v2) options_tips=(格式{key:value},请填写对应的value)选项: -# issue export +# issue import and issue export title=缺陷标题 description=缺陷描述 case_count=用例数 @@ -36,6 +36,10 @@ comment=评论 issue_resource=缺陷来源 issue_platform=缺陷平台 create_time=创建时间 +can_not_be_null=不能为空 +excel_field_not_exist=不存在该字段 +options_not_exist=选项值有误 +format_error=格式有误 #project project_name_is_null=项目名称不能为空 project_name_already_exists=项目名称已存在 diff --git a/test-track/backend/src/main/resources/i18n/commons_zh_TW.properties b/test-track/backend/src/main/resources/i18n/commons_zh_TW.properties index bbb4e136fb..035cfb58c5 100644 --- a/test-track/backend/src/main/resources/i18n/commons_zh_TW.properties +++ b/test-track/backend/src/main/resources/i18n/commons_zh_TW.properties @@ -28,7 +28,7 @@ int_import_cell_format_comment=單元格格式: 100001 float_import_cell_format_comment=單元格格式: 24 multiple_input_import_cell_format_comment=該單元格可輸入多個值,多個值請用逗號或分號隔開(v1;v2) options_tips=(格式{key:value},請填寫對應value)選項: -# issue export +# issue import and issue export title=缺陷標題 description=缺陷描述 case_count=用例數 @@ -36,6 +36,10 @@ comment=評論 issue_resource=缺陷來源 issue_platform=缺陷平臺 create_time=創建時間 +can_not_be_null=不能爲空 +excel_field_not_exist=不存在該字段 +options_not_exist=選項值有誤 +format_error=格式有誤 #project project_name_is_null=項目名稱不能為空 project_name_already_exists=項目名稱已存在