Commit 3dba4550827803bb2255d58951445025b6a9704c
1 parent
020fa9d5
feat(bpm): 完善流程模型导入导出逻辑
- 补全 BPMN XML 和简易模型数据导出 - 导入模型归属当前租户并保留完整配置 - 增加导入文件格式与参数校验 - 加强模型查询的租户隔离 - 补充流程模型服务单元测试
Showing
4 changed files
with
203 additions
and
7 deletions
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; |
| 4 | 4 | import cn.hutool.core.util.StrUtil; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 7 | +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | |
| 7 | 8 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; |
| 8 | 9 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| 9 | 10 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; |
| ... | ... | @@ -39,9 +40,11 @@ import java.util.Map; |
| 39 | 40 | import java.util.Set; |
| 40 | 41 | import java.util.stream.Stream; |
| 41 | 42 | |
| 43 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 42 | 44 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 43 | 45 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| 44 | 46 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| 47 | +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_IMPORT_FAIL_INVALID; | |
| 45 | 48 | |
| 46 | 49 | @Tag(name = "管理后台 - 流程模型") |
| 47 | 50 | @RestController |
| ... | ... | @@ -142,15 +145,23 @@ public class BpmModelController { |
| 142 | 145 | @PreAuthorize("@ss.hasPermission('bpm:model:import')") |
| 143 | 146 | public CommonResult<String> importModel(@RequestParam("file") MultipartFile file, |
| 144 | 147 | @RequestParam(value = "key", required = false) String key, |
| 145 | - @RequestParam(value = "name", required = false) String name) throws IOException | |
| 146 | - { | |
| 147 | - BpmModelSaveReqVO reqVO = JsonUtils.parseObject(file.getBytes(), BpmModelSaveReqVO.class); | |
| 148 | + @RequestParam(value = "name", required = false) String name) throws IOException { | |
| 149 | + BpmModelSaveReqVO reqVO; | |
| 150 | + try { | |
| 151 | + reqVO = JsonUtils.parseObject(file.getBytes(), BpmModelSaveReqVO.class); | |
| 152 | + } catch (RuntimeException ex) { | |
| 153 | + throw exception(MODEL_IMPORT_FAIL_INVALID); | |
| 154 | + } | |
| 155 | + if (reqVO == null) { | |
| 156 | + throw exception(MODEL_IMPORT_FAIL_INVALID); | |
| 157 | + } | |
| 148 | 158 | if (StrUtil.isNotEmpty(key)) { |
| 149 | 159 | reqVO.setKey(key); |
| 150 | 160 | } |
| 151 | 161 | if (StrUtil.isNotEmpty(name)) { |
| 152 | 162 | reqVO.setName(name); |
| 153 | 163 | } |
| 164 | + ValidationUtils.validate(reqVO); | |
| 154 | 165 | return success(modelService.importModel(reqVO)); |
| 155 | 166 | } |
| 156 | 167 | ... | ... |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
| ... | ... | @@ -25,6 +25,7 @@ public interface ErrorCodeConstants { |
| 25 | 25 | ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); |
| 26 | 26 | ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员"); |
| 27 | 27 | ErrorCode MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR = new ErrorCode(1_009_002_008, "部署流程失败,原因:首个任务({})的审批人不能是【审批人自选】"); |
| 28 | + ErrorCode MODEL_IMPORT_FAIL_INVALID = new ErrorCode(1_009_002_009, "导入流程失败,流程模型文件为空或 JSON 格式不正确"); | |
| 28 | 29 | |
| 29 | 30 | // ========== 流程定义 1-009-003-000 ========== |
| 30 | 31 | ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); | ... | ... |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
| ... | ... | @@ -131,7 +131,7 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 131 | 131 | throw exception(MODEL_KEY_EXISTS, reqVO.getKey()); |
| 132 | 132 | } |
| 133 | 133 | |
| 134 | - // 2. 创建 Model 对象 | |
| 134 | + // 2. 创建 Model 对象,并归属到当前租户 | |
| 135 | 135 | reqVO.setSort(System.currentTimeMillis()); |
| 136 | 136 | Model model = repositoryService.newModel(); |
| 137 | 137 | BpmModelConvert.INSTANCE.copyToModel(model, reqVO); |
| ... | ... | @@ -145,7 +145,14 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 145 | 145 | @Override |
| 146 | 146 | public BpmModelSaveReqVO exportModel(String id) { |
| 147 | 147 | Model model = validateModelExists(id); |
| 148 | - return BpmModelConvert.INSTANCE.convertToSaveReqVO(model); | |
| 148 | + BpmModelSaveReqVO result = BpmModelConvert.INSTANCE.convertToSaveReqVO(model); | |
| 149 | + if (Objects.equals(result.getType(), BpmModelTypeEnum.BPMN.getType())) { | |
| 150 | + byte[] bpmnBytes = getModelBpmnXML(id); | |
| 151 | + result.setBpmnXml(ArrayUtil.isEmpty(bpmnBytes) ? null : StrUtil.utf8Str(bpmnBytes)); | |
| 152 | + } else if (Objects.equals(result.getType(), BpmModelTypeEnum.SIMPLE.getType())) { | |
| 153 | + result.setSimpleModel(JsonUtils.parseObject(getModelSimpleJson(id), BpmSimpleModelNodeVO.class)); | |
| 154 | + } | |
| 155 | + return result; | |
| 149 | 156 | } |
| 150 | 157 | |
| 151 | 158 | @Override |
| ... | ... | @@ -216,7 +223,8 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 216 | 223 | } |
| 217 | 224 | |
| 218 | 225 | private Model validateModelExists(String id) { |
| 219 | - Model model = repositoryService.getModel(id); | |
| 226 | + Model model = repositoryService.createModelQuery() | |
| 227 | + .modelId(id).modelTenantId(FlowableUtils.getTenantId()).singleResult(); | |
| 220 | 228 | if (model == null) { |
| 221 | 229 | throw exception(MODEL_NOT_EXISTS); |
| 222 | 230 | } |
| ... | ... | @@ -458,7 +466,8 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 458 | 466 | |
| 459 | 467 | @Override |
| 460 | 468 | public Model getModel(String id) { |
| 461 | - return repositoryService.getModel(id); | |
| 469 | + return repositoryService.createModelQuery() | |
| 470 | + .modelId(id).modelTenantId(FlowableUtils.getTenantId()).singleResult(); | |
| 462 | 471 | } |
| 463 | 472 | |
| 464 | 473 | @Override | ... | ... |
yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImplTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.service.definition; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.StrUtil; | |
| 4 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 5 | +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | |
| 6 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | |
| 7 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; | |
| 8 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; | |
| 9 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | |
| 10 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; | |
| 11 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeTypeEnum; | |
| 12 | +import org.flowable.engine.RepositoryService; | |
| 13 | +import org.flowable.engine.repository.Model; | |
| 14 | +import org.flowable.engine.repository.ModelQuery; | |
| 15 | +import org.junit.jupiter.api.AfterEach; | |
| 16 | +import org.junit.jupiter.api.BeforeEach; | |
| 17 | +import org.junit.jupiter.api.Test; | |
| 18 | +import org.mockito.ArgumentCaptor; | |
| 19 | +import org.mockito.InjectMocks; | |
| 20 | +import org.mockito.Mock; | |
| 21 | + | |
| 22 | +import java.util.Arrays; | |
| 23 | +import java.util.Collections; | |
| 24 | +import java.util.Date; | |
| 25 | + | |
| 26 | +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; | |
| 27 | +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_NOT_EXISTS; | |
| 28 | +import static org.junit.jupiter.api.Assertions.*; | |
| 29 | +import static org.mockito.AdditionalMatchers.aryEq; | |
| 30 | +import static org.mockito.ArgumentMatchers.eq; | |
| 31 | +import static org.mockito.Mockito.*; | |
| 32 | + | |
| 33 | +/** | |
| 34 | + * {@link BpmModelServiceImpl} 的单元测试 | |
| 35 | + * | |
| 36 | + * @author 芋道源码 | |
| 37 | + */ | |
| 38 | +public class BpmModelServiceImplTest extends BaseMockitoUnitTest { | |
| 39 | + | |
| 40 | + private static final Long TENANT_ID = 1L; | |
| 41 | + private static final String MODEL_ID = "model-id"; | |
| 42 | + | |
| 43 | + @InjectMocks | |
| 44 | + private BpmModelServiceImpl modelService; | |
| 45 | + | |
| 46 | + @Mock | |
| 47 | + private RepositoryService repositoryService; | |
| 48 | + @Mock | |
| 49 | + private ModelQuery modelQuery; | |
| 50 | + | |
| 51 | + @BeforeEach | |
| 52 | + public void setUp() { | |
| 53 | + TenantContextHolder.setTenantId(TENANT_ID); | |
| 54 | + } | |
| 55 | + | |
| 56 | + @AfterEach | |
| 57 | + public void tearDown() { | |
| 58 | + TenantContextHolder.clear(); | |
| 59 | + } | |
| 60 | + | |
| 61 | + @Test | |
| 62 | + public void testExportModel_bpmn() { | |
| 63 | + // 准备参数 | |
| 64 | + Model model = mockModel(BpmModelTypeEnum.BPMN.getType()); | |
| 65 | + String bpmnXml = "<definitions />"; | |
| 66 | + // mock 方法(repositoryService) | |
| 67 | + mockGetModel(model); | |
| 68 | + when(repositoryService.getModelEditorSource(eq(MODEL_ID))).thenReturn(StrUtil.utf8Bytes(bpmnXml)); | |
| 69 | + | |
| 70 | + // 调用 | |
| 71 | + BpmModelSaveReqVO result = modelService.exportModel(MODEL_ID); | |
| 72 | + | |
| 73 | + // 断言 | |
| 74 | + assertEquals(model.getKey(), result.getKey()); | |
| 75 | + assertEquals(model.getName(), result.getName()); | |
| 76 | + assertEquals(model.getCategory(), result.getCategory()); | |
| 77 | + assertEquals(BpmModelTypeEnum.BPMN.getType(), result.getType()); | |
| 78 | + assertEquals(bpmnXml, result.getBpmnXml()); | |
| 79 | + assertNull(result.getSimpleModel()); | |
| 80 | + } | |
| 81 | + | |
| 82 | + @Test | |
| 83 | + public void testExportModel_simple() { | |
| 84 | + // 准备参数 | |
| 85 | + Model model = mockModel(BpmModelTypeEnum.SIMPLE.getType()); | |
| 86 | + BpmSimpleModelNodeVO simpleModel = new BpmSimpleModelNodeVO(); | |
| 87 | + simpleModel.setId("start"); | |
| 88 | + simpleModel.setType(BpmSimpleModelNodeTypeEnum.START_NODE.getType()); | |
| 89 | + // mock 方法(repositoryService) | |
| 90 | + mockGetModel(model); | |
| 91 | + when(repositoryService.getModelEditorSourceExtra(eq(MODEL_ID))) | |
| 92 | + .thenReturn(JsonUtils.toJsonByte(simpleModel)); | |
| 93 | + | |
| 94 | + // 调用 | |
| 95 | + BpmModelSaveReqVO result = modelService.exportModel(MODEL_ID); | |
| 96 | + | |
| 97 | + // 断言 | |
| 98 | + assertEquals(BpmModelTypeEnum.SIMPLE.getType(), result.getType()); | |
| 99 | + assertNotNull(result.getSimpleModel()); | |
| 100 | + assertEquals(simpleModel.getId(), result.getSimpleModel().getId()); | |
| 101 | + assertEquals(simpleModel.getType(), result.getSimpleModel().getType()); | |
| 102 | + assertNull(result.getBpmnXml()); | |
| 103 | + } | |
| 104 | + | |
| 105 | + @Test | |
| 106 | + public void testExportModel_notExists() { | |
| 107 | + // mock 方法(repositoryService) | |
| 108 | + when(repositoryService.createModelQuery()).thenReturn(modelQuery); | |
| 109 | + when(modelQuery.modelId(eq(MODEL_ID))).thenReturn(modelQuery); | |
| 110 | + when(modelQuery.modelTenantId(eq(TENANT_ID.toString()))).thenReturn(modelQuery); | |
| 111 | + when(modelQuery.singleResult()).thenReturn(null); | |
| 112 | + | |
| 113 | + // 调用,并断言异常 | |
| 114 | + assertServiceException(() -> modelService.exportModel(MODEL_ID), MODEL_NOT_EXISTS); | |
| 115 | + } | |
| 116 | + | |
| 117 | + @Test | |
| 118 | + public void testImportModel_bpmn() { | |
| 119 | + // 准备参数 | |
| 120 | + BpmModelSaveReqVO reqVO = new BpmModelSaveReqVO(); | |
| 121 | + reqVO.setKey("test_process"); | |
| 122 | + reqVO.setName("测试流程"); | |
| 123 | + reqVO.setCategory("OA"); | |
| 124 | + reqVO.setType(BpmModelTypeEnum.BPMN.getType()); | |
| 125 | + reqVO.setBpmnXml("<definitions />"); | |
| 126 | + reqVO.setStartUserIds(Arrays.asList(10L, 20L)); | |
| 127 | + reqVO.setStartDeptIds(Collections.singletonList(30L)); | |
| 128 | + reqVO.setManagerUserIds(Collections.singletonList(40L)); | |
| 129 | + Model model = mock(Model.class); | |
| 130 | + when(model.getId()).thenReturn(MODEL_ID); | |
| 131 | + // mock 方法(repositoryService) | |
| 132 | + when(repositoryService.createModelQuery()).thenReturn(modelQuery); | |
| 133 | + when(modelQuery.modelTenantId(eq(TENANT_ID.toString()))).thenReturn(modelQuery); | |
| 134 | + when(modelQuery.modelKey(eq(reqVO.getKey()))).thenReturn(modelQuery); | |
| 135 | + when(modelQuery.singleResult()).thenReturn(null); | |
| 136 | + when(repositoryService.newModel()).thenReturn(model); | |
| 137 | + | |
| 138 | + // 调用 | |
| 139 | + String result = modelService.importModel(reqVO); | |
| 140 | + | |
| 141 | + // 断言 | |
| 142 | + assertEquals(MODEL_ID, result); | |
| 143 | + verify(model).setTenantId(TENANT_ID.toString()); | |
| 144 | + verify(repositoryService).saveModel(same(model)); | |
| 145 | + verify(repositoryService).addModelEditorSource(eq(MODEL_ID), | |
| 146 | + aryEq(StrUtil.utf8Bytes(reqVO.getBpmnXml()))); | |
| 147 | + ArgumentCaptor<String> metaInfoCaptor = ArgumentCaptor.forClass(String.class); | |
| 148 | + verify(model).setMetaInfo(metaInfoCaptor.capture()); | |
| 149 | + BpmModelMetaInfoVO metaInfo = JsonUtils.parseObject(metaInfoCaptor.getValue(), BpmModelMetaInfoVO.class); | |
| 150 | + assertNotNull(metaInfo); | |
| 151 | + assertEquals(Arrays.asList(10L, 20L), metaInfo.getStartUserIds()); | |
| 152 | + assertEquals(Collections.singletonList(30L), metaInfo.getStartDeptIds()); | |
| 153 | + assertEquals(Collections.singletonList(40L), metaInfo.getManagerUserIds()); | |
| 154 | + } | |
| 155 | + | |
| 156 | + private Model mockModel(Integer type) { | |
| 157 | + Model model = mock(Model.class); | |
| 158 | + when(model.getKey()).thenReturn("test_process"); | |
| 159 | + when(model.getName()).thenReturn("测试流程"); | |
| 160 | + when(model.getCategory()).thenReturn("OA"); | |
| 161 | + when(model.getCreateTime()).thenReturn(new Date(1_000L)); | |
| 162 | + BpmModelMetaInfoVO metaInfo = new BpmModelMetaInfoVO(); | |
| 163 | + metaInfo.setType(type); | |
| 164 | + when(model.getMetaInfo()).thenReturn(JsonUtils.toJsonString(metaInfo)); | |
| 165 | + return model; | |
| 166 | + } | |
| 167 | + | |
| 168 | + private void mockGetModel(Model model) { | |
| 169 | + when(repositoryService.createModelQuery()).thenReturn(modelQuery); | |
| 170 | + when(modelQuery.modelId(eq(MODEL_ID))).thenReturn(modelQuery); | |
| 171 | + when(modelQuery.modelTenantId(eq(TENANT_ID.toString()))).thenReturn(modelQuery); | |
| 172 | + when(modelQuery.singleResult()).thenReturn(model); | |
| 173 | + } | |
| 174 | + | |
| 175 | +} | ... | ... |