Activiti7实战入门一:请假单流程的制定以及代码实现


原创不易,转载请标识出处 https://blog.csdn.net/rocklee/article/details/124615525

目标

利用activiti7画流程图、发布、跑流程实例、传变量、审批…

1、依赖

加入activiti7的依赖,网上说7要依赖spring-security才能跑,我的测试项目也改为spring-security鉴权,有空才尝试能不能撇掉它。

<!--activity 依赖-->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <version>7.1.0.M6</version>
            <exclusions>
                <exclusion>
                    <artifactId>mybatis</artifactId>
                    <groupId>org.mybatis</groupId>
                </exclusion>
            </exclusions>
        </dependency>

2、画流程图

试了好几款,包括网上推荐的idea几个插件或外挂camuada modeler都不好使,最终选用Eclipse插件Activiti Eclipse BPMN 2.0 designer。 下载地址
定义本地站点,然后通过本地站点安装此插件
在这里插入图片描述
在这里插入图片描述

然后利用它画一个简单2层审批的请假流程图,流程就是申请人填写请假申请->部门领导审批->HR领导审批, 如下:
在这里插入图片描述
流程图至少要有start event和end event,它们之间可以有若干个user task或其它task,而user task是最常用的,需要人手动做complete操作才能往下一步走。

2.1 变量设置

bpmn2流程图里面要设置变量,需要用${变量名}的格式设置,比如,我为candidate groups赋值了一个名为dept_leader的变量,这个变量在java代码运行时动态赋值,则相当于为candidate groups动态赋值:
在这里插入图片描述

2.2 关于表单属性

  Activiti7早就摒弃了FormService,改为用UserTask.getFormProperties来访问表单属性,可能作者也认为表单也不那么重要,因为很多应用场景人家有自己的业务系统,并不是太依赖activiti来实现具体业务内容。
  但是如果要做通用的审批流就可以借助这个表单属性来实现,用表单属性定义各个Form field,然后在系统中读出再动态生成UI让用户输入表单内容。
  下面代码用来访问某个任务的表单属性:

public static UserTask getUserTask(String processDefinitionId, String taskDefinitionKey, RepositoryService repositoryService){
    return (UserTask) repositoryService.getBpmnModel(processDefinitionId)
            .getFlowElement(taskDefinitionKey);
  }
  public static List<FormProperty> getFormProperties(String processDefinitionId, String taskDefinitionKey, RepositoryService repositoryService){
    UserTask userTask=getUserTask(processDefinitionId,taskDefinitionKey,repositoryService);
    return userTask.getFormProperties();
  }

3. SpringCloud环境跑流程

先为项目配置好spring security(吐槽一下security自带的BCryptPasswordEncoder,你千万别单步调试它的验证或加密,等待接近40秒).
将画好的bpmn文件和png文件复制到本项目的resources/process下:
在这里插入图片描述
按下面顺序一步一步操作

3.1 部署流程文件

画好的流程图必须先进行部署,也就是将其导入到activiti系统(它自带的数据表,act_re_xxx),
部署是通过RepositoryService来实现的,可以通过下面代码部署:

  /***
   * 部署resource下的bpmn文件
   * @param bpmnFileResource 如process/a.bpmn
   * @param pngFileResource 如process/a.png
   * @param name 流程模板的名称
   * @return
   */
  public static Deployment deployByResourceFile(String bpmnFileResource,String pngFileResource,String name){
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    RepositoryService service = engine.getRepositoryService();
    return service.createDeployment()
            .addClasspathResource(bpmnFileResource) // 添加bpmn资源
            .addClasspathResource(pngFileResource) // 添加png资源
            .name(name)
            .deploy();
  }

如果是bpmn文件和png打包成一个zip的,用下面代码部署:

/***
   * 部署resource下的bpmn文件
   * @param zipFileResource 在resource下的zip文件路径,里面包括有bpmn文件和png文件
   * @param name 流程模板的名称
   * @return
   */
  public static Deployment deployByResourceZipFile(String zipFileResource,String name) throws IOException {
    try(InputStream resourceAsStream =ReflectUtil.getResourceAsStream(zipFileResource);
        ZipInputStream zipInputStream = new ZipInputStream(resourceAsStream);) {
      ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
      RepositoryService service = engine.getRepositoryService();
      return service.createDeployment().addZipInputStream(zipInputStream).name(name).deploy();
    }
  }

3.2 新建流程实例

部署完毕,就可以新建流程实例(后面各user task的处理都是基于流程实例的,所以新建流程实例要先做!),建流程实例用RuntimeService对象,代码如下

