二级审批流程的实现
1.设计一个二级审批流程
2.创建三个不同用户组为流程参与者
3.参与者协同完成流程
second_approve.bpmn 设计
对应xml文件如下
1 <?xml version="1.0" encoding="UTF-8"?> 2 <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test"> 3 <process id="LeaveProcess" name="请假流程" isExecutable="true"> 4 <startEvent id="startevent" name="开始"></startEvent> 5 <userTask id="submitform" name="填写请假申请"> 6 <extensionElements> 7 <activiti:formProperty id="message" name="申请信息" type="string" required="true"></activiti:formProperty> 8 <activiti:formProperty id="name" name="申请人姓名" type="string" required="true"></activiti:formProperty> 9 <activiti:formProperty id="submitTime" name="提交时间" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty> 10 <activiti:formProperty id="submitType" name="确认申请" type="string" required="true"></activiti:formProperty> 11 </extensionElements> 12 </userTask> 13 <sequenceFlow id="flow1" sourceRef="startevent" targetRef="submitform"></sequenceFlow> 14 <exclusiveGateway id="decideSubmit" name="提交或取消"></exclusiveGateway> 15 <sequenceFlow id="flow2" sourceRef="submitform" targetRef="decideSubmit"></sequenceFlow> 16 <userTask id="ZG_approve" name="部门主管审批"> 17 <extensionElements> 18 <activiti:formProperty id="ZGapprove" name="主管审批结果" type="string" required="true"></activiti:formProperty> 19 <activiti:formProperty id="ZGmessage" name="主管备注" type="string" required="true"></activiti:formProperty> 20 </extensionElements> 21 </userTask> 22 <sequenceFlow id="flow3" sourceRef="decideSubmit" targetRef="ZG_approve"> 23 <conditionExpression xsi:type="tFormalExpression"><![CDATA[${submitType=="y" || submitType=="Y"}]]></conditionExpression> 24 </sequenceFlow> 25 <exclusiveGateway id="decideZGapprove" name="主管审批校验"></exclusiveGateway> 26 <sequenceFlow id="flow4" sourceRef="ZG_approve" targetRef="decideZGapprove"></sequenceFlow> 27 <userTask id="ZJL_approve" name="总经理审批"> 28 <extensionElements> 29 <activiti:formProperty id="ZJLapprove" name="总经理审批结果" type="string" required="true"></activiti:formProperty> 30 <activiti:formProperty id="ZJLmessage" name="总经理备注" type="string" required="true"></activiti:formProperty> 31 </extensionElements> 32 </userTask> 33 <sequenceFlow id="flow5" sourceRef="decideZGapprove" targetRef="ZJL_approve"> 34 <conditionExpression xsi:type="tFormalExpression"><![CDATA[${ZGapprove=="y" || ZGapprove=="Y"}]]></conditionExpression> 35 </sequenceFlow> 36 <exclusiveGateway id="decideZJLapprove" name="总经理审批校验"></exclusiveGateway> 37 <sequenceFlow id="flow6" sourceRef="ZJL_approve" targetRef="decideZJLapprove"></sequenceFlow> 38 <endEvent id="endevent" name="结束"></endEvent> 39 <sequenceFlow id="flow7" sourceRef="decideZJLapprove" targetRef="endevent"> 40 <conditionExpression xsi:type="tFormalExpression"><![CDATA[${ZJLapprove=="y" || ZJLapprove=="Y"}]]></conditionExpression> 41 </sequenceFlow> 42 <endEvent id="endeventCancel" name="取消"></endEvent> 43 <sequenceFlow id="flow8" sourceRef="decideSubmit" targetRef="endeventCancel"> 44 <conditionExpression xsi:type="tFormalExpression"><![CDATA[${submitType=="n" || submitType=="N"}]]></conditionExpression> 45 </sequenceFlow> 46 <sequenceFlow id="flow9" sourceRef="decideZGapprove" targetRef="submitform"> 47 <conditionExpression xsi:type="tFormalExpression"><![CDATA[${ZGapprove=="n" || ZGapprove=="N"}]]></conditionExpression> 48 </sequenceFlow> 49 <sequenceFlow id="flow10" sourceRef="decideZJLapprove" targetRef="submitform"> 50 <conditionExpression xsi:type="tFormalExpression"><![CDATA[${ZJLapprove=="n" || ZJLapprove=="N"}]]></conditionExpression> 51 </sequenceFlow> 52 </process> 53 <bpmndi:BPMNDiagram id="BPMNDiagram_LeaveProcess"> 54 <bpmndi:BPMNPlane bpmnElement="LeaveProcess" id="BPMNPlane_LeaveProcess"> 55 <bpmndi:BPMNShape bpmnElement="startevent" id="BPMNShape_startevent"> 56 <omgdc:Bounds height="35.0" width="35.0" x="65.0" y="279.0"></omgdc:Bounds> 57 </bpmndi:BPMNShape> 58 <bpmndi:BPMNShape bpmnElement="submitform" id="BPMNShape_submitform"> 59 <omgdc:Bounds height="55.0" width="105.0" x="145.0" y="269.0"></omgdc:Bounds> 60 </bpmndi:BPMNShape> 61 <bpmndi:BPMNShape bpmnElement="decideSubmit" id="BPMNShape_decideSubmit"> 62 <omgdc:Bounds height="40.0" width="40.0" x="295.0" y="277.0"></omgdc:Bounds> 63 </bpmndi:BPMNShape> 64 <bpmndi:BPMNShape bpmnElement="ZG_approve" id="BPMNShape_ZG_approve"> 65 <omgdc:Bounds height="55.0" width="105.0" x="380.0" y="270.0"></omgdc:Bounds> 66 </bpmndi:BPMNShape> 67 <bpmndi:BPMNShape bpmnElement="decideZGapprove" id="BPMNShape_decideZGapprove"> 68 <omgdc:Bounds height="40.0" width="40.0" x="530.0" y="278.0"></omgdc:Bounds> 69 </bpmndi:BPMNShape> 70 <bpmndi:BPMNShape bpmnElement="ZJL_approve" id="BPMNShape_ZJL_approve"> 71 <omgdc:Bounds height="55.0" width="105.0" x="615.0" y="271.0"></omgdc:Bounds> 72 </bpmndi:BPMNShape> 73 <bpmndi:BPMNShape bpmnElement="decideZJLapprove" id="BPMNShape_decideZJLapprove"> 74 <omgdc:Bounds height="40.0" width="40.0" x="765.0" y="279.0"></omgdc:Bounds> 75 </bpmndi:BPMNShape> 76 <bpmndi:BPMNShape bpmnElement="endevent" id="BPMNShape_endevent"> 77 <omgdc:Bounds height="35.0" width="35.0" x="850.0" y="282.0"></omgdc:Bounds> 78 </bpmndi:BPMNShape> 79 <bpmndi:BPMNShape bpmnElement="endeventCancel" id="BPMNShape_endeventCancel"> 80 <omgdc:Bounds height="35.0" width="35.0" x="395.0" y="329.0"></omgdc:Bounds> 81 </bpmndi:BPMNShape> 82 <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1"> 83 <omgdi:waypoint x="100.0" y="296.0"></omgdi:waypoint> 84 <omgdi:waypoint x="145.0" y="296.0"></omgdi:waypoint> 85 </bpmndi:BPMNEdge> 86 <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2"> 87 <omgdi:waypoint x="250.0" y="296.0"></omgdi:waypoint> 88 <omgdi:waypoint x="295.0" y="297.0"></omgdi:waypoint> 89 </bpmndi:BPMNEdge> 90 <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3"> 91 <omgdi:waypoint x="335.0" y="297.0"></omgdi:waypoint> 92 <omgdi:waypoint x="380.0" y="297.0"></omgdi:waypoint> 93 </bpmndi:BPMNEdge> 94 <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4"> 95 <omgdi:waypoint x="485.0" y="297.0"></omgdi:waypoint> 96 <omgdi:waypoint x="530.0" y="298.0"></omgdi:waypoint> 97 </bpmndi:BPMNEdge> 98 <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5"> 99 <omgdi:waypoint x="570.0" y="298.0"></omgdi:waypoint> 100 <omgdi:waypoint x="615.0" y="298.0"></omgdi:waypoint> 101 </bpmndi:BPMNEdge> 102 <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6"> 103 <omgdi:waypoint x="720.0" y="298.0"></omgdi:waypoint> 104 <omgdi:waypoint x="765.0" y="299.0"></omgdi:waypoint> 105 </bpmndi:BPMNEdge> 106 <bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7"> 107 <omgdi:waypoint x="805.0" y="299.0"></omgdi:waypoint> 108 <omgdi:waypoint x="850.0" y="299.0"></omgdi:waypoint> 109 </bpmndi:BPMNEdge> 110 <bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8"> 111 <omgdi:waypoint x="315.0" y="317.0"></omgdi:waypoint> 112 <omgdi:waypoint x="315.0" y="346.0"></omgdi:waypoint> 113 <omgdi:waypoint x="395.0" y="346.0"></omgdi:waypoint> 114 </bpmndi:BPMNEdge> 115 <bpmndi:BPMNEdge bpmnElement="flow9" id="BPMNEdge_flow9"> 116 <omgdi:waypoint x="550.0" y="318.0"></omgdi:waypoint> 117 <omgdi:waypoint x="549.0" y="382.0"></omgdi:waypoint> 118 <omgdi:waypoint x="397.0" y="382.0"></omgdi:waypoint> 119 <omgdi:waypoint x="197.0" y="382.0"></omgdi:waypoint> 120 <omgdi:waypoint x="197.0" y="324.0"></omgdi:waypoint> 121 </bpmndi:BPMNEdge> 122 <bpmndi:BPMNEdge bpmnElement="flow10" id="BPMNEdge_flow10"> 123 <omgdi:waypoint x="785.0" y="279.0"></omgdi:waypoint> 124 <omgdi:waypoint x="784.0" y="222.0"></omgdi:waypoint> 125 <omgdi:waypoint x="505.0" y="222.0"></omgdi:waypoint> 126 <omgdi:waypoint x="197.0" y="222.0"></omgdi:waypoint> 127 <omgdi:waypoint x="197.0" y="269.0"></omgdi:waypoint> 128 </bpmndi:BPMNEdge> 129 </bpmndi:BPMNPlane> 130 </bpmndi:BPMNDiagram> 131 </definitions>
新建springboot工程
引入依赖
<dependencies> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-engine</artifactId> <version>6.0.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.11</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>23.0</version> </dependency> <!-- h2内存级数据库 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.3.176</version> </dependency> </dependencies>
简化logback输出,logback.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <configuration> 3 <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径--> 4 <property name="LOG_HOME" value="/home/tomcatlog/logs/agent" /> 5 <property name="plain" value="%msg%n" /> 6 7 <!-- 控制台输出 --> 8 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 9 <encoder> 10 <pattern>${plain}</pattern> 11 </encoder> 12 </appender> 13 14 <!-- 按照每天生成日志文件 --> 15 <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> 16 <file>${LOG_HOME}/agent.log</file> 17 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 18 <!--日志文件输出的文件名 --> 19 <fileNamePattern>${LOG_HOME}/agent.%d{yyyy-MM-dd}.log 20 </fileNamePattern> 21 <!--日志文件保留天数 --> 22 <maxHistory>30</maxHistory> 23 </rollingPolicy> 24 <encoder> 25 <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 --> 26 <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n 27 </pattern> 28 </encoder> 29 </appender> 30 31 <logger name="jdbc.sqlonly" level="OFF" /> 32 <logger name="jdbc.resultsettable" level="OFF" /> 33 <logger name="jdbc.sqltiming" level="erroe" /> 34 <logger name="jdbc.audit" level="error" /> 35 <logger name="jdbc.connection" level="error" /> 36 <logger name="jdbc.resultset" level="error" /> 37 <logger name="root"> 38 <level value="ERROR"/> 39 </logger> 40 <logger name="com.imooc"> 41 <level value="DEBUG"/> 42 </logger> 43 44 <!-- 日志输出级别 --> 45 <root level="info"><!-- ERROR、WARN、INFO、DEBUG --> 46 <appender-ref ref="STDOUT" /> 47 <appender-ref ref="FILE" /> 48 </root> 49 </configuration>
resources引入second_approve.bpmn20.xml文件
新建启动类Main.class
创建流程引擎
部署流程定义文件
启动运行流程
处理流程任务
按照以上四步构建引动类,为了简化启动类,建议对每个方法进行重构,CTRL+ALT+M快捷键
1 package com.imooc.activiti.helloword; 2 3 4 import com.google.common.collect.Maps; 5 import org.activiti.engine.*; 6 import org.activiti.engine.form.FormProperty; 7 import org.activiti.engine.form.TaskFormData; 8 import org.activiti.engine.impl.form.DateFormType; 9 import org.activiti.engine.impl.form.StringFormType; 10 import org.activiti.engine.repository.Deployment; 11 import org.activiti.engine.repository.DeploymentBuilder; 12 import org.activiti.engine.repository.ProcessDefinition; 13 import org.activiti.engine.runtime.ProcessInstance; 14 import org.activiti.engine.task.Task; 15 import org.slf4j.Logger; 16 import org.slf4j.LoggerFactory; 17 18 import java.text.ParseException; 19 import java.text.SimpleDateFormat; 20 import java.util.Date; 21 import java.util.List; 22 import java.util.Map; 23 import java.util.Scanner; 24 25 public class Main { 26 27 private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); 28 29 public static void main(String[] args) throws ParseException { 30 LOGGER.info("启动程序"); 31 //创建流程引擎 32 ProcessEngine processEngine = getProcessEngine(); 33 34 //部署流程定义文件 35 ProcessDefinition processDefinition = getProcessDefinition(processEngine); 36 37 //启动运行流程 38 ProcessInstance processInstance =getProcessInstance(processEngine, processDefinition); 39 40 //处理流程任务 41 Scanner scanner = new Scanner(System.in); 42 while (processInstance != null && !processInstance.isEnded()) { //判断流程不为空,且流程没有结束 43 TaskService taskService = processEngine.getTaskService(); 44 List<Task> list = taskService.createTaskQuery().list(); //列出当前需要处理的任务 45 LOGGER.info("待处理任务数量 [{}]", list.size()); 46 for (Task task : list) { 47 48 LOGGER.info("待处理任务 [{}]", task.getName()); 49 Map<String, Object> variables = getStringObjectMap(processEngine, scanner, task); 50 taskService.complete(task.getId(),variables); 51 processInstance = processEngine.getRuntimeService().createProcessInstanceQuery() 52 .processInstanceId(processInstance.getId()).singleResult(); 53 } 54 } 55 scanner.close(); 56 LOGGER.info("结束程序"); 57 58 } 59 //获取变量 60 private static Map<String, Object> getStringObjectMap(ProcessEngine processEngine, Scanner scanner, Task task) throws ParseException { 61 FormService formService = processEngine.getFormService(); //通过formService来获取form表单输入 62 TaskFormData taskFormData = formService.getTaskFormData (task.getId()); 63 List<FormProperty> formProperties = taskFormData.getFormProperties(); //获取taskFormData的表单内容 64 Map<String,Object> variables = Maps.newHashMap(); //这个Map键值对来存对应表单用户输入的内容 65 for (FormProperty property : formProperties){ //property为表单中的内容 66 String line = null; //这里获取输入的信息 67 if(StringFormType.class.isInstance(property.getType())){ //如果是String类型的话 68 LOGGER.info("请输入 [{}] ?" , property.getName()); //输入form表单的某一项内容 69 line = scanner.nextLine(); 70 variables.put(property.getId(),line); 71 }else if(DateFormType.class.isInstance(property.getType())){ //如果是日期类型的话 72 LOGGER.info("请输入 [{}] ? 格式为(yyyy-MM-dd)" , property.getName()); //输入form表单的某一项内容 73 line = scanner.nextLine(); 74 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); //设置输入的日期格式 75 Date date = dateFormat.parse(line); 76 variables.put(property.getId(),date); 77 }else{ 78 LOGGER.info("类型不支持 [{}]",property.getType()); 79 } 80 LOGGER.info("您输入的内容是 [{}] " , line); 81 } 82 return variables; 83 } 84 85 private static ProcessInstance getProcessInstance(ProcessEngine processEngine, ProcessDefinition processDefinition) { 86 RuntimeService runtimeService = processEngine.getRuntimeService(); 87 ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId()); 88 LOGGER.info("启动流程{}",processInstance.getProcessDefinitionKey()); 89 return processInstance; 90 } 91 92 private static ProcessDefinition getProcessDefinition(ProcessEngine processEngine) { 93 RepositoryService repositoryService = processEngine.getRepositoryService(); 94 DeploymentBuilder deploymentBuilder = repositoryService.createDeployment(); 95 deploymentBuilder.addClasspathResource("second_approve.bpmn20.xml"); 96 Deployment deploy = deploymentBuilder.deploy(); 97 String id = deploy.getId(); 98 ProcessDefinition processDefinition = repositoryService 99 .createProcessDefinitionQuery() 100 .deploymentId(id) 101 .singleResult(); 102 LOGGER.info("流程定义对象{},流程定义id {}",processDefinition.getName(),processDefinition.getId()); 103 return processDefinition; 104 } 105 106 private static ProcessEngine getProcessEngine() { 107 ProcessEngineConfiguration cfg = ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration(); 108 ProcessEngine processEngine = cfg.buildProcessEngine(); 109 String name = processEngine.getName(); 110 String version = ProcessEngine.VERSION; 111 LOGGER.info("流程引擎名称{},版本{}",name,version); 112 return processEngine; 113 } 114 115 116 }
使用springboot打包
更新pom.xml
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> <relativePath/> </parent> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <!-- 基于plugin启动这个程序 --> </plugin> </plugins> </build>
在<dependencies></dependencies>中添加spring-boot的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
进入这个项目目录,然后输入 mvn package ,就可以将项目打包:mvn package
target目录中,可以看到两个jar包:
直接使用以下指令来执行我们编译好的二级审批流程文件
java-jar xxxxxx.jar
将二级审批流程创建archetype脚手架
打开源码目录 Activiti-6.x oolingarchetypesactiviti-archetype-unittest
安装脚手架 mvn clean install
copy activiti-archetype-unittest2
修改pom.xml中的 artifactId
创建main/java与main/resources两个目录
修改Main.java中的package,后面路径改为 ${package}; package ${package}
修改 META-INF/maven/archetype-metadata.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <archetype-descriptor name="activiti-archetype-unittest2"> 3 <fileSets> 4 <fileSet filtered="true" packaged="true"> <!-- filtered是否过滤,packaged:是否经过包结构过滤 --> 5 <directory>src/main/java</directory> 6 <includes> 7 <include>**/*.java</include> 8 </includes> 9 </fileSet> 10 <fileSet filtered="false" packaged="false"> 11 <directory>src/main/resources</directory> <!-- 资源目录下面的三个文件 --> 12 <includes> 13 <include>activiti.cfg.xml</include> 14 <include>LeaveProcess.bpmn20.xml</include> 15 <include>logback.xml</include> 16 </includes> 17 </fileSet> 18 <fileSet filtered="true" packaged="true"> 19 <directory>src/test/java</directory> 20 <includes> 21 <include>**/*.java</include> 22 </includes> 23 </fileSet> 24 <fileSet filtered="true" packaged="true"> 25 <directory>src/test/resources</directory> 26 <includes> 27 <include>second_approve.bpmn20.xml</include> 28 </includes> 29 </fileSet> 30 31 </fileSets> 32 </archetype-descriptor>
进入activiti-archetype-unittest2的路径下,执行 mvn clean install
在本地的maven仓库中已经创建好的脚手架
Create from archetype后Add Archetype
GroupId org.activiti
ArtifactId activiti-archetype-unittest2
Version 6.0.0
即可继续创建工程
完