diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java index f6c71c7..c09f223 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; @@ -39,9 +40,11 @@ import java.util.Map; import java.util.Set; import java.util.stream.Stream; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_IMPORT_FAIL_INVALID; @Tag(name = "管理后台 - 流程模型") @RestController @@ -142,15 +145,23 @@ public class BpmModelController { @PreAuthorize("@ss.hasPermission('bpm:model:import')") public CommonResult importModel(@RequestParam("file") MultipartFile file, @RequestParam(value = "key", required = false) String key, - @RequestParam(value = "name", required = false) String name) throws IOException - { - BpmModelSaveReqVO reqVO = JsonUtils.parseObject(file.getBytes(), BpmModelSaveReqVO.class); + @RequestParam(value = "name", required = false) String name) throws IOException { + BpmModelSaveReqVO reqVO; + try { + reqVO = JsonUtils.parseObject(file.getBytes(), BpmModelSaveReqVO.class); + } catch (RuntimeException ex) { + throw exception(MODEL_IMPORT_FAIL_INVALID); + } + if (reqVO == null) { + throw exception(MODEL_IMPORT_FAIL_INVALID); + } if (StrUtil.isNotEmpty(key)) { reqVO.setKey(key); } if (StrUtil.isNotEmpty(name)) { reqVO.setName(name); } + ValidationUtils.validate(reqVO); return success(modelService.importModel(reqVO)); } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index bfd350d..ca4bcb3 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -25,6 +25,7 @@ public interface ErrorCodeConstants { ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员"); ErrorCode MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR = new ErrorCode(1_009_002_008, "部署流程失败,原因:首个任务({})的审批人不能是【审批人自选】"); + ErrorCode MODEL_IMPORT_FAIL_INVALID = new ErrorCode(1_009_002_009, "导入流程失败,流程模型文件为空或 JSON 格式不正确"); // ========== 流程定义 1-009-003-000 ========== ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index 2d81cee..7151851 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -131,7 +131,7 @@ public class BpmModelServiceImpl implements BpmModelService { throw exception(MODEL_KEY_EXISTS, reqVO.getKey()); } - // 2. 创建 Model 对象 + // 2. 创建 Model 对象,并归属到当前租户 reqVO.setSort(System.currentTimeMillis()); Model model = repositoryService.newModel(); BpmModelConvert.INSTANCE.copyToModel(model, reqVO); @@ -145,7 +145,14 @@ public class BpmModelServiceImpl implements BpmModelService { @Override public BpmModelSaveReqVO exportModel(String id) { Model model = validateModelExists(id); - return BpmModelConvert.INSTANCE.convertToSaveReqVO(model); + BpmModelSaveReqVO result = BpmModelConvert.INSTANCE.convertToSaveReqVO(model); + if (Objects.equals(result.getType(), BpmModelTypeEnum.BPMN.getType())) { + byte[] bpmnBytes = getModelBpmnXML(id); + result.setBpmnXml(ArrayUtil.isEmpty(bpmnBytes) ? null : StrUtil.utf8Str(bpmnBytes)); + } else if (Objects.equals(result.getType(), BpmModelTypeEnum.SIMPLE.getType())) { + result.setSimpleModel(JsonUtils.parseObject(getModelSimpleJson(id), BpmSimpleModelNodeVO.class)); + } + return result; } @Override @@ -216,7 +223,8 @@ public class BpmModelServiceImpl implements BpmModelService { } private Model validateModelExists(String id) { - Model model = repositoryService.getModel(id); + Model model = repositoryService.createModelQuery() + .modelId(id).modelTenantId(FlowableUtils.getTenantId()).singleResult(); if (model == null) { throw exception(MODEL_NOT_EXISTS); } @@ -458,7 +466,8 @@ public class BpmModelServiceImpl implements BpmModelService { @Override public Model getModel(String id) { - return repositoryService.getModel(id); + return repositoryService.createModelQuery() + .modelId(id).modelTenantId(FlowableUtils.getTenantId()).singleResult(); } @Override diff --git a/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImplTest.java b/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImplTest.java new file mode 100644 index 0000000..217d1b3 --- /dev/null +++ b/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImplTest.java @@ -0,0 +1,175 @@ +package cn.iocoder.yudao.module.bpm.service.definition; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeTypeEnum; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.repository.Model; +import org.flowable.engine.repository.ModelQuery; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; + +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.AdditionalMatchers.aryEq; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +/** + * {@link BpmModelServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class BpmModelServiceImplTest extends BaseMockitoUnitTest { + + private static final Long TENANT_ID = 1L; + private static final String MODEL_ID = "model-id"; + + @InjectMocks + private BpmModelServiceImpl modelService; + + @Mock + private RepositoryService repositoryService; + @Mock + private ModelQuery modelQuery; + + @BeforeEach + public void setUp() { + TenantContextHolder.setTenantId(TENANT_ID); + } + + @AfterEach + public void tearDown() { + TenantContextHolder.clear(); + } + + @Test + public void testExportModel_bpmn() { + // 准备参数 + Model model = mockModel(BpmModelTypeEnum.BPMN.getType()); + String bpmnXml = ""; + // mock 方法(repositoryService) + mockGetModel(model); + when(repositoryService.getModelEditorSource(eq(MODEL_ID))).thenReturn(StrUtil.utf8Bytes(bpmnXml)); + + // 调用 + BpmModelSaveReqVO result = modelService.exportModel(MODEL_ID); + + // 断言 + assertEquals(model.getKey(), result.getKey()); + assertEquals(model.getName(), result.getName()); + assertEquals(model.getCategory(), result.getCategory()); + assertEquals(BpmModelTypeEnum.BPMN.getType(), result.getType()); + assertEquals(bpmnXml, result.getBpmnXml()); + assertNull(result.getSimpleModel()); + } + + @Test + public void testExportModel_simple() { + // 准备参数 + Model model = mockModel(BpmModelTypeEnum.SIMPLE.getType()); + BpmSimpleModelNodeVO simpleModel = new BpmSimpleModelNodeVO(); + simpleModel.setId("start"); + simpleModel.setType(BpmSimpleModelNodeTypeEnum.START_NODE.getType()); + // mock 方法(repositoryService) + mockGetModel(model); + when(repositoryService.getModelEditorSourceExtra(eq(MODEL_ID))) + .thenReturn(JsonUtils.toJsonByte(simpleModel)); + + // 调用 + BpmModelSaveReqVO result = modelService.exportModel(MODEL_ID); + + // 断言 + assertEquals(BpmModelTypeEnum.SIMPLE.getType(), result.getType()); + assertNotNull(result.getSimpleModel()); + assertEquals(simpleModel.getId(), result.getSimpleModel().getId()); + assertEquals(simpleModel.getType(), result.getSimpleModel().getType()); + assertNull(result.getBpmnXml()); + } + + @Test + public void testExportModel_notExists() { + // mock 方法(repositoryService) + when(repositoryService.createModelQuery()).thenReturn(modelQuery); + when(modelQuery.modelId(eq(MODEL_ID))).thenReturn(modelQuery); + when(modelQuery.modelTenantId(eq(TENANT_ID.toString()))).thenReturn(modelQuery); + when(modelQuery.singleResult()).thenReturn(null); + + // 调用,并断言异常 + assertServiceException(() -> modelService.exportModel(MODEL_ID), MODEL_NOT_EXISTS); + } + + @Test + public void testImportModel_bpmn() { + // 准备参数 + BpmModelSaveReqVO reqVO = new BpmModelSaveReqVO(); + reqVO.setKey("test_process"); + reqVO.setName("测试流程"); + reqVO.setCategory("OA"); + reqVO.setType(BpmModelTypeEnum.BPMN.getType()); + reqVO.setBpmnXml(""); + reqVO.setStartUserIds(Arrays.asList(10L, 20L)); + reqVO.setStartDeptIds(Collections.singletonList(30L)); + reqVO.setManagerUserIds(Collections.singletonList(40L)); + Model model = mock(Model.class); + when(model.getId()).thenReturn(MODEL_ID); + // mock 方法(repositoryService) + when(repositoryService.createModelQuery()).thenReturn(modelQuery); + when(modelQuery.modelTenantId(eq(TENANT_ID.toString()))).thenReturn(modelQuery); + when(modelQuery.modelKey(eq(reqVO.getKey()))).thenReturn(modelQuery); + when(modelQuery.singleResult()).thenReturn(null); + when(repositoryService.newModel()).thenReturn(model); + + // 调用 + String result = modelService.importModel(reqVO); + + // 断言 + assertEquals(MODEL_ID, result); + verify(model).setTenantId(TENANT_ID.toString()); + verify(repositoryService).saveModel(same(model)); + verify(repositoryService).addModelEditorSource(eq(MODEL_ID), + aryEq(StrUtil.utf8Bytes(reqVO.getBpmnXml()))); + ArgumentCaptor metaInfoCaptor = ArgumentCaptor.forClass(String.class); + verify(model).setMetaInfo(metaInfoCaptor.capture()); + BpmModelMetaInfoVO metaInfo = JsonUtils.parseObject(metaInfoCaptor.getValue(), BpmModelMetaInfoVO.class); + assertNotNull(metaInfo); + assertEquals(Arrays.asList(10L, 20L), metaInfo.getStartUserIds()); + assertEquals(Collections.singletonList(30L), metaInfo.getStartDeptIds()); + assertEquals(Collections.singletonList(40L), metaInfo.getManagerUserIds()); + } + + private Model mockModel(Integer type) { + Model model = mock(Model.class); + when(model.getKey()).thenReturn("test_process"); + when(model.getName()).thenReturn("测试流程"); + when(model.getCategory()).thenReturn("OA"); + when(model.getCreateTime()).thenReturn(new Date(1_000L)); + BpmModelMetaInfoVO metaInfo = new BpmModelMetaInfoVO(); + metaInfo.setType(type); + when(model.getMetaInfo()).thenReturn(JsonUtils.toJsonString(metaInfo)); + return model; + } + + private void mockGetModel(Model model) { + when(repositoryService.createModelQuery()).thenReturn(modelQuery); + when(modelQuery.modelId(eq(MODEL_ID))).thenReturn(modelQuery); + when(modelQuery.modelTenantId(eq(TENANT_ID.toString()))).thenReturn(modelQuery); + when(modelQuery.singleResult()).thenReturn(model); + } + +}