@Test
  public void testLaunchProcess(){
    //部署后(定义流程模板),可以启动一个新流程了
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = engine.getRuntimeService();
    String id= "study2-leave";
    Map<String, Object> vars=new HashMap<>();
    //给两级审批组分别赋予角色
    vars.put("dept_leader","dept-group1,dept-group2");
    vars.put("hr_leader","hr-group1");
    RepositoryService repositoryService= engine.getRepositoryService();
ProcessInstance processInstance = ActivitiUtil.launchProcessInstance(id,"bkey-123456",vars,runtimeService);
    //输出相关的流程实例信息
    System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
    System.out.println("流程实例的ID:" + processInstance.getId());
}
...
  /***
   * 新建流程实例
   * @param processDefinitionKey 流程图里面的process id
   * @param businessKey 业务key id
   * @param variables 传进去的变量列表
   * @param runtimeService 运行时刻服务
   * @return 返回流程实例对象
   */
  public static ProcessInstance launchProcessInstance(String processDefinitionKey, String businessKey,Map<String, Object> variables,RuntimeService runtimeService){
    return  runtimeService.startProcessInstanceByKey(processDefinitionKey,businessKey,variables);
  }

上面代码新建了一个请假申请流程实例,并传入两个审批角色的变量值,这里亦可以传入业务key ,用来与业务系统关联。

真正在项目里面用的时候,acitviti的几大对象都已经自动配置到ioc了,直接用

	@Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private HistoryService historyService;

3.3 填写请假申请

根据流程图,第一步是填写请假申请,我们先建一个请假单实体吧:

@Data
  @NoArgsConstructor
  @AllArgsConstructor
  @ToString
  public static class LeaveData implements Serializable {
     private String staffId;
     private String staffName;
     private int days;
  }

user task的操作是能过TaskService对象实现的,下面填写请假单的代码有点长,当然不是必要的,只是演示如何使用变量的传入与读出,activiti有点奇怪的是当前操作传入的变量用getProcessVariables是读不出来的,要下一个任务才能读出,但是runtimeService.getVariable则可以即时获得变量值。

 String userId="ChenDaWen"; //没错,请假人又是陈大文
    TaskService taskService=engine.getTaskService();
    Task task= taskService.createTaskQuery()
            .processInstanceId(processInstance.getId())
            .includeProcessVariables().includeTaskLocalVariables()
            .taskCandidateGroup("all_staff").singleResult();

    ActivitiUtil.setGlobalVariable("leave",new LeaveData("A001","陈大文",3),
            task.getProcessInstanceId(),runtimeService); //将请假单实体通过变量传到实例中

    //额外的附加信息,如外部表的记录id
    ActivitiUtil.setGlobalVariable("form_id","form-12345",task.getProcessInstanceId(),runtimeService);
    System.out.println("流程变量:"+task.getProcessVariables());//暂时分不出getProcessVariables和getTaskLocalVariables有什么不一样!
    //System.out.println("局部任务变量:"+task.getTaskLocalVariables());
    System.out.println("form id:"+ActivitiUtil.getGlobalVariable("form_id",task.getProcessInstanceId(),runtimeService));

    //根据当前的task打印form_id的值(按历史服务查)
    String formId=ActivitiUtil.getGlobalVariable("form_id",task.getProcessInstanceId(),engine.getHistoryService());
    System.out.println(formId);

    taskService.claim(task.getId(),userId); //认领此任务
    taskService.addComment(task.getId(),task.getProcessInstanceId(),"拟写请假单:"+userId); //增加批注
    Map<String,Object> params=new HashMap<>();
    params.put("form_id","task1-form-id"); //重写了form-id,complete前和complete后,请查看act_ru_variable表的变化,
    params.put("task1-var1","value1");
    taskService.complete(task.getId(),params,true); //审批动作。指定localscope为true全局的form_id还是会被覆盖,不知道啥用意
    //之后再打印form_id的值
    System.out.println("form id:"+ActivitiUtil.getGlobalVariable("form_id",task.getProcessInstanceId(),runtimeService));
    /*formId=ActivitiUtil.getGlobalVariable("form_id",task.getProcessInstanceId(),engine.getHistoryService());
    System.out.println(formId);*/
    System.out.println("局部任务变量:"+task.getTaskLocalVariables());//当前的任务变量中还是不能显示task1-var1和form_id的,这个只要影响下一个流向连接和任务

跑出的结果如下 ,:

businessKey:bkey-123456
流程变量:{dept_leader=dept-group1,dept-group2, hr_leader=hr-group1}
form id:form-12345
form-12345
form id:task1-form-id
局部任务变量:{dept_leader=dept-group1,dept-group2, hr_leader=hr-group1}

3.4 列出待处理任务

其实部门审批也是user task,和3.3的拟写请假单一样的动作甚至更简单,但是这里要将请假单内容取出来显示给审批人看… 这里为什么我用candidategroup而不用Assignee?
任务系统的设计都应该以角色鉴权,毕竟人员的变动频繁,基于此activiti的Assignee和candidateUsers我一般不用,统一用candidateGroups,而且要注意的是如果你在流程图里面填了assignee,那么candidateGroups就会失效了。

