-
摘要
本篇随笔主要记录springBoot2集成activiti流程引擎,并且嵌入activiti的在线设计器,可以通过浏览器直接编辑出我们需要的流程,不需要通过eclipse或者IDEA的actiBpm插件设计流程再直接部署到项目下,页面保存流程后可直接发布、发起流程。
-
所需软件版本
springBoot 2.0.1.RELEASE
activiti 5.22.0 官网下载地址
activiti-webapp-explorer2 我的github上的explorer项目 -
集成方法
activiti支持多种数据库,本身还有个h2的内存数据库,我这里用的是Mysql
1、pom.xml引入依赖,这里主要是activiti的依赖包,${activiti.version}为5.22.01<!--spring activiti start--> 2 <dependency> 3 <groupId>org.activiti</groupId> 4 <artifactId>activiti-spring-boot-starter-basic</artifactId> 5 <version>${activiti.version}</version> 6 <exclusions> 7 <exclusion> 8 <groupId>org.springframework.boot</groupId> 9 <artifactId>spring-boot-starter-actuator</artifactId> 10 </exclusion> 11 <exclusion> 12 <groupId>org.springframework.boot</groupId> 13 <artifactId>spring-boot-starter-web</artifactId> 14 </exclusion> 15 <exclusion> 16 <groupId>org.springframework.security</groupId> 17 <artifactId>spring-security-config</artifactId> 18 </exclusion> 19 </exclusions> 20 </dependency> 21 <dependency> 22 <groupId>org.activiti</groupId> 23 <artifactId>activiti-spring-boot-starter-actuator</artifactId> 24 <version>${activiti.version}</version> 25 <exclusions> 26 <exclusion> 27 <groupId>org.springframework.boot</groupId> 28 <artifactId>spring-boot-starter-web</artifactId> 29 </exclusion> 30 </exclusions> 31 </dependency> 32 <dependency> 33 <groupId>org.activiti</groupId> 34 <artifactId>activiti-rest</artifactId> 35 <version>${activiti.version}</version> 36 <exclusions> 37 <exclusion> 38 <groupId>org.springframework.security</groupId> 39 <artifactId>spring-security-config</artifactId> 40 </exclusion> 41 </exclusions> 42 </dependency> 43 <dependency> 44 <groupId>org.activiti</groupId> 45 <artifactId>activiti-explorer</artifactId> 46 <version>${activiti.version}</version> 47 <exclusions> 48 <exclusion> 49 <groupId>com.vaadin</groupId> 50 <artifactId>vaadin</artifactId> 51 </exclusion> 52 <exclusion> 53 <groupId>org.vaadin.addons</groupId> 54 <artifactId>dcharts-widget</artifactId> 55 </exclusion> 56 <exclusion> 57 <groupId>activiti-simple-workflow</groupId> 58 <artifactId>org.activiti</artifactId> 59 </exclusion> 60 </exclusions> 61 </dependency> 62 <dependency> 63 <groupId>org.activiti</groupId> 64 <artifactId>activiti-diagram-rest</artifactId> 65 <version>${activiti.version}</version> 66 </dependency> 67 <dependency> 68 <groupId>org.activiti</groupId> 69 <artifactId>activiti-simple-workflow</artifactId> 70 <version>${activiti.version}</version> 71 </dependency> 72 <dependency> 73 <groupId>org.activiti</groupId> 74 <artifactId>activiti-spring</artifactId> 75 <version>${activiti.version}</version> 76 </dependency> 77 <dependency> 78 <groupId>org.apache.xmlgraphics</groupId> 79 <artifactId>batik-codec</artifactId> 80 <version>1.7</version> 81 </dependency> 82 <dependency> 83 <groupId>org.apache.xmlgraphics</groupId> 84 <artifactId>batik-css</artifactId> 85 <version>1.7</version> 86 </dependency> 87 <dependency> 88 <groupId>org.apache.xmlgraphics</groupId> 89 <artifactId>batik-svg-dom</artifactId> 90 <version>1.7</version> 91 </dependency> 92 <dependency> 93 <groupId>org.apache.xmlgraphics</groupId> 94 <artifactId>batik-svggen</artifactId> 95 <version>1.7</version> 96 </dependency> 97 <dependency> 98 <groupId>org.springframework.security</groupId> 99 <artifactId>spring-security-config</artifactId> 100 <version>4.1.3.RELEASE</version> 101 </dependency> 102 103 <!--spring activiti end-->**2、拷贝项目资源
解压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。

**
**3、修改拷贝过来的资源文件
修改StencilsetRestResource.java、ModelEditorJsonRestResource.java、ModelSaveRestResource.java3个controller,加上@RequestMapping("/service")
修改editor-app下的app-cfg.js,把contextRoot后面改成 /service,和controller里面加的requestMapping要一致
** -
1 var ACTIVITI = ACTIVITI || {}; 2 3 ACTIVITI.CONFIG = { 4 'contextRoot' : '/service', 5 };**4、新增service、controller
service实现,这里只放实现类,接口就不放了,自己可以根据实现类自己添加接口方法,这个service实现了把流程和自定义表单结合的功能,completeTask方法通过java反射执行不同任务节点执行不同form
**

