package com.iailab.module.bpm.service.definition; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import com.iailab.framework.common.util.json.JsonUtils; import com.iailab.framework.common.util.validation.ValidationUtils; import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; import com.iailab.module.bpm.convert.definition.BpmModelConvert; import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import com.iailab.module.bpm.framework.flowable.core.util.SimpleModelUtils; import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.StartEvent; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.impl.db.SuspensionState; import org.flowable.engine.RepositoryService; import org.flowable.engine.repository.Model; import org.flowable.engine.repository.ModelQuery; import org.flowable.engine.repository.ProcessDefinition; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.util.List; import java.util.Map; import java.util.Objects; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.framework.common.util.collection.CollectionUtils.convertMap; import static com.iailab.module.bpm.enums.ErrorCodeConstants.*; /** * Flowable流程模型实现 * 主要进行 Flowable {@link Model} 的维护 * * @author iailab */ @Service @Validated @Slf4j public class BpmModelServiceImpl implements BpmModelService { @Resource private RepositoryService repositoryService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmFormService bpmFormService; @Resource private BpmTaskCandidateInvoker taskCandidateInvoker; @Override public List getModelList(String name) { ModelQuery modelQuery = repositoryService.createModelQuery(); if (StrUtil.isNotEmpty(name)) { modelQuery.modelNameLike(name); } return modelQuery.list(); } @Override @Transactional(rollbackFor = Exception.class) public String createModel(@Valid BpmModelSaveReqVO createReqVO) { if (!ValidationUtils.isXmlNCName(createReqVO.getKey())) { throw exception(MODEL_KEY_VALID); } // 1. 校验流程标识已经存在 Model keyModel = getModelByKey(createReqVO.getKey()); if (keyModel != null) { throw exception(MODEL_KEY_EXISTS, createReqVO.getKey()); } // 2.1 创建流程定义 createReqVO.setSort(System.currentTimeMillis()); // 使用当前时间,作为排序 Model model = repositoryService.newModel(); BpmModelConvert.INSTANCE.copyToModel(model, createReqVO); model.setTenantId(FlowableUtils.getTenantId()); // 2.2 保存流程定义 repositoryService.saveModel(model); return model.getId(); } @Override @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 public void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO) { // 1. 校验流程模型存在 Model model = validateModelManager(updateReqVO.getId(), userId); // 修改流程定义 BpmModelConvert.INSTANCE.copyToModel(model, updateReqVO); // 更新模型 repositoryService.saveModel(model); } @Override @Transactional(rollbackFor = Exception.class) public void updateModelSortBatch(Long userId, List ids) { // 1.1 校验流程模型存在 List models = repositoryService.createModelQuery() .modelTenantId(FlowableUtils.getTenantId()).list(); models.removeIf(model ->!ids.contains(model.getId())); if (ids.size() != models.size()) { throw exception(MODEL_NOT_EXISTS); } Map modelMap = convertMap(models, Model::getId); // 1.2 校验是否为管理员 ids.forEach(id -> validateModelManager(id, userId)); // 保存排序 long sort = System.currentTimeMillis(); // 使用时间戳 - i 作为排序 for (int i = ids.size() - 1; i > 0; i--) { Model model = modelMap.get(ids.get(i)); // 更新模型 BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model).setSort(sort); model.setMetaInfo(JsonUtils.toJsonString(metaInfo)); repositoryService.saveModel(model); // 更新排序 processDefinitionService.updateProcessDefinitionSortByModelId(model.getId(), sort); sort--; } } private Model validateModelExists(String id) { Model model = repositoryService.getModel(id); if (model == null) { throw exception(MODEL_NOT_EXISTS); } return model; } /** * 校验是否有流程模型的管理权限 * * @param id 流程模型编号 * @param userId 用户编号 * @return 流程模型 */ private Model validateModelManager(String id, Long userId) { Model model = validateModelExists(id); BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); if (metaInfo == null || !CollUtil.contains(metaInfo.getManagerUserIds(), userId)) { throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER, model.getName()); } return model; } @Override @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 public void deployModel(Long userId, String id) { // 1.1 校验流程模型存在 Model model = validateModelManager(id, userId); // 1.2 校验流程图 byte[] bpmnBytes = getModelBpmnXML(model.getId()); validateBpmnXml(bpmnBytes); // 1.3 校验表单已配 BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); BpmFormDO form = validateFormConfig(metaInfo); // 1.4 校验任务分配规则已配置 taskCandidateInvoker.validateBpmnConfig(bpmnBytes); // 1.5 获取仿钉钉流程设计器模型数据 String simpleJson = getModelSimpleJson(model.getId()); // 2.1 创建流程定义 String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleJson, form); // 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。 updateProcessDefinitionSuspended(model.getDeploymentId()); // 2.3 更新 model 的 deploymentId,进行关联 ProcessDefinition definition = processDefinitionService.getProcessDefinition(definitionId); model.setDeploymentId(definition.getDeploymentId()); repositoryService.saveModel(model); } private void validateBpmnXml(byte[] bpmnBytes) { BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes); if (bpmnModel == null) { throw exception(MODEL_NOT_EXISTS); } // 1. 没有 StartEvent StartEvent startEvent = BpmnModelUtils.getStartEvent(bpmnModel); if (startEvent == null) { throw exception(MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS); } // 2. 校验 UserTask 的 name 都配置了 List userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class); userTasks.forEach(userTask -> { if (StrUtil.isEmpty(userTask.getName())) { throw exception(MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS, userTask.getId()); } }); } @Override @Transactional(rollbackFor = Exception.class) public void deleteModel(Long userId, String id) { // 校验流程模型存在 Model model = validateModelManager(id, userId); // 执行删除 repositoryService.deleteModel(id); // 禁用流程定义 updateProcessDefinitionSuspended(model.getDeploymentId()); } @Override public void updateModelState(Long userId, String id, Integer state) { // 1.1 校验流程模型存在 Model model = validateModelManager(id, userId); // 1.2 校验流程定义存在 ProcessDefinition definition = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId()); if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } // 2. 更新状态 processDefinitionService.updateProcessDefinitionState(definition.getId(), state); } @Override public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) { return repositoryService.getBpmnModel(processDefinitionId); } @Override public BpmSimpleModelNodeVO getSimpleModel(String modelId) { Model model = validateModelExists(modelId); // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ ,获取仿钉钉快搭模型的 JSON 数据 String json = getModelSimpleJson(model.getId()); return JsonUtils.parseObject(json, BpmSimpleModelNodeVO.class); } @Override public void updateSimpleModel(Long userId, BpmSimpleModelUpdateReqVO reqVO) { // 1. 校验流程模型存在 Model model = validateModelManager(reqVO.getId(), userId); // 2.1 JSON 转换成 bpmnModel BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModel()); // 2.2 保存 Bpmn XML updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); // 2.3 保存 JSON 数据 updateModelSimpleJson(model.getId(), reqVO.getSimpleModel()); } /** * 校验流程表单已配置 * * @param metaInfo 流程模型元数据 * @return 表单配置 */ private BpmFormDO validateFormConfig(BpmModelMetaInfoVO metaInfo) { if (metaInfo == null || metaInfo.getFormType() == null) { throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG); } // 校验表单存在 if (Objects.equals(metaInfo.getFormType(), BpmModelFormTypeEnum.NORMAL.getType())) { if (metaInfo.getFormId() == null) { throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG); } BpmFormDO form = bpmFormService.getForm(metaInfo.getFormId()); if (form == null) { throw exception(FORM_NOT_EXISTS); } return form; } else { if (StrUtil.isEmpty(metaInfo.getFormCustomCreatePath()) || StrUtil.isEmpty(metaInfo.getFormCustomViewPath())) { throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG); } return null; } } @Override public void updateModelBpmnXml(String id, String bpmnXml) { if (StrUtil.isEmpty(bpmnXml)) { return; } repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml)); } @SuppressWarnings("JavaExistingMethodCanBeUsed") private String getModelSimpleJson(String id) { byte[] bytes = repositoryService.getModelEditorSourceExtra(id); if (ArrayUtil.isEmpty(bytes)) { return null; } return StrUtil.utf8Str(bytes); } private void updateModelSimpleJson(String id, BpmSimpleModelNodeVO node) { if (node == null) { return; } byte[] bytes = JsonUtils.toJsonByte(node); repositoryService.addModelEditorSourceExtra(id, bytes); } /** * 挂起 deploymentId 对应的流程定义 *

* 注意:这里一个 deploymentId 只关联一个流程定义 * * @param deploymentId 流程发布Id */ private void updateProcessDefinitionSuspended(String deploymentId) { if (StrUtil.isEmpty(deploymentId)) { return; } ProcessDefinition oldDefinition = processDefinitionService.getProcessDefinitionByDeploymentId(deploymentId); if (oldDefinition == null) { return; } processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode()); } private Model getModelByKey(String key) { return repositoryService.createModelQuery() .modelTenantId(FlowableUtils.getTenantId()) .modelKey(key).singleResult(); } @Override public Model getModel(String id) { return repositoryService.getModel(id); } @Override public byte[] getModelBpmnXML(String id) { return repositoryService.getModelEditorSource(id); } }