-
摘要
本篇隨筆主要記錄springBoot2集成activiti流程引擎,而且嵌入activiti的在線設計器,能夠經過瀏覽器直接編輯出咱們須要的流程,不須要經過eclipse或者IDEA的actiBpm插件設計流程再直接部署到項目下,頁面保存流程後可直接發佈、發起流程。javascript
-
所需軟件版本
springBoot 2.0.1.RELEASE
activiti 5.22.0 官網下載地址
activiti-webapp-explorer2 個人github上的explorer項目 -
集成方法
activiti支持多種數據庫,自己還有個h2的內存數據庫,我這裏用的是Mysql
一、pom.xml引入依賴,這裏主要是activiti的依賴包,${activiti.version}爲5.22.0<!--spring activiti start--> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter-basic</artifactId> <version>${activiti.version}</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </exclusion> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </exclusion> <exclusion> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter-actuator</artifactId> <version>${activiti.version}</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-rest</artifactId> <version>${activiti.version}</version> <exclusions> <exclusion> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-explorer</artifactId> <version>${activiti.version}</version> <exclusions> <exclusion> <groupId>com.vaadin</groupId> <artifactId>vaadin</artifactId> </exclusion> <exclusion> <groupId>org.vaadin.addons</groupId> <artifactId>dcharts-widget</artifactId> </exclusion> <exclusion> <groupId>activiti-simple-workflow</groupId> <artifactId>org.activiti</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-diagram-rest</artifactId> <version>${activiti.version}</version> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-simple-workflow</artifactId> <version>${activiti.version}</version> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring</artifactId> <version>${activiti.version}</version> </dependency> <dependency> <groupId>org.apache.xmlgraphics</groupId> <artifactId>batik-codec</artifactId> <version>1.7</version> </dependency> <dependency> <groupId>org.apache.xmlgraphics</groupId> <artifactId>batik-css</artifactId> <version>1.7</version> </dependency> <dependency> <groupId>org.apache.xmlgraphics</groupId> <artifactId>batik-svg-dom</artifactId> <version>1.7</version> </dependency> <dependency> <groupId>org.apache.xmlgraphics</groupId> <artifactId>batik-svggen</artifactId> <version>1.7</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.1.3.RELEASE</version> </dependency> <!--spring activiti end-->
二、拷貝項目資源
解壓activiti-webapp-explorer2,把webapp下面的diagram-viewer、editor-app、modeler.html複製到springboot項目下的static下,這是activiti的在線設計器,modeler.html就是設計的主界面,複製resources下stencilset.json到本身的resources下。
解壓activiti-5.22.0的包,將libs下的activiti-modeler-5.22.0-sources.jar解壓出來,把org\activiti\rest\editor路徑下的main、model文件夾複製到springboot項目的源碼路徑下,裏面有三個類,主要用於讀取stencilset.json。
三、修改拷貝過來的資源文件
修改StencilsetRestResource.java、ModelEditorJsonRestResource.java、ModelSaveRestResource.java3個controller,加上@RequestMapping("/service")
修改editor-app下的app-cfg.js,把contextRoot後面改爲 /service,和controller裏面加的requestMapping要一致
css -
var ACTIVITI = ACTIVITI || {}; ACTIVITI.CONFIG = { 'contextRoot' : '/service', };
4、新增service、controller
service實現,這裏只放實現類,接口就不放了,本身能夠根據實現類本身添加接口方法,這個service實現了把流程和自定義表單結合的功能,completeTask方法經過java反射執行不一樣任務節點執行不一樣form
htmlpackage com.djkj.activiti.service.Impl; import com.djkj.activiti.bean.ActivitiVariable; import com.djkj.activiti.common.BusinessTaskUtil; import com.djkj.activiti.service.ActivitiService; import com.djkj.activiti.service.ActivitiVariableService; import org.activiti.bpmn.model.BpmnModel; import org.activiti.engine.HistoryService; import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.TaskService; import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.history.HistoricActivityInstance; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.activiti.engine.impl.pvm.PvmTransition; import org.activiti.engine.impl.pvm.process.ActivityImpl; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; import org.activiti.image.ProcessDiagramGenerator; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import sun.misc.BASE64Encoder; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.lang.reflect.Method; import java.util.*; @Service public class ActivitiServiceImpl implements ActivitiService { @Autowired private RuntimeService runtimeService; @Autowired private TaskService taskService; @Autowired private HistoryService historyService; @Autowired private RepositoryService repositoryService; @Autowired private ProcessEngineConfigurationImpl processEngineConfiguration; @Autowired private ActivitiVariableService activitiVariableService; private static final Logger logger=Logger.getLogger(ActivitiServiceImpl.class); /** * 啓動流程 */ @Override public void startProcesses(String id,String business_key) { ProcessInstance pi = runtimeService.startProcessInstanceByKey(id, business_key);//流程圖id,業務表id System.out.println("流程啓動成功,流程id:"+pi.getId()); } /** * * <p>描述: 根據用戶id查詢待辦任務列表</p> * @author 範相如 * @date 2018年2月25日 */ @Override public List<Task> findTasksByUserId(String userId) { List<Task> resultTask = taskService.createTaskQuery().processDefinitionKey("process").taskCandidateOrAssigned(userId).list(); return resultTask; } @Override public Task findTaskById(String taskId){ List<Task> resultTask = taskService.createTaskQuery().taskId(taskId).list(); if(resultTask!=null){ return resultTask.get(0); } return null; } /** * * <p>描述:任務審批 (經過/拒接) </p> * @author 範相如 * @date 2018年2月25日 * @param taskId 任務id * @param userId 用戶id * @param result false OR true */ @Override public void completeTask(String taskId,String userId,String result) { //獲取流程實例 taskService.claim(taskId, userId); //獲取任務 Task task=taskService.createTaskQuery().taskId(taskId).singleResult(); //獲取流程實例ID String proInsId = task.getProcessInstanceId(); //獲取流程實例 ProcessInstance process = runtimeService.createProcessInstanceQuery().processInstanceId(proInsId).singleResult(); //獲取業務外鍵 String business_key = process.getBusinessKey(); String[] array = business_key.split(":"); String business_Id = array[1]; //業務處理 try { Class clazz=BusinessTaskUtil.class; Object obj = clazz.newInstance(); Method method = clazz.getMethod("actBusiness_"+task.getFormKey(),String.class,String.class,String.class); method.invoke(obj,userId,business_Id,result); }catch (Exception e){ e.printStackTrace(); logger.error("執行業務方法錯誤!"); } taskService.complete(taskId); } /** * 更改業務流程狀態#{ActivityDemoServiceImpl.updateBizStatus(execution,"tj")} * @param execution * @param status */ @Override public void updateBizStatus(DelegateExecution execution, String status) { String bizId = execution.getProcessBusinessKey(); //根據業務id自行處理業務表 System.out.println("業務表["+bizId+"]狀態更改爲功,狀態更改成:"+status); } //流程節點權限用戶列表${ActivityDemoServiceImpl.findUsers(execution,sign)} @Override public List<String> findUsersForSL(DelegateExecution execution){ return Arrays.asList("sly1","sly2"); } //流程節點權限用戶列表${ActivityDemoServiceImpl.findUsers(execution,sign)} @Override public List<String> findUsersForSP(DelegateExecution execution){ return Arrays.asList("spy1","uspy2"); } /** * * <p>描述: 生成流程圖 * 首先啓動流程,獲取processInstanceId,替換便可生成</p> * @author 範相如 * @date 2018年2月25日 * @param processInstanceId * @throws Exception */ @Override public void queryProImg(String processInstanceId) throws Exception { //獲取歷史流程實例 HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); //根據流程定義獲取輸入流 InputStream is = repositoryService.getProcessDiagram(processInstance.getProcessDefinitionId()); BufferedImage bi = ImageIO.read(is); File file = new File("demo2.png"); if(!file.exists()) file.createNewFile(); FileOutputStream fos = new FileOutputStream(file); ImageIO.write(bi, "png", fos); fos.close(); is.close(); System.out.println("圖片生成成功"); List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("userId").list(); for(Task t : tasks) { System.out.println(t.getName()); } } /** * 流程圖高亮顯示 * 首先啓動流程,獲取processInstanceId,替換便可生成 * @throws Exception */ @Override public String queryProHighLighted(String processInstanceId) throws Exception { //獲取歷史流程實例 HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); //獲取流程圖 BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator(); ProcessDefinitionEntity definitionEntity = (ProcessDefinitionEntity)repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId()); List<HistoricActivityInstance> highLightedActivitList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list(); //高亮環節id集合 List<String> highLightedActivitis = new ArrayList<String>(); //高亮線路id集合 List<String> highLightedFlows = getHighLightedFlows(definitionEntity,highLightedActivitList); for(HistoricActivityInstance tempActivity : highLightedActivitList){ String activityId = tempActivity.getActivityId(); highLightedActivitis.add(activityId); } //配置字體 InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivitis, highLightedFlows,"宋體","微軟雅黑","黑體",null,2.0); BufferedImage bi = ImageIO.read(imageStream); // File file = new File("demo2.png"); // if(!file.exists()) file.createNewFile(); // FileOutputStream fos = new FileOutputStream(file); ByteArrayOutputStream bos= new ByteArrayOutputStream(); ImageIO.write(bi, "png", bos); byte[] bytes = bos.toByteArray();//轉換成字節 BASE64Encoder encoder = new BASE64Encoder(); String png_base64 = encoder.encodeBuffer(bytes);//轉換成base64串 png_base64 = png_base64.replaceAll("\n", "").replaceAll("\r", "");//刪除 \r\n bos.close(); imageStream.close(); return png_base64; } /** * 獲取須要高亮的線 * @param processDefinitionEntity * @param historicActivityInstances * @return */ private List<String> getHighLightedFlows( ProcessDefinitionEntity processDefinitionEntity, List<HistoricActivityInstance> historicActivityInstances) { List<String> highFlows = new ArrayList<String>();// 用以保存高亮的線flowId for (int i = 0; i < historicActivityInstances.size() - 1; i++) {// 對歷史流程節點進行遍歷 ActivityImpl activityImpl = processDefinitionEntity .findActivity(historicActivityInstances.get(i) .getActivityId());// 獲得節點定義的詳細信息 List<ActivityImpl> sameStartTimeNodes = new ArrayList<ActivityImpl>();// 用以保存後需開始時間相同的節點 ActivityImpl sameActivityImpl1 = processDefinitionEntity .findActivity(historicActivityInstances.get(i + 1) .getActivityId()); // 將後面第一個節點放在時間相同節點的集合裏 sameStartTimeNodes.add(sameActivityImpl1); for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) { HistoricActivityInstance activityImpl1 = historicActivityInstances .get(j);// 後續第一個節點 HistoricActivityInstance activityImpl2 = historicActivityInstances .get(j + 1);// 後續第二個節點 if (activityImpl1.getStartTime().equals( activityImpl2.getStartTime())) { // 若是第一個節點和第二個節點開始時間相同保存 ActivityImpl sameActivityImpl2 = processDefinitionEntity .findActivity(activityImpl2.getActivityId()); sameStartTimeNodes.add(sameActivityImpl2); } else { // 有不相同跳出循環 break; } } List<PvmTransition> pvmTransitions = activityImpl .getOutgoingTransitions();// 取出節點的全部出去的線 for (PvmTransition pvmTransition : pvmTransitions) { // 對全部的線進行遍歷 ActivityImpl pvmActivityImpl = (ActivityImpl) pvmTransition .getDestination(); // 若是取出的線的目標節點存在時間相同的節點裏,保存該線的id,進行高亮顯示 if (sameStartTimeNodes.contains(pvmActivityImpl)) { highFlows.add(pvmTransition.getId()); } } } return highFlows; } }
controller實現,我寫了兩個controller,主要當時寫的時候沒有寫在一塊兒,大家能夠把這兩部分的內容合併在一塊兒
javapackage com.djkj.activiti.controller; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; 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.ProcessEngine; import org.activiti.engine.RepositoryService; import org.activiti.engine.repository.Deployment; import org.activiti.engine.repository.Model; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.List; @Controller @RequestMapping("/models") public class MyActivitiController { private static final Logger LOGGER = Logger.getLogger(MyActivitiController.class); @Autowired private RepositoryService repositoryService; @Autowired private ObjectMapper objectMapper; @RequestMapping("/modelList") public String modelList(org.springframework.ui.Model model,HttpServletRequest request){ LOGGER.info("-------------列表頁-------------"); List<Model> models = repositoryService.createModelQuery().orderByCreateTime().desc().list(); model.addAttribute("models",models); String info = request.getParameter("info"); if(StringUtils.isNotEmpty(info)){ model.addAttribute("info",info); } return "model/list"; } @RequestMapping("/create") public void newModel(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException { try { //初始化一個空模型 Model model = repositoryService.newModel(); //設置一些默認信息 String name = "new-process"; String description = ""; int revision = 1; String key = "process"; ObjectNode modelNode = objectMapper.createObjectNode(); modelNode.put(ModelDataJsonConstants.MODEL_NAME, name); modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description); modelNode.put(ModelDataJsonConstants.MODEL_REVISION, revision); model.setName(name); model.setKey(key); model.setMetaInfo(modelNode.toString()); repositoryService.saveModel(model); String id = model.getId(); //完善ModelEditorSource 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); repositoryService.addModelEditorSource(id, editorNode.toString().getBytes("utf-8")); response.sendRedirect(request.getContextPath() + "/static/modeler.html?modelId=" + id); }catch (IOException e){ e.printStackTrace(); LOGGER.info("模型建立失敗!"); } } @RequestMapping("/delete/{id}") public @ResponseBody String deleteModel(@PathVariable("id")String id){ repositoryService.deleteModel(id); return "刪除成功!"; } @RequestMapping("/deployment/{id}") public @ResponseBody String deploy(@PathVariable("id")String id) throws Exception { //獲取模型 Model modelData = repositoryService.getModel(id); byte[] bytes = repositoryService.getModelEditorSource(modelData.getId()); if (bytes == null) { return "模型數據爲空,請先設計流程併成功保存,再進行發佈。"; } JsonNode modelNode = new ObjectMapper().readTree(bytes); BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode); if(model.getProcesses().size()==0){ return "數據模型不符要求,請至少設計一條主線流程。"; } byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model); //發佈流程 String processName = modelData.getName() + ".bpmn20.xml"; Deployment deployment = repositoryService.createDeployment() .name(modelData.getName()) .addString(processName, new String(bpmnBytes, "UTF-8")) .deploy(); modelData.setDeploymentId(deployment.getId()); repositoryService.saveModel(modelData); return "流程發佈成功"; } @RequestMapping("/start/{id}") public @ResponseBody String startProcess(@PathVariable("id") String id){ try { }catch (Exception e){ e.printStackTrace(); return "流程啓動失敗!"; } return "流程啓動成功"; } }
顯示全部流程列表,以及流程的增刪改node
package com.djkj.activiti.controller; import com.djkj.activiti.bean.ActivitiVariable; import com.djkj.activiti.bean.FormData; import com.djkj.activiti.service.ActivitiService; import com.djkj.activiti.service.ActivitiVariableService; import org.activiti.engine.HistoryService; import org.activiti.engine.TaskService; import org.activiti.engine.task.Task; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; import javax.annotation.Resource; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @Controller @RequestMapping("/activiti") public class ActivitiProcessController { @Resource private ActivitiService activitiServiceImpl; @Autowired private TaskService taskService; @Autowired private HistoryService historyService; @Autowired private ActivitiVariableService activitiVariableService; // private static final String business = "testtask"; //啓動流程---215001 @RequestMapping("/start/{key}") public String startProcess(@PathVariable("key") String key, RedirectAttributesModelMap modelMap) { // Map<String,Object> map = new HashMap<String,Object>(); // map.put("userId",userIdSl); // map.put("businessKey",business_key); // String business_key = key + ":" + key; ActivitiVariable activitiVariable = new ActivitiVariable(); int resul=activitiVariableService.insert(activitiVariable); if(resul==1){ modelMap.addFlashAttribute("info","流程啓動成功!"); String business_key = key+":"+activitiVariable.getId(); activitiServiceImpl.startProcesses(key,business_key); }else{ modelMap.addFlashAttribute("info","流程啓動失敗!"); } return "redirect:/models/modelList"; } //獲取受理員任務列表 @RequestMapping("/queryTaskSl") public String findTasksForSL(ModelMap modelMap,FormData formData) { List<Task> lists = activitiServiceImpl.findTasksByUserId(formData.getUserId()); modelMap.addAttribute("tasks",lists); modelMap.addAttribute("userId",formData.getUserId()); return "model/taskList"; } @RequestMapping("/form") public String form(FormData formData,ModelMap modelMap){ Task task=activitiServiceImpl.findTaskById(formData.getId()); modelMap.addAttribute("data",formData); modelMap.addAttribute("task",task); if(StringUtils.isNotEmpty(task.getFormKey())){ return "activitiForm/"+task.getFormKey(); } return "model/form"; } //受理員受理數據 @RequestMapping("/completeTaskSl") public String completeTasksForSL(ModelMap modelMap,FormData formData) { activitiServiceImpl.completeTask(formData.getId(), formData.getUserId(), formData.getAttr1());//受理後,任務列表數據減小 return findTasksForSL(modelMap,formData); } // //獲取審批員任務列表 // @RequestMapping("/queryTaskSp") // public void findTasksForSP() { // List<Task> lists = activitiServiceImpl.findTasksByUserId(userIdSp); // System.out.println("任務列表:"+lists);//任務列表:[Task[id=220004, name=審批]] // } // // //審批員經過審覈 // @RequestMapping("/completeTaskSp/{id}") // public void completeTasksForSP(@PathVariable("id")String id) { // activitiServiceImpl.completeTask(id, userIdSp, "true");//審批後,任務列表數據減小 // } //設置流程變量 //設置流程變量【基本類型】 public void setTasksVar() { List<Task> lists = activitiServiceImpl.findTasksByUserId("sly1"); for(Task task : lists) {//不知爲什麼,變量保存成功,但數據表只有請假天數含有任務id,單獲取流程變量時,根據任務id都可獲取到(以下一測試) taskService.setVariable(task.getId(), "請假人", "sly1"); taskService.setVariableLocal(task.getId(), "請假天數",3); taskService.setVariable(task.getId(), "請假日期", new Date()); } } // //獲取流程變量 // @Test // public void getTasksVar() { // List<Task> lists = activitiServiceImpl.findTasksByUserId("sly1"); // for(Task task : lists) { // //獲取流程變量【基本類型】 // String person = (String) taskService.getVariable(task.getId(), "請假人"); // Integer day = (Integer) taskService.getVariableLocal(task.getId(), "請假天數"); // Date date = (Date) taskService.getVariable(task.getId(), "請假日期"); // // System.out.println("流程變量:"+person+"||"+day+"||"+date+"||"); // } // } // // //設置流程變量【實體】 // @Test // public void setTasksVarEntity() { // List<Task> lists = activitiServiceImpl.findTasksByUserId("sly1"); // for(Task task : lists) { // Person p = new Person(); // p.setName("翠花"); // p.setId(20); // p.setDate();; // p.setNote("回去探親,一塊兒吃個飯123"); // taskService.setVariable(task.getId(), "人員信息(添加固定版本)", p); // // System.out.println("設置流程變量成功!"); // } // } // // //獲取流程變量【實體】 實體必須序列化 // @Test // public void getTasksVarEntity() { // List<Task> lists = activitiServiceImpl.findTasksByUserId("sly1"); // for(Task task : lists) { // // 2.獲取流程變量,使用javaBean類型 // Person p = (Person)taskService.getVariable(task.getId(), "人員信息(添加固定版本)"); // System.out.println(" 請假人: "+p.getName()+" 請假天數: "+p.getId()+" 請假時間:"+ p.getDate()+ " 請假緣由: "+p.getNote()); // } // } // // // //生成流程圖---232501 // @RequestMapping("/queryProImg") // public void queryProImg() throws Exception { // activitiServiceImpl.queryProImg("232501"); // } //生成流程圖(高亮)---232501 @RequestMapping("/queryProHighLighted") public @ResponseBody String queryProHighLighted(String proInsId) throws Exception { String imageByteArray = activitiServiceImpl.queryProHighLighted(proInsId); return imageByteArray; } // // /** // * 查詢流程變量的歷史表,能夠根據變量名稱查詢該變量的全部歷史信息 // */ // @Test // public void findHistoryProcessVariables(){ // List<HistoricVariableInstance> list = historyService.createHistoricVariableInstanceQuery()//建立一個歷史的流程變量查詢對象 // .variableName("請假天數") // .list(); // if (list!=null &&list.size()>0) { // for (HistoricVariableInstance hvi : list) { // System.out.println(hvi.getId()+" "+hvi.getProcessInstanceId()+" "+hvi.getVariableName() // +" "+hvi.getVariableTypeName()+" "+hvi.getValue()); // System.out.println("########################################"); // } // } // // } // // // /** // * 歷史流程實例查詢 // * http://blog.csdn.net/luckyzhoustar/article/details/48652783 // */ // @Test // public void findHistoricProcessInstance() { // // 查詢已完成的流程 // List<HistoricProcessInstance> datas = historyService // .createHistoricProcessInstanceQuery().finished().list(); // System.out.println("使用finished方法:" + datas.size()); // // 根據流程定義ID查詢 // datas = historyService.createHistoricProcessInstanceQuery() // .processDefinitionId("processDefinitionId").list(); // System.out.println("使用processDefinitionId方法: " + datas.size()); // // 根據流程定義key(流程描述文件的process節點id屬性)查詢 // datas = historyService.createHistoricProcessInstanceQuery() // .processDefinitionKey("processDefinitionKey").list(); // System.out.println("使用processDefinitionKey方法: " + datas.size()); // // 根據業務主鍵查詢 // datas = historyService.createHistoricProcessInstanceQuery() // .processInstanceBusinessKey("processInstanceBusinessKey").list(); // System.out.println("使用processInstanceBusinessKey方法: " + datas.size()); // // 根據流程實例ID查詢 // datas = historyService.createHistoricProcessInstanceQuery() // .processInstanceId("processInstanceId").list(); // System.out.println("使用processInstanceId方法: " + datas.size()); // // 查詢沒有完成的流程實例 // historyService.createHistoricProcessInstanceQuery().unfinished().list(); // System.out.println("使用unfinished方法: " + datas.size()); // } // // /** // * 歷史任務查詢 // * @throws ParseException // */ // @Test // public void findHistoricTasks() throws ParseException { // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // // //歷史數據查詢 // List<HistoricTaskInstance> datas = historyService.createHistoricTaskInstanceQuery() // .finished().list(); // System.out.println("使用finished方法查詢:" + datas.size()); // datas = historyService.createHistoricTaskInstanceQuery() // .processDefinitionId("processDefinitionId").list(); // System.out.println("使用processDefinitionId方法查詢:" + datas.size()); // datas = historyService.createHistoricTaskInstanceQuery() // .processDefinitionKey("testProcess").list(); // System.out.println("使用processDefinitionKey方法查詢:" + datas.size()); // datas = historyService.createHistoricTaskInstanceQuery() // .processDefinitionName("testProcess2").list(); // System.out.println("使用processDefinitionName方法查詢:" + datas.size()); // datas = historyService.createHistoricTaskInstanceQuery() // .processFinished().list(); // System.out.println("使用processFinished方法查詢:" + datas.size()); // datas = historyService.createHistoricTaskInstanceQuery() // .processInstanceId("processInstanceId").list(); // System.out.println("使用processInstanceId方法查詢:" + datas.size()); // datas = historyService.createHistoricTaskInstanceQuery() // .processUnfinished().list(); // System.out.println("使用processUnfinished方法查詢:" + datas.size()); // datas = historyService.createHistoricTaskInstanceQuery() // .taskAssignee("crazyit").list(); // System.out.println("使用taskAssignee方法查詢:" + datas.size()); // datas = historyService.createHistoricTaskInstanceQuery() // .taskAssigneeLike("%zy%").list(); // System.out.println("使用taskAssigneeLike方法查詢:" + datas.size()); // datas = historyService.createHistoricTaskInstanceQuery() // .taskDefinitionKey("usertask1").list(); // System.out.println("使用taskDefinitionKey方法查詢:" + datas.size()); // datas = historyService.createHistoricTaskInstanceQuery() // .taskDueAfter(sdf.parse("2020-10-11 06:00:00")).list(); // System.out.println("使用taskDueAfter方法查詢:" + datas.size()); // datas = historyService.createHistoricTaskInstanceQuery() // .taskDueBefore(sdf.parse("2022-10-11 06:00:00")).list(); // System.out.println("使用taskDueBefore方法查詢:" + datas.size()); // datas = historyService.createHistoricTaskInstanceQuery() // .taskDueDate(sdf.parse("2020-10-11 06:00:00")).list(); // System.out.println("使用taskDueDate方法查詢:" + datas.size()); // datas = historyService.createHistoricTaskInstanceQuery() // .unfinished().list(); // System.out.println("使用unfinished方法查詢:" + datas.size()); // } // /** // * 歷史行爲查詢 // * 流程在進行過程當中,往往走一個節點,都會記錄流程節點的信息,包括節點的id,名稱、類型、時間等,保存到ACT_HI_ACTINST表中。 // * @throws ParseException // */ // @Test // public void findHistoricActivityInstance() { // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // // //查詢數據 // List<HistoricActivityInstance> datas = historyService.createHistoricActivityInstanceQuery() // .activityId("endevent1").list(); // System.out.println("使用activityId查詢:" + datas.size()); // datas = historyService.createHistoricActivityInstanceQuery() // .activityInstanceId(datas.get(0).getId()).list(); // System.out.println("使用activityInstanceId查詢:" + datas.size()); // datas = historyService.createHistoricActivityInstanceQuery() // .activityType("intermediateSignalCatch").list(); // System.out.println("使用activityType查詢:" + datas.size()); // datas = historyService.createHistoricActivityInstanceQuery() // .executionId("executionId").list(); // System.out.println("使用executionId查詢:" + datas.size()); // datas = historyService.createHistoricActivityInstanceQuery().finished().list(); // System.out.println("使用finished查詢:" + datas.size()); // datas = historyService.createHistoricActivityInstanceQuery() // .processInstanceId("processInstanceId").list(); // System.out.println("使用processInstanceId查詢:" + datas.size()); // datas = historyService.createHistoricActivityInstanceQuery() // .taskAssignee("crazyit").list(); // System.out.println("使用taskAssignee查詢:" + datas.size()); // datas = historyService.createHistoricActivityInstanceQuery().unfinished().list(); // System.out.println("使用unfinished查詢:" + datas.size()); // } // // /** // * 歷史流程明細查詢 // * 在流程進行的過程當中,會產生許多明細數據,只有將History設置爲最高級別的時候,纔會被記錄到ACT_HI_DETAIL表中。 // * @throws ParseException // */ // @Test // public void findHistoricDetail() { // // 查詢歷史行爲 // HistoricActivityInstance act = historyService.createHistoricActivityInstanceQuery() // .activityName("First Task").finished().singleResult(); // List<HistoricDetail> datas = historyService.createHistoricDetailQuery() // .activityInstanceId(act.getId()).list(); // System.out.println("使用activityInstanceId方法查詢:" + datas.size()); // datas = historyService.createHistoricDetailQuery().excludeTaskDetails().list(); // System.out.println("使用excludeTaskDetails方法查詢:" + datas.size()); // datas = historyService.createHistoricDetailQuery().formProperties().list(); // System.out.println("使用formProperties方法查詢:" + datas.size()); // datas = historyService.createHistoricDetailQuery().processInstanceId("processInstanceId").list(); // System.out.println("使用processInstanceId方法查詢:" + datas.size()); // datas = historyService.createHistoricDetailQuery().taskId("taskId").list(); // System.out.println("使用taskId方法查詢:" + datas.size()); // datas = historyService.createHistoricDetailQuery().variableUpdates().list(); // System.out.println("使用variableUpdates方法查詢:" + datas.size()); // } }
啓動流程、用戶任務task查詢、業務處理、流程進度查看等等,歷史流程查看功能沒有驗證過,這部分代碼是註釋掉的
5、頁面實現
在templates放置顯示頁面,可本身根據喜愛放置,我這裏主要是跟controller裏返回的路徑對應
model文件夾下放的是模型列表頁和用戶任務列表頁,form.html已經廢棄,改成上面的activitiForm裏面的form頁面,不一樣的頁面對應不一樣流程節點
list.html模型列表頁
jquery -
<!DOCTYPE html> <html lang="zh" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="/static/js/jquery.min.js"></script> <script th:inline="javascript"> function deployment(obj){ var id=obj.attributes['objectid'].nodeValue; $.ajax({ url:"/models/deployment/"+id, type:"GET", success:function(res){ alert(res); } }); } function deleteProcess(obj){ var id=obj.attributes['objectid'].nodeValue; $.ajax({ url:"/models/delete/"+id, type:"GET", success:function(res){ alert(res); } }); } function start(obj){ var id=obj.attributes['objectid'].nodeValue; $.ajax({ url:'/models/start/'+id, type:'GET', success:function(res){ alert(res); } }); } function taskList(){ $("#taskList").submit(); } </script> </head> <body> <div class="info"><span th:text="${info}"></span></div> <a href="/models/create">新建</a> <form id="taskList" action="/activiti/queryTaskSl"> <div> <div style="float:left;width:50%;"> <select id="userId" name="userId"> <option value="userzhou">userzhou</option> <option value="usertest">usertest</option> </select> </div> <div style="width:50%;"> <button onclick="taskList();">任務列表</button> </div> </div> </form> <table class="table"> <thead> <tr> <th>ID</th> <th>模型名稱</th> <th>key</th> <th>版本</th> <th>部署ID</th> <th>建立時間</th> <th>最後更新時間</th> <th>操做</th> </tr> </thead> <tbody> <tr th:each="data : ${models}"> <td th:text="${data.id}"></td> <td><a th:href="@{/static/modeler.html(modelId=${data.id})}" class="font-blue" th:text="${data.name}"></a> </td> <td th:text="${data.key}"></td> <td th:text="${data.version}"></td> <td th:text="${data.deploymentId}"></td> <td th:text="${data.createTime}"> 2018-02-25 17:28:35</td> <td th:text="${data.lastUpdateTime}"> 2018-02-25 17:28:35</td> <td><a href="javascript:void(0);" onclick="deployment(this);" th:attrappend="objectId=${data.id}" lass="font-blue deployBtn">發佈</a> <a th:href="${'/activiti/start/'+data.key}" th:attrappend="objectId=${data.id}" lass="font-blue deployBtn">發起流程</a> <a th:href="@{/crm/model/export/{modelId}(modelId=${data.id})}" class="font-blue">導出</a> <a href="javascript:void(0);" onclick="deleteProcess(this);" th:attrappend="objectId=${data.id}" class="font-blue delBtn">刪除</a></td> </tr> </tbody> </table> </body> </html>
taskList.html任務列表頁
git<!DOCTYPE html> <html lang="zh" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="/static/js/jquery.min.js"></script> </head> <body> <a href="/models/modelList"><button>返回模型列表頁</button></a> <div> <span th:text="'用戶:'+${userId}"></span> <table> <thead> <tr> <th>ID</th> <th>任務名稱</th> <th>受理人</th> <th>任務ID</th> <th>流程ID</th> <th>建立時間</th> <th>操做</th> </tr> </thead> <tbody> <tr th:each="data : ${tasks}"> <td th:text="${data.id}">LeaveBill:1:4</td> <td th:text="${data.name}"></td> <td th:text="${data.assignee}">1</td> <td th:text="${data.taskDefinitionKey}"></td> <td th:text="${data.processDefinitionId}"></td> <td th:text="${data.createTime}"> 2018-02-25 17:28:35</td> <td> <a th:href="${'/activiti/form?id='+data.id+'&userId='+data.assignee}" th:attrappend="objectId=${data.id}" lass="font-blue deployBtn">處理</a> </td> </tr> </tbody> </table> </div> </body> </html>
applyForm.html任務列表頁
github<!DOCTYPE html> <html lang="zh" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>申請頁面</title> <script src="/static/js/jquery.min.js"></script> <style> .title{ text-align: center; } </style> <script> function showProcessImg(processInstanceId) { $.ajax({ url:'/activiti/queryProHighLighted', type:'POST', data:{ proInsId:processInstanceId }, success:function(res){ $("#processImg").attr('src',"data:image/png;base64,"+res); } }) } function returnBack(userId){ var form = $("<form></form>"); form.attr("action","/activiti/queryTaskSl"); form.attr("method","post"); var input = $("<input type='text' name='userId'/>") input.attr("value",userId); form.append(input); form.appendTo("body"); form.css("display","none"); form.submit(); } </script> </head> <body> <div class="title">申請表單</div> <a th:onclick="'javascript:returnBack(\''+${data.userId}+'\');'"><button>返回用戶任務列表</button></a> <a href="javascript:void(0);" th:onclick="'javascript:showProcessImg(\''+${task.processInstanceId}+'\')'"><button>查看流程圖</button></a> <form action="/activiti/completeTaskSl"> <input type="hidden" id="id" name="id" th:value="${data.id}"/> <input type="hidden" id="userId" name="userId" th:value="${data.userId}"/> <input type="text" id="attr1" name="attr1"/> <button type="submit">提交</button> <button>取消</button> </form> <img src="" id="processImg"/> </body> </html>
approveForm.html任務列表頁web<!DOCTYPE html> <html lang="zh" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>審批頁面</title> <script src="/static/js/jquery.min.js"></script> <style> .title{ text-align: center; } </style> <script> function showProcessImg(processInstanceId) { $.ajax({ url:'/activiti/queryProHighLighted', type:'POST', data:{ proInsId:processInstanceId }, success:function(res){ $("#processImg").attr('src',"data:image/png;base64,"+res); } }) } function returnBack(userId){ var form = $("<form></form>"); form.attr("action","/activiti/queryTaskSl"); form.attr("method","post"); var input = $("<input type='text' name='userId'/>") input.attr("value",userId); form.append(input); form.appendTo("body"); form.css("display","none"); form.submit(); } </script> </head> <body> <div class="title">審批表單</div> <a th:onclick="'javascript:returnBack(\''+${data.userId}+'\');'"><button>返回任務列表</button></a> <a href="javascript:void(0);" th:onclick="'javascript:showProcessImg(\''+${task.processInstanceId}+'\')'"><button>查看流程圖</button></a> <form action="/activiti/completeTaskSl"> <input type="hidden" id="id" name="id" th:value="${data.id}"/> <input type="hidden" id="userId" name="userId" th:value="${data.userId}"/> <input type="text" id="attr1" name="attr1"/> <button type="submit">提交</button> <button>取消</button> </form> <img src="" id="processImg"/> </body> </html>
-
運行界面以下
list.html列表頁
新建頁面
這裏主要注意連個地方,assignments表示當前節點的處理人的id,我本身定義了兩個用戶userzhou和usertest,form key填寫處理當前節點所用的form表單,也就是以前的applyForm和approveForm -
須要注意的地方
一、去除activiti的security校驗,修改application啓動類
@SpringBootApplication(scanBasePackages = {"com.djkj","org.activiti"} , exclude = { org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class, org.activiti.spring.boot.SecurityAutoConfiguration.class,})
這是按照網上的寫的,但仍是存在校驗,最後沒辦法在act_id_user裏面加了一條數據,你們有解決的辦法能夠在下方給我評論,謝謝。springboot配置文件添加security.basic.enabled=false,未驗證
二、訪問靜態資源的時候,可能會被dispatcherservlet 給攔截掉,我是添加了個資源處理器,攔截static靜態資源
package com.djkj.activiti.common; import groovy.util.logging.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @Slf4j @EnableWebMvc public class StaticResourceConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); } }
三、關閉編輯器的時候能夠自定義返回的頁面
修改editor-app\configuration下的toolbar-default-actions.js,有倆個地方closeEditor方法和$scope.saveAndClose方法,一個是關閉一個是保存並關閉,能夠修改放回路徑,對應的是controller裏的路徑closeEditor: function(services) { window.location.href = "/models/modelList"; }, $scope.saveAndClose = function () { $scope.save(function() { window.location.href = "/models/modelList"; }); };
源碼已經發到碼雲,各位能夠去拉取
地址: https://gitee.com/zhouyun123/springBoot-activiti-modelerajax