srpign boot + activiti7 + 在线编辑器整合
先看效果
参考地址:https://blog.csdn.net/huashenghn/article/details/103088775
1.导入静态资源 diagram-viewer、editor-app
2.配置 editor-app 的 app-cfg.js 中的 contextRoot
3.新增编辑流程所需要的resource
ModelEditorJsonRestResource.java
/* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ***.***.activiti.resource; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.annotations.Api; import org.activiti.editor.constants.ModelDataJsonConstants; import org.activiti.engine.ActivitiException; import org.activiti.engine.RepositoryService; import org.activiti.engine.repository.Model; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * @author */ @RestController @RequestMapping("/service") @Api(value = "aciviti-app model editor json", tags = {"保存模型"}) public class ModelEditorJsonRestResource implements ModelDataJsonConstants { protected static final Logger LOGGER = LoggerFactory.getLogger(ModelEditorJsonRestResource.class); @Autowired private RepositoryService repositoryService; @Autowired private ObjectMapper objectMapper; @RequestMapping(value="/model/{modelId}/json", method = RequestMethod.GET, produces = "application/json") public ObjectNode getEditorJson(@PathVariable String modelId) { ObjectNode modelNode = null; Model model = repositoryService.getModel(modelId); if (model != null) { try { if (StringUtils.isNotEmpty(model.getMetaInfo())) { modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo()); } else { modelNode = objectMapper.createObjectNode(); modelNode.put(MODEL_NAME, model.getName()); } modelNode.put(MODEL_ID, model.getId()); ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree( new String(repositoryService.getModelEditorSource(model.getId()), "utf-8")); modelNode.put("model", editorJsonNode); } catch (Exception e) { LOGGER.error("Error creating model JSON", e); throw new ActivitiException("Error creating model JSON", e); } } return modelNode; } }
ModelSaveRestResource.java
/* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ***.***.activiti.resource; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.annotations.Api; import org.activiti.bpmn.converter.BpmnXMLConverter; import org.activiti.bpmn.model.BpmnModel; import org.activiti.editor.constants.ModelDataJsonConstants; import org.activiti.editor.language.json.converter.BpmnJsonConverter; import org.activiti.engine.ActivitiException; import org.activiti.engine.RepositoryService; import org.activiti.engine.repository.Deployment; import org.activiti.engine.repository.DeploymentBuilder; import org.activiti.engine.repository.Model; import org.activiti.engine.repository.ProcessDefinition; import org.apache.batik.transcoder.TranscoderInput; import org.apache.batik.transcoder.TranscoderOutput; import org.apache.batik.transcoder.image.PNGTranscoder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.List; /** * @author */ @RestController @RequestMapping("/service") @Api(value = "aciviti-app model", tags = {"保存模型"}) public class ModelSaveRestResource implements ModelDataJsonConstants { protected static final Logger LOGGER = LoggerFactory.getLogger(ModelSaveRestResource.class); @Autowired private RepositoryService repositoryService; @Autowired private ObjectMapper objectMapper; @RequestMapping(value="/model/{modelId}/save", method = RequestMethod.PUT) @ResponseStatus(value = HttpStatus.OK) public void saveModel(@PathVariable String modelId,HttpServletRequest request) { String name =request.getParameter("name"); String description =request.getParameter("description"); String json_xml =request.getParameter("json_xml"); String svg_xml =request.getParameter("svg_xml"); try { Model model = repositoryService.getModel(modelId); ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo()); modelJson.put(MODEL_NAME, name); modelJson.put(MODEL_DESCRIPTION, description); model.setMetaInfo(modelJson.toString()); model.setName(name); repositoryService.saveModel(model); repositoryService.addModelEditorSource(model.getId(), json_xml.getBytes("utf-8")); InputStream svgStream = new ByteArrayInputStream(svg_xml.getBytes("utf-8")); TranscoderInput input = new TranscoderInput(svgStream); PNGTranscoder transcoder = new PNGTranscoder(); // Setup output ByteArrayOutputStream outStream = new ByteArrayOutputStream(); TranscoderOutput output = new TranscoderOutput(outStream); // Do the transformation transcoder.transcode(input, output); final byte[] result = outStream.toByteArray(); repositoryService.addModelEditorSourceExtra(model.getId(), result); outStream.close(); } catch (Exception e) { LOGGER.error("Error saving model", e); throw new ActivitiException("Error saving model", e); } } }
StencilsetRestResource.java
/* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ***.***.activiti.resource; import io.swagger.annotations.Api; import org.activiti.engine.ActivitiException; import org.apache.commons.io.IOUtils; import org.springframework.web.bind.annotation.*; import java.io.InputStream; /** * @author */ @RestController @RequestMapping("/service") @Api(value = "aciviti-app stencilset", tags = {"获取 stencilset.json"}) @CrossOrigin(origins = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT}) public class StencilsetRestResource { @RequestMapping(value="/editor/stencilset", method = RequestMethod.GET, produces = "application/json;charset=utf-8") public @ResponseBody String getStencilset() { InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("stencilset.json"); try { return IOUtils.toString(stencilsetStream, "utf-8"); } catch (Exception e) { throw new ActivitiException("Error while loading stencil set", e); } } }
4.新增维护模型、流程的接口
SimpleUserTaskApiController.java
package ***.***.activiti7.controller; import static ***.***.constant.AllocationConstant.APPLICATION_JSON_UTF8; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; import javax.annotation.Resource; import org.activiti.api.task.model.builders.CompleteTaskPayloadBuilder; import org.activiti.api.task.model.builders.TaskPayloadBuilder; import org.activiti.api.task.runtime.TaskRuntime; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import org.activiti.bpmn.model.SequenceFlow; import org.activiti.bpmn.model.UserTask; import org.activiti.engine.HistoryService; import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.TaskService; import org.activiti.engine.history.HistoricActivityInstance; import org.activiti.engine.history.HistoricActivityInstanceQuery; import org.activiti.engine.history.HistoricTaskInstance; import org.activiti.engine.impl.persistence.entity.TaskEntity; import org.activiti.engine.repository.Deployment; import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.runtime.Execution; import org.activiti.engine.runtime.ProcessInstance; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import ***.***.activiti7.bean.AssigneeVO; import ***.***.activiti7.bean.BackReq; import ***.***.activiti7.bean.BackRsp; import ***.***.activiti7.bean.BatchBackReq; import ***.***.activiti7.bean.BatchBackRsp; import ***.***.activiti7.bean.BatchComplateReq; import ***.***.activiti7.bean.BatchComplateRsp; import ***.***.activiti7.bean.BatchStartReq; import ***.***.activiti7.bean.BatchStartRsp; import ***.***.activiti7.bean.BatchStopReq; import ***.***.activiti7.bean.BatchStopRsp; import ***.***.activiti7.bean.ClaimReq; import ***.***.activiti7.bean.ClaimRsp; import ***.***.activiti7.bean.ComplateReq; import ***.***.activiti7.bean.ComplateRsp; import ***.***.activiti7.bean.DiagramReq; import ***.***.activiti7.bean.DiagramRsp; import ***.***.activiti7.bean.GetInstanceReq; import ***.***.activiti7.bean.GetInstanceRsp; import ***.***.activiti7.bean.GetReq; import ***.***.activiti7.bean.GetRsp; import ***.***.activiti7.bean.StartReq; import ***.***.activiti7.bean.StartRsp; import ***.***.activiti7.bean.StopReq; import ***.***.activiti7.bean.StopRsp; import ***.***.activiti7.listeners.ProcessListener; import ***.***.activiti7.mapper.CommActivityMapper; import ***.***.activiti7.util.ActivitiUtils; import ***.***.intf.service.CallService; import ***.***.security.SecurityUtils; import ***.***.system.exception.MyExceptionEnum; import ***.***.user.mapper.UserMapper; import ***.***.util.Assert; import lombok.extern.slf4j.Slf4j; import net.platform.center.exception.BusinessException; @Slf4j @RestController @RequestMapping(path = "/task/", consumes = APPLICATION_JSON_UTF8, produces = APPLICATION_JSON_UTF8) public class SimpleUserTaskApiController { // #region 属性注入 private String consUsername = "username"; /** * 流程定义和部署相关的存储服务 */ @Autowired private RepositoryService repositoryService; /** * 流程运行时相关的服务 */ @Resource private RuntimeService runtimeService; /** * 节点任务相关操作接口 */ @Resource private TaskService taskService; @Resource private TaskRuntime taskRuntime; /** * 历史记录相关服务接口 */ @Resource private HistoryService historyService; @Autowired private SecurityUtils securityUtils; @Autowired private CommActivityMapper commActivityMapper; @Autowired private CallService callService; @Autowired private UserMapper userMapper; // #endregion /** * 启动流程实例 * * @param req * @return */ @PostMapping(path = "/start", consumes = APPLICATION_JSON_UTF8, produces = APPLICATION_JSON_UTF8) @ResponseBody public StartRsp start(@RequestBody StartReq req) { Assert.notNull(req, MyExceptionEnum.INVALID_PARAMETER); Assert.notNull(req.getBody().getDeploymentKey(), MyExceptionEnum.INVALID_PARAMETER); Assert.notNull(req.getBody().getBusinessKey(), MyExceptionEnum.INVALID_PARAMETER); ProcessListener tempProcessListener = getProcessListenerByKey(req.getBody().getDeploymentKey(), req.getBody().getBusinessKey()); Assert.notNull(tempProcessListener, MyExceptionEnum.NULL_ACT_PROCESS_MAPPING); Assert.notNull(tempProcessListener.getCommActivity(), MyExceptionEnum.NULL_ACT_PROCESS_MAPPING); tempProcessListener.getCommActivity(); tempProcessListener.setBusinessKey(req.getBody().getBusinessKey()); if (!tempProcessListener.checkCanStart()) { throw new BusinessException(MyExceptionEnum.INVALID_ACT_PROCESS_STATE); } String username = req.getHeader().get(consUsername); securityUtils.logInAs(username); ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .deploymentId(tempProcessListener.getCommActivity().getDeploymentId()) .singleResult(); Assert.notNull(processDefinition, MyExceptionEnum.NULL_ACT_PROCESS_DEFINITION); ProcessInstance instance = runtimeService.startProcessInstanceById(processDefinition.getId(), req.getBody().getBusinessKey(), resetVariables(req.getBody().getVariables())); // org.activiti.engine.task.Task tempTask = taskService.createTaskQuery() // .processInstanceId(instance.getId()) // .orderByTaskCreateTime() // .desc() // .singleResult(); runtimeService.setProcessInstanceName(instance.getId(), req.getBody().getName()); // 获取表单属性 org.activiti.engine.task.Task task = taskService.createTaskQuery() .processInstanceId(instance.getId()) .orderByTaskCreateTime() .desc() .singleResult(); // #TODO 待确认是否需要此步骤 // 是否是没有具体指派人的时候,才需要 setAssignee // taskService.setAssignee(task.getId(),username); StartRsp rsp = StartRsp.builder() .body(StartRsp.StartRspBody.builder() .taskId(task.getId()) .processInstanceId(instance.getId()) .businessKey(req.getBody().getBusinessKey()) .build()) .build(); return rsp; } @PostMapping(path = "/batchStart", consumes = APPLICATION_JSON_UTF8, produces = APPLICATION_JSON_UTF8) @ResponseBody public BatchStartRsp batchStart(@RequestBody BatchStartReq req) { BatchStartRsp rsp = new BatchStartRsp(); BatchStartReq.BatchStartReqBody body = req.getBody(); for (String businessKey : body.getBusinessKeys()) { StartReq tempReq = StartReq.builder() .body(StartReq.StartReqBody.builder() .businessKey(businessKey) .deploymentKey(body.getDeploymentKey()) .name(body.getName()) .variables(body.getVariables()) .build()) .build(); tempReq.setHeader(req.getHeader()); try { StartRsp tempRsp = start(tempReq); rsp.getBody().add(tempRsp.getBody()); } catch (Exception ex) { rsp.getBody().add(StartRsp.StartRspBody.builder().businessKey(businessKey).build()); log.error("循环 start 异常", ex); } } return rsp; } private HashMap<String, Object> resetVariables(HashMap<String, Object> variables) { HashMap<String, Object> newVariables = new HashMap<>(); for (Map.Entry<String, Object> entry : variables.entrySet()) { String key = entry.getKey().replace("-", "_"); newVariables.put(key, entry.getValue()); } return newVariables; } /** * 根据流程部署Id获取流程信息 * * @param req * @return */ @PostMapping(path = "/get", consumes = APPLICATION_JSON_UTF8, produces = APPLICATION_JSON_UTF8) @ResponseBody public GetRsp get(@RequestBody GetReq req) { Assert.notNull(req, MyExceptionEnum.INVALID_PARAMETER); ProcessListener tempProcessListener = getProcessListenerByKey(req.getBody().getDeploymentKey(), null); Assert.notNull(tempProcessListener, MyExceptionEnum.NULL_ACT_PROCESS_MAPPING); Assert.notNull(tempProcessListener.getCommActivity(), MyExceptionEnum.NULL_ACT_PROCESS_MAPPING); Deployment deployment = repositoryService.createDeploymentQuery() .deploymentId(tempProcessListener.getCommActivity().getDeploymentId()) .singleResult(); Assert.notNull(deployment, MyExceptionEnum.NULL_ACT_PROCESS_DEFINITION); ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .deploymentId(tempProcessListener.getCommActivity().getDeploymentId()) .singleResult(); Assert.notNull(req, MyExceptionEnum.NULL_ACT_PROCESS_DEFINITION); BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); Assert.notNull(req, MyExceptionEnum.NULL_ACT_BPMN_MODEL); List<UserTask> allUserTasks = ActivitiUtils.getAllUserTasks(bpmnModel); Assert.notNull(allUserTasks, MyExceptionEnum.NULL_ACT_USER_TASK_DEFINITION); GetRsp rsp = GetRsp.builder() .body(GetRsp.GetRspBody.builder() .tasks(new ArrayList<>()) .formProperties(new ArrayList<>()) .deploymentId(deployment.getId()) .deploymentName(deployment.getName()) .build()) .build(); // 设置第一环节审批表单格式 UserTask firstUserTask = allUserTasks.get(0); rsp.getBody().setFormProperties(firstUserTask.getFormProperties()); for (UserTask userTask : allUserTasks) { GetRsp.Task tempTask = GetRsp.Task.builder().id(userTask.getId()).name(userTask.getName()).build(); if (!CollectionUtils.isEmpty(userTask.getCandidateGroups())) { tempTask.setCandidateGroups(userTask.getCandidateGroups()); } else if (!CollectionUtils.isEmpty(userTask.getCandidateUsers())) { // region 设置候选人 for (String candidateUserName : userTask.getCandidateUsers()) { log.trace("模拟查询用户user,待实现"); String name = userMapper.getNameByUserName(candidateUserName); if (!StringUtils.isEmpty(name)) { AssigneeVO candidateAssignee = AssigneeVO.builder().username(candidateUserName).name(name).build(); tempTask.getCandidateUsers().add(candidateAssignee); } } // endregion } else if (!StringUtils.isEmpty(userTask.getAssignee()) && !ActivitiUtils.isEl(userTask.getAssignee())) { String name = userMapper.getNameByUserName(userTask.getAssignee()); if (!StringUtils.isEmpty(name)) { AssigneeVO assigneeVO = AssigneeVO.builder().username(userTask.getAssignee()).name(name).build(); tempTask.setAssignee(assigneeVO); } } rsp.getBody().getTasks().add(tempTask); } // String url = String.format(tempProcessListener.getCommActivity().getFormUrl(), // req.getBody().getBusinessKey()); // rsp.getBody().setFormUrl(url); return rsp; } /** * 签收 * * @param req * @return */ @PostMapping(path = "/claim", consumes = APPLICATION_JSON_UTF8, produces = APPLICATION_JSON_UTF8) @ResponseBody public ClaimRsp claim(@RequestBody ClaimReq req) { Assert.notNull(req, MyExceptionEnum.INVALID_PARAMETER); String username = req.getHeader().get(consUsername); securityUtils.logInAs(username); org.activiti.engine.task.Task task = taskService.createTaskQuery().taskId(req.getBody().getTaskId()).singleResult(); Assert.notNull(task, MyExceptionEnum.NULL_ACT_USER_TASK); taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(req.getBody().getTaskId()).build()); return ClaimRsp.builder().body(ClaimRsp.ClaimRspBody.builder().success(true).build()).build(); } /** * 驳回 * * @param req * @return */ @PostMapping(path = "/back", consumes = APPLICATION_JSON_UTF8, produces = APPLICATION_JSON_UTF8) @ResponseBody public BackRsp back(@RequestBody BackReq req) { Assert.notNull(req, MyExceptionEnum.INVALID_PARAMETER); org.activiti.engine.task.Task task = taskService.createTaskQuery().taskId(req.getBody().getTaskId()).singleResult(); Assert.notNull(task, MyExceptionEnum.NULL_ACT_USER_TASK); String username = req.getHeader().get(consUsername); if (!task.getAssignee().equals(username)) { throw new BusinessException(MyExceptionEnum.INVALID_PERMISSION); } securityUtils.logInAs(username); String processInstanceId = task.getProcessInstanceId(); // 取得所有历史任务按时间降序排序 List<HistoricTaskInstance> htiList = historyService.createHistoricTaskInstanceQuery() .processInstanceId(processInstanceId) .orderByTaskCreateTime() .desc() .list(); if (ObjectUtils.isEmpty(htiList) || htiList.size() < 2) { throw new BusinessException(MyExceptionEnum.ERR_ACT_CAT_NOT_BACK); } // list里的第二条代表上一个任务 HistoricTaskInstance lastTask = htiList.get(1); // list里第二条代表当前任务 HistoricTaskInstance curTask = htiList.get(0); // 当前节点的executionId String curExecutionId = curTask.getExecutionId(); // 上个节点的taskId String lastTaskId = lastTask.getId(); // 上个节点的executionId String lastExecutionId = lastTask.getExecutionId(); if (StringUtils.isEmpty(lastTaskId)) { throw new BusinessException(MyExceptionEnum.ERR_ACT_CAT_NOT_BACK); } String processDefinitionId = lastTask.getProcessDefinitionId(); BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); String lastActivityId = null; List<HistoricActivityInstance> haiFinishedList = historyService.createHistoricActivityInstanceQuery().executionId(lastExecutionId).finished().list(); for (HistoricActivityInstance hai : haiFinishedList) { if (lastTaskId.equals(hai.getTaskId())) { // 得到ActivityId,只有HistoricActivityInstance对象里才有此方法 // lastActivityId = hai.getActivityId(); break; } } // 取得当前节点的信息 Execution execution = runtimeService.createExecutionQuery().executionId(curExecutionId).singleResult(); String curActivityId = execution.getActivityId(); FlowNode curFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(curActivityId); SequenceFlow targetSequenceFlow = curFlowNode.getIncomingFlows().get(0); FlowNode targetFlowNode = (FlowNode) curFlowNode.getIncomingFlows().get(0).getSourceFlowElement(); FlowNode sourceFlowNode = (FlowNode) targetSequenceFlow.getSourceFlowElement(); // 记录当前节点的原活动方向 List<SequenceFlow> oriSequenceFlows = new ArrayList<>(); oriSequenceFlows.addAll(curFlowNode.getOutgoingFlows()); // 清理活动方向 curFlowNode.getOutgoingFlows().clear(); // 建立新方向 List<SequenceFlow> newSequenceFlowList = new ArrayList<>(); SequenceFlow newSequenceFlow = new SequenceFlow(); newSequenceFlow.setId("nid_" + UUID.randomUUID().toString().replace("-", "_")); newSequenceFlow.setSourceFlowElement(sourceFlowNode); newSequenceFlow.setTargetFlowElement(targetFlowNode); newSequenceFlowList.add(newSequenceFlow); curFlowNode.setOutgoingFlows(newSequenceFlowList); // 完成任务 CompleteTaskPayloadBuilder completeTaskPayloadBuilder = TaskPayloadBuilder.complete().withTaskId(task.getId()); // 每个环节对应的审批状态,是独立的 taskService.addComment(task.getId(), task.getProcessInstanceId(), ActivitiUtils.getCommentKey(), req.getBody().getComment()); taskService.addComment(task.getId(), task.getProcessInstanceId(), ActivitiUtils.getApproveKey(), String.valueOf(ActivitiUtils.APPROVE_BACK)); taskRuntime.complete(completeTaskPayloadBuilder.build()); // 恢复原方向 curFlowNode.setOutgoingFlows(oriSequenceFlows); org.activiti.engine.task.Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult(); // 设置执行人 if (nextTask != null) { taskService.setAssignee(nextTask.getId(), lastTask.getAssignee()); } return BackRsp.builder().body(BackRsp.BackRspBody.builder().success(true).build()).build(); } @PostMapping(path = "/batchBack", consumes = APPLICATION_JSON_UTF8, produces = APPLICATION_JSON_UTF8) @ResponseBody public BatchBackRsp batchBack(@RequestBody BatchBackReq req) { BatchBackRsp rsp = BatchBackRsp.builder().build(); for (BatchBackReq.BatchBackBody backReqBody : req.getBody()) { org.activiti.engine.task.Task task = taskService.createTaskQuery().processInstanceId(backReqBody.getInstanceId()).singleResult(); if (task == null) { rsp.getBody() .add(BatchBackRsp.BatchBackRspBody.builder() .success(false) .instanceId(backReqBody.getInstanceId()) .build()); log.info("流程实例 {} 获取不到 task", backReqBody.getInstanceId()); continue; } BackReq tempReq = BackReq.builder() .body(BackReq.BackReqBody.builder().taskId(task.getId()).comment(backReqBody.getComment()).build()) .build(); tempReq.setHeader(req.getHeader()); try { BackRsp tempRsp = back(tempReq); rsp.getBody() .add(BatchBackRsp.BatchBackRspBody.builder() .success(tempRsp.getBody().isSuccess()) .instanceId(backReqBody.getInstanceId()) .build()); } catch (Exception ex) { rsp.getBody() .add(BatchBackRsp.BatchBackRspBody.builder() .success(false) .instanceId(backReqBody.getInstanceId()) .build()); log.error("循环 complete 异常", ex); } } return rsp; } /** * 終止 * * @param req * @return */ @PostMapping(path = "/stop", consumes = APPLICATION_JSON_UTF8, produces = APPLICATION_JSON_UTF8) @ResponseBody public StopRsp stop(@RequestBody StopReq req) { Assert.notNull(req, MyExceptionEnum.INVALID_PARAMETER); org.activiti.engine.task.Task task = taskService.createTaskQuery().processInstanceId(req.getBody().getProcessInstanceId()).singleResult(); Assert.notNull(task, MyExceptionEnum.NULL_ACT_USER_TASK); // String username = req.getHeader().get(consUsername); // // if (!task.getAssignee().equals(username)) { // throw new BusinessException(MyExceptionEnum.INVALID_PERMISSION); // } String username = task.getAssignee(); if (StringUtils.isEmpty(username)) { Map<String, Object> variables = taskService.getVariables(task.getId()); String assigneeKey = ActivitiUtils.getAssigneeKey(task.getTaskDefinitionKey()); if (StringUtils.isEmpty(task.getAssignee()) && variables.containsKey(assigneeKey)) { username = variables.get(assigneeKey).toString(); } } securityUtils.logInAs(username); String processInstanceId = task.getProcessInstanceId(); // 取得所有历史任务按时间降序排序 List<HistoricTaskInstance> htiList = historyService.createHistoricTaskInstanceQuery() .processInstanceId(processInstanceId) .orderByTaskCreateTime() .desc() .list(); // list里第一条代表当前任务 HistoricTaskInstance curTask = htiList.get(0); // 当前节点的executionId String curExecutionId = curTask.getExecutionId(); String processDefinitionId = curTask.getProcessDefinitionId(); BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); // 获取结束的节点信息 FlowNode endFlowNode = (FlowNode) ActivitiUtils.getEndEventFlowElement(bpmnModel); // 取得当前节点的信息 Execution execution = runtimeService.createExecutionQuery().executionId(curExecutionId).singleResult(); String curActivityId = execution.getActivityId(); FlowNode curFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(curActivityId); // 记录当前节点的原活动方向 List<SequenceFlow> oriSequenceFlows = new ArrayList<>(); oriSequenceFlows.addAll(curFlowNode.getOutgoingFlows()); // 清理活动方向 curFlowNode.getOutgoingFlows().clear(); // 建立新方向 List<SequenceFlow> newSequenceFlowList = new ArrayList<>(); SequenceFlow newSequenceFlow = new SequenceFlow(); newSequenceFlow.setId("newSequenceFlowId"); newSequenceFlow.setSourceFlowElement(curFlowNode); newSequenceFlow.setTargetFlowElement(endFlowNode); newSequenceFlowList.add(newSequenceFlow); curFlowNode.setOutgoingFlows(newSequenceFlowList); // 完成任务 CompleteTaskPayloadBuilder completeTaskPayloadBuilder = TaskPayloadBuilder.complete() .withVariable(ActivitiUtils.getApproveKey(), ActivitiUtils.APPROVE_END) .withTaskId(task.getId()); taskRuntime.complete(completeTaskPayloadBuilder.build()); // 恢复原方向 curFlowNode.setOutgoingFlows(oriSequenceFlows); return StopRsp.builder().body(StopRsp.StopRspBody.builder().success(true).build()).build(); } @PostMapping(path = "/batchStop", consumes = APPLICATION_JSON_UTF8, produces = APPLICATION_JSON_UTF8) @ResponseBody public BatchStopRsp batchStop(@RequestBody BatchStopReq req) { BatchStopRsp rsp = BatchStopRsp.builder().build(); for (StopReq.StopReqBody stopReqBody : req.getBody()) { StopReq tempReq = StopReq.builder().body(stopReqBody).build(); tempReq.setHeader(req.getHeader()); try { StopRsp tempRsp = stop(tempReq); rsp.getBody() .add(BatchStopRsp.BatchStopRspBody.builder() .success(tempRsp.getBody().isSuccess()) .processInstanceId(stopReqBody.getProcessInstanceId()) .build()); } catch (Exception ex) { rsp.getBody() .add(BatchStopRsp.BatchStopRspBody.builder() .success(false) .processInstanceId(stopReqBody.getProcessInstanceId()) .build()); log.error("循环 stop 异常", ex); } } return rsp; } /** * 处理环节 * * @param req * @return */ @PostMapping(path = "/batchComplete", consumes = APPLICATION_JSON_UTF8, produces = APPLICATION_JSON_UTF8) @ResponseBody public BatchComplateRsp batchComplete(@RequestBody BatchComplateReq req) { BatchComplateRsp rsp = BatchComplateRsp.builder().build(); for (BatchComplateReq.BatchComplateReqBody complateReqBody : req.getBody()) { org.activiti.engine.task.Task task = taskService.createTaskQuery().processInstanceId(complateReqBody.getInstanceId()).singleResult(); if (task == null) { rsp.getBody() .add(BatchComplateRsp.BatchComplateRspBody.builder() .success(false) .instanceId(complateReqBody.getInstanceId()) .build()); log.info("流程实例 {} 获取不到 task", complateReqBody.getInstanceId()); continue; } ComplateReq tempReq = ComplateReq.builder() .body(ComplateReq.ComplateReqBody.builder() .taskId(task.getId()) .comment(complateReqBody.getComment()) .approve(complateReqBody.getApprove()) .build()) .build(); tempReq.setHeader(req.getHeader()); try { ComplateRsp tempRsp = complete(tempReq); rsp.getBody() .add(BatchComplateRsp.BatchComplateRspBody.builder() .success(tempRsp.getBody().isSuccess()) .instanceId(complateReqBody.getInstanceId()) .build()); } catch (Exception ex) { rsp.getBody() .add(BatchComplateRsp.BatchComplateRspBody.builder() .success(false) .instanceId(complateReqBody.getInstanceId()) .build()); log.error("循环 complete 异常", ex); } } return rsp; } /** * 处理环节 * * @param req * @return */ @PostMapping(path = "/complete", consumes = APPLICATION_JSON_UTF8, produces = APPLICATION_JSON_UTF8) @ResponseBody public ComplateRsp complete(@RequestBody ComplateReq req) { Assert.notNull(req, MyExceptionEnum.INVALID_PARAMETER); String username = req.getHeader().get(consUsername); securityUtils.logInAs(username); // TaskImpl task = (TaskImpl)taskRuntime.task(req.getBody().getTaskId()); TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(req.getBody().getTaskId()).singleResult(); Assert.notNull(task, MyExceptionEnum.NULL_ACT_USER_TASK); Map<String, Object> variables = taskService.getVariables(task.getId()); String assigneeKey = ActivitiUtils.getAssigneeKey(task.getTaskDefinitionKey()); if (StringUtils.isEmpty(task.getAssignee()) && variables.containsKey(assigneeKey)) { task.setAssignee(variables.get(assigneeKey).toString()); taskService.setAssignee(task.getId(), variables.get(assigneeKey).toString()); // taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(req.getBody().getTaskId()).build()); } if (!username.equals(task.getAssignee())) { throw new BusinessException(MyExceptionEnum.INVALID_PERMISSION); } BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId()); Assert.notNull(bpmnModel, MyExceptionEnum.NULL_ACT_BPMN_MODEL); CompleteTaskPayloadBuilder completeTaskPayloadBuilder = TaskPayloadBuilder.complete().withTaskId(req.getBody().getTaskId()); // region 表单数据处理 UserTask userTask = (UserTask) bpmnModel.getFlowElement(task.getTaskDefinitionKey()); if (userTask.getFormProperties().size() > 0) { if (!CollectionUtils.isEmpty(req.getBody().getVariables())) { completeTaskPayloadBuilder = completeTaskPayloadBuilder.withVariable(ActivitiUtils.getFormsKey(), req.getBody().getVariables()); } } // endregion // region 审批意见处理 if (ActivitiUtils.APPROVE_UN_KNOW != req.getBody().getApprove()) { // 此处审批状态和下面审批状态作用不一样,主要给流程的el条件判断使用 completeTaskPayloadBuilder = completeTaskPayloadBuilder.withVariable(ActivitiUtils.getApproveKey(), req.getBody().getApprove()); if (!StringUtils.isEmpty(req.getBody().getComment())) { taskService.addComment(task.getId(), task.getProcessInstanceId(), ActivitiUtils.getCommentKey(), req.getBody().getComment()); } // 每个环节对应的审批状态,是独立的 taskService.addComment(task.getId(), task.getProcessInstanceId(), ActivitiUtils.getApproveKey(), String.valueOf(req.getBody().getApprove())); } // endregion // #TODO 需要看看是否可以将这个写在审批意见处理上面 taskRuntime.complete(completeTaskPayloadBuilder.build()); // 不同意时,退回到第一步可能没指派人时处理逻辑 org.activiti.engine.task.Task newTask = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); if (ActivitiUtils.APPROVE_UN_PASS == req.getBody().getApprove() && StringUtils.isEmpty(newTask.getAssignee())) { org.activiti.engine.task.Task task1 = taskService.createTaskQuery().taskId(newTask.getId()).singleResult(); String assignees = taskService.getVariable(task1.getId(), ActivitiUtils.getAssigneeKey(task.getTaskDefinitionKey()), String.class); if (!StringUtils.isEmpty(assignees)) { taskService.setAssignee(task1.getId(), assignees); } } return ComplateRsp.builder().body(ComplateRsp.ComplateRspBody.builder().success(true).build()).build(); } /** * 画流程图 * * @param req * @return * @throws IOException */ @PostMapping(path = "/diagram", consumes = APPLICATION_JSON_UTF8, produces = APPLICATION_JSON_UTF8) @ResponseBody public DiagramRsp diagram(@RequestBody DiagramReq req) throws IOException { Assert.notNull(req, MyExceptionEnum.INVALID_PARAMETER); String processDefinitionId = null; if (!StringUtils.isEmpty(req.getBody().getDeploymentId())) { ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .deploymentId(req.getBody().getDeploymentId()) .singleResult(); Assert.notNull(processDefinition, MyExceptionEnum.NULL_ACT_PROCESS_DEFINITION); processDefinitionId = processDefinition.getId(); } else { org.activiti.engine.task.Task task = taskService.createTaskQuery().processInstanceId(req.getBody().getProcessInstanceId()).singleResult(); Assert.notNull(task, MyExceptionEnum.NULL_ACT_PROCESS_INSTANCE); processDefinitionId = task.getProcessDefinitionId(); } Assert.notNull(processDefinitionId, MyExceptionEnum.NULL_ACT_PROCESS_DEFINITION); BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); Assert.notNull(processDefinitionId, MyExceptionEnum.NULL_ACT_BPMN_MODEL); List<HistoricActivityInstance> historicActivityInstanceList = null; if (!StringUtils.isEmpty(req.getBody().getProcessInstanceId())) { // 构造历史流程查询 HistoricActivityInstanceQuery historyInstanceQuery = historyService.createHistoricActivityInstanceQuery() .processInstanceId(req.getBody().getProcessInstanceId()); // 查询历史节点 historicActivityInstanceList = historyInstanceQuery.orderByHistoricActivityInstanceStartTime().asc().list(); } InputStream fileOssRecordInputStream = null; try { fileOssRecordInputStream = ActivitiUtils.getProcessDiagram(bpmnModel, historicActivityInstanceList); if (fileOssRecordInputStream != null) { String content = IOUtils.toString(fileOssRecordInputStream, StandardCharsets.UTF_8); return DiagramRsp.builder().body(DiagramRsp.DiagramRspBody.builder().content(content).build()).build(); } else { throw new BusinessException(MyExceptionEnum.ERR_OPT_FAIL); } } finally { if (fileOssRecordInputStream != null) { fileOssRecordInputStream.close(); } } } /** * 查看流程实例信息 * * @param req * @return */ @PostMapping(path = "/getInstance", consumes = APPLICATION_JSON_UTF8, produces = APPLICATION_JSON_UTF8) @ResponseBody public GetInstanceRsp getInstance(@RequestBody GetInstanceReq req) { Assert.notNull(req, MyExceptionEnum.INVALID_PARAMETER); ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() .processInstanceId(req.getBody().getProcessInstanceId()) .singleResult(); Assert.notNull(processInstance, MyExceptionEnum.NULL_ACT_PROCESS_INSTANCE); org.activiti.engine.task.Task task = taskService.createTaskQuery().processInstanceId(req.getBody().getProcessInstanceId()).singleResult(); Assert.notNull(task, MyExceptionEnum.NULL_ACT_USER_TASK); BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId()); Assert.notNull(bpmnModel, MyExceptionEnum.NULL_ACT_BPMN_MODEL); UserTask firstUserTask = ActivitiUtils.getFirstUserTasks(bpmnModel); Assert.notNull(firstUserTask, MyExceptionEnum.NULL_ACT_USER_TASK); List<UserTask> allUserTasks = ActivitiUtils.getAllUserTasks(bpmnModel); Assert.notNull(allUserTasks, MyExceptionEnum.NULL_ACT_USER_TASK_DEFINITION); GetInstanceRsp.GetInstanceRspBody rspBody = GetInstanceRsp.GetInstanceRspBody.builder() .processInstanceId(processInstance.getId()) .processInstanceName(processInstance.getName()) .taskId(task.getId()) .taskName(task.getName()) .businessKey(task.getBusinessKey()) .historyTasks(new ArrayList<>()) .formProperties(firstUserTask.getFormProperties()) .build(); Collection<String> vals = new ArrayList<>(); vals.add(ActivitiUtils.getFormsKey()); rspBody.setVariables(runtimeService.getVariables(task.getExecutionId())); GetInstanceRsp rsp = GetInstanceRsp.builder().body(rspBody).build(); List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery() // 查询结果包括 local变量 .includeTaskLocalVariables() .processInstanceId(processInstance.getId()) .orderByHistoricTaskInstanceStartTime() .asc() .list(); if (!CollectionUtils.isEmpty(historicTaskInstances)) { for (HistoricTaskInstance historicTaskInstance : historicTaskInstances) { GetInstanceRsp.HistoricTask tempHistoricTask = GetInstanceRsp.HistoricTask.builder() .taskId(historicTaskInstance.getId()) .name(historicTaskInstance.getName()) .createTime(historicTaskInstance.getCreateTime()) .build(); AssigneeVO tempAssigness = new AssigneeVO(); if (StringUtils.isEmpty(historicTaskInstance.getAssignee())) { String assigneeKey = ActivitiUtils.getAssigneeKey(historicTaskInstance.getTaskDefinitionKey()); tempAssigness.setUsername((String) historicTaskInstance.getTaskLocalVariables().get(assigneeKey)); } else { tempAssigness.setUsername(historicTaskInstance.getAssignee()); } String name = userMapper.getNameByUserName(tempAssigness.getUsername()); if (!StringUtils.isEmpty(name)) { tempAssigness.setName(name); } tempHistoricTask.setAssignee(tempAssigness); // 获取comment Optional<org.activiti.engine.task.Comment> commentOptional = taskService.getTaskComments(historicTaskInstance.getId(), ActivitiUtils.getCommentKey()) .stream() .findFirst(); if (commentOptional.isPresent()) { tempHistoricTask.setComment(commentOptional.get().getFullMessage()); } // 获取approve Optional<org.activiti.engine.task.Comment> approveOptional = taskService.getTaskComments(historicTaskInstance.getId(), ActivitiUtils.getApproveKey()) .stream() .findFirst(); if (approveOptional.isPresent()) { tempHistoricTask.setApprove(Integer.parseInt(approveOptional.get().getFullMessage())); } rspBody.getHistoryTasks().add(tempHistoricTask); } } Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(processInstance.getDeploymentId()).singleResult(); Assert.notNull(deployment, MyExceptionEnum.NULL_ACT_USER_TASK); // ProcessListener tempProcessListener = // getProcessListenerByKey(deployment.getKey(), req.getBody().getBusinessKey()); // if (tempProcessListener != null) { // String url = // String.format(tempProcessListener.getCommActivity().getFormUrl(), req.getBody().getBusinessKey()); // rsp.getBody().setFormUrl(url); // } return rsp; } private ProcessListener getProcessListenerByKey(String deploymentKey, String businessKey) { ProcessListener customProcessListener = ProcessListener.builder() .deploymentKey(deploymentKey) .businessKey(businessKey) .commActivityMapper(commActivityMapper) .callService(callService) .build(); return customProcessListener; } }
ProcessesApiController.java
package ***.***.activiti.controller; import com.bootdo.activiti.command.DeleteDeploymentCommand; import com.bootdo.activiti.command.DeploymentByResourceCommand; import com.bootdo.activiti.command.DeploymentByZipCommand; import com.bootdo.activiti.command.SearchProcesseCommand; import com.bootdo.activiti.enums.CommonResponseEnum; import com.bootdo.activiti.interfaces.model.JsonResult; import com.bootdo.activiti.interfaces.model.JsonResultPage; import com.bootdo.activiti.model.CopyDeploymentToModel; import com.bootdo.activiti.model.DeploymentResponseModel; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.activiti.bpmn.model.BpmnModel; import org.activiti.editor.constants.ModelDataJsonConstants; import org.activiti.editor.language.json.converter.BpmnJsonConverter; import org.activiti.engine.RepositoryService; import org.activiti.engine.repository.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipInputStream; @RestController @RequestMapping(value = "/activiti/processes/api/",method = RequestMethod.POST) @Api(value = "processes", tags = {"流程接口"}) @CrossOrigin(origins = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT}) public class ProcessesApiController { private static final Logger LOGGER = LoggerFactory.getLogger(ProcessesApiController.class); /** * 流程定义和部署相关的存储服务 */ @Resource RepositoryService repositoryService; @RequestMapping("/deploymentByResource") @ApiOperation(value = "发布流程",notes = "项目内部资源文件部署") public JsonResult deploymentByResource(@RequestBody DeploymentByResourceCommand command) { if(command==null || StringUtils.isEmpty(command.getName()) || StringUtils.isEmpty(command.getBpmpResourcePath())) return JsonResult.error(CommonResponseEnum.Invalid_Parameter); DeploymentBuilder deploymentBuilder = repositoryService.createDeployment(); //创建Deployment对象 deploymentBuilder = deploymentBuilder.addClasspathResource(command.getBpmpResourcePath()); //添加png文件 if(!StringUtils.isEmpty(command.getPngResourcePath())) deploymentBuilder = deploymentBuilder.addClasspathResource(command.getPngResourcePath()); //部署 Deployment deployment = deploymentBuilder.name(command.getName()).key(command.getKey()).deploy(); return JsonResult.success(deployment); } @RequestMapping("/categories") @ApiOperation(value = "流程分类",notes = "获取所有流程分类") public JsonResult categories() { List<String> categories = new ArrayList<String>(){ { add("事假"); add("病假"); add("会议"); add("出差"); } }; return JsonResult.success(categories); } @RequestMapping("/deploymentByZipFile") @ApiOperation(value = "发布流程",notes = "外部 zip 文件部署") public JsonResult deploymentByZipFile(@RequestBody DeploymentByZipCommand command) throws IOException { if(command==null || StringUtils.isEmpty(command.getName()) || StringUtils.isEmpty(command.getZipFilePath())) return JsonResult.error(CommonResponseEnum.Invalid_Parameter); InputStream in=null; ZipInputStream zipInputStream=null; try { in = new FileInputStream(new File(command.getZipFilePath())); zipInputStream = new ZipInputStream(in); Deployment deployment = repositoryService.createDeployment().name(command.getName()) // 指定zip格式的文件完成部署 .addZipInputStream(zipInputStream).deploy();// 完成部署 return JsonResult.success(deployment); } finally { if(in!=null) { zipInputStream.close(); } } } /** * 删除已经部署的流程定义 * 影响的activiti表有哪些 * act_re_deployment 部署信息 * act_re_procdef 流程定义的一些信息 * act_ge_bytearray 流程定义的bpmn文件以及png文件 **/ @RequestMapping("/delete") @ApiOperation(value = "删除已经部署的流程定义") public JsonResult delete(@RequestBody DeleteDeploymentCommand command) { if(command==null || StringUtils.isEmpty(command.getDeploymentId())) return JsonResult.error(CommonResponseEnum.Invalid_Parameter); //删除流程定义 参数表述流程部署id 可以通过查询部署的信息得到流程部署id repositoryService.deleteDeployment(command.getDeploymentId(),command.isCascade()); return JsonResult.success(); } @RequestMapping("/list") @ApiOperation(value = "查询已部署的流程",notes = "List<Deployment>") public JsonResultPage list(@RequestBody SearchProcesseCommand command) { if(command==null) return JsonResultPage.error(CommonResponseEnum.Invalid_Parameter); DeploymentQuery query = repositoryService.createDeploymentQuery().orderByDeploymentName().desc(); if(!StringUtils.isEmpty(command.getName())) query = query.deploymentName(command.getName()); if(!StringUtils.isEmpty(command.getKey())) query = query.processDefinitionKey(command.getKey()); if(!StringUtils.isEmpty(command.getCategory())) query = query.deploymentCategory(command.getCategory()); long total = query.count(); List<Deployment> deployments = query.orderByDeploymentName().asc() .listPage((command.getPage()-1)*command.getLimit(),command.getLimit()); List<DeploymentResponseModel> processResponseModels = new ArrayList<>(); for (Deployment deployment : deployments) { processResponseModels.add( new DeploymentResponseModel(){ { setDeploymentId(deployment.getId()); setName(deployment.getName()); setDeploymentTime(deployment.getDeploymentTime()); setCategory(deployment.getCategory()); setKey(deployment.getKey()); setTenantId(deployment.getTenantId()); } } ); } return new JsonResultPage<>(processResponseModels,total,command.getLimit()); } @RequestMapping("/copyToModel") @ApiOperation(value = "复制已部署的流程模型") public JsonResult copToModel(@RequestBody CopyDeploymentToModel command) throws IOException { Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(command.getDeploymentId()).singleResult(); if(deployment==null) { return JsonResult.error(CommonResponseEnum.Deployment_NotFind); } ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(command.getDeploymentId()).singleResult(); if(processDefinition==null) { return JsonResult.error(CommonResponseEnum.ProcessDefinition_NotFind); } BpmnModel bpmnModel =repositoryService.getBpmnModel(processDefinition.getId()); if(processDefinition==null) { return JsonResult.error(CommonResponseEnum.BpmnModel_NotFind); } Model modelData = repositoryService.newModel(); ObjectMapper objectMapper = new ObjectMapper(); ObjectNode modelObjectNode = objectMapper.createObjectNode(); modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, deployment.getName()); modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1); modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, processDefinition.getDescription()); modelData.setMetaInfo(modelObjectNode.toString()); modelData.setName( deployment.getName()); modelData.setKey(deployment.getKey()); modelData.setCategory(deployment.getCategory()); modelData.setVersion(1); //保存模型 repositoryService.saveModel(modelData); //保存模型对应的流程图json BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter(); ObjectNode objectNode = bpmnJsonConverter.convertToJson(bpmnModel); repositoryService.addModelEditorSource(modelData.getId(), objectNode.toString().getBytes("utf-8")); return JsonResult.success(modelData.getId()); } }
5.服务接口
SimpleUserTaskApiController.java
package ***.***.activiti.controller; import com.bootdo.activiti.command.*; import com.bootdo.activiti.enums.CommonResponseEnum; import com.bootdo.activiti.enums.GlobalExceptionResponseEnum; import com.bootdo.activiti.interfaces.model.JsonResult; import com.bootdo.activiti.interfaces.model.JsonResultPage; import com.bootdo.system.domain.DeptDO; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.annotations.Api; import org.activiti.bpmn.converter.BpmnXMLConverter; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.EventListener; import org.activiti.bpmn.model.Process; import org.activiti.bpmn.model.StartEvent; import org.activiti.editor.constants.ModelDataJsonConstants; import org.activiti.editor.language.json.converter.BpmnJsonConverter; import org.activiti.engine.RepositoryService; import org.activiti.engine.repository.Deployment; import org.activiti.engine.repository.Model; import org.activiti.engine.repository.ModelQuery; import org.activiti.engine.repository.ProcessDefinition; import org.apache.commons.compress.utils.IOUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import springfox.documentation.annotations.ApiIgnore; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.List; @RestController @RequestMapping(value = "/activiti/model/api/",method = RequestMethod.POST) @Api(value = "model", tags = {"模型接口"}) @CrossOrigin(origins = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT}) public class ModelApiController { /** * 流程定义和部署相关的存储服务 */ @Resource RepositoryService repositoryService; private static final Logger LOGGER = LoggerFactory.getLogger(ModelApiController.class); /** * 创建模型 */ @RequestMapping("/create") public JsonResult apiCreate(@RequestBody CreateModelCommand command) { if(command==null || StringUtils.isEmpty(command.getName()) || StringUtils.isEmpty(command.getKey())) return JsonResult.error(CommonResponseEnum.Invalid_Parameter); // securityUtils.logInAs(command.getOperatedByUserName()); try { ObjectMapper objectMapper = new ObjectMapper(); ObjectNode editorNode = objectMapper.createObjectNode(); editorNode.put("id", "canvas"); editorNode.put("resourceId", "canvas"); ObjectNode stencilSetNode = objectMapper.createObjectNode(); stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#"); editorNode.put("stencilset", stencilSetNode); Model modelData = repositoryService.newModel(); ObjectNode modelObjectNode = objectMapper.createObjectNode(); modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, command.getName()); modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1); modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, command.getDescription()); modelData.setMetaInfo(modelObjectNode.toString()); modelData.setName( command.getName()); modelData.setKey(command.getKey()); modelData.setCategory(command.getCategory()); //保存模型 repositoryService.saveModel(modelData); repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8")); return JsonResult.success(modelData.getId()); } catch (Exception e) { LOGGER.error("创建模型错误",e); return JsonResult.error(GlobalExceptionResponseEnum.RuntimeException); } } /** * 查询模型列表 * @param command * @return */ @RequestMapping("/list") public JsonResultPage<Model> apiList(@RequestBody SearchModelCommand command) { if(command==null) return JsonResultPage.error(CommonResponseEnum.Invalid_Parameter); ModelQuery modelQuery =repositoryService.createModelQuery().orderByModelCategory().desc(); if(!StringUtils.isEmpty(command.getName())) modelQuery = modelQuery.modelName(command.getName()); if(!StringUtils.isEmpty(command.getKey())) modelQuery = modelQuery.modelKey(command.getKey()); if(!StringUtils.isEmpty(command.getCategory())) modelQuery = modelQuery.modelCategory(command.getCategory()); long total = modelQuery.count(); List<Model> modelList = modelQuery.listPage((command.getPage()-1)*command.getLimit(),command.getLimit()); return new JsonResultPage(modelList,total,command.getLimit()); } /** * 删除模型 * @param command * @return */ @RequestMapping("/delete") public JsonResult apiDelete(@RequestBody DeleteModelCommand command) { if(command==null || StringUtils.isEmpty(command.getModelId())) return JsonResult.error(CommonResponseEnum.Invalid_Parameter); // securityUtils.logInAs(command.getOperatedByUserName()); repositoryService.deleteModel(command.getModelId()); return JsonResult.success(); } /** * 发布模型 * @param command * @return */ @RequestMapping("/deployment") public JsonResult apiDeployment(@RequestBody DeploymentModelCommand command) throws Exception { if(command==null || StringUtils.isEmpty(command.getModelId())) return JsonResult.error(CommonResponseEnum.Invalid_Parameter); // securityUtils.logInAs(command.getOperatedByUserName()); //获取模型 Model modelData = repositoryService.getModel(command.getModelId()); byte[] bytes = repositoryService.getModelEditorSource(modelData.getId()); if (bytes == null) { LOGGER.info("模型数据为空,请先设计流程并成功保存,再进行发布。"); return JsonResult.error(CommonResponseEnum.Model_NotFind); } JsonNode modelNode = new ObjectMapper().readTree(bytes); BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(modelNode); List<Process> processes = bpmnModel.getProcesses(); if (CollectionUtils.isEmpty(processes)) { LOGGER.info("数据模型不符要求,请至少设计一条主线流程。"); return JsonResult.error(CommonResponseEnum.Model_LeastOneMainProcess); } for (Process process: processes) { } byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(bpmnModel); if(!command.isIgnoreDuplicates()) { int rows = repositoryService.createProcessDefinitionQuery().processDefinitionKey(modelData.getKey()).list().size(); if(rows>0) { return JsonResult.error(CommonResponseEnum.Duplicate_key); } } //发布流程 String processName = modelData.getName() + ".bpmn20.xml"; Deployment deployment = repositoryService.createDeployment() .name(modelData.getName()) .key(modelData.getKey()) .category(StringUtils.isEmpty(modelData.getCategory())?"其它":modelData.getCategory()) .addString(processName, new String(bpmnBytes, "UTF-8")) .deploy(); //发布完毕,删除模型 if(command.isDeleteModel()) repositoryService.deleteModel(command.getModelId()); return JsonResult.success(deployment); } }
6.自定义全局侦听器
CustomGlobalActivitiEventListener.java
package ***.***.activiti7.listeners; import java.util.Date; import org.activiti.engine.RepositoryService; import org.activiti.engine.delegate.event.ActivitiEntityEvent; import org.activiti.engine.delegate.event.ActivitiEvent; import org.activiti.engine.delegate.event.ActivitiEventListener; import org.activiti.engine.delegate.event.ActivitiProcessStartedEvent; import org.activiti.engine.delegate.event.impl.ActivitiEntityEventImpl; import org.activiti.engine.delegate.event.impl.ActivitiEntityWithVariablesEventImpl; import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.impl.persistence.entity.TaskEntity; import org.activiti.engine.impl.persistence.entity.TaskEntityImpl; import org.activiti.engine.repository.Deployment; import org.activiti.engine.repository.ProcessDefinition; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import ***.***.activiti7.mapper.CommActivityMapper; import ***.***.activiti7.util.ActivitiUtils; import ***.***.intf.service.CallService; import net.platform.center.util.ApplicationContextHolder; /** * @ClassName WsjActivitiEventListener * @Description: 全局 * @Author wsj * @Date 2020/8/19 **/ @Component public class CustomGlobalActivitiEventListener implements ActivitiEventListener { /** * 流程定义和部署相关的存储服务 * 不自动注入,是应为油循环依赖问题 */ @Override public void onEvent(ActivitiEvent activitiEvent) { ProcessListener processListener; switch (activitiEvent.getType()) { // 流程开始 case PROCESS_STARTED: if (activitiEvent instanceof ActivitiProcessStartedEvent) { ExecutionEntity executionEntity = (ExecutionEntity) ((ActivitiProcessStartedEvent) activitiEvent).getEntity(); processListener = getProcessListener(activitiEvent); if (processListener == null) { break; } if (executionEntity.getTasks().size() != 1) { break; } processListener.instanceId = activitiEvent.getProcessInstanceId(); processListener.businessKey = executionEntity.getTasks().get(0).getBusinessKey(); processListener.taskName = "发起审批"; processListener.state = 1; TaskEntity taskEntity = executionEntity.getTasks().get(0); if (StringUtils.isEmpty(taskEntity.getAssignee())) { String key = ActivitiUtils.getAssigneeKey(taskEntity.getTaskDefinitionKey()); processListener.by = processListener.nextBy = (String) taskEntity.getVariable(key); taskEntity.setAssignee(processListener.by); } else { processListener.by = processListener.nextBy = executionEntity.getTasks().get(0).getAssignee(); } processListener.at = new Date(); processListener.processStarted(executionEntity); } break; case PROCESS_COMPLETED: processListener = getProcessListener(activitiEvent); if (processListener == null) { break; } if (activitiEvent instanceof ActivitiEntityEvent) { ActivitiEntityEvent activitiEntityEvent = (ActivitiEntityEvent) activitiEvent; ExecutionEntity executionEntity = (ExecutionEntity) activitiEntityEvent.getEntity(); processListener.instanceId = activitiEvent.getProcessInstanceId(); processListener.businessKey = executionEntity.getBusinessKey(); processListener.at = executionEntity.getStartTime(); // .get(executionEntity.getIdentityLinks().size() - 1) // .getUserId(); int approve = (int) executionEntity.getVariable(ActivitiUtils.getApproveKey()); if (approve == ActivitiUtils.APPROVE_PASS) { // #TODO 需要判断是通过还是不通过,不过目前是单线程,到达终止节点,就表示通过 processListener.taskName = "审批通过"; processListener.state = 2; } else if (approve == ActivitiUtils.APPROVE_UN_PASS) { // #TODO 需要判断是通过还是不通过,不过目前是单线程,到达终止节点,就表示通过 processListener.taskName = "审批不通过"; processListener.state = 3; } else if (approve == ActivitiUtils.APPROVE_END) { // #TODO 需要判断是通过还是不通过,不过目前是单线程,到达终止节点,就表示通过 processListener.taskName = "审批终止"; processListener.state = 4; processListener.nextBy = null; } else { processListener.taskName = "未知"; processListener.state = 5; } // 一定要 getCommActivity(),不然没值 processListener.getCommActivity(); processListener.processCompleted(executionEntity); } break; case TASK_CREATED: if (activitiEvent instanceof ActivitiEntityEventImpl) { processListener = getProcessListener(activitiEvent); if (processListener == null) { break; } ActivitiEntityEventImpl entityEvent = (ActivitiEntityEventImpl) activitiEvent; TaskEntityImpl taskEntity = (TaskEntityImpl) entityEvent.getEntity(); processListener.instanceId = activitiEvent.getProcessInstanceId(); processListener.businessKey = taskEntity.getBusinessKey(); if (StringUtils.isEmpty(taskEntity.getAssignee())) { String key = ActivitiUtils.getAssigneeKey(taskEntity.getTaskDefinitionKey()); processListener.nextBy = (String) taskEntity.getVariable(key); taskEntity.setAssignee(processListener.nextBy); } else { processListener.nextBy = taskEntity.getAssignee(); } processListener.taskCreated(taskEntity); } break; case TASK_COMPLETED: if (activitiEvent instanceof ActivitiEntityWithVariablesEventImpl) { processListener = getProcessListener(activitiEvent); if (processListener == null) { break; } ActivitiEntityWithVariablesEventImpl entityEvent = (ActivitiEntityWithVariablesEventImpl) activitiEvent; TaskEntity taskEntity = (TaskEntity) entityEvent.getEntity(); processListener.instanceId = activitiEvent.getProcessInstanceId(); processListener.businessKey = taskEntity.getBusinessKey(); processListener.at = taskEntity.getCreateTime(); if (StringUtils.isEmpty(taskEntity.getAssignee())) { processListener.by = ActivitiUtils.getAssigneeKey(taskEntity.getTaskDefinitionKey()); } else { processListener.by = taskEntity.getAssignee(); } processListener.taskName = taskEntity.getName(); int approve = (int) taskEntity.getVariable(ActivitiUtils.getApproveKey()); switch (approve) { case ActivitiUtils.APPROVE_BACK: processListener.state = 5; break; case ActivitiUtils.APPROVE_END: processListener.state = 4; processListener.nextBy = null; processListener.taskName = "审批终止"; break; default: processListener.state = 1; break; } processListener.taskCompleted(taskEntity); } break; default: break; } } private RepositoryService getRepositoryService() { return ApplicationContextHolder.getBean(RepositoryService.class); } private CommActivityMapper getCommActivityMapper() { return ApplicationContextHolder.getBean(CommActivityMapper.class); } private CallService getCallService() { return ApplicationContextHolder.getBean(CallService.class); } private ProcessListener getProcessListener(ActivitiEvent activitiEvent) { RepositoryService repositoryService = getRepositoryService(); if (repositoryService == null) { return null; } // 获取流程定义 ProcessDefinition processDefinition = repositoryService.getProcessDefinition(activitiEvent.getProcessDefinitionId()); if (processDefinition == null) { return null; } Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(processDefinition.getDeploymentId()).singleResult(); if (deployment == null) { return null; } ProcessListener processListener = new ProcessListener(); processListener.setCommActivityMapper(getCommActivityMapper()); processListener.setCallService(getCallService()); processListener.setDeploymentKey(deployment.getKey()); return processListener; } /** * 这个是必须要的,监听异常处理策略 * * @return */ @Override public boolean isFailOnException() { return false; } }
业务列表中,需要显示目前是在哪个审批环节,各位根据自己实际业务看看是否需要
CustomerActivitiConfig.java
package ***.***.activiti7; import java.util.ArrayList; import java.util.List; import org.activiti.engine.delegate.event.ActivitiEventListener; import org.activiti.spring.SpringProcessEngineConfiguration; import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import ***.***.activiti7.listeners.CustomGlobalActivitiEventListener; @Component public class CustomerActivitiConfig implements ProcessEngineConfigurationConfigurer { @Autowired private CustomGlobalActivitiEventListener customGlobalActivitiEventListener; @Override public void configure(SpringProcessEngineConfiguration springProcessEngineConfiguration) { List<ActivitiEventListener> eventListeners = new ArrayList<>(); eventListeners.add(customGlobalActivitiEventListener); springProcessEngineConfiguration.setEventListeners(eventListeners); } }
IProcessListener.java
package ***.***.activiti7.listeners; import java.text.ParseException; import java.util.Date; import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.impl.persistence.entity.TaskEntity; import ***.***.activiti7.bean.CommActivityPO; import ***.***.intf.service.CallService; public interface IProcessListener { // #region 属性、变量 // String getKeyFileName(); String getInstanceId(); String getBusinessKey(); Integer getState(); String getTaskName(); String getBy(); String getNextBy(); Date getAt(); CommActivityPO getCommActivity(); String getDeploymentKey(); // #endregion // #region 方法 CallService getCallService(); /** * 流程开始 */ void processStarted(ExecutionEntity executionEntity); /** * 流程结束 */ void processCompleted(ExecutionEntity executionEntity) throws ParseException; /** * 任务完成 */ void taskCompleted(TaskEntity taskEntity); /** * 任务创建 */ void taskCreated(TaskEntity taskEntity); /** * 检查是否允许开启流程 * * @return */ boolean checkCanStart(); // #endregion }
ProcessListener.java
package ***.***.activiti7.listeners; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.impl.persistence.entity.TaskEntity; import org.activiti.engine.impl.persistence.entity.VariableInstance; import org.springframework.context.annotation.Scope; import org.springframework.http.ResponseEntity; import ***.***.activiti7.bean.CallBackReq; import ***.***.activiti7.bean.CallBackRsp; import ***.***.activiti7.bean.CommActivityPO; import ***.***.activiti7.mapper.CommActivityMapper; import ***.***.intf.service.CallService; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import net.platform.center.util.JsonUtil; @Slf4j @AllArgsConstructor @NoArgsConstructor @Data @Builder @Scope("prototype") public class ProcessListener implements IProcessListener { // /** // * 主键字段名 // */ // protected String keyFileName; /** * 流程实例ID */ protected String instanceId; /** * 业务数据主键 */ protected String businessKey; /** * 审批状态 * 1:发起审批 * 2:审批通过 * 3:审批不通过 * 4:审批终止 * 5:退回 * 6:未知 */ protected Integer state; /** * 当前环节 */ protected String taskName; /** * 本环节审批人 */ protected String by; /** * 下一个环节审批人 */ protected String nextBy; /** * 审批时间 */ protected Date at; private String deploymentKey; private CommActivityPO commActivity; private CommActivityMapper commActivityMapper; private CallService callService; @Override public CommActivityPO getCommActivity() { if (commActivity == null) { commActivity = commActivityMapper.get(deploymentKey); } return commActivity; } @Override public void processStarted(ExecutionEntity executionEntity) { commActivityMapper.processStarted(this); if (getCommActivity() != null && getCommActivity().isProcessStartedCallBack()) { CallBackRsp callBackRsp = callBack("processStartedCallBackUrl", executionEntity.getProcessVariables()); // #TODO 判断 CallBackRsp,输出日志 log.info("processStarted processStartedCallBackUrl: {}", callBackRsp.toString()); } } @Override public void processCompleted(ExecutionEntity executionEntity) { commActivityMapper.processCompleted(this); if (getCommActivity() != null && getCommActivity().isProcessCompletedCallBack()) { Map<String, VariableInstance> variableInstances = executionEntity.getVariableInstances(); Map<String, Object> data = new HashMap<>(); // 1. entrySet遍历,在键和值都需要时使用(最常用) for (Map.Entry<String, VariableInstance> entry : variableInstances.entrySet()) { data.put(entry.getKey(), entry.getValue().getValue()); } CallBackRsp callBackRsp = callBack("processCompletedCallBackUrl", data); log.info("processCompleted processCompletedCallBackUrl: {}", JsonUtil.toJsonString(callBackRsp)); } } @Override public void taskCompleted(TaskEntity taskEntity) { commActivityMapper.taskCompleted(this); if (getCommActivity() != null && getCommActivity().isTaskCompletedCallBack()) { CallBackRsp callBackRsp = callBack("taskCompletedCallBackUrl", taskEntity.getVariables()); // #TODO 判断 CallBackRsp,输出日志 log.info("taskCompleted taskCompletedCallBackUrl: {}", JsonUtil.toJsonString(callBackRsp)); } } @Override public void taskCreated(TaskEntity taskEntity) { commActivityMapper.taskCreated(this); } @Override public boolean checkCanStart() { // 检查后是否已经发起了审批 int result = commActivityMapper.getProcessStarted(this); if (result == 1) { return false; } CallBackReq callBackReq = CallBackReq.builder() .body(CallBackReq.CallBackReqBody.builder() .deploymentKey(this.deploymentKey) .businessKey(this.businessKey) .build()) .build(); if (getCommActivity() != null && getCommActivity().isCheckCanStartCallBack()) { CallBackRsp callBackRsp = callBack("checkCanStartCallBackUrl", callBackReq); return "1".equals(callBackRsp.getCode()); } else { return true; } } private CallBackRsp callBack(String serviceName, Object data) { CallBackReq req = CallBackReq.builder() .body(CallBackReq.CallBackReqBody.builder() .businessKey(businessKey) .deploymentKey(deploymentKey) .data(data) .build()) .build(); ResponseEntity<CallBackRsp> responseEntity = callService.callService(serviceName, req, CallBackRsp.class); log.info("callService req: {}", req.toString()); log.info("callService rsp: {}", JsonUtil.toJsonString(responseEntity)); return responseEntity.getBody(); } @Override public String toString() { return "ProcessListener(instanceId=" + instanceId + ", businessKey=" + businessKey + ", state=" + state + ", taskName=" + taskName + ", by=" + by + ", nextBy=" + nextBy + ", at=" + at + ", deploymentKey=" + deploymentKey + ")"; } }
工作流配置表