From 495f4c9463dd52f0022fd5af6c670fa3a417fd6a Mon Sep 17 00:00:00 2001 From: WangXu10 Date: Thu, 8 Aug 2024 10:34:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B):=20?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=E8=84=91=E5=9B=BE=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../functional/domain/ExportTask.java | 19 +- .../functional/domain/ExportTaskExample.java | 70 ++++++++ .../functional/mapper/ExportTaskMapper.xml | 41 +++-- .../migration/3.2.0/ddl/V3.2.0_2__ga_ddl.sql | 2 + .../controller/FunctionalCaseController.java | 14 +- .../service/FunctionalCaseFileService.java | 71 +++++--- .../service/FunctionalCaseService.java | 2 +- .../service/FunctionalCaseXmindService.java | 168 +++++++++++++++++- .../xmind/domain/FunctionalCaseXmindDTO.java | 4 +- .../xmind/domain/FunctionalCaseXmindData.java | 49 +++++ .../xmind/utils/XmindExportUtil.java | 143 +++++++++++++-- .../FunctionalCaseControllerTests.java | 45 ++++- .../resources/dml/init_file_metadata_test.sql | 5 + .../system/manager/ExportTaskManager.java | 20 ++- 14 files changed, 576 insertions(+), 77 deletions(-) diff --git a/backend/framework/domain/src/main/java/io/metersphere/functional/domain/ExportTask.java b/backend/framework/domain/src/main/java/io/metersphere/functional/domain/ExportTask.java index 756ad134d1..ae0b889efb 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/functional/domain/ExportTask.java +++ b/backend/framework/domain/src/main/java/io/metersphere/functional/domain/ExportTask.java @@ -1,12 +1,15 @@ package io.metersphere.functional.domain; -import io.metersphere.validation.groups.*; +import io.metersphere.validation.groups.Created; +import io.metersphere.validation.groups.Updated; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; -import lombok.Data; @Data public class ExportTask implements Serializable { @@ -43,6 +46,11 @@ public class ExportTask implements Serializable { @Schema(description = "创建时间") private Long updateTime; + @Schema(description = "项目id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{export_task.project_id.not_blank}", groups = {Created.class}) + @Size(min = 1, max = 50, message = "{export_task.project_id.length_range}", groups = {Created.class, Updated.class}) + private String projectId; + private static final long serialVersionUID = 1L; public enum Column { @@ -54,7 +62,8 @@ public class ExportTask implements Serializable { createUser("create_user", "createUser", "VARCHAR", false), createTime("create_time", "createTime", "BIGINT", false), updateUser("update_user", "updateUser", "VARCHAR", false), - updateTime("update_time", "updateTime", "BIGINT", false); + updateTime("update_time", "updateTime", "BIGINT", false), + projectId("project_id", "projectId", "VARCHAR", false); private static final String BEGINNING_DELIMITER = "`"; @@ -99,7 +108,7 @@ public class ExportTask implements Serializable { return this.getEscapedColumnName() + " ASC"; } - public static Column[] excludes(Column ... excludes) { + public static Column[] excludes(Column... excludes) { ArrayList columns = new ArrayList<>(Arrays.asList(Column.values())); if (excludes != null && excludes.length > 0) { columns.removeAll(new ArrayList<>(Arrays.asList(excludes))); diff --git a/backend/framework/domain/src/main/java/io/metersphere/functional/domain/ExportTaskExample.java b/backend/framework/domain/src/main/java/io/metersphere/functional/domain/ExportTaskExample.java index 343c2f5791..bcf5f6fe31 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/functional/domain/ExportTaskExample.java +++ b/backend/framework/domain/src/main/java/io/metersphere/functional/domain/ExportTaskExample.java @@ -713,6 +713,76 @@ public class ExportTaskExample { addCriterion("update_time not between", value1, value2, "updateTime"); return (Criteria) this; } + + public Criteria andProjectIdIsNull() { + addCriterion("project_id is null"); + return (Criteria) this; + } + + public Criteria andProjectIdIsNotNull() { + addCriterion("project_id is not null"); + return (Criteria) this; + } + + public Criteria andProjectIdEqualTo(String value) { + addCriterion("project_id =", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdNotEqualTo(String value) { + addCriterion("project_id <>", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdGreaterThan(String value) { + addCriterion("project_id >", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdGreaterThanOrEqualTo(String value) { + addCriterion("project_id >=", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdLessThan(String value) { + addCriterion("project_id <", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdLessThanOrEqualTo(String value) { + addCriterion("project_id <=", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdLike(String value) { + addCriterion("project_id like", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdNotLike(String value) { + addCriterion("project_id not like", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdIn(List values) { + addCriterion("project_id in", values, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdNotIn(List values) { + addCriterion("project_id not in", values, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdBetween(String value1, String value2) { + addCriterion("project_id between", value1, value2, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdNotBetween(String value1, String value2) { + addCriterion("project_id not between", value1, value2, "projectId"); + return (Criteria) this; + } } public static class Criteria extends GeneratedCriteria { diff --git a/backend/framework/domain/src/main/java/io/metersphere/functional/mapper/ExportTaskMapper.xml b/backend/framework/domain/src/main/java/io/metersphere/functional/mapper/ExportTaskMapper.xml index 52252b9dbc..d35aa8c8de 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/functional/mapper/ExportTaskMapper.xml +++ b/backend/framework/domain/src/main/java/io/metersphere/functional/mapper/ExportTaskMapper.xml @@ -11,6 +11,7 @@ + @@ -71,7 +72,8 @@ - id, `name`, `type`, fileId, `state`, create_user, create_time, update_user, update_time + id, `name`, `type`, fileId, `state`, create_user, create_time, update_user, update_time, + project_id @@ -210,6 +218,9 @@ update_time = #{record.updateTime,jdbcType=BIGINT}, + + project_id = #{record.projectId,jdbcType=VARCHAR}, + @@ -225,7 +236,8 @@ create_user = #{record.createUser,jdbcType=VARCHAR}, create_time = #{record.createTime,jdbcType=BIGINT}, update_user = #{record.updateUser,jdbcType=VARCHAR}, - update_time = #{record.updateTime,jdbcType=BIGINT} + update_time = #{record.updateTime,jdbcType=BIGINT}, + project_id = #{record.projectId,jdbcType=VARCHAR} @@ -257,6 +269,9 @@ update_time = #{updateTime,jdbcType=BIGINT}, + + project_id = #{projectId,jdbcType=VARCHAR}, + where id = #{id,jdbcType=VARCHAR} @@ -269,19 +284,20 @@ create_user = #{createUser,jdbcType=VARCHAR}, create_time = #{createTime,jdbcType=BIGINT}, update_user = #{updateUser,jdbcType=VARCHAR}, - update_time = #{updateTime,jdbcType=BIGINT} + update_time = #{updateTime,jdbcType=BIGINT}, + project_id = #{projectId,jdbcType=VARCHAR} where id = #{id,jdbcType=VARCHAR} insert into export_task - (id, `name`, `type`, fileId, `state`, create_user, create_time, update_user, update_time - ) + (id, `name`, `type`, fileId, `state`, create_user, create_time, update_user, update_time, + project_id) values (#{item.id,jdbcType=VARCHAR}, #{item.name,jdbcType=VARCHAR}, #{item.type,jdbcType=VARCHAR}, #{item.fileid,jdbcType=VARCHAR}, #{item.state,jdbcType=VARCHAR}, #{item.createUser,jdbcType=VARCHAR}, - #{item.createTime,jdbcType=BIGINT}, #{item.updateUser,jdbcType=VARCHAR}, #{item.updateTime,jdbcType=BIGINT} - ) + #{item.createTime,jdbcType=BIGINT}, #{item.updateUser,jdbcType=VARCHAR}, #{item.updateTime,jdbcType=BIGINT}, + #{item.projectId,jdbcType=VARCHAR}) @@ -321,6 +337,9 @@ #{item.updateTime,jdbcType=BIGINT} + + #{item.projectId,jdbcType=VARCHAR} + ) diff --git a/backend/framework/domain/src/main/resources/migration/3.2.0/ddl/V3.2.0_2__ga_ddl.sql b/backend/framework/domain/src/main/resources/migration/3.2.0/ddl/V3.2.0_2__ga_ddl.sql index ae917e6fe7..192fe0b066 100644 --- a/backend/framework/domain/src/main/resources/migration/3.2.0/ddl/V3.2.0_2__ga_ddl.sql +++ b/backend/framework/domain/src/main/resources/migration/3.2.0/ddl/V3.2.0_2__ga_ddl.sql @@ -12,6 +12,7 @@ CREATE TABLE export_task( `name` VARCHAR(255) COMMENT '名称' , `type` VARCHAR(50) NOT NULL COMMENT '资源类型' , `fileId` VARCHAR(255) COMMENT '文件id' , + `project_id` VARCHAR(255) NOT NULL COMMENT '项目id' , `state` VARCHAR(50) NOT NULL COMMENT '状态' , `create_user` VARCHAR(50) NOT NULL COMMENT '创建人' , `create_time` BIGINT NOT NULL COMMENT '创建时间' , @@ -24,6 +25,7 @@ CREATE TABLE export_task( CREATE INDEX idx_create_user ON export_task(`create_user`); +CREATE INDEX idx_project_id ON export_task(`project_id`); CREATE INDEX idx_state ON export_task(`state`); CREATE INDEX idx_create_time ON export_task(`create_time`); CREATE INDEX idx_type ON export_task(`type`); diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/controller/FunctionalCaseController.java b/backend/services/case-management/src/main/java/io/metersphere/functional/controller/FunctionalCaseController.java index da4fd77e17..40fd4d46ed 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/controller/FunctionalCaseController.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/controller/FunctionalCaseController.java @@ -258,12 +258,12 @@ public class FunctionalCaseController { functionalCaseFileService.export(SessionUtils.getUserId(), request); } - @GetMapping("/stop/{projectId}") + @GetMapping("/stop/{taskId}") @Operation(summary = "用例管理-功能用例-导出-停止导出") @RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_EXPORT) @CheckOwner(resourceId = "#projectId", resourceType = "project") - public void caseStopExport(@PathVariable String projectId) { - functionalCaseFileService.stopExport(projectId, SessionUtils.getUserId()); + public void caseStopExport(@PathVariable String taskId) { + functionalCaseFileService.stopExport(taskId, SessionUtils.getUserId()); } @GetMapping("/download/xmind/template/{projectId}") @@ -289,4 +289,12 @@ public class FunctionalCaseController { public ResponseEntity downloadImgById(@PathVariable String projectId, @PathVariable String fileId) { return functionalCaseFileService.downloadFile(projectId, fileId); } + + + @PostMapping("/export/xmind") + @Operation(summary = "用例管理-功能用例-xmind导出") + @RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_EXPORT) + public void caseExportXmind(@Validated @RequestBody FunctionalCaseExportRequest request) { + functionalCaseXmindService.exportFunctionalCaseXmind(request, SessionUtils.getUserId()); + } } diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseFileService.java b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseFileService.java index e88a5c3953..4b4e605c02 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseFileService.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseFileService.java @@ -64,10 +64,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.Serial; +import java.io.*; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; @@ -104,11 +102,12 @@ public class FunctionalCaseFileService { private FunctionalCaseLogService functionalCaseLogService; @Resource private SystemParameterMapper systemParameterMapper; - private static final String EXPORT_FILE_NAME = "case_export"; @Resource private ExportTaskManager exportTaskManager; @Resource private ExportTaskMapper exportTaskMapper; + private static final String XMIND = ".xmind"; + private static final String XLSX = ".xlsx"; /** * 下载excel导入模板 @@ -333,17 +332,17 @@ public class FunctionalCaseFileService { } } - public void export(String userId, FunctionalCaseExportRequest request){ + public void export(String userId, FunctionalCaseExportRequest request) { try { ExportTaskExample exportTaskExample = new ExportTaskExample(); exportTaskExample.createCriteria().andTypeEqualTo(ExportConstants.ExportType.CASE.toString()).andStateEqualTo(ExportConstants.ExportState.PREPARED.toString()); long preparedCount = exportTaskMapper.countByExample(exportTaskExample); - if (preparedCount>0) { + if (preparedCount > 0) { throw new MSException(Translator.get("export_case_task_existed")); } - exportTaskManager.exportAsyncTask(userId, ExportConstants.ExportType.CASE.toString(), request, t->exportFunctionalCaseZip(request)); + exportTaskManager.exportAsyncTask(request.getProjectId(), userId, ExportConstants.ExportType.CASE.toString(), request, t -> exportFunctionalCaseZip(request, userId)); } catch (InterruptedException e) { - LogUtils.error("导出失败:"+e); + LogUtils.error("导出失败:" + e); throw new MSException(e); } } @@ -354,7 +353,7 @@ public class FunctionalCaseFileService { * * @param request */ - public String exportFunctionalCaseZip(FunctionalCaseExportRequest request) { + public String exportFunctionalCaseZip(FunctionalCaseExportRequest request, String userId) { File tmpDir = null; Project project = projectMapper.selectByPrimaryKey(request.getProjectId()); try { @@ -375,11 +374,11 @@ public class FunctionalCaseFileService { uploadFileToMinio(singeFile, request.getFileId()); } functionalCaseLogService.exportExcelLog(request); - List exportTasks = getExportTasks(); + List exportTasks = getExportTasks(request.getProjectId(), userId); String taskId; if (CollectionUtils.isNotEmpty(exportTasks)) { taskId = exportTasks.getFirst().getId(); - updateExportTask(ExportConstants.ExportState.SUCCESS.toString(), taskId); + updateExportTask(ExportConstants.ExportState.SUCCESS.name(), taskId, request.getFileId()); } else { taskId = MsgType.CONNECT.name(); } @@ -387,9 +386,9 @@ public class FunctionalCaseFileService { socketMsgDTO.setReportId(request.getFileId()); ExportWebSocketHandler.sendMessageSingle(socketMsgDTO); } catch (Exception e) { - List exportTasks = getExportTasks(); + List exportTasks = getExportTasks(request.getProjectId(), userId); if (CollectionUtils.isNotEmpty(exportTasks)) { - updateExportTask(ExportConstants.ExportState.SUCCESS.toString(), exportTasks.getFirst().getId()); + updateExportTask(ExportConstants.ExportState.ERROR.name(), exportTasks.getFirst().getId(), request.getFileId()); } LogUtils.error(e); throw new MSException(e); @@ -397,23 +396,26 @@ public class FunctionalCaseFileService { return null; } - private List getExportTasks() { + public List getExportTasks(String projectId, String userId) { ExportTaskExample exportTaskExample = new ExportTaskExample(); - exportTaskExample.createCriteria().andTypeEqualTo(ExportConstants.ExportType.CASE.toString()).andStateEqualTo(ExportConstants.ExportState.PREPARED.toString()); + exportTaskExample.createCriteria().andTypeEqualTo(ExportConstants.ExportType.CASE.toString()).andStateEqualTo(ExportConstants.ExportState.PREPARED.toString()) + .andCreateUserEqualTo(userId).andProjectIdEqualTo(projectId); + exportTaskExample.setOrderByClause("create_time desc"); return exportTaskMapper.selectByExample(exportTaskExample); } - private void updateExportTask(String state, String taskId) { + public void updateExportTask(String state, String taskId, String fileId) { ExportTask exportTask = new ExportTask(); exportTask.setState(state); + exportTask.setFileid(fileId); exportTask.setId(taskId); - exportTaskMapper.updateByPrimaryKey(exportTask); + exportTaskMapper.updateByPrimaryKeySelective(exportTask); } - private void uploadFileToMinio(File file, String fileId) { + public void uploadFileToMinio(File file, String fileId) { FileRequest fileRequest = new FileRequest(); - fileRequest.setFileName(EXPORT_FILE_NAME); - fileRequest.setFolder(DefaultRepositoryDir.getExportExcelTempDir() + "/" + fileId); + fileRequest.setFileName(fileId); + fileRequest.setFolder(DefaultRepositoryDir.getExportExcelTempDir()); fileRequest.setStorage(StorageType.MINIO.name()); try { FileInputStream inputStream = new FileInputStream(file); @@ -787,22 +789,33 @@ public class FunctionalCaseFileService { Project project = projectMapper.selectByPrimaryKey(projectId); byte[] bytes; FileRequest fileRequest = new FileRequest(); - fileRequest.setFileName(EXPORT_FILE_NAME); - fileRequest.setFolder(DefaultRepositoryDir.getExportExcelTempDir() + "/" + fileId); + fileRequest.setFileName(fileId); + fileRequest.setFolder(DefaultRepositoryDir.getExportExcelTempDir()); fileRequest.setStorage(StorageType.MINIO.name()); try { bytes = fileService.download(fileRequest); } catch (Exception e) { throw new MSException("get file error"); } + String fileName = ""; + if (StringUtils.endsWith(fileId, XMIND)) { + fileName = "Metersphere_case_" + project.getName() + XMIND; + } + if (StringUtils.endsWith(fileId, XLSX)) { + fileName = "Metersphere_case_" + project.getName() + XLSX; + } - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType("application/octet-stream")) - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + "Metersphere_case_" + project.getName() + "\"") - .body(bytes); + try { + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType("application/octet-stream")) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())) + .body(bytes); + } catch (UnsupportedEncodingException e) { + throw new MSException("Utf-8 encoding is not supported"); + } } - public void stopExport(String projectId, String userId) { - exportTaskManager.sendStopMessage(projectId, userId); + public void stopExport(String taskId, String userId) { + exportTaskManager.sendStopMessage(taskId, userId); } } diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseService.java b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseService.java index 9dbab35081..ef9f22f45b 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseService.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseService.java @@ -1372,7 +1372,7 @@ public class FunctionalCaseService { * @param ids * @return */ - private List getCaseDataByIds(List ids) { + public List getCaseDataByIds(List ids) { FunctionalCaseExample example = new FunctionalCaseExample(); example.createCriteria().andIdIn(ids); return functionalCaseMapper.selectByExample(example); diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseXmindService.java b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseXmindService.java index 3e1ec58f79..2dfa9ab4b0 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseXmindService.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseXmindService.java @@ -1,21 +1,41 @@ package io.metersphere.functional.service; +import io.metersphere.functional.domain.ExportTask; +import io.metersphere.functional.domain.FunctionalCase; +import io.metersphere.functional.domain.FunctionalCaseBlob; +import io.metersphere.functional.domain.FunctionalCaseCustomField; +import io.metersphere.functional.request.FunctionalCaseExportRequest; +import io.metersphere.functional.socket.ExportWebSocketHandler; +import io.metersphere.functional.xmind.domain.FunctionalCaseXmindDTO; import io.metersphere.functional.xmind.domain.FunctionalCaseXmindData; import io.metersphere.functional.xmind.utils.XmindExportUtil; +import io.metersphere.sdk.constants.ModuleConstants; +import io.metersphere.sdk.constants.MsgType; +import io.metersphere.sdk.dto.SocketMsgDTO; import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.LogUtils; import io.metersphere.sdk.util.Translator; +import io.metersphere.system.constants.ExportConstants; +import io.metersphere.system.dto.sdk.BaseTreeNode; import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO; +import io.metersphere.system.manager.ExportTaskManager; +import io.metersphere.system.uid.IDGenerator; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.File; import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * @author wx @@ -28,7 +48,17 @@ public class FunctionalCaseXmindService { @Resource private FunctionalCaseFileService functionalCaseFileService; - + @Resource + private FunctionalCaseService functionalCaseService; + @Resource + private FunctionalCaseCustomFieldService functionalCaseCustomFieldService; + @Resource + private FunctionalCaseModuleService functionalCaseModuleService; + private static final String EXPORT_CASE_TMP_DIR = "tmp"; + @Resource + private ExportTaskManager exportTaskManager; + @Resource + private FunctionalCaseLogService functionalCaseLogService; public void downloadXmindTemplate(String projectId, HttpServletResponse response) { List customFields = functionalCaseFileService.getCustomFields(projectId); @@ -59,5 +89,141 @@ public class FunctionalCaseXmindService { } } + /** + * 导出xmind + * + * @param request + */ + public void exportFunctionalCaseXmind(FunctionalCaseExportRequest request, String userId) { + try { + exportTaskManager.exportAsyncTask(request.getProjectId(), userId, ExportConstants.ExportType.CASE.toString(), request, t -> exportXmind(request, userId)); + } catch (InterruptedException e) { + LogUtils.error("导出失败:" + e); + throw new MSException(e); + } + } + + private String exportXmind(FunctionalCaseExportRequest request, String userId) { + //获取导出的ids集合 + List ids = functionalCaseService.doSelectIds(request, request.getProjectId()); + if (CollectionUtils.isEmpty(ids)) { + return null; + } + + try { + FunctionalCaseXmindData xmindData = buildXmindData(ids, request); + File tmpFile = new File(getClass().getClassLoader().getResource(StringUtils.EMPTY).getPath() + + File.separatorChar + EXPORT_CASE_TMP_DIR + "_" + IDGenerator.nextStr() + ".xmind"); + List templateCustomFields = functionalCaseFileService.getCustomFields(request.getProjectId()); + TemplateCustomFieldDTO templateCustomFieldDTO = templateCustomFields.stream().filter(item -> StringUtils.equalsIgnoreCase(item.getFieldName(), Translator.get("custom_field.functional_priority"))).findFirst().get(); + XmindExportUtil.export(xmindData, request, tmpFile, templateCustomFieldDTO); + functionalCaseFileService.uploadFileToMinio(tmpFile, request.getFileId()); + + functionalCaseLogService.exportExcelLog(request); + List exportTasks = functionalCaseFileService.getExportTasks(request.getProjectId(), userId); + String taskId; + if (CollectionUtils.isNotEmpty(exportTasks)) { + taskId = exportTasks.getFirst().getId(); + functionalCaseFileService.updateExportTask(ExportConstants.ExportState.SUCCESS.name(), taskId, request.getFileId()); + } else { + taskId = MsgType.CONNECT.name(); + } + SocketMsgDTO socketMsgDTO = new SocketMsgDTO(request.getFileId(), "", MsgType.CONNECT.name(), taskId); + socketMsgDTO.setReportId(request.getFileId()); + ExportWebSocketHandler.sendMessageSingle(socketMsgDTO); + } catch (Exception e) { + List exportTasks = functionalCaseFileService.getExportTasks(request.getProjectId(), userId); + if (CollectionUtils.isNotEmpty(exportTasks)) { + functionalCaseFileService.updateExportTask(ExportConstants.ExportState.ERROR.name(), exportTasks.getFirst().getId(), request.getFileId()); + } + LogUtils.error(e); + throw new MSException(e); + } + return null; + } + + + private FunctionalCaseXmindData buildXmindData(List ids, FunctionalCaseExportRequest request) { + FunctionalCaseXmindData xmindData = new FunctionalCaseXmindData(); + xmindData.setModuleId("MODULE"); + xmindData.setModuleName("MODULE"); + //基础信息 + List caseList = functionalCaseService.getCaseDataByIds(ids); + //大字段 + Map functionalCaseBlobMap = functionalCaseService.copyBlobInfo(ids); + //自定义字段 + Map> customFieldMap = functionalCaseCustomFieldService.getCustomFieldMapByCaseIds(ids); + + + Map> moduleCaseMap = caseList.stream().collect(Collectors.groupingBy(FunctionalCase::getModuleId)); + List tree = functionalCaseModuleService.getTree(request.getProjectId()); + + for (Map.Entry> entry : moduleCaseMap.entrySet()) { + String moduleId = entry.getKey(); + List dataList = entry.getValue(); + List dtos = buildXmindDTO(dataList, functionalCaseBlobMap, customFieldMap); + if (StringUtils.equals(moduleId, ModuleConstants.DEFAULT_NODE_ID)) { + xmindData.setFunctionalCaseList(dtos); + } else { + LinkedList returnList = new LinkedList<>(); + LinkedList modulePathDataList = getModuleById(moduleId, tree, returnList); + xmindData.setItem(modulePathDataList, dtos); + System.out.println("modulePathDataList: " + modulePathDataList); + } + } + + return xmindData; + + } + + private LinkedList getModuleById(String moduleId, List tree, LinkedList returnList) { + + for (BaseTreeNode baseTreeNode : tree) { + if (StringUtils.equals(baseTreeNode.getId(), moduleId)) { + BaseTreeNode node = new BaseTreeNode(); + node.setId(baseTreeNode.getId()); + node.setName(baseTreeNode.getName()); + returnList.addFirst(node); + return returnList; + } else { + List children = baseTreeNode.getChildren(); + if (CollectionUtils.isNotEmpty(children)) { + LinkedList result = getModuleById(moduleId, children, returnList); + if (CollectionUtils.isNotEmpty(result)) { + BaseTreeNode node = new BaseTreeNode(); + node.setId(baseTreeNode.getId()); + node.setName(baseTreeNode.getName()); + returnList.addFirst(node); + return returnList; + } + } + } + } + return returnList; + } + + + private List buildXmindDTO(List dataList, Map functionalCaseBlobMap, Map> customFieldMap) { + List caseXmindDTOS = new ArrayList<>(); + dataList.forEach(item -> { + FunctionalCaseBlob functionalCaseBlob = functionalCaseBlobMap.get(item.getId()); + List customFields = customFieldMap.get(item.getId()); + FunctionalCaseXmindDTO dto = new FunctionalCaseXmindDTO(); + dto.setId(item.getId()); + dto.setNum(item.getNum().toString()); + dto.setProjectId(item.getProjectId()); + dto.setName(item.getName()); + dto.setTags(item.getTags().toString()); + dto.setCaseEditType(item.getCaseEditType()); + dto.setSteps(new String(functionalCaseBlob.getSteps() == null ? new byte[0] : functionalCaseBlob.getSteps(), StandardCharsets.UTF_8)); + dto.setTextDescription(new String(functionalCaseBlob.getTextDescription() == null ? new byte[0] : functionalCaseBlob.getTextDescription(), StandardCharsets.UTF_8)); + dto.setExpectedResult(new String(functionalCaseBlob.getExpectedResult() == null ? new byte[0] : functionalCaseBlob.getExpectedResult(), StandardCharsets.UTF_8)); + dto.setPrerequisite(new String(functionalCaseBlob.getPrerequisite() == null ? new byte[0] : functionalCaseBlob.getPrerequisite(), StandardCharsets.UTF_8)); + dto.setDescription(new String(functionalCaseBlob.getDescription() == null ? new byte[0] : functionalCaseBlob.getDescription(), StandardCharsets.UTF_8)); + dto.setCustomFieldDTOList(customFields); + caseXmindDTOS.add(dto); + }); + return caseXmindDTOS; + } } diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindDTO.java b/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindDTO.java index 277542a347..2945a1cf8d 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindDTO.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindDTO.java @@ -1,6 +1,6 @@ package io.metersphere.functional.xmind.domain; -import io.metersphere.functional.dto.FunctionalCaseCustomFieldDTO; +import io.metersphere.functional.domain.FunctionalCaseCustomField; import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -47,7 +47,7 @@ public class FunctionalCaseXmindDTO { private String description; @Schema(description = "自定义字段") - private List customFieldDTOList; + private List customFieldDTOList; @Schema(description = "模板自定义字段") private List templateCustomFieldDTOList; diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindData.java b/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindData.java index 3b1ce2ffc8..a154f7b975 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindData.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindData.java @@ -1,10 +1,14 @@ package io.metersphere.functional.xmind.domain; +import io.metersphere.system.dto.sdk.BaseTreeNode; import lombok.Data; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import java.io.Serial; import java.io.Serializable; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; /** @@ -21,4 +25,49 @@ public class FunctionalCaseXmindData implements Serializable { private List children = new ArrayList<>(); + public void setItem(LinkedList modulePathDataList, List dataList) { + if (CollectionUtils.isNotEmpty(modulePathDataList) && CollectionUtils.isNotEmpty(dataList)) { + if (modulePathDataList.size() == 1) { + this.setData(modulePathDataList.getFirst(), dataList); + } else { + BaseTreeNode caseNode = modulePathDataList.getFirst(); + if (caseNode != null) { + FunctionalCaseXmindData matchedData = null; + for (FunctionalCaseXmindData item : children) { + if (StringUtils.equals(item.getModuleId(), caseNode.getId())) { + matchedData = item; + break; + } + } + if(matchedData == null){ + matchedData = new FunctionalCaseXmindData(); + matchedData.setModuleId(caseNode.getId()); + matchedData.setModuleName(caseNode.getName()); + this.children.add(matchedData); + } + modulePathDataList.removeFirst(); + matchedData.setItem(modulePathDataList,dataList); + } + } + } + } + + private void setData(BaseTreeNode caseNode, List dataList) { + if (caseNode != null && CollectionUtils.isNotEmpty(dataList)) { + boolean matching = false; + for (FunctionalCaseXmindData item : children) { + if (StringUtils.equals(item.getModuleId(), caseNode.getId())) { + matching = true; + item.setFunctionalCaseList(dataList); + } + } + if (!matching) { + FunctionalCaseXmindData child = new FunctionalCaseXmindData(); + child.setModuleId(caseNode.getId()); + child.setModuleName(caseNode.getName()); + child.setFunctionalCaseList(dataList); + this.children.add(child); + } + } + } } diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/utils/XmindExportUtil.java b/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/utils/XmindExportUtil.java index 354dbe14a9..6d9ed92fd8 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/utils/XmindExportUtil.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/utils/XmindExportUtil.java @@ -1,6 +1,10 @@ package io.metersphere.functional.xmind.utils; import io.metersphere.functional.constants.FunctionalCaseTypeConstants; +import io.metersphere.functional.domain.FunctionalCaseCustomField; +import io.metersphere.functional.excel.domain.FunctionalCaseExportColumns; +import io.metersphere.functional.excel.domain.FunctionalCaseHeader; +import io.metersphere.functional.request.FunctionalCaseExportRequest; import io.metersphere.functional.xmind.domain.FunctionalCaseXmindDTO; import io.metersphere.functional.xmind.domain.FunctionalCaseXmindData; import io.metersphere.sdk.constants.CustomFieldType; @@ -16,12 +20,14 @@ import org.xmind.core.*; import org.xmind.core.style.IStyle; import org.xmind.core.style.IStyleSheet; +import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * @author wx @@ -37,7 +43,7 @@ public class XmindExportUtil { * @param template */ public static void downloadTemplate(HttpServletResponse response, FunctionalCaseXmindData caseData, boolean template, Map> customFieldOptionsMap) { - IWorkbook workBook = createXmindByCaseData(caseData, template, customFieldOptionsMap); + IWorkbook workBook = createXmindByCaseData(caseData, template, customFieldOptionsMap, null, null); response.setContentType("application/octet-stream"); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); @@ -53,7 +59,7 @@ public class XmindExportUtil { } } - private static IWorkbook createXmindByCaseData(FunctionalCaseXmindData caseData, boolean template, Map> customFieldOptionsMap) { + private static IWorkbook createXmindByCaseData(FunctionalCaseXmindData caseData, boolean template, Map> customFieldOptionsMap, FunctionalCaseExportRequest request, TemplateCustomFieldDTO priority) { // 创建思维导图的工作空间 IWorkbookBuilder workbookBuilder = Core.getWorkbookBuilder(); IWorkbook workbook = workbookBuilder.createWorkbook(); @@ -75,14 +81,18 @@ public class XmindExportUtil { if (CollectionUtils.isNotEmpty(caseData.getChildren())) { for (FunctionalCaseXmindData data : caseData.getChildren()) { - addItemTopic(rootTopic, workbook, styleMap, data, true, template, customFieldOptionsMap); + if (template) { + addTemplateTopic(rootTopic, workbook, styleMap, data, true, customFieldOptionsMap); + } else { + addTopic(rootTopic, workbook, styleMap, data, true, request, priority); + } } } return workbook; } - private static void addItemTopic(ITopic parentTpoic, IWorkbook workbook, Map styleMap, FunctionalCaseXmindData xmindData, - boolean isFirstLevel, boolean template, Map> customFieldOptionsMap) { + private static void addTemplateTopic(ITopic parentTopic, IWorkbook workbook, Map styleMap, FunctionalCaseXmindData xmindData, + boolean isFirstLevel, Map> customFieldOptionsMap) { ITopic topic = workbook.createTopic(); topic.setTitleText(xmindData.getModuleName()); if (isFirstLevel) { @@ -94,7 +104,7 @@ public class XmindExportUtil { topic.setStyleId(styleMap.get("subTopicStyle").getId()); } } - parentTpoic.add(topic); + parentTopic.add(topic); if (CollectionUtils.isNotEmpty(xmindData.getFunctionalCaseList())) { IStyle style = null; @@ -107,21 +117,18 @@ public class XmindExportUtil { if (style != null) { itemTopic.setStyleId(style.getId()); } - if (template) { - // 模板 - buildTemplateTopic(topic, style, dto, itemTopic, workbook, customFieldOptionsMap); - } + buildTemplateTopic(topic, style, dto, itemTopic, workbook, customFieldOptionsMap); } } if (CollectionUtils.isNotEmpty(xmindData.getChildren())) { for (FunctionalCaseXmindData data : xmindData.getChildren()) { - addItemTopic(topic, workbook, styleMap, data, false, template, customFieldOptionsMap); + addTemplateTopic(topic, workbook, styleMap, data, false, customFieldOptionsMap); } } } - private static void buildTemplateTopic(ITopic topic, IStyle style, FunctionalCaseXmindDTO dto, ITopic itemTopic, IWorkbook workbook,Map> customFieldOptionsMap) { + private static void buildTemplateTopic(ITopic topic, IStyle style, FunctionalCaseXmindDTO dto, ITopic itemTopic, IWorkbook workbook, Map> customFieldOptionsMap) { //用例名称 TemplateCustomFieldDTO priorityDto = dto.getTemplateCustomFieldDTOList().stream().filter(item -> StringUtils.equalsIgnoreCase(item.getFieldName(), Translator.get("custom_field.functional_priority"))).findFirst().get(); @@ -230,7 +237,7 @@ public class XmindExportUtil { dto.getTemplateCustomFieldDTOList().forEach(item -> { if (!StringUtils.equalsIgnoreCase(item.getFieldName(), Translator.get("custom_field.functional_priority"))) { ITopic customTopic = workbook.createTopic(); - String fieldComment = getComment(item,customFieldOptionsMap); + String fieldComment = getComment(item, customFieldOptionsMap); customTopic.setTitleText(item.getFieldName().concat(": ").concat(fieldComment)); if (style != null) { customTopic.setStyleId(style.getId()); @@ -242,7 +249,7 @@ public class XmindExportUtil { topic.add(itemTopic); } - private static String getComment(TemplateCustomFieldDTO templateCustomFieldDTO,Map> customFieldOptionsMap) { + private static String getComment(TemplateCustomFieldDTO templateCustomFieldDTO, Map> customFieldOptionsMap) { String comment = ""; if (StringUtils.equalsAnyIgnoreCase(templateCustomFieldDTO.getType(), CustomFieldType.MULTIPLE_MEMBER.name(), CustomFieldType.MEMBER.name())) { if (templateCustomFieldDTO.getRequired()) { @@ -356,4 +363,112 @@ public class XmindExportUtil { return styleMap; } + + + public static void export(FunctionalCaseXmindData xmindData, FunctionalCaseExportRequest request, File tmpFile, TemplateCustomFieldDTO priority) { + IWorkbook workBook = createXmindByCaseData(xmindData, false, null, request, priority); + try { + workBook.save(tmpFile.getAbsolutePath()); + } catch (UnsupportedEncodingException e) { + LogUtils.error(e.getMessage(), e); + throw new MSException("Utf-8 encoding is not supported"); + } catch (Exception e) { + LogUtils.error(e.getMessage(), e); + throw new MSException("IO exception"); + } + + } + + private static void addTopic(ITopic parentTopic, IWorkbook workbook, Map styleMap, FunctionalCaseXmindData xmindData, boolean isFirstLevel, FunctionalCaseExportRequest request, TemplateCustomFieldDTO priority) { + ITopic topic = workbook.createTopic(); + topic.setTitleText(xmindData.getModuleName()); + if (isFirstLevel) { + if (styleMap.containsKey("mainTopicStyle")) { + topic.setStyleId(styleMap.get("mainTopicStyle").getId()); + } + } else { + if (styleMap.containsKey("subTopicStyle")) { + topic.setStyleId(styleMap.get("subTopicStyle").getId()); + } + } + parentTopic.add(topic); + if (CollectionUtils.isNotEmpty(xmindData.getFunctionalCaseList())) { + IStyle style = null; + if (styleMap.containsKey("subTopicStyle")) { + style = styleMap.get("subTopicStyle"); + } + for (FunctionalCaseXmindDTO dto : xmindData.getFunctionalCaseList()) { + // 创建小节节点 + ITopic itemTopic = workbook.createTopic(); + if (style != null) { + itemTopic.setStyleId(style.getId()); + } + buildTopic(topic, style, dto, itemTopic, workbook, request, xmindData.getModuleName(), priority); + } + } + if (CollectionUtils.isNotEmpty(xmindData.getChildren())) { + for (FunctionalCaseXmindData data : xmindData.getChildren()) { + addTopic(topic, workbook, styleMap, data, false, request, priority); + } + } + } + + private static void buildTopic(ITopic topic, IStyle style, FunctionalCaseXmindDTO dto, ITopic itemTopic, IWorkbook workbook, FunctionalCaseExportRequest request, String moduleName, TemplateCustomFieldDTO priority) { + List systemColumns = request.getSystemFields().stream().map(FunctionalCaseHeader::getId).toList(); + FunctionalCaseExportColumns columns = new FunctionalCaseExportColumns(); + + Map customFieldMap = dto.getCustomFieldDTOList().stream().collect(Collectors.toMap(FunctionalCaseCustomField::getFieldId, FunctionalCaseCustomField::getValue)); + + //用例名称 + String casePriority = customFieldMap.get(priority.getFieldId()); + itemTopic.setTitleText("case-".concat(StringUtils.defaultIfBlank(casePriority,StringUtils.EMPTY)).concat(": ").concat(dto.getName())); + //系统字段 + systemColumns.forEach(item -> { + if (columns.getSystemColumns().containsKey(item) && !StringUtils.equalsIgnoreCase(item, "name")) { + ITopic preTopic = workbook.createTopic(); + switch (item) { + case "num": + preTopic.setTitleText(columns.getSystemColumns().get(item).concat(": ").concat(dto.getNum())); + break; + case "prerequisite": + preTopic.setTitleText(columns.getSystemColumns().get(item).concat(": ").concat(dto.getPrerequisite())); + break; + case "module": + preTopic.setTitleText(columns.getSystemColumns().get(item).concat(": ").concat(moduleName)); + break; + case "text_description": + preTopic.setTitleText(columns.getSystemColumns().get(item).concat(": ").concat(dto.getTextDescription())); + break; + case "expected_result": + preTopic.setTitleText(columns.getSystemColumns().get(item).concat(": ").concat(dto.getExpectedResult())); + break; + default: + break; + } + if (style != null) { + preTopic.setStyleId(style.getId()); + } + itemTopic.add(preTopic, ITopic.ATTACHED); + } + }); + + //自定义字段 + Map customColumnsMap = request.getCustomFields().stream().collect(Collectors.toMap(FunctionalCaseHeader::getId, FunctionalCaseHeader::getName)); + + + customColumnsMap.forEach((k, v) -> { + if (customFieldMap.containsKey(k)) { + ITopic preTopic = workbook.createTopic(); + preTopic.setTitleText(v.concat(": ").concat(customFieldMap.get(k))); + if (style != null) { + preTopic.setStyleId(style.getId()); + } + itemTopic.add(preTopic, ITopic.ATTACHED); + } + }); + + topic.add(itemTopic); + } + + } diff --git a/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseControllerTests.java b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseControllerTests.java index 690347ffd9..6957d60436 100644 --- a/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseControllerTests.java +++ b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseControllerTests.java @@ -91,6 +91,7 @@ public class FunctionalCaseControllerTests extends BaseTest { public static final String EXPORT_COLUMNS_URL = "/functional/case/export/columns/"; public static final String DOWNLOAD_FILE_URL = "/functional/case/download/file/"; public static final String STOP_EXPORT_URL = "/functional/case/stop/"; + public static final String EXPORT_XMIND_URL = "/functional/case/export/xmind"; @Resource private NotificationMapper notificationMapper; @@ -856,8 +857,8 @@ public class FunctionalCaseControllerTests extends BaseTest { @Order(24) public void downloadFile() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get(DOWNLOAD_FILE_URL + DEFAULT_PROJECT_ID + "/" + "123142342") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken)); + .header(SessionConstants.HEADER_TOKEN, sessionId) + .header(SessionConstants.CSRF_TOKEN, csrfToken)); } @Test @@ -865,4 +866,44 @@ public class FunctionalCaseControllerTests extends BaseTest { public void stopExport() throws Exception { this.requestGetExcel(STOP_EXPORT_URL + DEFAULT_PROJECT_ID); } + + @Test + @Order(3) + public void exportXmind() throws Exception { + FunctionalCaseExportRequest request = new FunctionalCaseExportRequest(); + request.setProjectId(DEFAULT_PROJECT_ID); + request.setSelectIds(List.of("TEST_FUNCTIONAL_CASE_ID")); + List sysHeaders = new ArrayList<>() {{ + add(new FunctionalCaseHeader() {{ + setId("num"); + setName("ID"); + }}); + add(new FunctionalCaseHeader() {{ + setId("name"); + setName("用例名称"); + }}); + }}; + request.setSystemFields(sysHeaders); + List customHeaders = new ArrayList<>() {{ + add(new FunctionalCaseHeader() {{ + setId("A"); + setName("测试3"); + }}); + }}; + request.setCustomFields(customHeaders); + List otherHeaders = new ArrayList<>() {{ + add(new FunctionalCaseHeader() {{ + setId("createTime"); + setName("创建时间"); + }}); + }}; + request.setOtherFields(otherHeaders); + + request.setFileId("123142342"); + this.requestPost(EXPORT_XMIND_URL, request); + request.setSelectIds(new ArrayList<>()); + this.requestPost(EXPORT_XMIND_URL, request); + request.setSelectIds(List.of("TEST_FUNCTIONAL_CASE_ID_8")); + this.requestPost(EXPORT_XMIND_URL, request); + } } diff --git a/backend/services/case-management/src/test/resources/dml/init_file_metadata_test.sql b/backend/services/case-management/src/test/resources/dml/init_file_metadata_test.sql index b503458141..c818315a02 100644 --- a/backend/services/case-management/src/test/resources/dml/init_file_metadata_test.sql +++ b/backend/services/case-management/src/test/resources/dml/init_file_metadata_test.sql @@ -30,6 +30,10 @@ VALUES ('TEST_FUNCTIONAL_CASE_ID_6', 6, 'TEST_MODULE_ID_GYQ', '100001100001', '1 INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time) VALUES ('TEST_FUNCTIONAL_CASE_ID_7', 7, 'TEST_MODULE_ID_GYQ', '100001100001', '100001', 'copy_long_name_11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111', 'UN_REVIEWED', NULL, 'STEP', 0, 'v3.0.0', 'TEST_REF_ID_2', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL); +INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time) +VALUES ('TEST_FUNCTIONAL_CASE_ID_8', 1, 'root', '100001100001', '100001', '测试', 'UN_REVIEWED', NULL, 'STEP', 0, 'v1.0.0', 'v1.0.0', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL); + + INSERT INTO functional_case_blob(id, steps, text_description, expected_result, prerequisite, description) VALUES ('TEST_FUNCTIONAL_CASE_ID', 'STEP', '1111', '', '', 'TEST'); INSERT INTO functional_case_blob(id, steps, text_description, expected_result, prerequisite, description) VALUES ('TEST_FUNCTIONAL_CASE_ID_1', 'STEP', '1111', '', '', '1111'); INSERT INTO functional_case_blob(id, steps, text_description, expected_result, prerequisite, description) VALUES ('TEST_FUNCTIONAL_CASE_ID_2', 'STEP', '2222', '', '', '2222'); @@ -38,6 +42,7 @@ INSERT INTO functional_case_blob(id, steps, text_description, expected_result, p INSERT INTO functional_case_blob(id, steps, text_description, expected_result, prerequisite, description) VALUES ('TEST_FUNCTIONAL_CASE_ID_5', 'STEP', '5555', '', '', '5555'); INSERT INTO functional_case_blob(id, steps, text_description, expected_result, prerequisite, description) VALUES ('TEST_FUNCTIONAL_CASE_ID_6', 'STEP', '6666', '', '', '6666'); INSERT INTO functional_case_blob(id, steps, text_description, expected_result, prerequisite, description) VALUES ('TEST_FUNCTIONAL_CASE_ID_7', 'STEP', '7777', '', '', '7777'); +INSERT INTO functional_case_blob(id, steps, text_description, expected_result, prerequisite, description) VALUES ('TEST_FUNCTIONAL_CASE_ID_8', NULL, NUll, NUll, NUll, NUll); INSERT INTO functional_case_custom_field(case_id, field_id, value) VALUES ('TEST_FUNCTIONAL_CASE_ID', '100548878725546079', '22'); diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/manager/ExportTaskManager.java b/backend/services/system-setting/src/main/java/io/metersphere/system/manager/ExportTaskManager.java index ea39a82f14..15436c5f3d 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/manager/ExportTaskManager.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/manager/ExportTaskManager.java @@ -34,7 +34,8 @@ public class ExportTaskManager { public static final String EXPORT_CONSUME = "export_consume"; - public void exportAsyncTask(String userId, String type, T t, Function selectListFunc) throws InterruptedException { + public void exportAsyncTask(String projectId, String userId, String type, T t, Function selectListFunc) throws InterruptedException { + ExportTask exportTask = buildExportTask(projectId, userId, type); ExecutorService executorService = Executors.newFixedThreadPool(1); Future future = executorService.submit(() -> { while (!Thread.currentThread().isInterrupted()) { @@ -44,12 +45,10 @@ public class ExportTaskManager { } LogUtils.info("Thread has been interrupted."); }); - Thread.sleep(6000); - ExportTask exportTask = buildExportTask(userId, type); map.put(exportTask.getId(), future); } - private ExportTask buildExportTask(String userId, String type) { + private ExportTask buildExportTask(String projectId, String userId, String type) { ExportTask exportTask = new ExportTask(); exportTask.setId(IDGenerator.nextStr()); exportTask.setType(type); @@ -58,6 +57,7 @@ public class ExportTaskManager { exportTask.setState(ExportConstants.ExportState.PREPARED.toString()); exportTask.setUpdateUser(userId); exportTask.setUpdateTime(System.currentTimeMillis()); + exportTask.setProjectId(projectId); exportTaskMapper.insert(exportTask); return exportTask; } @@ -71,15 +71,17 @@ public class ExportTaskManager { kafkaTemplate.send(KafkaTopicConstants.EXPORT, JSON.toJSONString(exportTask)); } - @KafkaListener(id=EXPORT_CONSUME, topics = KafkaTopicConstants.EXPORT, groupId = EXPORT_CONSUME + "_" + "${random.uuid}") + @KafkaListener(id = EXPORT_CONSUME, topics = KafkaTopicConstants.EXPORT, groupId = EXPORT_CONSUME + "_" + "${random.uuid}") public void stop(ConsumerRecord record) { LogUtils.info("Service consume platform_plugin message: " + record.value()); ExportTask exportTask = JSON.parseObject(record.value(), ExportTask.class); - if (exportTask!=null && StringUtils.isNotBlank(exportTask.getId())) { + if (exportTask != null && StringUtils.isNotBlank(exportTask.getId())) { String id = exportTask.getId(); - map.get(id).cancel(true); - map.remove(id); - exportTaskMapper.updateByPrimaryKey(exportTask); + if (map.containsKey(id)) { + map.get(id).cancel(true); + map.remove(id); + } + exportTaskMapper.updateByPrimaryKeySelective(exportTask); } }