最近领导让我研究下工作流,于是查啊查就查到了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 依赖
5.21.0 org.activiti activiti-engine ${activiti.version} org.activiti activiti-spring ${activiti.version} org.activiti activiti-rest ${activiti.version}
2.新建 applicationContext-activiti.xml,别忘了在主配置文件中将其import
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定义如下:
3}]]>
流程部署
有了流程图,我们就可以部署该流程了,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 data(@PathVariable String assignee){
List tasks = Workflow.findTaskByAssignee(assignee);
List 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 findTaskByAssignee(String assignee) {
TaskService taskService = processEngine.getTaskService();
List 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 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
流程审批
通过 taskId 就可以对当前执行的任务进行审批,这里的 day 应该从 leave 表中查询出来,方便起见就写死了,这个day也就是在顺序流的Condition中指定的变量,小于等于3就会流向项目经理(pm)审批任务了
@ResponseBody
@RequestMapping(value = "/check/{taskId}")
public Result check(@PathVariable String taskId) {
Result result = new Result();
Map 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 map map) {
TaskService taskService = processEngine.getTaskService();
taskService.complete(taskid, map);
}
审批成功后,就需要通过办理人(pm)来查询任务了,并且请假办理(apply)中的任务被移除了
同时流程图也流向了下一节点
项目经理再执行一次审批方法,这个流程就算走完了
最后
以上就是一次相对简单的流程:部署,启动,查询任务,显示流程图,审批。Activiti流程引擎还有很多核心操作,包括驳回、会签、转办、中止、挂起等,等有空的时候再深入研究吧
最后的最后,初来乍到,不喜勿喷或轻喷 - -!Thanks...