diff --git a/backend/framework/domain/src/main/resources/migration/3.2.0/dml/V3.2.0_2_1__data.sql b/backend/framework/domain/src/main/resources/migration/3.2.0/dml/V3.2.0_2_1__data.sql new file mode 100644 index 0000000000..76693879f0 --- /dev/null +++ b/backend/framework/domain/src/main/resources/migration/3.2.0/dml/V3.2.0_2_1__data.sql @@ -0,0 +1,7 @@ +-- set innodb lock wait timeout +SET SESSION innodb_lock_wait_timeout = 7200; + + +-- 项目管理员、项目成员增加权限 +INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'FUNCTIONAL_CASE:READ+EXPORT'); +INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'FUNCTIONAL_CASE:READ+EXPORT'); diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/ExportMsgDTO.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/ExportMsgDTO.java index 14b96ae8ae..95afd0c38e 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/ExportMsgDTO.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/ExportMsgDTO.java @@ -28,7 +28,12 @@ public class ExportMsgDTO implements Serializable { */ private int count; /** - * 消息类型(LINK-链接标识,HEARTBEAT-心跳检查标识,EXEC_START-开始执行标识,EXEC_RESULT-执行结果标识) + * 导出状态 + */ + private boolean isSuccessful; + + /** + * 消息类型(CONNECT-链接标识,HEARTBEAT-心跳检查标识,EXEC_START-开始执行标识,EXEC_RESULT-执行结果标识) */ private String msgType; } 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 33775401dc..3482a718dd 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 @@ -4,6 +4,7 @@ import com.alibaba.excel.util.StringUtils; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import io.metersphere.functional.domain.FunctionalCase; +import io.metersphere.functional.dto.ExportTaskDTO; import io.metersphere.functional.dto.FunctionalCaseDetailDTO; import io.metersphere.functional.dto.FunctionalCasePageDTO; import io.metersphere.functional.dto.FunctionalCaseVersionDTO; @@ -272,8 +273,8 @@ public class FunctionalCaseController { @PostMapping("/export/excel") @Operation(summary = "用例管理-功能用例-excel导出") @RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_EXPORT) - public void testCaseExport(@Validated @RequestBody FunctionalCaseExportRequest request) { - functionalCaseFileService.export(SessionUtils.getUserId(), request); + public String testCaseExport(@Validated @RequestBody FunctionalCaseExportRequest request) { + return functionalCaseFileService.export(SessionUtils.getUserId(), request); } @GetMapping("/stop/{taskId}") @@ -312,14 +313,14 @@ public class FunctionalCaseController { @PostMapping("/export/xmind") @Operation(summary = "用例管理-功能用例-xmind导出") @RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_EXPORT) - public void caseExportXmind(@Validated @RequestBody FunctionalCaseExportRequest request) { - functionalCaseXmindService.exportFunctionalCaseXmind(request, SessionUtils.getUserId()); + public String caseExportXmind(@Validated @RequestBody FunctionalCaseExportRequest request) { + return functionalCaseXmindService.exportFunctionalCaseXmind(request, SessionUtils.getUserId()); } @GetMapping(value = "/check/export-task") @Operation(summary = "用例管理-功能用例-导出任务校验") @RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_EXPORT) - public String checkExportTask() { + public ExportTaskDTO checkExportTask() { return functionalCaseFileService.checkExportTask(SessionUtils.getCurrentProjectId(), SessionUtils.getUserId()); } } diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/dto/ExportTaskDTO.java b/backend/services/case-management/src/main/java/io/metersphere/functional/dto/ExportTaskDTO.java new file mode 100644 index 0000000000..db4a1b0687 --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/dto/ExportTaskDTO.java @@ -0,0 +1,22 @@ +package io.metersphere.functional.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author wx + */ +@Data +public class ExportTaskDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + @Schema(description = "文件id") + private String fileId; + + @Schema(description = "任务id") + private String taskId; +} 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 cd8e30c6f1..1e85c49ba0 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 @@ -10,6 +10,7 @@ import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.metadata.style.WriteFont; import io.metersphere.functional.constants.FunctionalCaseTypeConstants; import io.metersphere.functional.domain.*; +import io.metersphere.functional.dto.ExportTaskDTO; import io.metersphere.functional.dto.response.FunctionalCaseImportResponse; import io.metersphere.functional.excel.constants.FunctionalCaseImportFiled; import io.metersphere.functional.excel.converter.FunctionalCaseExportConverter; @@ -298,7 +299,7 @@ public class FunctionalCaseFileService { * * @return FunctionalCaseImportResponse */ - public FunctionalCaseImportResponse preCheckXMind(FunctionalCaseImportRequest request,SessionUser user, MultipartFile multipartFile) { + public FunctionalCaseImportResponse preCheckXMind(FunctionalCaseImportRequest request, SessionUser user, MultipartFile multipartFile) { if (multipartFile == null) { throw new MSException(Translator.get("file_cannot_be_null")); } @@ -313,11 +314,11 @@ public class FunctionalCaseFileService { Long lasePos = nextPos + ((long) ServiceUtils.POS_STEP * Integer.parseInt(request.getCount())); //获取当前项目默认模板的自定义字段 List customFields = getCustomFields(request.getProjectId()); - XMindCaseParser xMindParser = new XMindCaseParser(request, customFields, user,lasePos); + XMindCaseParser xMindParser = new XMindCaseParser(request, customFields, user, lasePos); errList = xMindParser.parse(multipartFile); xMindParser.clear(); response.setErrorMessages(errList); - response.setSuccessCount(xMindParser.getList().size()+xMindParser.getUpdateList().size()); + response.setSuccessCount(xMindParser.getList().size() + xMindParser.getUpdateList().size()); response.setFailCount(errList.size()); return response; } catch (Exception e) { @@ -381,7 +382,7 @@ public class FunctionalCaseFileService { Long lasePos = nextPos + ((long) ServiceUtils.POS_STEP * Integer.parseInt(request.getCount())); //获取当前项目默认模板的自定义字段 List customFields = getCustomFields(request.getProjectId()); - XMindCaseParser xmindParser = new XMindCaseParser(request, customFields, user,lasePos); + XMindCaseParser xmindParser = new XMindCaseParser(request, customFields, user, lasePos); errList = xmindParser.parse(multipartFile); if (CollectionUtils.isEmpty(xmindParser.getList()) && CollectionUtils.isEmpty(xmindParser.getUpdateList())) { @@ -394,7 +395,7 @@ public class FunctionalCaseFileService { xmindParser.saveData(); xmindParser.clear(); response.setErrorMessages(errList); - response.setSuccessCount(xmindParser.getList().size()+xmindParser.getUpdateList().size()); + response.setSuccessCount(xmindParser.getList().size() + xmindParser.getUpdateList().size()); response.setFailCount(errList.size()); return response; } catch (Exception e) { @@ -403,21 +404,24 @@ public class FunctionalCaseFileService { } } - public void export(String userId, FunctionalCaseExportRequest request) { + public String 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) { - throw new MSException(Translator.get("export_case_task_existed")); - } - exportTaskManager.exportAsyncTask(request.getProjectId(), request.getFileId(), userId, ExportConstants.ExportType.CASE.toString(), request, t -> exportFunctionalCaseZip(request, userId)); + exportCheck(request, userId); + ExportTask exportTask = exportTaskManager.exportAsyncTask(request.getProjectId(), request.getFileId(), userId, ExportConstants.ExportType.CASE.toString(), request, t -> exportFunctionalCaseZip(request, userId)); + return exportTask.getId(); } catch (InterruptedException e) { LogUtils.error("导出失败:" + e); throw new MSException(e); } } + public void exportCheck(FunctionalCaseExportRequest request, String userId) { + List exportTasks = getExportTasks(request.getProjectId(), userId); + if (CollectionUtils.isNotEmpty(exportTasks)) { + throw new MSException(Translator.get("export_case_task_existed")); + } + } + /** * 导出excel @@ -462,9 +466,11 @@ public class FunctionalCaseFileService { } else { taskId = MsgType.CONNECT.name(); } - ExportMsgDTO exportMsgDTO = new ExportMsgDTO(request.getFileId(), taskId, ids.size(), MsgType.CONNECT.name()); + ExportMsgDTO exportMsgDTO = new ExportMsgDTO(request.getFileId(), taskId, ids.size(), true, MsgType.EXEC_RESULT.name()); ExportWebSocketHandler.sendMessageSingle(exportMsgDTO); } catch (Exception e) { + ExportMsgDTO exportMsgDTO = new ExportMsgDTO(request.getFileId(), "", 0, false, MsgType.EXEC_RESULT.name()); + ExportWebSocketHandler.sendMessageSingle(exportMsgDTO); List exportTasks = getExportTasks(request.getProjectId(), userId); if (CollectionUtils.isNotEmpty(exportTasks)) { updateExportTask(ExportConstants.ExportState.ERROR.name(), exportTasks.getFirst().getId(), fileType); @@ -862,7 +868,7 @@ public class FunctionalCaseFileService { public ResponseEntity downloadFile(String projectId, String fileId, String userId) { List exportTasks = getExportTasksByFileId(projectId, userId, fileId); if (CollectionUtils.isEmpty(exportTasks)) { - throw new MSException("任务不存在"); + return ResponseEntity.notFound().build(); } ExportTask tasksFirst = exportTasks.getFirst(); Project project = projectMapper.selectByPrimaryKey(projectId); @@ -900,12 +906,15 @@ public class FunctionalCaseFileService { exportTaskManager.sendStopMessage(taskId, userId); } - public String checkExportTask(String projectId, String userId) { + public ExportTaskDTO checkExportTask(String projectId, String userId) { + ExportTaskDTO exportTaskDTO = new ExportTaskDTO(); List exportTasks = getExportTasks(projectId, userId); if (CollectionUtils.isNotEmpty(exportTasks)) { - return exportTasks.getFirst().getFileId(); + exportTaskDTO.setFileId(exportTasks.getFirst().getFileId()); + exportTaskDTO.setTaskId(exportTasks.getFirst().getId()); + return exportTaskDTO; } else { - return StringUtils.EMPTY; + return exportTaskDTO; } } } 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 358d75b508..5059911ab6 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 @@ -95,9 +95,11 @@ public class FunctionalCaseXmindService { * * @param request */ - public void exportFunctionalCaseXmind(FunctionalCaseExportRequest request, String userId) { + public String exportFunctionalCaseXmind(FunctionalCaseExportRequest request, String userId) { try { - exportTaskManager.exportAsyncTask(request.getProjectId(), request.getFileId(), userId, ExportConstants.ExportType.CASE.toString(), request, t -> exportXmind(request, userId)); + functionalCaseFileService.exportCheck(request, userId); + ExportTask exportTask = exportTaskManager.exportAsyncTask(request.getProjectId(), request.getFileId(), userId, ExportConstants.ExportType.CASE.toString(), request, t -> exportXmind(request, userId)); + return exportTask.getId(); } catch (InterruptedException e) { LogUtils.error("导出失败:" + e); throw new MSException(e); @@ -130,9 +132,11 @@ public class FunctionalCaseXmindService { } else { taskId = MsgType.CONNECT.name(); } - ExportMsgDTO exportMsgDTO = new ExportMsgDTO(request.getFileId(), taskId, ids.size(), MsgType.CONNECT.name()); + ExportMsgDTO exportMsgDTO = new ExportMsgDTO(request.getFileId(), taskId, ids.size(), true, MsgType.EXEC_RESULT.name()); ExportWebSocketHandler.sendMessageSingle(exportMsgDTO); } catch (Exception e) { + ExportMsgDTO exportMsgDTO = new ExportMsgDTO(request.getFileId(), "", 0, false, MsgType.EXEC_RESULT.name()); + ExportWebSocketHandler.sendMessageSingle(exportMsgDTO); List exportTasks = functionalCaseFileService.getExportTasks(request.getProjectId(), userId); if (CollectionUtils.isNotEmpty(exportTasks)) { functionalCaseFileService.updateExportTask(ExportConstants.ExportState.ERROR.name(), exportTasks.getFirst().getId(), XMIND); diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/socket/ExportWebSocketHandler.java b/backend/services/case-management/src/main/java/io/metersphere/functional/socket/ExportWebSocketHandler.java index f561e030b7..e8e271ca86 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/socket/ExportWebSocketHandler.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/socket/ExportWebSocketHandler.java @@ -49,7 +49,7 @@ public class ExportWebSocketHandler { ONLINE_EXPORT_EXCEL_SESSIONS.put(fileId, session); RemoteEndpoint.Async async = session.getAsyncRemote(); if (async != null) { - async.sendText(JSON.toJSONString(new ExportMsgDTO(fileId, "", 0, MsgType.CONNECT.name()))); + async.sendText(JSON.toJSONString(new ExportMsgDTO(fileId, "", 0, true, MsgType.CONNECT.name()))); session.setMaxIdleTimeout(180000); } LogUtils.info("客户端: [" + fileId + "] : 连接成功!" + ExportWebSocketHandler.ONLINE_EXPORT_EXCEL_SESSIONS.size(), fileId); @@ -92,7 +92,7 @@ public class ExportWebSocketHandler { @Scheduled(fixedRate = 60000) public void heartbeatCheck() { ExportWebSocketHandler.sendMessageSingle( - new ExportMsgDTO(MsgType.HEARTBEAT.name(), MsgType.HEARTBEAT.name(), 0, "heartbeat check") + new ExportMsgDTO(MsgType.HEARTBEAT.name(), MsgType.HEARTBEAT.name(), 0, true, MsgType.HEARTBEAT.name()) ); } } 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 6d9ed92fd8..6d4145c5b9 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 @@ -173,7 +173,7 @@ public class XmindExportUtil { //文本描述 ITopic textDesTopic = workbook.createTopic(); String desc = dto.getTextDescription(); - textDesTopic.setTitleText(desc == null ? Translator.get("xmind_textDescription").concat(": ") : Translator.get("xmind_textDescription").concat(": ").concat(Translator.get("xmind_textDescription")).concat(": ").concat(desc)); + textDesTopic.setTitleText(desc == null ? Translator.get("xmind_textDescription").concat(": ") : Translator.get("xmind_textDescription").concat(": ").concat(desc)); if (style != null) { textDesTopic.setStyleId(style.getId()); } diff --git a/backend/services/case-management/src/main/resources/permission.json b/backend/services/case-management/src/main/resources/permission.json index 60d4d30e2e..0bb76eb3ea 100644 --- a/backend/services/case-management/src/main/resources/permission.json +++ b/backend/services/case-management/src/main/resources/permission.json @@ -28,6 +28,9 @@ { "id": "FUNCTIONAL_CASE:READ+IMPORT" }, + { + "id": "FUNCTIONAL_CASE:READ+EXPORT" + }, { "id": "FUNCTIONAL_CASE:READ+MINDER", "name": "permission.functional_case.minder" 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 c20389f97d..7e2f187d00 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 @@ -6,6 +6,7 @@ import io.metersphere.functional.dto.FunctionalCaseAttachmentDTO; import io.metersphere.functional.dto.FunctionalCasePageDTO; import io.metersphere.functional.dto.response.FunctionalCaseImportResponse; import io.metersphere.functional.excel.domain.FunctionalCaseHeader; +import io.metersphere.functional.mapper.ExportTaskMapper; import io.metersphere.functional.mapper.FunctionalCaseAttachmentMapper; import io.metersphere.functional.mapper.FunctionalCaseCustomFieldMapper; import io.metersphere.functional.mapper.FunctionalCaseMapper; @@ -113,6 +114,8 @@ public class FunctionalCaseControllerTests extends BaseTest { private FunctionalCaseMapper functionalCaseMapper; protected static String functionalCaseId; + @Resource + private ExportTaskMapper exportTaskMapper; @Test @@ -990,11 +993,30 @@ public class FunctionalCaseControllerTests extends BaseTest { @Test @Order(24) public void downloadFile() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get(DOWNLOAD_FILE_URL + DEFAULT_PROJECT_ID + "/" + "123142342") + download("12222"); + ExportTask exportTask = new ExportTask(); + exportTask.setId("12314234222"); + exportTask.setProjectId(DEFAULT_PROJECT_ID); + exportTask.setFileId("123142342"); + exportTask.setFileType("xlsx"); + exportTask.setType("CASE"); + exportTask.setState("SUCCESS"); + exportTask.setCreateTime(System.currentTimeMillis()); + exportTask.setCreateUser("admin"); + exportTask.setUpdateUser("admin"); + exportTask.setUpdateTime(System.currentTimeMillis()); + exportTaskMapper.insertSelective(exportTask); + download("123142342"); + + } + + private void download(String fileId) throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get(DOWNLOAD_FILE_URL + DEFAULT_PROJECT_ID + "/" + fileId) .header(SessionConstants.HEADER_TOKEN, sessionId) .header(SessionConstants.CSRF_TOKEN, csrfToken)); } + @Test @Order(25) public void stopExport() throws Exception { @@ -1033,7 +1055,7 @@ public class FunctionalCaseControllerTests extends BaseTest { }}; request.setOtherFields(otherHeaders); - request.setFileId("123142342"); + request.setFileId("1231423421"); this.requestPost(EXPORT_XMIND_URL, request); request.setSelectIds(new ArrayList<>()); this.requestPost(EXPORT_XMIND_URL, request); @@ -1044,7 +1066,7 @@ public class FunctionalCaseControllerTests extends BaseTest { @Test @Order(4) public void checkExportTask() throws Exception { - this.requestGetExcel( EXPORT_XMIND_CHECK_URL); + this.requestGetExcel(EXPORT_XMIND_CHECK_URL); } } 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 b842514a26..928accd990 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,7 @@ public class ExportTaskManager { public static final String EXPORT_CONSUME = "export_consume"; - public void exportAsyncTask(String projectId, String fileId, String userId, String type, T t, Function selectListFunc) throws InterruptedException { + public ExportTask exportAsyncTask(String projectId, String fileId, String userId, String type, T t, Function selectListFunc) throws InterruptedException { ExportTask exportTask = buildExportTask(projectId, fileId, userId, type); ExecutorService executorService = Executors.newFixedThreadPool(1); Future future = executorService.submit(() -> { @@ -46,9 +46,10 @@ public class ExportTaskManager { LogUtils.info("Thread has been interrupted."); }); map.put(exportTask.getId(), future); + return exportTask; } - private ExportTask buildExportTask(String projectId, String fileId, String userId, String type) { + public ExportTask buildExportTask(String projectId, String fileId, String userId, String type) { ExportTask exportTask = new ExportTask(); exportTask.setId(IDGenerator.nextStr()); exportTask.setType(type); @@ -59,7 +60,7 @@ public class ExportTaskManager { exportTask.setUpdateTime(System.currentTimeMillis()); exportTask.setProjectId(projectId); exportTask.setFileType(fileId); - exportTaskMapper.insert(exportTask); + exportTaskMapper.insertSelective(exportTask); return exportTask; }