目录
三:使用activiti-spring-boot-starter-basic开发activiti项目
本文代码参考:https://gitee.com/shenzhanwang/Spring-activiti,感谢大佬们的无私奉献
一:简介
上一篇文章已经简要的介绍了activiti的流程图绘制和流程运行,用到了几个activiti核心API,比如ProcessEngineConfiguration、RepositoryService、RuntimeService、TaskService,对activiti的运作流程有了基础的理解。但是实际开发时我们不可能每次都new这些service,这里我们就从如何用springboot配置初始化创建一个processEngine引擎开始,一步一步带你完成一个完整的请假流程,完整的代码地址会在最后贴上。
二:创建流程引擎
推荐使用idea开发,使用eclipse等其他开发工具的小伙伴也可以在网站上创建初始化的springboot项目。
2.1创建一个web项目
因为activiti底层ORM是使用mybatis实现的,所以需要添加mybatis和mysql依赖
idea创建工程:file-new-project-spring Initializr
选择pom依赖选择Lombok、spring web starter、Thymeleaf、MySql Driver、Mybatis Framework
在pom中添加activiti-engine依赖
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>6.0.0</version>
</dependency>
在application文件中配置mysql数据库路径
spring:
datasource:
url: jdbc:mysql://url?serverTimezone=GMT&useSSL=false&characterEncoding=UTF-8&nullCatalogMeansCurrent=true
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
2.2编写初始化引擎配置代码
import javax.sql.DataSource;
@Configuration
public class ActivitiConfig {
@Autowired
@Qualifier("dataSource")
private DataSource dataSource;
/**
* Description 初始化配置,将创建28张表
* processEngineConfiguration
* @Author zhangcheng
*/
@Bean
public StandaloneProcessEngineConfiguration processEngineConfiguration(){
StandaloneProcessEngineConfiguration configuration = new StandaloneProcessEngineConfiguration();
configuration.setDataSource(dataSource);
configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
configuration.setAsyncExecutorActivate(false);
return configuration;
}
/**
* Description 创建引擎
* processEngine
* @Author zhangcheng
*/
@Bean
public ProcessEngine processEngine(){
return processEngineConfiguration().buildProcessEngine();
}
}
2.3生成28张activiti任务表
https://github.com/changgongcheng00/activiti_demo.git
启动项目,可以看到数据库生成了Activiti6的28表
三:使用activiti-spring-boot-starter-basic开发activiti项目
二中使用的activiti-engine是activiti工作流最核心的组件,但用于spring web开发时activiti-engine就显得力不从心了,activiti官方为我们提供了一个和springboot整合好的jar-->activiti-spring-boot-starter-basic,前面的都是演习,真正的战斗开始了。
3.1开发准备工作
注释:本文使用的springboot版本是2.1.5.RELEASE
修改:去除之前引入的activiti-engine依赖,注释掉刚刚用来初始化创建流程引擎的ActivitiConfig类,添加如下开发依赖
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.176</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
[重点!!!activiti6.0.0完美整合springboot2.x]
因为activiti6.0.0开发时,springboot是1.x版本,所以正常部署运行会报错,需要对activiti6.0.0的源码包做出修改以匹配springboot2.x。本人已经将修改后的maven包放进项目代码的rsource/static/sql里,项目编译好后,将该包复制到activiti6.0.0对应的repository目录解压,并加pom文件的依赖版本号修改为6.0.0-boot2
此处修改参考https://www.jianshu.com/p/9b1dbb2c85e7
删除刚刚数据库生成的28张表,重启项目,若activiti-spring-boot-starter-basic成功为我们生成28张表,则可以开始正式开发工作了
3.2请假流程图绘制
我们假定一个请假流程:员工发起请假审批申请,部门主管审批,若请假时间大于等于一天,则平台领导(老板)也需要审批,HR完成审批后流程结束;其中任意一角色都可以驳回申请,被驳回的申请流转到员工,由员工决定放弃申请或者是修改申请条件继续申请。
由此我们使用上文使用的eclipse插件画出下图的BPMN流程图
3.2.1主管审批填充(人事审批,平台主管审批)
如上图,同上一篇博文一样,主管审批需要填写id和name,还要填写form表单信息用以存储主管审批时的操作信息。大家发现了,我们还在main config里填写了Candidate Group,这个是用来表示操作人员的分组,在后面的代码中用来作为TaskList的筛选条件。
人事审批,平台主管审批和主管审批填写方式一样;可以从我后面的github地址下载源码,在resource/processes/找到leave.bpmn文件查询详情
3.2.2重新申请(填写表单信息)填充
如上图,填写表单信息和主管审批的区别就是Main config没有在Candidate group填写角色信息,而是在assignee里填写了applyuserid。这是因为主管、HR是审批角色,而若是审批被拒绝的话,流程需要流转到申请人名下,而不是某个权限组。
这边的applyuserid可以任意命名,由代码在初始化创建该员工请假流程任务时确定,因此要和代码中的userid键的名称一致,后面在分析拆解代码时会再次说明。
3.2.3流程线flow填充
如上图所示,流程线的id可以任意命名但不得重复,name建议改成有意义的名词,需要在main config->condition 中定义流程的判断条件,语法同jsp的表达式一样,其余所有flow流程指针线类同。
3.3登录和拦截器
完成了流程图的绘制,我们就可以构建项目了,因为涉及到了不同角色权限的操作,我们需要创建一组简易RBAC权限控制表来绑定账号-角色-权限的关系。RBAC权限控制此处不赘述,sql见代码resource/static/sql/leaveapplysql
创建好RBAC的表和查询语句后,我们需要做一个登录来保存用户session信息,做一个拦截器来确保session失效时让用户重新登录。
3.3.1拦截器核心代码
拦截请求,如果获取不到session信息则重定向至登录页面
@Component
public class WebInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws IOException {
User user = (User)request.getSession().getAttribute("user");
if(user==null){
response.sendRedirect(request.getContextPath()+"/toLogin");
return false;
}
return true;
}
}
3.3.2 WebMvcConfigurer配置
引入刚刚的拦截器,加入到mvc配置中,并设置拦截所有url,忽略指定的url如重定向登录的“/toLogin”和登录的“/login”以及一些静态文件路径。
addCorsMappings是配置跨域访问,addViewControllers是配置无业务逻辑跳转,关系不大可以忽略
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
WebInterceptor webInterceptor;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/activiti/**")
.allowedOrigins("*","https://www.baidu.com/")
.allowCredentials(true)
.allowedMethods("GET","POST","PUT","DELETE","HEAD");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/toLogin").setViewName("login");
}
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(webInterceptor).addPathPatterns("/**").excludePathPatterns(
"/toLogin","/login","/error","/img/**","/css/**","/js/**","/font/**","/ico/**"
);
}
}
3.3.3登录和退出登录
如下图代码所示,登录在session中保存了用户信息,退出登录清除了session的用户信息。此处的key:user要和拦截器中的attribute key:user 名称一致。
@PostMapping(value="/login")
public ResponseData login(@RequestBody User user,HttpServletRequest request){
//根据登录账号查询用户信息
User oldUser = userService.login(user);
if(oldUser == null){
return ResponseData.error(5000,"账号不存在");
}
String password = oldUser.getPassword();
if(!password.equalsIgnoreCase(user.getPassword())){
return ResponseData.error(5000,"密码错误");
}
HttpSession session = request.getSession();
session.setAttribute("user",oldUser);
return ResponseData.success();
}
@GetMapping(value="/loginout")
public ModelAndView loginout(HttpServletRequest request){
request.getSession().removeAttribute("user");
request.getSession().invalidate();
return new ModelAndView("login");
}
3.4前端页面绘制和前端框架选型
3.4.1页面结构规划和搭建
如图,这是完成后的页面,low是low了点,但咱也不是专业前端不是,达到效果就凑合着用。
本页面分为标题、左边菜单栏和中间正文部分。因为springboot优先支持html,为了方便js等静态文件的引入和使用,所以这里我们使用thymeleaf插件,maven依赖已经在创建项目时导入。
标题和左边菜单栏我们可以做一个固定的页面,然后使用include将其嵌入;另外我们还需要做一个页面专门引入js、css。代码如下
3.4.2-->这是includebase.html页面,全局的js、css由此引入
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<link th:href="@{/css/bootstrap.css}" rel="stylesheet"/>
<link th:href="@{/css/bootstrap-theme.css}" rel="stylesheet"/>
<link th:href="@{/css/main.css}" rel="stylesheet"/>
<script th:src="@{/js/jquery-3.4.1.min.js}"></script>
<script th:src="@{/js/bootstrap.js}"></script>
<script th:src="@{/js/jquery.bootgrid.min.js}"></script>
<script th:src="@{/js/base.js}"></script>
</html>
3.4.3-->这是main.html,是固定的标题栏和左侧菜单栏,用于嵌入所有业务页面,形成一个整体
<header class="head">
<div class="head-left">Workflow</div>
<div class="head-right">
<span>欢迎你</span>
<span id="currentUser"></span>
<span><a href="loginout">,退出登录</a></span>
</div>
</header>
<div class="body-left">
<ul>
<li class="submit">
<a href="activiti">请假OA开始</a>
</li>
<li class="resubmit">
<a href="submitform">调整申请(被驳回)</a>
</li>
<li class="tlapprove">
<a href="tlapprove">部门领导审批</a>
</li>
<li class="plapprove">
<a href="plapprove">平台负责人审批</a>
</li>
<li class="hrapprove">
<a href="hrapprove">人事审批</a>
</li>
<li class="myprocess">
<a href="myprocess">我发起的请假流程</a>
</li>
<li class="myhistory">
<a href="myhistory">我的请假历史</a>
</li>
<li class="userinfo">
<a href="userinfo">用户信息</a>
</li>
</ul>
</div>
<script>
$(function () {
$.ajax({
url:"getUser",
type:"post",
success : function(data){
$("#currentUser").html(data.username);
}
})
})
</script>
在其他页面使用<head th:include="include/includebase">引入js css,
使用<div th:replace="include/main" ></div>引入main页面
前端js框架使用bootgrid。
3.5开始流程,请假表单创建和提交
完成登录后就可以开始工作流的开发了,第一步当然是员工填写请假表单并发起请假流程,关键位置我都加了注释
3.5.1业务核心代码
//controller代码
@PostMapping(value="/startleave")
public String start_leave(LeaveApply apply, HttpSession session){
User user=(User) session.getAttribute("user");
Map<String,Object> variables=new HashMap<String, Object>();
//此处把用户id作为标识传入,key->applyuserid和我们填写表单时main config ->Assignee时${applyuserid}要一致,相当于声明流程的发起人。
variables.put("applyuserid", String.valueOf(user.getId()));
ProcessInstance ins=leaveService.startWorkflow(apply, user.getId(), variables);
logger.info("流程id{}已经启动",ins.getId());
return JSON.toJSONString("sucess");
}
//Service代码
@Override
public ProcessInstance startWorkflow(LeaveApply apply, int userid, Map<String, Object> variables) {
apply.setApplyTime(new Date().toString());
apply.setUserId(userid);
//本地表保存员工提交的表单信息
leaveDao.save(apply);
//使用刚刚保存的本地表leaveapply的主键作为businesskey,连接业务数据和流程数据,相当于把本地表数据和activiti的28张工作表作了关联
String businesskey=String.valueOf(apply.getId());
identityService.setAuthenticatedUserId(String.valueOf(userid));
//开启流程,myProcess是bpmn的id,和第一篇博文的方法是不是很像
ProcessInstance instance=runtimeService.startProcessInstanceByKey("myProcess",businesskey,variables);
System.out.println(businesskey);
//获取流程实例id,并保存在本地表中
String instanceid=instance.getId();
apply.setProcessInstanceId(Integer.valueOf(instanceid));
leaveDao.update(apply);
return instance;
}
3.5.2前端 html和js代码
此处其实就是一个表单页面,发起了一个ajax请求
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:include="include/includebase">
<meta charset="UTF-8">
<title>Title</title>
<meta name="description" content="description"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
</head>
<body>
<div th:replace="include/main" ></div>
<style>
.submit{
background:#419641;
margin-right:20px;
border-radius: 10px;
text-align: center;
}
</style>
<div class="body-right main-form">
<div class="row">
<div id="breadcrumb" class="col-xs-12">
<ol class="breadcrumb pull-left">
<li><a href="#">我要请假</a></li>
</ol>
</div>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-lg-12">
<div class="box ui-draggable ui-droppable" id="dept">
<div class="box-header">
<div class="box-name">
<i class="fa fa-coffee"></i> <span>填写申请</span>
</div>
<div class="box-icons">
<a class="collapse-link"> <i class="fa fa-chevron-up"></i>
</a> <a class="expand-link"> <i class="fa fa-expand"></i>
</a> <a class="close-link"> <i class="fa fa-times"></i>
</a>
</div>
<div class="no-move"></div>
</div>
<div class="box-content">
<form role="form" action="startleave" method="post">
<div class="form-group">
<label>请假类型</label>
<select name="leaveType" class="form-control">
<option value="事假">事假</option>
<option value="病假">病假</option>
<option value="年假">年假</option>
<option value="丧假">丧假</option>
<option value="产假">产假</option>
</select>
</div>
<div class="form-group has-feedback">
<label class="control-label">开始时间</label>
<input id="start" class="form-control" name="startTime" placeholder="开始时间"/><span class="fa fa-calendar txt-danger form-control-feedback"></span>
</div>
<div class="form-group has-feedback">
<label>结束时间</label>
<input id="end" class="form-control" name="endTime" placeholder="结束时间"/><span class="fa fa-calendar txt-danger form-control-feedback"></span>
</div>
<div class="form-group has-feedback">
<label>请假时长</label>
<input id="leaveTime" class="form-control" name="leaveTime" placeholder="请假时长"/><span class="fa fa-calendar txt-danger form-control-feedback"></span>
</div>
<div class="form-group">
<label>请假原因</label>
<textarea class="form-control" name="reason" rows="3"></textarea>
</div>
<div class="form-group">
<label>职务代理人</label>
<select name="userJobId" class="form-control">
<option value="1">a</option>
<option value="2">b</option>
<option value="3">c</option>
<option value="4">d</option>
</select>
</div>
<button id="btn" type="button" class="btn btn-primary">开始申请</button>
<button type="reset" class="btn btn-primary">重置</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function(){
$("#btn").click(function(){
$("#btn").attr("disabled","disabled");
var data = $("form").serialize();
if (/=(&|$)/.test(data)){
alert("所有值不能为空");
return;
}
$.post("startleave",data,function(){
alert("申请成功");
$("form")[0].reset();
$("#btn").removeAttr("disabled");
});
});
});
</script>
</body>
</html>
3.6角色查看任务列表
部门领导、平台主管、HR处理任务任务的代码类似,可以重构出一个公共的方法和泛型的构造器,因此此处合并为一个方法
3.6.1领导查询待操作的流程Controller代码(以领导审批为例)
//用到的静态常量,分别对应Candidate Group类型和RBAC权限表的权限
private static final String TLROLE = "部门经理";
private static final String TLPERMISSION = "部门领导审批";
@PostMapping(value="/getTLTaskList")
public DataGrid<LeaveTask> getTLTaskList(HttpServletRequest request,@RequestParam("current") int current,@RequestParam("rowCount") int rowCount){
//判断登录用户是否有部门领导权限,无权限返回空,有权限查询Task
ResponseData<LeaveTask> responseData = new ResponseData<>();
DataGrid<LeaveTask> grid = getLeaveTaskDataGrid(current, rowCount);
return getLeaveTaskResponseData(request, grid,LeaveController.TLPERMISSION,LeaveController.TLROLE);//填写表单信息
}
private DataGrid<LeaveTask> getLeaveTaskResponseData(HttpServletRequest request, DataGrid<LeaveTask> grid,String perm,String role) {
//查询用户的权限列表
User user = (User)request.getSession().getAttribute("user");
List<Permisssion> permissions = userService.getPermission(user.getId());
boolean flag = false;
for(Permisssion permission:permissions){
if(perm.equalsIgnoreCase(permission.getPermissionName())){
flag = true;
}
}
if(flag == false){
return grid;
}else{
//调用service方法
List<LeaveApply> approveTaskList = leaveService.getApproveTaskList(role, String.valueOf(user.getId()));
List<LeaveTask> tasks =new ArrayList<>();
for(LeaveApply apply:approveTaskList){
LeaveTask task = new LeaveTask();
task.setProcessInstanceId(apply.getProcessInstanceId());
task.setUserId(apply.getUserId());
task.setStartTime(apply.getStartTime());
task.setEndTime(apply.getEndTime());
task.setLeaveTime(apply.getLeaveTime());
task.setLeaveType(apply.getLeaveType());
task.setReason(apply.getReason());
task.setApplyTime(apply.getApplyTime());
task.setUserJobId(apply.getUserJobId());
task.setTaskid(apply.getTask().getId());
task.setTaskname(apply.getTask().getName());
task.setTaskCreateTime(apply.getTask().getCreateTime());
task.setProcessdefid(apply.getTask().getProcessDefinitionId());
tasks.add(task);
}
grid.setTotal(tasks.size());
grid.setRows(tasks);
return grid;
}
}
3.6.2领导查询待操作的流程Service代码
@Override
public List<LeaveApply> getApproveTaskList(String type,String userid) {
List<LeaveApply> results = new ArrayList<>();
//通过CandidateGroup查询任务
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup(type).list();
for(Task task:tasks){
String instanceId = task.getProcessInstanceId();
//根据任务的processInstanceId获取流程实例
ProcessInstance ins = runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult();
String businesskey = ins.getBusinessKey();
//查询本地库保存的申请信息,创建流程时使用leaveapply表的主键作为businesskey ,此处用上了
LeaveApply a = leaveDao.get(Integer.parseInt(businesskey));
a.setTask(task);
results.add(a);
}
return results;
}
3.6.3领导审批该流程核心代码
此处将领导的审批条件对应的结果传给task,tlapprove - true/false(同意/拒绝),leaveTime-1/2...请假天数
@PostMapping(value="/task/tlcomplete")
public ResponseData tlcomplete(@RequestBody ApproveData approveData, HttpServletRequest request){
User user = (User)request.getSession().getAttribute("user");
Map<String,Object> variables=new HashMap<String,Object>();
variables.put("tlApprove", approveData.getApprove());
variables.put("leaveTime",approveData.getLeaveTime());
try {
taskService.claim(approveData.getTaskid(), String.valueOf(user.getId()));
} catch (ActivitiObjectNotFoundException e) {
return ResponseData.error(500,"任务已处理");
}
taskService.complete(approveData.getTaskid(), variables);
return ResponseData.success();
}
3.6.4领导查询代操作流程的js代码
此处相当于一个bootstrap的table代码,下面的on代表页面加载完毕的监控事件
html中有data-formatter="taskCreateTime",所以此处可以使用formatters格式化事件显示方式
前端说起来简单,就是table的展示和ajax监控事件回调(如点击),实际上这儿是花我时间最多的地方,一言难尽
$(document).ready(function(){
var grid=$("#grid-data").bootgrid({
navigation:2,
columnSelection:false,
ajax:true,
url:"getTLTaskList",
formatters: {
"taskCreateTime":function(column, row){
return getTime(row.taskCreateTime);
},
"leaveTime" : function(column, row){
return "<span id='leaveTime'>"+row.leaveTime+"</span>";
},
"options" : function(column, row){
return "<select id=\"tlApproveSelect\"><option value=\"true\">同意</option><option value=\"false\">拒绝</option></select>";
},
"commands": function(column, row)
{
return "<button class=\"btn btn-xs btn-default ajax-link command-run1\" data-row-id=\"" + row.taskid + "\">提交</button>";
}
}
}).on("loaded.rs.jquery.bootgrid", function(){
grid.find(".btn").on("click", function(e) {
var taskid=$(this).data("row-id");
var leaveTime =$("#leaveTime").text();
var tlApprove = $("#tlApproveSelect option:selected").val();
var obj = {};
obj.leaveTime = leaveTime;
obj.taskid = taskid;
obj.approve = tlApprove;
$(".btn").attr("disabled","disabled");
$.ajax({
url:"task/tlcomplete/",
type:"post",
contentType:"application/json;charset=UTF-8",
data:JSON.stringify(obj),
dataType:"json",
success : function(data){
if(data.code == 0){
alert("处理成功");
$(".btn").removeAttr("disabled");
LoadAjaxContent("tlapprove");
}else{
alert(data.msg);
}
}
})
})
});
});
3.7历史记录处理
历史记录调用的是act_hi_系列表,主要涉及两种,一个我发起的正在审批中的流程,一个是我发起过的已经结束的流程。因方法比较相似,此处只展示审批中流程的代码
3.7.1查询我发起的请假流程记录
代码功能简述:查询了本流程实例的所有还存活的流程实例,筛选出不为空的businessKey用来查出本地表对应的流程实例,根据当前登录用户和本地表保存的用户id比对筛选出当前用户发起的流程实例,保存。
@PostMapping(value="getProcessList")
public DataGrid<RunningProcess> getProcessList(HttpServletRequest request,@RequestParam("current") int current,@RequestParam("rowCount") int rowCount){
DataGrid<RunningProcess> grid = getLeaveTaskDataGrid(current, rowCount);
User user = (User)request.getSession().getAttribute("user");
//查询了本流程实例的所有还存活的流程实例
List<ProcessInstance> a = runtimeService.createProcessInstanceQuery().processDefinitionKey("myProcess")
.involvedUser(String.valueOf(user.getId())).list();
List<RunningProcess> list = new ArrayList<>();
for (ProcessInstance p : a) {
if(StringUtils.isEmpty(p.getBusinessKey())){
continue;
}
//筛选出不为空的businessKey用来查出本地表对应的流程实例
LeaveApply leaveApply = leaveService.getleave(Integer.valueOf(p.getBusinessKey()));
if(user.getId() != leaveApply.getUserId()){
continue;
}
RunningProcess process = new RunningProcess();
process.setExecutionId(p.getId());
process.setProcessInstanceId(p.getProcessInstanceId());
process.setBusinessKey(p.getBusinessKey());
process.setActivityId(p.getActivityId());
list.add(process);
}
grid.setTotal(list.size());
grid.setRows(list);
return grid;
}
3.7.2 撤销审批流程
@GetMapping(value = "endProcess/{processInstanceId}")
public ResponseData processInstanceId(@PathVariable("processInstanceId")String processInstanceId){
try {
runtimeService.deleteProcessInstance(processInstanceId,"任务主动撤销");
historyService.deleteHistoricProcessInstance(processInstanceId);
} catch (ActivitiObjectNotFoundException e) {
return ResponseData.error(5000,"资源已经删除");
}
return ResponseData.success();
}
3.7.3 绘制我发起的审批流程实时流程进度图
@GetMapping(value = "traceProcess/{processInstanceId}")
public void traceProcess(@PathVariable("processInstanceId") String processInstanceId,HttpServletResponse response) throws IOException {
//获取所有活动,用于测试
//List<String> activeActivityIds = runtimeService.getActiveActivityIds(processInstanceId);
ProcessInstance process = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
BpmnModel bpmnModel = repositoryService.getBpmnModel(process.getProcessDefinitionId());
//获得所有历史活动,按时间升序排序
List<HistoricActivityInstance> historicList = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
//计算活动线路,已执行的历史节点
List<String> executedActivitiIds = new ArrayList<>();
historicList.forEach(e->{executedActivitiIds.add(e.getActivityId());});
//已执行的flow集合
List<String> excutedFlowList = new ArrayList<>();
for (HistoricActivityInstance historic : historicList) {
FlowNode flowNode = (FlowNode)bpmnModel.getFlowElement(historic.getActivityId());
List<SequenceFlow> sequenceFlows = flowNode.getOutgoingFlows();
sequenceFlows.forEach(e->{
if(e.getTargetFlowElement().getId().equalsIgnoreCase(historic.getActivityId())){
excutedFlowList.add(e.getId());
}
});
}
InputStream png = new DefaultProcessDiagramGenerator().generateDiagram(bpmnModel, "png", executedActivitiIds,
excutedFlowList,"黑体","黑体","黑体",null,1.0);
ServletOutputStream outputStream = response.getOutputStream();
IOUtils.copy(png,outputStream);
}
3.7.4 我发起的请假审批流程前端代码
此处为一个html的table初始化页面,流程图查看是href打开新页面,撤销使用了onclik事件发起ajax请求
$(document).ready(function(){
var grid=$("#grid-data").bootgrid({
navigation:2,
columnSelection:false,
ajax:true,
url:"getProcessList",
formatters: {
"commands": function(column, row)
{
return "<a class=\"btn-xs btn-default ajax-link\" target=\"_blank\" href=\"traceProcess/" + row.processInstanceId + "\">查看流程图</a>"+
"<button id='remove' class=\"btn btn-xs btn-default ajax-link\" data-row-id=\"" + row.processInstanceId + "\">撤销</button>";
}
}
}).on("loaded.rs.jquery.bootgrid", function(){
grid.find(".btn").on("click", function(e) {
var taskid=$(this).data("row-id");
$("#remove").attr("disabled","disabled");
$.ajax({
url:"endProcess/"+taskid,
type:"get",
success : function(data){
if(data.code == 0){
alert("处理成功");
$("#remove").removeAttr("disabled");
LoadAjaxContent("myprocess");
}else{
alert(data.msg);
}
}
})
});
});
});
四:总结
activiti请假流程的主要代码拆解就到这里了,我也是过来人,直接看代码可能会比较蒙,所以在此处留下了源码地址,先下载源码本地跑起来,再一点点了解每一个功能的实现方式,这样的学习方式不容易疲倦也更加富有效率。
activiti是一个很强大的中间件工具,我所学会的只是皮毛。工作流社区包括activiti和flowable等,正在不断的推出新版本,希望在日后的工作中再次用到他时,她能变得更加厉害,而我能更进一步,了解她更多一点。
因本人学识所限,若有错误欢迎指出,共同学习进步。
本文代码github地址https://github.com/changgongcheng00/Workflow.git