Commit 3dba4550827803bb2255d58951445025b6a9704c

Authored by YunaiV
1 parent 020fa9d5

feat(bpm): 完善流程模型导入导出逻辑

- 补全 BPMN XML 和简易模型数据导出
- 导入模型归属当前租户并保留完整配置
- 增加导入文件格式与参数校验
- 加强模型查询的租户隔离
- 补充流程模型服务单元测试
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 +}
... ...