下面列出待处理任务的代码

@Test
  public void testListTask(){
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    //根据用户组查询任务
    TaskService taskService=engine.getTaskService();
    // 根据流程的key和任务负责人 查询任务
    String group= "dept-group2";
    String id = "study2-leave";
    List<Task> list =ActivitiUtil.getTaskList(id,group,taskService);
    // 输出当前用户具有的任务
    for (Task task : list) {
      System.out.println("流程实例id:" + task.getProcessInstanceId());
      System.out.println("任务id:" + task.getId());
      System.out.println("任务名称:" + task.getName());
      System.out.println("流程变量:"+task.getProcessVariables());//这时候,leave和上一任务创建的变量task1-var1和最新的form_id都出来了
      //System.out.println("局部任务变量:"+task.getTaskLocalVariables());
      System.out.println("请假单信息:");
      //输出首任务信息
      HistoricTaskInstance firstTask=ActivitiUtil.getFirstFinishedTask(task.getProcessInstanceId(),engine.getHistoryService());
      System.out.println("填写时间:"+firstTask.getCreateTime());
      System.out.println("递交人:"+firstTask.getAssignee());
      System.out.println("内容:"+task.getProcessVariables().get("leave"));
      System.out.println("备注:"+ActivitiUtil.getComment(firstTask.getId(),engine.getTaskService()).getFullMessage());
    }

  }

结果发现3人请假:

流程实例id:63a253f5-cdb5-11ec-813f-3863bb4563c2
任务id:3cb1a6b5-cdb6-11ec-ac69-3863bb4563c2
任务名称:部门领导审批
流程变量:{dept_leader=dept-group1,dept-group2, hr_leader=hr-group1, leave=MyActivitiTest2.LeaveData(staffId=A001, staffName=陈大文, days=3), form_id=task1-form-id, task1-var1=value1}
请假单信息:
填写时间:Sat May 07 11:25:48 CST 2022
递交人:ChenDaWen
内容:MyActivitiTest2.LeaveData(staffId=A001, staffName=陈大文, days=3)
备注:拟写请假单:ChenDaWen
流程实例id:4ef3f562-cdb8-11ec-9560-3863bb4563c2
任务id:6ccd6870-cdb8-11ec-9560-3863bb4563c2
任务名称:部门领导审批
流程变量:{dept_leader=dept-group1,dept-group2, hr_leader=hr-group1, leave=MyActivitiTest2.LeaveData(staffId=A002, staffName=京东, days=3), form_id=task1-form-id, task1-var1=value1}
请假单信息:
填写时间:Sat May 07 11:46:42 CST 2022
递交人:jingdong
内容:MyActivitiTest2.LeaveData(staffId=A002, staffName=京东, days=3)
备注:拟写请假单:jingdong
流程实例id:9730d90a-cdb8-11ec-a6a2-3863bb4563c2
任务id:a7771ba8-cdb8-11ec-a6a2-3863bb4563c2
任务名称:部门领导审批
流程变量:{dept_leader=dept-group1,dept-group2, hr_leader=hr-group1, leave=MyActivitiTest2.LeaveData(staffId=A003, staffName=阿里, days=3), form_id=task1-form-888, task1-var1=value1}
请假单信息:
填写时间:Sat May 07 11:48:43 CST 2022
递交人:user3
内容:MyActivitiTest2.LeaveData(staffId=A003, staffName=阿里, days=3)
备注:拟写请假单:user3

3.5 审批请假单

其实也就两句话,认领和完成,加批注是可选的,带参数完成也是可选的:

	taskService.claim(task.getId(),userId); //认领任务
    taskService.addComment(task.getId(),task.getProcessInstanceId(),"同意");//加批注
    taskService.complete(task.getId())

入门一的教程结束,后面有时间再写一下用条件流向实现审批和拒绝。

3.6 列出请假单所有实例信息

  如果要统计请假单所有已结束数据或查等审批状态的请假单数据,应该如何实现。
