最近领导让我研究下工作流,于是查啊查就查到了Activiti,特么刚开始一直查的是Activity,查出来一堆Android的东西,我也是醉了。话不多说,下面就记录下这2天的研究成果吧。
所用环境
Maven工程
JDK:jdk1.8.0_73
IDE:eclipse Mars.2 Release (4.5.2)
数据库:mysql 5.1.39-ndb-7.0.9-cluster-gpl
SSM框架:(spring + spring-mvc)4.3.2.RELEASE + mybatis3.4.1
Activiti:5.21.0
spring+mvc+mybatis整合就不贴了,网上一大堆了
eclipse安装流程设计插件
eclipse依次点击 Help -> Install New Software -> Add:
Name:Activiti Designer
Location:http://activiti.org/designer/update/
点击OK选中插件安装即可
添加 Activiti 到项目中
-
在 pom.xml 中添加 Activiti 依赖
<activiti.version>5.21.0</activiti.version><dependency> <groupId>org.activiti</groupId> <artifactId>activiti-engine</artifactId> <version>${activiti.version}</version> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring</artifactId> <version>${activiti.version}</version> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-rest</artifactId> <version>${activiti.version}</version> </dependency>
2.新建 applicationContext-activiti.xml,别忘了在主配置文件中将其import
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
<!-- 流程配置 -->
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseSchemaUpdate" value="true" />
<property name="jobExecutorActivate" value="true" />
<!-- 以下2个是为了防止生成流程图片时出现乱码 -->
<property name="activityFontName" value="宋体"/>
<property name="labelFontName" value="宋体"/>
</bean>
<!-- 流程引擎 -->
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>
<!-- 流程服务 -->
<bean id="repositoryService" factory-bean="processEngine"
factory-method="getRepositoryService" />
<bean id="runtimeService" factory-bean="processEngine"
factory-method="getRuntimeService" />
<bean id="taskService" factory-bean="processEngine"
factory-method="getTaskService" />
<bean id="historyService" factory-bean="processEngine"
factory-method="getHistoryService" />
<bean id="managementService" factory-bean="processEngine"
factory-method="getManagementService" />
<bean id="IdentityService" factory-bean="processEngine"
factory-method="getIdentityService" />
</beans>
3.启动项目,如果未出现错误,Activiti会在连接的数据库中自动新建25张表,如下:
至此Activiti流程引擎添加完毕,下面开始使用Activiti,实现一次请假流程
设计流程
流程设计插件安装成功后会在eclipse新建向导中出现Activiti向导,如图
1.我们新建一个 Activiti Diagram 命名为 leave.bpmn,完成时如下图:
主要就是拖拖拉拉,从左至右控件分别为
StartEvent,UserTask,ExlusiveGateway,UserTask,EndEvent;连线都是SequenceFlow
属性可在Properties视图中设置,如果没有这个视图,可在 eclipse 中依次点击
Window->Show View->Other 搜索Properties点击OK即可
- 流程属性:一般设置一个Id,Name,NameSpace就可以了,此处为分别为leaveProcess、Leave Process、http://www.mario.com; 这个Id在之后启动流程时会用到,不同流程Id不可相同
- 开始事件(StartEvent):流程开始
- 结束事件(EndEvent):流程结束
- 用户任务(UserTask):主要用到Id,Name,Assignee
Assignee为此任务的办理人,在查找任务时需要用到,三个任务分别指派给 apply,pm,boss
- 排他网关(ExlusiveGateway):只会寻找唯一一条能走完的顺序流
- 顺序流(SequenceFlow):主要用到Id,Name,Condition
Condition用于指定该顺序流表达式 ,day的值会在调用执行任务方法时传入
除了使用可视化组件,我们也可以通过xml来设计流程,以上流程的xml定义如下:
<?xml version="1.0" encoding="UTF-8"?>
<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.mario.com">
<process id="leaveProcess" name="Leave Process" isExecutable="true">
<startEvent id="startevent1" name="开始"></startEvent>
<userTask id="usertask1" name="请假申请" activiti:assignee="apply"></userTask>
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
<sequenceFlow id="flow2" name="天数判断" sourceRef="usertask1" targetRef="exclusivegateway1"></sequenceFlow>
<userTask id="usertask2" name="审批(项目经理)" activiti:assignee="pm"></userTask>
<sequenceFlow id="flow3" name="小于等于三天" sourceRef="exclusivegateway1" targetRef="usertask2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${day<=3}]]></conditionExpression>
</sequenceFlow>
<userTask id="usertask3" name="审批(老板)" activiti:assignee="boss"></userTask>
<sequenceFlow id="flow4" name="大于三天" sourceRef="exclusivegateway1" targetRef="usertask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${day>3}]]></conditionExpression>
</sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow5" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow6" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow7" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_leaveProcess">
<bpmndi:BPMNPlane bpmnElement="leaveProcess" id="BPMNPlane_leaveProcess">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="30.0" y="211.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55.0" width="105.0" x="110.0" y="201.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1">
<omgdc:Bounds height="40.0" width="40.0" x="285.0" y="208.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
<omgdc:Bounds height="55.0" width="105.0" x="400.0" y="120.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
<omgdc:Bounds height="55.0" width="105.0" x="400.0" y="290.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="560.0" y="211.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="215.0" y="228.0"></omgdi:waypoint>
<omgdi:waypoint x="285.0" y="228.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="14.0" width="48.0" x="230.0" y="228.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="305.0" y="208.0"></omgdi:waypoint>
<omgdi:waypoint x="452.0" y="175.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="14.0" width="100.0" x="295.0" y="180.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
<omgdi:waypoint x="305.0" y="248.0"></omgdi:waypoint>
<omgdi:waypoint x="452.0" y="290.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="14.0" width="48.0" x="285.0" y="257.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
<omgdi:waypoint x="452.0" y="175.0"></omgdi:waypoint>
<omgdi:waypoint x="577.0" y="211.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
<omgdi:waypoint x="452.0" y="290.0"></omgdi:waypoint>
<omgdi:waypoint x="577.0" y="246.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
<omgdi:waypoint x="65.0" y="228.0"></omgdi:waypoint>
<omgdi:waypoint x="110.0" y="228.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
流程部署
有了流程图,我们就可以部署该流程了,Activiti提供多种部署方法(有自动部署,手动部署等),这里演示两种手动部署方法
Workflow.java代码片段
private static ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
/**
* 通过定义好的流程图文件部署,一次只能部署一个流程
*/
public static void deploy() {
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("death/note/lawliet/web/workflow/leave.bpmn").deploy();
}
/**
* 将多个流程文件打包部署,一次可以部署多个流程
*/
public void deployByZip() {
InputStream is = this.getClass().getClassLoader().getResourceAsStream("diagrams/bpm.zip");
ZipInputStream zip = new ZipInputStream(is);
Deployment deployment = processEngine
.getRepositoryService()
.createDeployment()
.addZipInputStream(zip)
.deploy();
}
方便起见,通过一个Deploy按钮来部署流程
部署成功后,会分别在 act_ge_bytearray,act_re_deployment,act_re_procdef三张表插入相应数据,多次部署同一流程的话会增加版本号,以此获取最新的流程
启动流程
我们通过一个表单来用于请假申请
我们定义一个 Leave 对象用于保存请假信息,相应的数据表为 leave 。Result 对象用于封装一些返回信息。这里的 "leaveProcess" 就是在流程属性中定义的Id。启动成功后会返回一个 ProcessInstance 对象,这个对象主要是一些流程的基本信息,具体可以查看文档或源码。这里将返回的流程实例 id 存入到我们的 leave 表,以便以后可以通过这个 id 查询相关的流程。
@ResponseBody
@RequestMapping(value = "/save", method = RequestMethod.POST)
public Result save(@RequestBody Leave user) {
Result result = new Result();
ProcessInstance pi = Workflow.startInstanceByKey("leaveProcess");
user.setInstaceId(pi.getId());
leaveService.insert(user);
result.info(true, 0, "保存成功");
return result;
}
Workflow.java代码片段
public static ProcessInstance startInstanceByKey(String instanceByKey) {
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance instance = runtimeService.startProcessInstanceByKey(instanceByKey);
return instance;
}
流程启动成功后会在 act_hi_actinst,act_hi_identitylink,act_hi_procinst,act_hi_taskinst,act_ru_execution,act_ru_identitylink,act_ru_task 表中插入相应数据。我们比较关心的就是 act_ru_task 表了,它存储了任务的相关信息。
查看任务
流程启动完毕后,应该就是进入了请假申请任务,我们可以通过请假申请的办理人 apply 来查询该任务(当然还有其他方法),这里可以指定不同查询条件和过滤条件。返回 taskList 就是当前的任务列表了,Task是Activiti为我们定义好的接口对象,主要封装了任务的信息。
这里由于 Activiti 的Task是接口对象无法转换为json,所以自定义了一个Task对象,来转换成json
@ResponseBody
@RequestMapping(value = "/data/{assignee}")
public List<death.note.lawliet.web.model.Task> data(@PathVariable String assignee){
List<Task> tasks = Workflow.findTaskByAssignee(assignee);
List<death.note.lawliet.web.model.Task> list = new ArrayList<>();
for(Task task : tasks){
death.note.lawliet.web.model.Task t = new death.note.lawliet.web.model.Task();
t.setTaskId(task.getId());
t.setName(task.getName());
t.setAssignee(task.getAssignee());
t.setExecutionId(task.getExecutionId());
t.setProcessInstanceId(task.getProcessInstanceId());
t.setProcessDefinitionId(task.getProcessDefinitionId());
list.add(t);
}
return list;
}
Workflow.java代码片段
public static List<Task> findTaskByAssignee(String assignee) {
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery().taskAssignee(assignee).list();
return taskList;
}
查看流程图
我们可以通过流程定义ID(processDefinitionId)来获取流程图
@RequestMapping(value = "/shwoImg/{procDefId}")
public void shwoImg(@PathVariable String procDefId,HttpServletResponse response){
try {
InputStream pic = Workflow.findProcessPic(procDefId);
byte[] b = new byte[1024];
int len = -1;
while((len = pic.read(b, 0, 1024)) != -1) {
response.getOutputStream().write(b, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}
}
Workflow.java代码片段
public static InputStream findProcessPic(String procDefId) throws Exception {
RepositoryService repositoryService = processEngine.getRepositoryService();
ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId)
.singleResult();
String diagramResourceName = procDef.getDiagramResourceName();
InputStream imageStream = repositoryService.getResourceAsStream(procDef.getDeploymentId(), diagramResourceName);
return imageStream;
}
然后通过processDefinitionId,executionId 来获取当前正在执行任务的位置坐标,以便用于标识流程图上的位置。ActivityImpl 对象封装了任务的位置信息,包括xy坐标,长和宽。自定义 Rect 对象用于转换json
@ResponseBody
@RequestMapping(value = "/showImg/{procDefId}/{executionId}")
public Rect showImg(@PathVariable String procDefId,@PathVariable String executionId ) {
Rect rect = new Rect();
try {
ActivityImpl img = Workflow.getProcessMap(procDefId,executionId );
rect.setX(img.getX());
rect.setY(img.getY());
rect.setWidth(img.getWidth());
rect.setHeight(img.getHeight());
} catch (Exception e) {
e.printStackTrace();
}
return rect;
}
Workflow.java代码片段
public static ActivityImpl getProcessMap(String procDefId, String executionId) throws Exception {
ActivityImpl actImpl = null;
RepositoryService repositoryService = processEngine.getRepositoryService();
//获取流程定义实体
ProcessDefinitionEntity def = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
.getDeployedProcessDefinition(procDefId);
RuntimeService runtimeService = processEngine.getRuntimeService();
//获取执行实体
ExecutionEntity execution = (ExecutionEntity) runtimeService.createExecutionQuery().executionId(executionId)
.singleResult();
// 获取当前任务执行到哪个节点
String activitiId = execution.getActivityId();
// 获得当前任务的所有节点
List<ActivityImpl> activitiList = def.getActivities();
for (ActivityImpl activityImpl : activitiList) {
String id = activityImpl.getId();
if (id.equals(activitiId)) {
actImpl = activityImpl;
break;
}
}
return actImpl;
}
最终生成图片时分别调用2个 showImg 方法即可,红框可以根据返回的 Rect 来绘制,起始坐标根据自己的布局自行调整
LeavePicController.js片段
var pic.rect = {};
var pic.procDefId = $stateParams.procDefId;
$http.get('workflow/showImg/'+$stateParams.procDefId +'/'+$stateParams.executionId)
.success(function(data) {
pic.rect.x = data.x;
pic.rect.y = data.y;
pic.rect.width = data.width;
pic.rect.height = data.height;
});
picture.html
<div class="container-fluid" ng-controller="LeavePicController as pic">
<img src="workflow/showImg/{{pic.procDefId}}">
<div style="position:absolute; border:2px solid red;
left:{{pic.rect.x + 20 }}px;
top:{{pic.rect.y + 88 }}px;
width:{{pic.rect.width }}px;
height:{{pic.rect.height }}px;">
</div>
</div>
流程审批
通过 taskId 就可以对当前执行的任务进行审批,这里的 day 应该从 leave 表中查询出来,方便起见就写死了,这个day也就是在顺序流的Condition中指定的变量,小于等于3就会流向项目经理(pm)审批任务了
@ResponseBody
@RequestMapping(value = "/check/{taskId}")
public Result check(@PathVariable String taskId) {
Result result = new Result();
Map<String, Object> map = new HashMap<>();
map.put("day", 3);
Workflow.completeTask(taskId,map);
result.info(true, 0, "审批成功");
return result;
}
Workflow.java代码片段
public static void completeTask(String taskid,Map<String, Object> map map) {
TaskService taskService = processEngine.getTaskService();
taskService.complete(taskid, map);
}
审批成功后,就需要通过办理人(pm)来查询任务了,并且请假办理(apply)中的任务被移除了
同时流程图也流向了下一节点
项目经理再执行一次审批方法,这个流程就算走完了
最后
以上就是一次相对简单的流程:部署,启动,查询任务,显示流程图,审批。Activiti流程引擎还有很多核心操作,包括驳回、会签、转办、中止、挂起等,等有空的时候再深入研究吧
最后的最后,初来乍到,不喜勿喷或轻喷 - -!Thanks...