Activiti工作流进阶-搭建一个MVC项目并实现请假流程

目录

一:简介

二:创建流程引擎

2.1创建一个web项目

2.2编写初始化引擎配置代码

2.3生成28张activiti任务表

三:使用activiti-spring-boot-starter-basic开发activiti项目

3.1开发准备工作

3.2请假流程图绘制

3.3登录和拦截器

3.4前端页面绘制和前端框架选型

3.5开始流程,请假表单创建和提交

3.6角色查看任务列表

3.7历史记录处理

四:总结


本文代码参考: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

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值