首先应该先根据流程定义key id用historyService的createHistoricProcessInstanceQuery获得HistoricProcessInstance列表,判断HistoricProcessInstanceQuery的endTime为空则为等审批状态,也就是我们说的“在单状态”,如果endTime不为空则此流程实例已结束。
  拿到HistoricProcessInstance后,就得查询这个实例的首用户任务(拟写请假单环节)以获得谁什么时候写的请假单,请假单内容是什么。也根据这个实例查得最后审批环节数据。
  如果为在单的实例,那么还需要提供下一个审批环节(有可能是多个并行的)的信息,这时候需要用taskService.的createTaskQuery来查询 。

    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    //列出请假单实例
    String id = "study2-leave";
    List<HistoricProcessInstance> processInstances=ActivitiUtil.getProcessInstances(id,engine.getHistoryService());
    int tasksId=1;
    for (HistoricProcessInstance p:processInstances){
      HistoricTaskInstance firstTask=ActivitiUtil.getFirstFinishedTask(p.getId(),engine.getHistoryService());
      HistoricTaskInstance task=ActivitiUtil.getLastFinishedTask(p.getId(),engine.getHistoryService(),true);
      System.out.println("任务"+(tasksId++)+":"+p.getId());
      System.out.println("  状态:"+(p.getEndTime()==null?"在单":"**已完结**"));
      System.out.println("  拟写人:"+firstTask.getAssignee());
      System.out.println("  拟写时间:"+firstTask.getClaimTime());
      if (p.getEndTime()==null) {
        System.out.println("  内容:" + ActivitiUtil.getGlobalVariable("leave", p.getId(), engine.getRuntimeService()));
      }
      else {
        System.out.println("  完结时间:"+p.getEndTime());
        System.out.println("  内容:" + ActivitiUtil.getGlobalVariable("leave", p.getId(), engine.getHistoryService()));
        System.out.println("  审批耗时时长:" + Duration.ofMillis(p.getDurationInMillis()).toString());
      }
      System.out.println("  ======");
      List<HistoricTaskInstance> tasks=ActivitiUtil.getFinishedTasks(p.getId(),engine.getHistoryService());
      System.out.println("  已完成环节:"+tasks.stream().map(m->m.getName()).collect(Collectors.joining(",")));
      System.out.println("  最后审批信息:"+task.getName());
      System.out.println("    审批时间:"+task.getClaimTime());
      System.out.println("    审批人:"+task.getAssignee());
      Comment comment=ActivitiUtil.getComment(task.getId(),engine.getTaskService());
      if (comment!=null) {
        System.out.println("    批注:" +comment.getFullMessage());
      }
      System.out.println("    流程实例变量:"+task.getProcessVariables());
      if (p.getEndTime()==null) {
        System.out.println("  ======");
        System.out.println("  下一个审批环节:" + ActivitiUtil.getNextTasks(p.getId(), engine.getTaskService()).stream().map(m -> m.getName()).collect(Collectors.joining(",")));
      }
    }

显示结果:

任务1:63a253f5-cdb5-11ec-813f-3863bb4563c2
  状态:**已完结**
  拟写人:ChenDaWen
  拟写时间:Sat May 07 11:31:43 CST 2022
  完结时间:Mon May 09 15:29:39 CST 2022
  内容:MyActivitiTest2.LeaveData(staffId=A001, staffName=陈大文, days=3)
  审批耗时时长:PT52H3M50.993S
  ======
  已完成环节:填写请假申请,部门领导审批,HR领导审批
  最后审批信息:HR领导审批
    审批时间:Mon May 09 15:29:37 CST 2022
    审批人:HR用户
    批注:HR同意
    流程实例变量:{dept_leader=dept-group1,dept-group2, hr_leader=hr-group1, leave=MyActivitiTest2.LeaveData(staffId=A001, staffName=陈大文, days=3), form_id=task1-form-id, task1-var1=value1}
任务2:4ef3f562-cdb8-11ec-9560-3863bb4563c2
  状态:在单
  拟写人:jingdong
  拟写时间:Sat May 07 11:47:31 CST 2022
  内容:MyActivitiTest2.LeaveData(staffId=A002, staffName=京东, days=3)
  ======
  已完成环节:填写请假申请
  最后审批信息:填写请假申请
    审批时间:Sat May 07 11:47:31 CST 2022
    审批人:jingdong
    批注:拟写请假单:jingdong
    流程实例变量:{dept_leader=dept-group1,dept-group2, hr_leader=hr-group1, leave=MyActivitiTest2.LeaveData(staffId=A002, staffName=京东, days=3), form_id=task1-form-id, task1-var1=value1}
  ======
  下一个审批环节:部门领导审批
任务3:9730d90a-cdb8-11ec-a6a2-3863bb4563c2
  状态:在单
  拟写人:user3
  拟写时间:Sat May 07 11:49:10 CST 2022
  内容:MyActivitiTest2.LeaveData(staffId=A003, staffName=阿里, days=3)
  ======
  已完成环节:填写请假申请
  最后审批信息:填写请假申请
    审批时间:Sat May 07 11:49:10 CST 2022
    审批人:user3
    批注:拟写请假单:user3
    流程实例变量:{dept_leader=dept-group1,dept-group2, hr_leader=hr-group1, leave=MyActivitiTest2.LeaveData(staffId=A003, staffName=阿里, days=3), form_id=task1-form-888, task1-var1=value1}
  ======
  下一个审批环节:部门领导审批
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值