1 package com.djkj.activiti.service.Impl; 2 3 import com.djkj.activiti.bean.ActivitiVariable; 4 import com.djkj.activiti.common.BusinessTaskUtil; 5 import com.djkj.activiti.service.ActivitiService; 6 import com.djkj.activiti.service.ActivitiVariableService; 7 import org.activiti.bpmn.model.BpmnModel; 8 import org.activiti.engine.HistoryService; 9 import org.activiti.engine.RepositoryService; 10 import org.activiti.engine.RuntimeService; 11 import org.activiti.engine.TaskService; 12 import org.activiti.engine.delegate.DelegateExecution; 13 import org.activiti.engine.history.HistoricActivityInstance; 14 import org.activiti.engine.history.HistoricProcessInstance; 15 import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl; 16 import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; 17 import org.activiti.engine.impl.pvm.PvmTransition; 18 import org.activiti.engine.impl.pvm.process.ActivityImpl; 19 import org.activiti.engine.runtime.ProcessInstance; 20 import org.activiti.engine.task.Task; 21 import org.activiti.image.ProcessDiagramGenerator; 22 import org.apache.log4j.Logger; 23 import org.springframework.beans.factory.annotation.Autowired; 24 import org.springframework.stereotype.Service; 25 import sun.misc.BASE64Encoder; 26 27 import javax.imageio.ImageIO; 28 import java.awt.image.BufferedImage; 29 import java.io.ByteArrayOutputStream; 30 import java.io.File; 31 import java.io.FileOutputStream; 32 import java.io.InputStream; 33 import java.lang.reflect.Method; 34 import java.util.*; 35 36 @Service 37 public class ActivitiServiceImpl implements ActivitiService { 38 39 @Autowired 40 private RuntimeService runtimeService; 41 @Autowired 42 private TaskService taskService; 43 @Autowired 44 private HistoryService historyService; 45 @Autowired 46 private RepositoryService repositoryService; 47 @Autowired 48 private ProcessEngineConfigurationImpl processEngineConfiguration; 49 @Autowired 50 private ActivitiVariableService activitiVariableService; 51 52 private static final Logger logger=Logger.getLogger(ActivitiServiceImpl.class); 53 54 /** 55 * 启动流程 56 */ 57 @Override 58 public void startProcesses(String id,String business_key) { 59 ProcessInstance pi = runtimeService.startProcessInstanceByKey(id, business_key);//流程图id,业务表id 60 System.out.println("流程启动成功,流程id:"+pi.getId()); 61 } 62 63 /** 64 * 65 * <p>描述: 根据用户id查询待办任务列表</p> 66 * @author 范相如 67 * @date 2018年2月25日 68 */ 69 @Override 70 public List<Task> findTasksByUserId(String userId) { 71 List<Task> resultTask = taskService.createTaskQuery().processDefinitionKey("process").taskCandidateOrAssigned(userId).list(); 72 return resultTask; 73 } 74 75 @Override 76 public Task findTaskById(String taskId){ 77 List<Task> resultTask = taskService.createTaskQuery().taskId(taskId).list(); 78 if(resultTask!=null){ 79 return resultTask.get(0); 80 } 81 return null; 82 } 83 84 /** 85 * 86 * <p>描述:任务审批 (通过/拒接) </p> 87 * @author 范相如 88 * @date 2018年2月25日 89 * @param taskId 任务id 90 * @param userId 用户id 91 * @param result false OR true 92 */ 93 @Override 94 public void completeTask(String taskId,String userId,String result) { 95 //获取流程实例 96 taskService.claim(taskId, userId); 97 //获取任务 98 Task task=taskService.createTaskQuery().taskId(taskId).singleResult(); 99 //获取流程实例ID 100 String proInsId = task.getProcessInstanceId(); 101 //获取流程实例 102 ProcessInstance process = runtimeService.createProcessInstanceQuery().processInstanceId(proInsId).singleResult(); 103 //获取业务外键 104 String business_key = process.getBusinessKey(); 105 String[] array = business_key.split(":"); 106 String business_Id = array[1]; 107 //业务处理 108 try { 109 Class clazz=BusinessTaskUtil.class; 110 Object obj = clazz.newInstance(); 111 Method method = clazz.getMethod("actBusiness_"+task.getFormKey(),String.class,String.class,String.class); 112 method.invoke(obj,userId,business_Id,result); 113 }catch (Exception e){ 114 e.printStackTrace(); 115 logger.error("执行业务方法错误!"); 116 117 } 118 taskService.complete(taskId); 119 } 120 121 /** 122 * 更改业务流程状态#{ActivityDemoServiceImpl.updateBizStatus(execution,"tj")} 123 * @param execution 124 * @param status 125 */ 126 @Override 127 public void updateBizStatus(DelegateExecution execution, String status) { 128 String bizId = execution.getProcessBusinessKey(); 129 //根据业务id自行处理业务表 130 System.out.println("业务表["+bizId+"]状态更改成功,状态更改为:"+status); 131 } 132 133 134 //流程节点权限用户列表${ActivityDemoServiceImpl.findUsers(execution,sign)} 135 @Override 136 public List<String> findUsersForSL(DelegateExecution execution){ 137 return Arrays.asList("sly1","sly2"); 138 } 139 140 //流程节点权限用户列表${ActivityDemoServiceImpl.findUsers(execution,sign)} 141 @Override 142 public List<String> findUsersForSP(DelegateExecution execution){ 143 return Arrays.asList("spy1","uspy2"); 144 } 145 146 /** 147 * 148 * <p>描述: 生成流程图 149 * 首先启动流程,获取processInstanceId,替换即可生成</p> 150 * @author 范相如 151 * @date 2018年2月25日 152 * @param processInstanceId 153 * @throws Exception 154 */ 155 @Override 156 public void queryProImg(String processInstanceId) throws Exception { 157 //获取历史流程实例 158 HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); 159 160 //根据流程定义获取输入流 161 InputStream is = repositoryService.getProcessDiagram(processInstance.getProcessDefinitionId()); 162 BufferedImage bi = ImageIO.read(is); 163 File file = new File("demo2.png"); 164 if(!file.exists()) file.createNewFile(); 165 FileOutputStream fos = new FileOutputStream(file); 166 ImageIO.write(bi, "png", fos); 167 fos.close(); 168 is.close(); 169 System.out.println("图片生成成功"); 170 171 List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("userId").list(); 172 for(Task t : tasks) { 173 System.out.println(t.getName()); 174 } 175 } 176 177 178 /** 179 * 流程图高亮显示 180 * 首先启动流程,获取processInstanceId,替换即可生成 181 * @throws Exception 182 */ 183 @Override 184 public String queryProHighLighted(String processInstanceId) throws Exception { 185 //获取历史流程实例 186 HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); 187 //获取流程图 188 BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); 189 190 ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator(); 191 ProcessDefinitionEntity definitionEntity = (ProcessDefinitionEntity)repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId()); 192 193 List<HistoricActivityInstance> highLightedActivitList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list(); 194 //高亮环节id集合 195 List<String> highLightedActivitis = new ArrayList<String>(); 196 197 //高亮线路id集合 198 List<String> highLightedFlows = getHighLightedFlows(definitionEntity,highLightedActivitList); 199 200 for(HistoricActivityInstance tempActivity : highLightedActivitList){ 201 String activityId = tempActivity.getActivityId(); 202 highLightedActivitis.add(activityId); 203 } 204 //配置字体 205 InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivitis, highLightedFlows,"宋体","微软雅黑","黑体",null,2.0); 206 BufferedImage bi = ImageIO.read(imageStream); 207 // File file = new File("demo2.png"); 208 // if(!file.exists()) file.createNewFile(); 209 // FileOutputStream fos = new FileOutputStream(file); 210 ByteArrayOutputStream bos= new ByteArrayOutputStream(); 211 ImageIO.write(bi, "png", bos); 212 byte[] bytes = bos.toByteArray();//转换成字节 213 BASE64Encoder encoder = new BASE64Encoder(); 214 String png_base64 = encoder.encodeBuffer(bytes);//转换成base64串 215 png_base64 = png_base64.replaceAll("\n", "").replaceAll("\r", "");//删除 \r\n 216 bos.close(); 217 imageStream.close(); 218 return png_base64; 219 } 220 /** 221 * 获取需要高亮的线 222 * @param processDefinitionEntity 223 * @param historicActivityInstances 224 * @return 225 */ 226 private List<String> getHighLightedFlows( 227 ProcessDefinitionEntity processDefinitionEntity, 228 List<HistoricActivityInstance> historicActivityInstances) { 229 230 List<String> highFlows = new ArrayList<String>();// 用以保存高亮的线flowId 231 for (int i = 0; i < historicActivityInstances.size() - 1; i++) {// 对历史流程节点进行遍历 232 ActivityImpl activityImpl = processDefinitionEntity 233 .findActivity(historicActivityInstances.get(i) 234 .getActivityId());// 得到节点定义的详细信息 235 List<ActivityImpl> sameStartTimeNodes = new ArrayList<ActivityImpl>();// 用以保存后需开始时间相同的节点 236 ActivityImpl sameActivityImpl1 = processDefinitionEntity 237 .findActivity(historicActivityInstances.get(i + 1) 238 .getActivityId()); 239 // 将后面第一个节点放在时间相同节点的集合里 240 sameStartTimeNodes.add(sameActivityImpl1); 241 for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) { 242 HistoricActivityInstance activityImpl1 = historicActivityInstances 243 .get(j);// 后续第一个节点 244 HistoricActivityInstance activityImpl2 = historicActivityInstances 245 .get(j + 1);// 后续第二个节点 246 if (activityImpl1.getStartTime().equals( 247 activityImpl2.getStartTime())) { 248 // 如果第一个节点和第二个节点开始时间相同保存 249 ActivityImpl sameActivityImpl2 = processDefinitionEntity 250 .findActivity(activityImpl2.getActivityId()); 251 sameStartTimeNodes.add(sameActivityImpl2); 252 } else { 253 // 有不相同跳出循环 254 break; 255 } 256 } 257 List<PvmTransition> pvmTransitions = activityImpl 258 .getOutgoingTransitions();// 取出节点的所有出去的线 259 for (PvmTransition pvmTransition : pvmTransitions) { 260 // 对所有的线进行遍历 261 ActivityImpl pvmActivityImpl = (ActivityImpl) pvmTransition 262 .getDestination(); 263 // 如果取出的线的目标节点存在时间相同的节点里,保存该线的id,进行高亮显示 264 if (sameStartTimeNodes.contains(pvmActivityImpl)) { 265 highFlows.add(pvmTransition.getId()); 266 } 267 } 268 } 269 return highFlows; 270 } 271 }View Code
controller实现,我写了两个controller,主要当时写的时候没有写在一起,你们可以把这两部分的内容合并在一起

1 package com.djkj.activiti.controller; 2 3 import com.fasterxml.jackson.databind.JsonNode; 4 import com.fasterxml.jackson.databind.ObjectMapper; 5 import com.fasterxml.jackson.databind.node.ObjectNode; 6 import org.activiti.bpmn.converter.BpmnXMLConverter; 7 import org.activiti.bpmn.model.BpmnModel; 8 import org.activiti.editor.constants.ModelDataJsonConstants; 9 import org.activiti.editor.language.json.converter.BpmnJsonConverter; 10 import org.activiti.engine.ProcessEngine; 11 import org.activiti.engine.RepositoryService; 12 import org.activiti.engine.repository.Deployment; 13 import org.activiti.engine.repository.Model; 14 import org.apache.commons.lang3.StringUtils; 15 import org.apache.log4j.Logger; 16 import org.springframework.beans.factory.annotation.Autowired; 17 import org.springframework.stereotype.Controller; 18 import org.springframework.ui.ModelMap; 19 import org.springframework.web.bind.annotation.PathVariable; 20 import org.springframework.web.bind.annotation.RequestMapping; 21 import org.springframework.web.bind.annotation.ResponseBody; 22 import org.springframework.web.bind.annotation.RestController; 23 24 import javax.servlet.http.HttpServletRequest; 25 import javax.servlet.http.HttpServletResponse; 26 import java.io.IOException; 27 import java.io.UnsupportedEncodingException; 28 import java.util.List; 29 30 @Controller 31 @RequestMapping("/models") 32 public class MyActivitiController { 33 34 private static final Logger LOGGER = Logger.getLogger(MyActivitiController.class); 35 36 @Autowired 37 private RepositoryService repositoryService; 38 @Autowired 39 private ObjectMapper objectMapper; 40 41 @RequestMapping("/modelList") 42 public String modelList(org.springframework.ui.Model model,HttpServletRequest request){ 43 LOGGER.info("-------------列表页-------------"); 44 List<Model> models = repositoryService.createModelQuery().orderByCreateTime().desc().list(); 45 model.addAttribute("models",models); 46 String info = request.getParameter("info"); 47 if(StringUtils.isNotEmpty(info)){ 48 model.addAttribute("info",info); 49 } 50 return "model/list"; 51 } 52 53 @RequestMapping("/create") 54 public void newModel(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException { 55 try { 56 //初始化一个空模型 57 Model model = repositoryService.newModel(); 58 59 //设置一些默认信息 60 String name = "new-process"; 61 String description = ""; 62 int revision = 1; 63 String key = "process"; 64 65 ObjectNode modelNode = objectMapper.createObjectNode(); 66 modelNode.put(ModelDataJsonConstants.MODEL_NAME, name); 67 modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description); 68 modelNode.put(ModelDataJsonConstants.MODEL_REVISION, revision); 69 70 model.setName(name); 71 model.setKey(key); 72 model.setMetaInfo(modelNode.toString()); 73 74 repositoryService.saveModel(model); 75 String id = model.getId(); 76 77 //完善ModelEditorSource 78 ObjectNode editorNode = objectMapper.createObjectNode(); 79 editorNode.put("id", "canvas"); 80 editorNode.put("resourceId", "canvas"); 81 ObjectNode stencilSetNode = objectMapper.createObjectNode(); 82 stencilSetNode.put("namespace", 83 "http://b3mn.org/stencilset/bpmn2.0#"); 84 editorNode.put("stencilset", stencilSetNode); 85 repositoryService.addModelEditorSource(id, editorNode.toString().getBytes("utf-8")); 86 87 response.sendRedirect(request.getContextPath() + "/static/modeler.html?modelId=" + id); 88 }catch (IOException e){ 89 e.printStackTrace(); 90 LOGGER.info("模型创建失败!"); 91 } 92 93 } 94 95 @RequestMapping("/delete/{id}") 96 public @ResponseBody String deleteModel(@PathVariable("id")String id){ 97 repositoryService.deleteModel(id); 98 return "删除成功!"; 99 } 100 101 102 @RequestMapping("/deployment/{id}") 103 public @ResponseBody String deploy(@PathVariable("id")String id) throws Exception { 104 105 //获取模型 106 Model modelData = repositoryService.getModel(id); 107 byte[] bytes = repositoryService.getModelEditorSource(modelData.getId()); 108 109 if (bytes == null) { 110 return "模型数据为空,请先设计流程并成功保存,再进行发布。"; 111 } 112 113 JsonNode modelNode = new ObjectMapper().readTree(bytes); 114 115 BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode); 116 if(model.getProcesses().size()==0){ 117 return "数据模型不符要求,请至少设计一条主线流程。"; 118 } 119 byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model); 120 121 //发布流程 122 String processName = modelData.getName() + ".bpmn20.xml"; 123 Deployment deployment = repositoryService.createDeployment() 124 .name(modelData.getName()) 125 .addString(processName, new String(bpmnBytes, "UTF-8")) 126 .deploy(); 127 modelData.setDeploymentId(deployment.getId()); 128 repositoryService.saveModel(modelData); 129 return "流程发布成功"; 130 } 131 132 @RequestMapping("/start/{id}") 133 public @ResponseBody String startProcess(@PathVariable("id") String id){ 134 try { 135 136 137 }catch (Exception e){ 138 e.printStackTrace(); 139 return "流程启动失败!"; 140 } 141 return "流程启动成功"; 142 } 143 144 145 }View Code
显示所有流程列表,以及流程的增删改

1 package com.djkj.activiti.controller; 2 3 4 import com.djkj.activiti.bean.ActivitiVariable; 5 import com.djkj.activiti.bean.FormData; 6 import com.djkj.activiti.service.ActivitiService; 7 import com.djkj.activiti.service.ActivitiVariableService; 8 import org.activiti.engine.HistoryService; 9 import org.activiti.engine.TaskService; 10 import org.activiti.engine.task.Task; 11 import org.apache.commons.lang3.StringUtils; 12 import org.springframework.beans.factory.annotation.Autowired; 13 import org.springframework.stereotype.Controller; 14 import org.springframework.ui.ModelMap; 15 import org.springframework.web.bind.annotation.PathVariable; 16 import org.springframework.web.bind.annotation.RequestMapping; 17 import org.springframework.web.bind.annotation.ResponseBody; 18 import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; 19 20 import javax.annotation.Resource; 21 import java.util.Date; 22 import java.util.HashMap; 23 import java.util.List; 24 import java.util.Map; 25 26 @Controller 27 @RequestMapping("/activiti") 28 public class ActivitiProcessController { 29 @Resource 30 private ActivitiService activitiServiceImpl; 31 @Autowired 32 private TaskService taskService; 33 @Autowired 34 private HistoryService historyService; 35 @Autowired 36 private ActivitiVariableService activitiVariableService; 37 38 // private static final String business = "testtask"; 39 40 //启动流程---215001 41 @RequestMapping("/start/{key}") 42 public String startProcess(@PathVariable("key") String key, RedirectAttributesModelMap modelMap) { 43 // Map<String,Object> map = new HashMap<String,Object>(); 44 // map.put("userId",userIdSl); 45 // map.put("businessKey",business_key); 46 // String business_key = key + ":" + key; 47 ActivitiVariable activitiVariable = new ActivitiVariable(); 48 int resul=activitiVariableService.insert(activitiVariable); 49 if(resul==1){ 50 modelMap.addFlashAttribute("info","流程启动成功!"); 51 String business_key = key+":"+activitiVariable.getId(); 52 activitiServiceImpl.startProcesses(key,business_key); 53 }else{ 54 modelMap.addFlashAttribute("info","流程启动失败!"); 55 } 56 return "redirect:/models/modelList"; 57 } 58 59 //获取受理员任务列表 60 @RequestMapping("/queryTaskSl") 61 public String findTasksForSL(ModelMap modelMap,FormData formData) { 62 List<Task> lists = activitiServiceImpl.findTasksByUserId(formData.getUserId()); 63 modelMap.addAttribute("tasks",lists); 64 modelMap.addAttribute("userId",formData.getUserId()); 65 return "model/taskList"; 66 } 67 68 69 @RequestMapping("/form") 70 public String form(FormData formData,ModelMap modelMap){ 71 Task task=activitiServiceImpl.findTaskById(formData.getId()); 72 modelMap.addAttribute("data",formData); 73 modelMap.addAttribute("task",task); 74 if(StringUtils.isNotEmpty(task.getFormKey())){ 75 return "activitiForm/"+task.getFormKey(); 76 } 77 return "model/form"; 78 } 79 80 81 //受理员受理数据 82 @RequestMapping("/completeTaskSl") 83 public String completeTasksForSL(ModelMap modelMap,FormData formData) { 84 activitiServiceImpl.completeTask(formData.getId(), formData.getUserId(), formData.getAttr1());//受理后,任务列表数据减少 85 return findTasksForSL(modelMap,formData); 86 } 87 88 // //获取审批员任务列表 89 // @RequestMapping("/queryTaskSp") 90 // public void findTasksForSP() { 91 // List<Task> lists = activitiServiceImpl.findTasksByUserId(userIdSp); 92 // System.out.println("任务列表:"+lists);//任务列表:[Task[id=220004, name=审批]] 93 // } 94 // 95 // //审批员通过审核 96 // @RequestMapping("/completeTaskSp/{id}") 97 // public void completeTasksForSP(@PathVariable("id")String id) { 98 // activitiServiceImpl.completeTask(id, userIdSp, "true");//审批后,任务列表数据减少 99 // } 100 101 102 //设置流程变量 103 //设置流程变量【基本类型】 104 public void setTasksVar() { 105 List<Task> lists = activitiServiceImpl.findTasksByUserId("sly1"); 106 for(Task task : lists) {//不知为何,变量保存成功,但数据表只有请假天数含有任务id,单获取流程变量时,根据任务id均可获取到(如下一测试) 107 taskService.setVariable(task.getId(), "请假人", "sly1"); 108 taskService.setVariableLocal(task.getId(), "请假天数",3); 109 taskService.setVariable(task.getId(), "请假日期", new Date()); 110 } 111 } 112 113 // //获取流程变量 114 // @Test 115 // public void getTasksVar() { 116 // List<Task> lists = activitiServiceImpl.findTasksByUserId("sly1"); 117 // for(Task task : lists) { 118 // //获取流程变量【基本类型】 119 // String person = (String) taskService.getVariable(task.getId(), "请假人"); 120 // Integer day = (Integer) taskService.getVariableLocal(task.getId(), "请假天数"); 121 // Date date = (Date) taskService.getVariable(task.getId(), "请假日期"); 122 // 123 // System.out.println("流程变量:"+person+"||"+day+"||"+date+"||"); 124 // } 125 // } 126 // 127 // //设置流程变量【实体】 128 // @Test 129 // public void setTasksVarEntity() { 130 // List<Task> lists = activitiServiceImpl.findTasksByUserId("sly1"); 131 // for(Task task : lists) { 132 // Person p = new Person(); 133 // p.setName("翠花"); 134 // p.setId(20); 135 // p.setDate();; 136 // p.setNote("回去探亲,一起吃个饭123"); 137 // taskService.setVariable(task.getId(), "人员信息(添加固定版本)", p); 138 // 139 // System.out.println("设置流程变量成功!"); 140 // } 141 // } 142 // 143 // //获取流程变量【实体】 实体必须序列化 144 // @Test 145 // public void getTasksVarEntity() { 146 // List<Task> lists = activitiServiceImpl.findTasksByUserId("sly1"); 147 // for(Task task : lists) { 148 // // 2.获取流程变量,使用javaBean类型 149 // Person p = (Person)taskService.getVariable(task.getId(), "人员信息(添加固定版本)"); 150 // System.out.println(" 请假人: "+p.getName()+" 请假天数: "+p.getId()+" 请假时间:"+ p.getDate()+ " 请假原因: "+p.getNote()); 151 // } 152 // } 153 // 154 // 155 // //生成流程图---232501 156 // @RequestMapping("/queryProImg") 157 // public void queryProImg() throws Exception { 158 // activitiServiceImpl.queryProImg("232501"); 159 // } 160 161 //生成流程图(高亮)---232501 162 @RequestMapping("/queryProHighLighted") 163 public @ResponseBody String queryProHighLighted(String proInsId) throws Exception { 164 String imageByteArray = activitiServiceImpl.queryProHighLighted(proInsId); 165 return imageByteArray; 166 } 167 // 168 // /** 169 // * 查询流程变量的历史表,可以根据变量名称查询该变量的所有历史信息 170 // */ 171 // @Test 172 // public void findHistoryProcessVariables(){ 173 // List<HistoricVariableInstance> list = historyService.createHistoricVariableInstanceQuery()//创建一个历史的流程变量查询对象 174 // .variableName("请假天数") 175 // .list(); 176 // if (list!=null &&list.size()>0) { 177 // for (HistoricVariableInstance hvi : list) { 178 // System.out.println(hvi.getId()+" "+hvi.getProcessInstanceId()+" "+hvi.getVariableName() 179 // +" "+hvi.getVariableTypeName()+" "+hvi.getValue()); 180 // System.out.println("########################################"); 181 // } 182 // } 183 // 184 // } 185 // 186 // 187 // /** 188 // * 历史流程实例查询 189 // * http://blog.csdn.net/luckyzhoustar/article/details/48652783 190 // */ 191 // @Test 192 // public void findHistoricProcessInstance() { 193 // // 查询已完成的流程 194 // List<HistoricProcessInstance> datas = historyService 195 // .createHistoricProcessInstanceQuery().finished().list(); 196 // System.out.println("使用finished方法:" + datas.size()); 197 // // 根据流程定义ID查询 198 // datas = historyService.createHistoricProcessInstanceQuery() 199 // .processDefinitionId("processDefinitionId").list(); 200 // System.out.println("使用processDefinitionId方法: " + datas.size()); 201 // // 根据流程定义key(流程描述文件的process节点id属性)查询 202 // datas = historyService.createHistoricProcessInstanceQuery() 203 // .processDefinitionKey("processDefinitionKey").list(); 204 // System.out.println("使用processDefinitionKey方法: " + datas.size()); 205 // // 根据业务主键查询 206 // datas = historyService.createHistoricProcessInstanceQuery() 207 // .processInstanceBusinessKey("processInstanceBusinessKey").list(); 208 // System.out.println("使用processInstanceBusinessKey方法: " + datas.size()); 209 // // 根据流程实例ID查询 210 // datas = historyService.createHistoricProcessInstanceQuery() 211 // .processInstanceId("processInstanceId").list(); 212 // System.out.println("使用processInstanceId方法: " + datas.size()); 213 // // 查询没有完成的流程实例 214 // historyService.createHistoricProcessInstanceQuery().unfinished().list(); 215 // System.out.println("使用unfinished方法: " + datas.size()); 216 // } 217 // 218 // /** 219 // * 历史任务查询 220 // * @throws ParseException 221 // */ 222 // @Test 223 // public void findHistoricTasks() throws ParseException { 224 // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 225 // 226 // //历史数据查询 227 // List<HistoricTaskInstance> datas = historyService.createHistoricTaskInstanceQuery() 228 // .finished().list(); 229 // System.out.println("使用finished方法查询:" + datas.size()); 230 // datas = historyService.createHistoricTaskInstanceQuery() 231 // .processDefinitionId("processDefinitionId").list(); 232 // System.out.println("使用processDefinitionId方法查询:" + datas.size()); 233 // datas = historyService.createHistoricTaskInstanceQuery() 234 // .processDefinitionKey("testProcess").list(); 235 // System.out.println("使用processDefinitionKey方法查询:" + datas.size()); 236 // datas = historyService.createHistoricTaskInstanceQuery() 237 // .processDefinitionName("testProcess2").list(); 238 // System.out.println("使用processDefinitionName方法查询:" + datas.size()); 239 // datas = historyService.createHistoricTaskInstanceQuery() 240 // .processFinished().list(); 241 // System.out.println("使用processFinished方法查询:" + datas.size()); 242 // datas = historyService.createHistoricTaskInstanceQuery() 243 // .processInstanceId("processInstanceId").list(); 244 // System.out.println("使用processInstanceId方法查询:" + datas.size()); 245 // datas = historyService.createHistoricTaskInstanceQuery() 246 // .processUnfinished().list(); 247 // System.out.println("使用processUnfinished方法查询:" + datas.size()); 248 // datas = historyService.createHistoricTaskInstanceQuery() 249 // .taskAssignee("crazyit").list(); 250 // System.out.println("使用taskAssignee方法查询:" + datas.size()); 251 // datas = historyService.createHistoricTaskInstanceQuery() 252 // .taskAssigneeLike("%zy%").list(); 253 // System.out.println("使用taskAssigneeLike方法查询:" + datas.size()); 254 // datas = historyService.createHistoricTaskInstanceQuery() 255 // .taskDefinitionKey("usertask1").list(); 256 // System.out.println("使用taskDefinitionKey方法查询:" + datas.size()); 257 // datas = historyService.createHistoricTaskInstanceQuery() 258 // .taskDueAfter(sdf.parse("2020-10-11 06:00:00")).list(); 259 // System.out.println("使用taskDueAfter方法查询:" + datas.size()); 260 // datas = historyService.createHistoricTaskInstanceQuery() 261 // .taskDueBefore(sdf.parse("2022-10-11 06:00:00")).list(); 262 // System.out.println("使用taskDueBefore方法查询:" + datas.size()); 263 // datas = historyService.createHistoricTaskInstanceQuery() 264 // .taskDueDate(sdf.parse("2020-10-11 06:00:00")).list(); 265 // System.out.println("使用taskDueDate方法查询:" + datas.size()); 266 // datas = historyService.createHistoricTaskInstanceQuery() 267 // .unfinished().list(); 268 // System.out.println("使用unfinished方法查询:" + datas.size()); 269 // } 270 // /** 271 // * 历史行为查询 272 // * 流程在进行过程中,每每走一个节点,都会记录流程节点的信息,包括节点的id,名称、类型、时间等,保存到ACT_HI_ACTINST表中。 273 // * @throws ParseException 274 // */ 275 // @Test 276 // public void findHistoricActivityInstance() { 277 // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 278 // 279 // //查询数据 280 // List<HistoricActivityInstance> datas = historyService.createHistoricActivityInstanceQuery() 281 // .activityId("endevent1").list(); 282 // System.out.println("使用activityId查询:" + datas.size()); 283 // datas = historyService.createHistoricActivityInstanceQuery() 284 // .activityInstanceId(datas.get(0).getId()).list(); 285 // System.out.println("使用activityInstanceId查询:" + datas.size()); 286 // datas = historyService.createHistoricActivityInstanceQuery() 287 // .activityType("intermediateSignalCatch").list(); 288 // System.out.println("使用activityType查询:" + datas.size()); 289 // datas = historyService.createHistoricActivityInstanceQuery() 290 // .executionId("executionId").list(); 291 // System.out.println("使用executionId查询:" + datas.size()); 292 // datas = historyService.createHistoricActivityInstanceQuery().finished().list(); 293 // System.out.println("使用finished查询:" + datas.size()); 294 // datas = historyService.createHistoricActivityInstanceQuery() 295 // .processInstanceId("processInstanceId").list(); 296 // System.out.println("使用processInstanceId查询:" + datas.size()); 297 // datas = historyService.createHistoricActivityInstanceQuery() 298 // .taskAssignee("crazyit").list(); 299 // System.out.println("使用taskAssignee查询:" + datas.size()); 300 // datas = historyService.createHistoricActivityInstanceQuery().unfinished().list(); 301 // System.out.println("使用unfinished查询:" + datas.size()); 302 // } 303 // 304 // /** 305 // * 历史流程明细查询 306 // * 在流程进行的过程中,会产生许多明细数据,只有将History设置为最高级别的时候,才会被记录到ACT_HI_DETAIL表中。 307 // * @throws ParseException 308 // */ 309 // @Test 310 // public void findHistoricDetail() { 311 // // 查询历史行为 312 // HistoricActivityInstance act = historyService.createHistoricActivityInstanceQuery() 313 // .activityName("First Task").finished().singleResult(); 314 // List<HistoricDetail> datas = historyService.createHistoricDetailQuery() 315 // .activityInstanceId(act.getId()).list(); 316 // System.out.println("使用activityInstanceId方法查询:" + datas.size()); 317 // datas = historyService.createHistoricDetailQuery().excludeTaskDetails().list(); 318 // System.out.println("使用excludeTaskDetails方法查询:" + datas.size()); 319 // datas = historyService.createHistoricDetailQuery().formProperties().list(); 320 // System.out.println("使用formProperties方法查询:" + datas.size()); 321 // datas = historyService.createHistoricDetailQuery().processInstanceId("processInstanceId").list(); 322 // System.out.println("使用processInstanceId方法查询:" + datas.size()); 323 // datas = historyService.createHistoricDetailQuery().taskId("taskId").list(); 324 // System.out.println("使用taskId方法查询:" + datas.size()); 325 // datas = historyService.createHistoricDetailQuery().variableUpdates().list(); 326 // System.out.println("使用variableUpdates方法查询:" + datas.size()); 327 // } 328 329 }View Code
启动流程、用户任务task查询、业务处理、流程进度查看等等,历史流程查看功能没有验证过,这部分代码是注释掉的
**5、页面实现
在templates放置显示页面,可自己根据喜好放置,我这里主要是跟controller里返回的路径对应
**
**model文件夹下放的是模型列表页和用户任务列表页,form.html已经废弃,改为上面的activitiForm里面的form页面,不同的页面对应不同流程节点
list.html模型列表页** -

1<!DOCTYPE html> 2<html lang="zh" xmlns:th="http://www.thymeleaf.org"> 3<head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="/static/js/jquery.min.js"></script> 7 <script th:inline="javascript"> 8 function deployment(obj){ 9 var id=obj.attributes['objectid'].nodeValue; 10 $.ajax({ 11 url:"/models/deployment/"+id, 12 type:"GET", 13 success:function(res){ 14 alert(res); 15 } 16 }); 17 18 } 19 20 function deleteProcess(obj){ 21 var id=obj.attributes['objectid'].nodeValue; 22 $.ajax({ 23 url:"/models/delete/"+id, 24 type:"GET", 25 success:function(res){ 26 alert(res); 27 } 28 29 }); 30 31 } 32 33 function start(obj){ 34 var id=obj.attributes['objectid'].nodeValue; 35 $.ajax({ 36 url:'/models/start/'+id, 37 type:'GET', 38 success:function(res){ 39 alert(res); 40 } 41 }); 42 43 44 } 45 46 function taskList(){ 47 $("#taskList").submit(); 48 } 49 </script> 50</head> 51<body> 52<div class="info"><span th:text="${info}"></span></div> 53<a href="/models/create">新建</a> 54<form id="taskList" action="/activiti/queryTaskSl"> 55<div> 56 <div style="float:left;width:50%;"> 57 <select id="userId" name="userId"> 58 <option value="userzhou">userzhou</option> 59 <option value="usertest">usertest</option> 60 </select> 61 </div> 62 <div style="width:50%;"> 63 <button onclick="taskList();">任务列表</button> 64 </div> 65</div> 66</form> 67 68<table class="table"> 69 <thead> 70 <tr> 71 <th>ID</th> 72 <th>模型名称</th> 73 <th>key</th> 74 <th>版本</th> 75 <th>部署ID</th> 76 <th>创建时间</th> 77 <th>最后更新时间</th> 78 <th>操作</th> 79 </tr> 80 </thead> 81 <tbody> 82 <tr th:each="data : ${models}"> 83 <td th:text="${data.id}"></td> 84 <td><a th:href="@{/static/modeler.html(modelId=${data.id})}" class="font-blue" th:text="${data.name}"></a> 85 </td> 86 <td th:text="${data.key}"></td> 87 <td th:text="${data.version}"></td> 88 <td th:text="${data.deploymentId}"></td> 89 <td th:text="${data.createTime}"> 2018-02-25 17:28:35</td> 90 <td th:text="${data.lastUpdateTime}"> 2018-02-25 17:28:35</td> 91 <td><a href="javascript:void(0);" onclick="deployment(this);" th:attrappend="objectId=${data.id}" lass="font-blue deployBtn">发布</a> 92 <a th:href="${'/activiti/start/'+data.key}" th:attrappend="objectId=${data.id}" lass="font-blue deployBtn">发起流程</a> 93 <a th:href="@{/crm/model/export/{modelId}(modelId=${data.id})}" class="font-blue">导出</a> 94 <a href="javascript:void(0);" onclick="deleteProcess(this);" th:attrappend="objectId=${data.id}" class="font-blue delBtn">删除</a></td> 95 </tr> 96 </tbody> 97</table> 98</body> 99</html>View Code
**
taskList.html任务列表页
**

1<!DOCTYPE html> 2<html lang="zh" xmlns:th="http://www.thymeleaf.org"> 3<head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="/static/js/jquery.min.js"></script> 7</head> 8<body> 9<a href="/models/modelList"><button>返回模型列表页</button></a> 10<div> 11 <span th:text="'用户:'+${userId}"></span> 12 <table> 13 <thead> 14 <tr> 15 <th>ID</th> 16 <th>任务名称</th> 17 <th>受理人</th> 18 <th>任务ID</th> 19 <th>流程ID</th> 20 <th>创建时间</th> 21 <th>操作</th> 22 </tr> 23 </thead> 24 <tbody> 25 <tr th:each="data : ${tasks}"> 26 <td th:text="${data.id}">LeaveBill:1:4</td> 27 <td th:text="${data.name}"></td> 28 <td th:text="${data.assignee}">1</td> 29 <td th:text="${data.taskDefinitionKey}"></td> 30 <td th:text="${data.processDefinitionId}"></td> 31 <td th:text="${data.createTime}"> 2018-02-25 17:28:35</td> 32 <td> 33 <a th:href="${'/activiti/form?id='+data.id+'&userId='+data.assignee}" th:attrappend="objectId=${data.id}" lass="font-blue deployBtn">处理</a> 34 </td> 35 </tr> 36 </tbody> 37 </table> 38</div> 39</body> 40</html>View Code
**applyForm.html任务列表页
**

1<!DOCTYPE html> 2<html lang="zh" xmlns:th="http://www.thymeleaf.org"> 3<head> 4 <meta charset="UTF-8"> 5 <title>申请页面</title> 6 <script src="/static/js/jquery.min.js"></script> 7 <style> 8 .title{ 9 text-align: center; 10 } 11 </style> 12 <script> 13 function showProcessImg(processInstanceId) { 14 $.ajax({ 15 url:'/activiti/queryProHighLighted', 16 type:'POST', 17 data:{ 18 proInsId:processInstanceId 19 }, 20 success:function(res){ 21 $("#processImg").attr('src',"data:image/png;base64,"+res); 22 } 23 }) 24 } 25 function returnBack(userId){ 26 var form = $("<form></form>"); 27 form.attr("action","/activiti/queryTaskSl"); 28 form.attr("method","post"); 29 30 var input = $("<input type='text' name='userId'/>") 31 input.attr("value",userId); 32 form.append(input); 33 34 form.appendTo("body"); 35 36 form.css("display","none"); 37 38 form.submit(); 39 40 } 41 </script> 42</head> 43<body> 44<div class="title">申请表单</div> 45<a th:onclick="'javascript:returnBack(\''+${data.userId}+'\');'"><button>返回用户任务列表</button></a> 46<a href="javascript:void(0);" th:onclick="'javascript:showProcessImg(\''+${task.processInstanceId}+'\')'"><button>查看流程图</button></a> 47<form action="/activiti/completeTaskSl"> 48 <input type="hidden" id="id" name="id" th:value="${data.id}"/> 49 <input type="hidden" id="userId" name="userId" th:value="${data.userId}"/> 50 <input type="text" id="attr1" name="attr1"/> 51 <button type="submit">提交</button> 52 <button>取消</button> 53</form> 54<img src="" id="processImg"/> 55</body> 56</html>View Code
approveForm.html任务列表页

1<!DOCTYPE html> 2<html lang="zh" xmlns:th="http://www.thymeleaf.org"> 3<head> 4 <meta charset="UTF-8"> 5 <title>审批页面</title> 6 <script src="/static/js/jquery.min.js"></script> 7 <style> 8 .title{ 9 text-align: center; 10 } 11 </style> 12 <script> 13 function showProcessImg(processInstanceId) { 14 $.ajax({ 15 url:'/activiti/queryProHighLighted', 16 type:'POST', 17 data:{ 18 proInsId:processInstanceId 19 }, 20 success:function(res){ 21 $("#processImg").attr('src',"data:image/png;base64,"+res); 22 } 23 }) 24 } 25 26 function returnBack(userId){ 27 var form = $("<form></form>"); 28 form.attr("action","/activiti/queryTaskSl"); 29 form.attr("method","post"); 30 31 var input = $("<input type='text' name='userId'/>") 32 input.attr("value",userId); 33 form.append(input); 34 35 form.appendTo("body"); 36 37 form.css("display","none"); 38 39 form.submit(); 40 41 } 42 </script> 43</head> 44<body> 45<div class="title">审批表单</div> 46<a th:onclick="'javascript:returnBack(\''+${data.userId}+'\');'"><button>返回任务列表</button></a> 47<a href="javascript:void(0);" th:onclick="'javascript:showProcessImg(\''+${task.processInstanceId}+'\')'"><button>查看流程图</button></a> 48<form action="/activiti/completeTaskSl"> 49 <input type="hidden" id="id" name="id" th:value="${data.id}"/> 50 <input type="hidden" id="userId" name="userId" th:value="${data.userId}"/> 51 <input type="text" id="attr1" name="attr1"/> 52 <button type="submit">提交</button> 53 <button>取消</button> 54</form> 55<img src="" id="processImg"/> 56</body> 57</html>View Code
-
运行界面如下
list.html列表页

新建页面

**这里主要注意连个地方,assignments表示当前节点的处理人的id,我自己定义了两个用户userzhou和usertest,form key填写处理当前节点所用的form表单,也就是之前的applyForm和approveForm




**
-
**需要注意的地方
**1、去除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,未验证2、访问静态资源的时候,可能会被dispatcherservlet 给拦截掉,我是添加了个资源处理器,拦截static静态资源

1package com.djkj.activiti.common; 2 3import groovy.util.logging.Slf4j; 4import org.springframework.context.annotation.Configuration; 5import org.springframework.web.servlet.config.annotation.EnableWebMvc; 6import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 7import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 9@Configuration 10@Slf4j 11@EnableWebMvc 12public class StaticResourceConfig implements WebMvcConfigurer { 13 14 @Override 15 public void addResourceHandlers(ResourceHandlerRegistry registry) { 16 registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); 17 } 18 19}View Code
3、关闭编辑器的时候可以自定义返回的页面
修改editor-app\configuration下的toolbar-default-actions.js,有俩个地方closeEditor方法和$scope.saveAndClose方法,一个是关闭一个是保存并关闭,可以修改放回路径,对应的是controller里的路径1closeEditor: function(services) { 2 window.location.href = "/models/modelList"; 3 }, 4 5$scope.saveAndClose = function () { 6 $scope.save(function() { 7 window.location.href = "/models/modelList"; 8 }); 9 };
源码已经发到码云,各位可以去拉取
地址: https://gitee.com/zhouyun123/springBoot-activiti-modeler