工作流 订阅
工作流(Workflow),指“业务过程的部分或整体在计算机应用环境下的自动化”。是对工作流程及其各操作步骤之间业务规则的抽象、概括描述。在计算机中,工作流属于计算机支持的协同工作(CSCW)的一部分。后者是普遍地研究一个群体如何在计算机的帮助下实现协同工作的。工作流主要解决的主要问题是:为了实现某个业务目标,利用计算机在多个参与者之间按某种预定规则自动传递文档、信息或者任务。工作流概念起源于生产组织和办公自动化领域,是针对日常工作中具有固定程序活动而提出的一个概念,目的是通过将工作分解成定义良好的任务或角色,按照一定的规则和过程来执行这些任务并对其进行监控,达到提高工作效率、更好的控制过程、增强对客户的服务、有效管理业务流程等目的。尽管工作流已经取得了相当的成就,但对工作流的定义还没有能够统一和明确。Georgakopoulos给出的工作流定义是:工作流是将一组任务组织起来以完成某个经营过程:定义了任务的触发顺序和触发条件,每个任务可以由一个或多个软件系统完成,也可以由一个或一组人完成,还可以由一个或多个人与软件系统协作完成。1993年工作流管理联盟(Workflow Management Coalition,WfMC)作为工作流管理的标准化组织而成立,标志着工作流技术逐步走向成熟。WfMC对工作流给出定义为:工作流是指一类能够完全自动执行的经营过程,根据一系列过程规则,将文档、信息或任务在不同的执行者之间进行传递与执行。 展开全文
工作流(Workflow),指“业务过程的部分或整体在计算机应用环境下的自动化”。是对工作流程及其各操作步骤之间业务规则的抽象、概括描述。在计算机中,工作流属于计算机支持的协同工作(CSCW)的一部分。后者是普遍地研究一个群体如何在计算机的帮助下实现协同工作的。工作流主要解决的主要问题是:为了实现某个业务目标,利用计算机在多个参与者之间按某种预定规则自动传递文档、信息或者任务。工作流概念起源于生产组织和办公自动化领域,是针对日常工作中具有固定程序活动而提出的一个概念,目的是通过将工作分解成定义良好的任务或角色,按照一定的规则和过程来执行这些任务并对其进行监控,达到提高工作效率、更好的控制过程、增强对客户的服务、有效管理业务流程等目的。尽管工作流已经取得了相当的成就,但对工作流的定义还没有能够统一和明确。Georgakopoulos给出的工作流定义是:工作流是将一组任务组织起来以完成某个经营过程:定义了任务的触发顺序和触发条件,每个任务可以由一个或多个软件系统完成,也可以由一个或一组人完成,还可以由一个或多个人与软件系统协作完成。1993年工作流管理联盟(Workflow Management Coalition,WfMC)作为工作流管理的标准化组织而成立,标志着工作流技术逐步走向成熟。WfMC对工作流给出定义为:工作流是指一类能够完全自动执行的经营过程,根据一系列过程规则,将文档、信息或任务在不同的执行者之间进行传递与执行。
信息
应用学科
计算机科学、管理学
适用领域
云计算
中文名
工作流
外文名
Workflow
工作流工作流类型
工作流(WorkFlow)就是工作流程的计算模型,即将工作流程中的工作如何前后组织在一起的逻辑和规则在计算机中以恰当的模型进行表示并对其实施计算。工作流要解决的主要问题是:为实现某个业务目标,在多个参与者之间,利用计算机,按某种预定规则自动传递工作流属于计算机支持的协同工作(Computer Supported Cooperative Work,CSCW)的一部分。后者是普遍地研究一个群体如何在计算机的帮助下实现协同工作的。许多公司采用纸张表单,手工传递的方式,一级一级审批签字,工作效率非常低下,对于统计报表功能则不能实现。而采用工作流软件,使用者只需在电脑上填写有关表单,会按照定义好的流程自动往下跑,下一级审批者将会收到相关资料,并可以根据需要修改、跟踪、管理、查询、统计、打印等,大大提高了效率,实现了知识管理,提升了公司的核心竞争力。 工作流2.0的定义是:实现工作过程管理的自动化、智能化和整合化。工作流2.0最主要的特征就是可以灵便的实现数据整合和数据统计,消除信息孤岛,既能实现OA办公系统内部工作流之间的数据整合,如借款与报销、预算与决算等,又能实现OA办公系统工作流与其他业务系统之间的数据整合,如HR、ERP、CRM等。工作流2.0能彻底的弥补工作流1.0的不足,它不但实现OA办公系统内部的数据整合,也实现OA办公系统和第三方应用系统之间的数据整合。如果给工作流1.0打上标签的话,那就是“无纸化、重复工作、流程孤岛、系统孤岛、数据孤岛”;工作流2.0对应的便是“智能化、效率质量提升、外部数据整合、消除信息孤岛、内部数据整合”。毫无疑问,工作流2.0更加智能,更加整合,能够实现数据的同步交换和共享的特征更受用户欢迎,能有效帮助企业简化多余流程,是未来工作流技术发展的方向。
收起全文
精华内容
下载资源
问答
  • 工作流

    万次阅读 2018-10-17 10:10:54
    一、 什么是工作流  以请假为例,现在大多数公司的请假流程是这样的  员工打电话(或网聊)向上级提出请假申请——上级口头同意——上级将请假记录下来——月底将请假记录上交公司——公司将请假录入电脑  ...

    一、 什么是工作流

      以请假为例,现在大多数公司的请假流程是这样的

      员工打电话(或网聊)向上级提出请假申请——上级口头同意——上级将请假记录下来——月底将请假记录上交公司——公司将请假录入电脑

      采用工作流技术的公司的请假流程是这样的

      员工使用账户登录系统——点击请假——上级登录系统点击允许

      就这样,一个请假流程就结束了

      有人会问,那上级不用向公司提交请假记录?公司不用将记录录入电脑?答案是,用的。但是这一切的工作都会在上级点击允许后自动运行!

      这就是工作流技术。

      Georgakopoulos给出的工作流定义是:工作流是将一组任务组织起来以完成某个经营过程:定义了任务的触发顺序和触发条件,每个任务可以由一个或多个软件系统完成,也可以由一个或一组人完成,还可以由一个或多个人与软件系统协作完 
       
       
      二、 工作流技术的优点 
       
      从上面的例子,很容易看出

      工作流系统,实现了工作流程的自动化,提高了企业运营效率、改善企业资源利用、提高企业运作的灵活性和适应性、提高量化考核业务处理的效率、减少浪费(时间就是金钱)。

      而手工处理工作流程,一方面无法对整个流程状况进行有效跟踪、了解,另一方面难免会出现人为的失误和时间上的延时导致效率低下,特别是无法进行量化统计,不利于查询、报表及绩效评估。

      三、 Java开发者会为什么要学Activity工作流 
       
      在Java领域,JBPM和Activity是两个主流的工作流系统,而Activity的出现无疑将会取代JBPM(Activity的开发者就是从Jbpm开发者出来的)。

      四、 Activity工作流学习要点

      1、1个插件

      在Eclipse中安装Activity插件,让你可以在Eclipse中绘制Activity工作流图

      2、1个引擎

      ProcessEngine对象,Activity工作流引擎。这是Activiti工作的核心。负责生成流程运行时的各种实例及数据、监控和管理流程的运行。

      所有的操作都是从获取引擎开始的,所以一般会把引擎作为全局变量

    ProcessEngine processEngine =ProcessEngines.getDefaultProcessEngine();
    1
      3、1个配置文件

      activiti.cfg.xml。Activiti核心配置文件,配置流程引擎创建工具的基本参数和数据库连接池参数

      4、5种数据库表

      Activiti的后台是有数据库的支持,所有的表都以ACT_开头。 第二部分是表示表的用途的两个字母标识。用途也和服务的API对应。

      ACT_RE_*: ‘RE’表示repository。 这个前缀的表包含了流程定义和流程静态资源(图片,规则,等等)。

      ACT_RU_*: ‘RU’表示runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。

      ACT_ID_*: ‘ID’表示identity。 这些表包含身份信息,比如用户,组等等。

      ACT_HI_*: ‘HI’表示history。 这些表包含历史数据,比如历史流程实例,变量,任务等等。

      ACT_GE_*: 通用数据,用于不同场景下,如存放资源文件。 
      
      5、23张表

      不同的表存放不同方面的数据,有流程定义表、任务结点表、流程变量表、任务历史表等等。 
       
       
      6、5项Service

      不同的Service类对应不同的功能。

      比如TaskService,是activiti的任务服务类。可以从这个类中获取任务的信息。

      而HistoryService,则是activiti的查询历史信息的类。在一个流程执行完成后,这个对象为我们提供查询历史信息。

      7、7项基本操作

        设计流程图(各种组件,如连线、用户任务、网关)

        流程定义增删改查

        流程变量增删改查

        启动流程定义

        任务增删改查

        完成任务

        历史信息查询

      学习7项基本操作时只需在JavaSE下执行即可,当然,做web项目的时候就要考虑和SSH等开发框架结合的问题。当然,只有基本操作学会了,综合开发并不难。 
    --------------------- 
    作者:莱克寇丁 
    来源:CSDN 
    原文:https://blog.csdn.net/zbdxcyg/article/details/78519773?utm_source=copy 
    版权声明:本文为博主原创文章,转载请附上博文链接!

    展开全文
  • 三分钟明白 Activiti工作流 -- java运用

    万次阅读 多人点赞 2017-06-14 18:03:03
    一、 什么是工作流 以请假为例,现在大多数公司的请假流程是这样的 员工打电话(或网聊)向上级提出请假申请——上级口头同意——上级将请假记录下来——月底将请假记录上交公司——公司将请假录入电脑 采用工作...

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到教程。

    一、 什么是工作流

    以请假为例,现在大多数公司的请假流程是这样的

    员工打电话(或网聊)向上级提出请假申请——上级口头同意——上级将请假记录下来——月底将请假记录上交公司——公司将请假录入电脑

    采用工作流技术的公司的请假流程是这样的

    员工使用账户登录系统——点击请假——上级登录系统点击允许

    就这样,一个请假流程就结束了

    有人会问,那上级不用向公司提交请假记录?公司不用将记录录入电脑?答案是,用的。但是这一切的工作都会在上级点击允许后自动运行!

    这就是工作流技术。

     

    Georgakopoulos给出的工作流定义是:工作流是将一组任务组织起来以完成某个经营过程:定义了任务的触发顺序和触发条件,每个任务可以由一个或多个软件系统完成,也可以由一个或一组人完成,还可以由一个或多个人与软件系统协作完

     

    二、 工作流技术的优点

    从上面的例子,很容易看出

    工作流系统,实现了工作流程的自动化,提高了企业运营效率、改善企业资源利用、提高企业运作的灵活性和适应性、提高量化考核业务处理的效率、减少浪费(时间就是金钱)。

    而手工处理工作流程,一方面无法对整个流程状况进行有效跟踪、了解,另一方面难免会出现人为的失误和时间上的延时导致效率低下,特别是无法进行量化统计,不利于查询、报表及绩效评估。

     

    三、 Java开发者会为什么要学Activiti 工作流

    在Java领域,JBPM和Activiti 是两个主流的工作流系统,而Activiti 的出现无疑将会取代JBPM(Activiti 的开发者就是从Jbpm开发者出来的)。

     

    四、 Activiti 工作流学习要点

     

    1. 1个插件

    在Eclipse中安装Activiti 插件,让你可以在Eclipse中绘制Activiti 工作流图

     

    2. 1个引擎

    ProcessEngine对象,Activiti 工作流引擎。这是Activiti工作的核心。负责生成流程运行时的各种实例及数据、监控和管理流程的运行。

    所有的操作都是从获取引擎开始的,所以一般会把引擎作为全局变量

    ProcessEngine processEngine =ProcessEngines.getDefaultProcessEngine();

     

    3. 1个配置文件

    activiti.cfg.xml。Activiti核心配置文件,配置流程引擎创建工具的基本参数和数据库连接池参数

     

    4. 5种数据库表

    Activiti的后台是有数据库的支持,所有的表都以ACT_开头。 第二部分是表示表的用途的两个字母标识。用途也和服务的API对应。

    ACT_RE_*: 'RE'表示repository。 这个前缀的表包含了流程定义和流程静态资源(图片,规则,等等)。

    ACT_RU_*: 'RU'表示runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。

    ACT_ID_*: 'ID'表示identity。 这些表包含身份信息,比如用户,组等等。

    ACT_HI_*: 'HI'表示history。 这些表包含历史数据,比如历史流程实例,变量,任务等等。

    ACT_GE_*: 通用数据,用于不同场景下,如存放资源文件。

     

    5. 23张表

    不同的表存放不同方面的数据,有流程定义表、任务结点表、流程变量表、任务历史表等等。

     

    6. 5项Service

    不同的Service类对应不同的功能。

    比如TaskService,是activiti的任务服务类。可以从这个类中获取任务的信息。

    而HistoryService,则是activiti的查询历史信息的类。在一个流程执行完成后,这个对象为我们提供查询历史信息。

     

    7. 7项基本操作

    设计流程图(各种组件,如连线、用户任务、网关)

    流程定义增删改查

    流程变量增删改查

    启动流程定义

    任务增删改查

    完成任务

    历史信息查询

     

     

     

     

    转自:http://www.it165.net/pro/html/201504/37443.html

    展开全文
  • Activiti7工作流+SpringBoot

    万次阅读 多人点赞 2018-12-10 14:13:39
    一、Activiti介绍: Activiti是基于Apache许可的开源BPM平台,创始人Tom Baeyens原是JBPM架构师,可以理解为与JBPM出自...是由Alfresco软件发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理、工作流、服务...

    一. Activiti相关概念

    1. Activiti介绍

        Activiti是基于Apache许可的开源BPM平台,创始人Tom Baeyens原是JBPM架构师,可以理解为与JBPM出自同一祖师爷。它提供了Eclipse插件,开发可以通过插件直接绘制业务流程图。基于Spring,ibatis等框架,并在此之上构建了非常清晰的开发框架。是由Alfresco软件发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理、工作流、服务协作等领域的一个开源的、灵活的、易扩展的可执行流程语言框架。 本文基于Activiti7的Activiti Core,基于Spring Boot做简单学习总结。(Activiti最新版本向微服务这边靠齐了,并分Activiti Core与Activiti Cloud两块,Activiti Cloud还没研究)

    2. 核心类

    2.1 ProcessEngine

        流程引擎的抽象,可以通过此类获取需要的所有服务。

    2.2 服务(Service)类

        通过ProcessEngine获取,Activiti将不同生命周期的服务封装在不同Service中,包括定义、部署、运行。通过服务类可获取相关生命周期中的服务信息。

    2.2.1 TaskService

        流程运行过程中,每个任务节点的相关操作接口,如complete,delete,delegate等。

    2.2.2 RepositoryService

        流程定义和部署相关的存储服务。

    2.2.3 RuntimeService

        流程运行时相关的服务,如根据流程好启动流程实例startProcessInstanceByKey。

    2.2.3 HistoryService

        历史记录相关服务接口。

    2.3 CommandContextIntercepter或CommandExecutor

        Activiti使用命令模式作为基础开发模式,如Service中调用的各个方法都对应相应的命令对象。Service将请求委托给命令对象,命令对象来命令接受者,接受者接收后执行并返回结果。而CommandContextIntercepter的作用是拦截所有命令,并在命令前后执行一些公共方法。

    2.4 核心业务对象

        org.activiti.engine.impl.persistence.entity包下的类,包括Task,ProcessInstance,Execution等。会根据不同职责实现相应接口的方法(如需要持久化则继承PersistentObject接口),与传统的实体类不同。

    3. 上下文组件(Context)

        用来保存生命周期比较长,全局性的信息,类似Application,主要包括如下三类。

    3.1 CommandContext

        命令上下文,保存每个命令必要的资源,如持久化需要的session。

    3.2 ProcessEngineConfigurationImpl

        流程引擎相关配置信息,整个引擎全局的配置信息,如数据源DataSource等。该对象为单例,在流程引擎创建的时候初始化。

    3.3 ExecutionContext

        持有ExecutionEntity对象。

    4. 持久化组件

        Activiti使用ibatis作OR映射,并在此基础上增加设计了自己的持久化框架。在流程引擎创建时初始化。顶层接口Session、SessionFactory。Session有两个实现类:DbSqlSession,负责sql表达式的执行。AbstractManager负责对象的持久化操作。SessionFactory有两个实现类:DbSqlSessionFactory负责DbSqlSession相关操作,GenericManagerFactory负责AbstractManager相关操作。

    5. Event-Listener组件

        Activiti允许客户代码介入流程执行,提供了事件监听组件。监听的事件类型可以分为TaskListener、JavaDelegate、Expression、ExecutionListener。ProcessEngineConfigurationImpl持有DelegateInterceptor的某个实例,方便调用handleInvocation。

    6. Cache组件

        DbSqlSession中有cache的实现,Activiti基于List和Map来做缓存。如查询时先查缓存,没有则直接查询并放入缓存。

    7. 异步执行组件

        Activiti可以执行任务,JobExecutor为启核心类,JobExecutor包含三个主要属性:JobAcquisitionThread,BlockingQueue,ThreadPoolExecutor。方法ProcessEngines在引擎启动时调用JobExecutor.start,JobAcquisitionThread 线程即开始工作,其run方法不断循环执行AcquiredJobs中的job,执行一次后线程等待一定时间直到超时或者JobExecutor.jobWasAdded方法,因为有新任务而被调用。

    8. PVM:Process Virtal Machine

        流程虚拟机API暴露了流程虚拟机的POJO核心,流程虚拟机API描述了一个工作流流程必备的组件,这些组件包括:
        PvmProcessDefinition:流程的定义,形象点说就是用户画的那个图。静态含义。
        PvmProcessInstance:流程实例,用户发起的某个PvmProcessDefinition的一个实例,动态含义。
        PvmActivity:流程中的一个节点
        PvmTransition:衔接各个节点之间的路径,形象点说就是图中各个节点之间的连接线。
        PvmEvent:流程执行过程中触发的事件

    二. Eclipse插件安装:

        我的Eclipse版本如下:
    在这里插入图片描述
        下载离线安装包(在线安装始终失败,应该出于网络限制),地址:http://www.activiti.org/designer/archived/activiti-designer-5.18.0.zip
        离线安装包安装安装依然提示相关包找不到,于是下载另外三个依赖包(org.eclipse.emf.transaction_1.4.0.v20100331-1738.jar、org.eclipse.emf.validation_1.7.0.201306111341.jar、org.eclipse.emf.workspace_1.5.1.v20120328-0001.jar,在Maven仓库可以找到),放到Eclipse的plugin目录下,继续安装,如下:
    在这里插入图片描述
        确定后勾选相关选项,完成安装,重启Eclipse,New->Other
    在这里插入图片描述
        出现以上标志,则安装完成。

    三. 项目搭建

    1. 新建Spring Boot工程

        (我的Eclipse已经安装Spring Boot插件)
    我的Eclipse已经安装Spring Boot插件
        然后Next->Next…->Finish即可,然后改application.properties为application.yml(个人习惯)
    在这里插入图片描述

    2. 引入Activiti相关依赖

        在pom属性中定义版本号,并添加Activiti相关依赖:

    <activiti-dependencies.version>7.0.56</activiti-dependencies.version>
    
    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>org.activiti.dependencies</groupId>
          <artifactId>activiti-dependencies</artifactId>
          <version>${activiti-dependencies.version}</version>
          <scope>import</scope>
          <type>pom</type>
        </dependency>
      </dependencies>
    </dependencyManagement>
    
    <dependency>
          <groupId>org.activiti</groupId>
          <artifactId>activiti-spring-boot-starter</artifactId>
    </dependency>
    

        由于Activiti默认使用H2数据库,所以需添加H2数据库支持(这里使用此SpringBoot版本默认1.4.197):

    <dependency>
          <groupId>com.h2database</groupId>
          <artifactId>h2</artifactId>
    </dependency>
    

        出现错误:Missing artifact org.activiti:activiti-spring-boot-starter:jar:7.0.56

    在这里插入图片描述
        添加私服仓库地址:

    <repositories>
    	    <repository>
    	      <id>alfresco</id>
    	      <name>Activiti Releases</name>
    	      <url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases/</url>
    	      <releases>
    	        <enabled>true</enabled>
    	      </releases>
    	    </repository>
    </repositories>
    

    在这里插入图片描述
        错误消失。

    3. 创建流程图

        在此版本Activiti+SpringBoot,默认加载/processes/目录下流程,于是在resources下新建processes目录,并在目录下new->Other,如下:
    在这里插入图片描述
        使用Activiti图编辑工具打开,创建如下流程(创建过程在此不介绍,就是右侧工具栏的运用):
    在这里插入图片描述

    4. 启动工程

        看日志:
    在这里插入图片描述
        从日志中可以看出,流程引擎已经默认创建,并可以看到使用的默认数据源是H2的数据源,我们创建的流程也已经部署。

    5. 修改配置

        在正常使用中,一般系统会有自己的数据库,而不会采用默认内存的H2数据库,这里以MySQL为例。修改application.yml。

    5.1 添加MySQL依赖

    <!--使用mysql数据库,导入mysql驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    

    5.2 修改数据库

    spring:
      ##数据库连接信息
      datasource:
        # 数据源配置
        url: jdbc:mysql://127.0.0.1:3306/activity?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: root
        driver-class-name: com.mysql.jdbc.Driver
        # SQLException: XAER_INVAL: Invalid arguments (or unsupported command)问题
        xa:
          properties:
            pinGlobalTxToPhysicalConnection: true
            useServerPrepStmts: true
    

    5.3 Activiti相关配置

      # 参考配置https://www.cnblogs.com/liaojie970/p/8857710.html
      activiti:
        # 自动建表
        database-schema: ACTIVITI
        database-schema-update: true
        history-level: full
        db-history-used: true
    

        注意:
        database-schema-update表示启动时检查数据库表,不存在则创建
        history-level表示哪种情况下使用历史表,这里配置为full表示全部记录历史,方便绘制流程图
        db-history-used为true表示使用历史表,如果不配置,则工程启动后可以检查数据库,只建立了17张表,历史表没有建立,则流程图及运行节点无法展示(暂未找到可行方式)

    5.4 附上application.yml完整配置:

    # 服务配置
    server:
      display-name: actdemo
      port: 8085
      
    # Spring相关配置
    spring:
      ##数据库连接信息
      datasource:
        # 数据源配置
        url: jdbc:mysql://127.0.0.1:3306/activity?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: 888
        driver-class-name: com.mysql.jdbc.Driver
        
        # SQLException: XAER_INVAL: Invalid arguments (or unsupported command)问题
        xa:
          properties:
            pinGlobalTxToPhysicalConnection: true
            useServerPrepStmts: true
    
      thymeleaf:
        mode: HTML
        encoding: utf-8
        # 禁用缓存
        cache: false
      application:
        # 注册应用名
        name: actdemo
      mvc:
        # 静态资源路径
        static-path-pattern: /static/**
      # 参考配置https://www.cnblogs.com/liaojie970/p/8857710.html
      activiti:
        # 自动建表
        database-schema: ACTIVITI
        database-schema-update: true
        history-level: full
        db-history-used: true
    

    5.5 启动工程

        观察日志,流程引擎已成功加载,并已使用MySQL数据库,如下:
    在这里插入图片描述
        再看数据库,已经创建25张表(老版本的Activiti创建表有手动执行SQL和通过调用流程引擎创建两种方式,该版本与SpringBoot整合后,启动默认创建需要的表):
    在这里插入图片描述
        注意:
        原SpringBoot工程使用版本2.1.1.RELEASE,启动始终失败,各种错误,后改为2.0.4.RELEASE版本,则启动正常。

    6. 编写实例

        本例子使用Thymeleaf做前端页面展示(SpringBoot推荐使用),并创建控制器Controller调用工作流接口与前端交互。

    6.1 引入Thymeleaf依赖,(前端使用Thymeleaf的配置已经在application.yml中)

    <!-- Thymeleaf依赖 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    

    6.2 创建Controller控制器

        创建启动流程方法,主要代码如下

    /**
    	 * <p>启动请假流程</p>
    	 * @return String 流程实例ID
    	 * @author FRH
    	 * @time 2018年12月10日上午11:03:36
    	 * @version 1.0
    	 */
    	@RequestMapping(value="/start")
    	@ResponseBody
    	public String start() {
    		// xml中定义的ID
    		String instanceKey = "leaveProcess";
    		logger.info("开启请假流程...");
    		
    		// 设置流程参数,开启流程
    		Map<String,Object> map = new HashMap<String,Object>();
            map.put("jobNumber","A1001");
            map.put("busData","bus data");
    		ProcessInstance instance = runtimeService.startProcessInstanceByKey(instanceKey, map);//使用流程定义的key启动流程实例,key对应helloworld.bpmn文件中id的属性值,使用key值启动,默认是按照最新版本的流程定义启动
    		
    		logger.info("启动流程实例成功:{}", instance);
    		logger.info("流程实例ID:{}", instance.getId());
    		logger.info("流程定义ID:{}", instance.getProcessDefinitionId());
    		
    		
    		//验证是否启动成功
    	    //通过查询正在运行的流程实例来判断
    	    ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
    	    //根据流程实例ID来查询
    	    List<ProcessInstance> runningList = processInstanceQuery.processInstanceId(instance.getProcessInstanceId()).list();
    	    logger.info("根据流程ID查询条数:{}", runningList.size());
    		
    	    // 返回流程ID
    		return instance.getId();
    	}
    

    6.3 流程跟踪与流程图展示

        Activiti流程图展示,使用流程图生成器,本例生成的流程图在页面使用如下方式展示即可:

    <embed src="/demo/showImg?instanceId=5070fd58-f859-11e8-a359-484d7ec5762d" style="display:block;width:1000px;height:450px" />
    

        引入相关工具包,版本使用默认版本7.0.65

    <!-- Activiti生成流程图 -->
    <dependency>
      <groupId>org.activiti</groupId>
      <artifactId>activiti-image-generator</artifactId>
    </dependency>
    

        调用输出流程图

    /**
    	 * <p>查看当前流程图</p>
    	 * @param instanceId 流程实例
    	 * @param response void 响应
    	 * @author FRH
    	 * @time 2018年12月10日上午11:14:12
    	 * @version 1.0
    	 */
    	@ResponseBody
    	@RequestMapping(value="/showImg")
    	public void showImg(String instanceId, HttpServletResponse response) {
    		/*
    		 * 参数校验
    		 */
    		logger.info("查看完整流程图!流程实例ID:{}", instanceId);
    		if(StringUtils.isBlank(instanceId)) return;
    		
    		
    		/*
    		 *  获取流程实例
    		 */
    		HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceId).singleResult();
    		if(processInstance == null) {
    			logger.error("流程实例ID:{}没查询到流程实例!", instanceId);
    			return;
    		}
    		
    		// 根据流程对象获取流程对象模型
    		BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
    		
    		
    		/*
    		 *  查看已执行的节点集合
    		 *  获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序
    		 */
    		// 构造历史流程查询
    		HistoricActivityInstanceQuery historyInstanceQuery = historyService.createHistoricActivityInstanceQuery().processInstanceId(instanceId);
    		// 查询历史节点
    		List<HistoricActivityInstance> historicActivityInstanceList = historyInstanceQuery.orderByHistoricActivityInstanceStartTime().asc().list();
    		if(historicActivityInstanceList == null || historicActivityInstanceList.size() == 0) {
    			logger.info("流程实例ID:{}没有历史节点信息!", instanceId);
    			outputImg(response, bpmnModel, null, null);
    			return;
    		}
    		// 已执行的节点ID集合(将historicActivityInstanceList中元素的activityId字段取出封装到executedActivityIdList)
    		List<String> executedActivityIdList = historicActivityInstanceList.stream().map(item -> item.getActivityId()).collect(Collectors.toList());
    		
    		/*
    		 *  获取流程走过的线
    		 */
    		// 获取流程定义
    		ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService).getDeployedProcessDefinition(processInstance.getProcessDefinitionId());
    		List<String> flowIds = ActivitiUtils.getHighLightedFlows(bpmnModel, processDefinition, historicActivityInstanceList);
    		
    		
    		/*
    		 * 输出图像,并设置高亮
    		 */
    		outputImg(response, bpmnModel, flowIds, executedActivityIdList);
    	}
    
    	/**
    	 * <p>输出图像</p>
    	 * @param response 响应实体
    	 * @param bpmnModel 图像对象
    	 * @param flowIds 已执行的线集合
    	 * @param executedActivityIdList void 已执行的节点ID集合
    	 * @author FRH
    	 * @time 2018年12月10日上午11:23:01
    	 * @version 1.0
    	 */
    	private void outputImg(HttpServletResponse response, BpmnModel bpmnModel, List<String> flowIds, List<String> executedActivityIdList) {
    		InputStream imageStream = null;
    		try {
    			imageStream = processDiagramGenerator.generateDiagram(bpmnModel, executedActivityIdList, flowIds, "宋体", "微软雅黑", "黑体", true, "png");
    			// 输出资源内容到相应对象
    			byte[] b = new byte[1024];
    			int len;
    			while ((len = imageStream.read(b, 0, 1024)) != -1) {
    				response.getOutputStream().write(b, 0, len);
    			}
    			response.getOutputStream().flush();
    		}catch(Exception e) {
    			logger.error("流程图输出异常!", e);
    		} finally { // 流关闭
    			StreamUtils.closeInputStream(imageStream);
    		}
    	}
    

        流程图工具类ActivitiUtils

    package com.mypro.activiti.utils;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.activiti.bpmn.model.BpmnModel;
    import org.activiti.bpmn.model.FlowNode;
    import org.activiti.bpmn.model.SequenceFlow;
    import org.activiti.engine.history.HistoricActivityInstance;
    import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
    
    /**
     * <p>Activiti工作流工具类</p>
     * @author FRH
     * @time 2018年12月10日上午11:26:02
     * @version 1.0
     */
    public class ActivitiUtils {
    	
    
    	/**
    	 * <p>获取流程走过的线</p>
    	 * @param bpmnModel 流程对象模型
    	 * @param processDefinitionEntity 流程定义对象
    	 * @param historicActivityInstances 历史流程已经执行的节点,并已经按执行的先后顺序排序
    	 * @return List<String> 流程走过的线
    	 * @author FRH
    	 * @time 2018年12月10日上午11:26:19
    	 * @version 1.0
    	 */
    	public static List<String> getHighLightedFlows(BpmnModel bpmnModel, ProcessDefinitionEntity processDefinitionEntity, List<HistoricActivityInstance> historicActivityInstances) {
    		// 用以保存高亮的线flowId
    		List<String> highFlows = new ArrayList<String>();
    		if(historicActivityInstances == null || historicActivityInstances.size() == 0) return highFlows;
    
    		// 遍历历史节点
    		for (int i = 0; i < historicActivityInstances.size() - 1; i++) {
    			// 取出已执行的节点
    			HistoricActivityInstance activityImpl_ = historicActivityInstances.get(i);
    
    			// 用以保存后续开始时间相同的节点
    			List<FlowNode> sameStartTimeNodes = new ArrayList<FlowNode>();
    
    			// 获取下一个节点(用于连线)
    			FlowNode sameActivityImpl = getNextFlowNode(bpmnModel, historicActivityInstances, i, activityImpl_);
    //			FlowNode sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i + 1).getActivityId());
    			
    			// 将后面第一个节点放在时间相同节点的集合里
    			if(sameActivityImpl != null) sameStartTimeNodes.add(sameActivityImpl);
    			
    			// 循环后面节点,看是否有与此后继节点开始时间相同的节点,有则添加到后继节点集合
    			for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) {
    				HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);// 后续第一个节点
    				HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);// 后续第二个节点
    				if (activityImpl1.getStartTime().getTime() != activityImpl2.getStartTime().getTime()) break;
    				
    				// 如果第一个节点和第二个节点开始时间相同保存
    				FlowNode sameActivityImpl2 = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityImpl2.getActivityId());
    				sameStartTimeNodes.add(sameActivityImpl2);
    			}
    			
    			// 得到节点定义的详细信息
    			FlowNode activityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i).getActivityId());
    			// 取出节点的所有出去的线,对所有的线进行遍历
    			List<SequenceFlow> pvmTransitions = activityImpl.getOutgoingFlows();
    			for (SequenceFlow pvmTransition : pvmTransitions) {
    				// 获取节点
    				FlowNode pvmActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(pvmTransition.getTargetRef());
    				
    				// 不是后继节点
    				if(!sameStartTimeNodes.contains(pvmActivityImpl)) continue;
    				
    				// 如果取出的线的目标节点存在时间相同的节点里,保存该线的id,进行高亮显示
    				highFlows.add(pvmTransition.getId());
    			}
    		}
    		
    		//返回高亮的线
    		return highFlows;
    	}
    
    
    
    	/**
    	 * <p>获取下一个节点信息</p>
    	 * @param bpmnModel 流程模型
    	 * @param historicActivityInstances 历史节点
    	 * @param i 当前已经遍历到的历史节点索引(找下一个节点从此节点后)
    	 * @param activityImpl_ 当前遍历到的历史节点实例
    	 * @return FlowNode 下一个节点信息
    	 * @author FRH
    	 * @time 2018年12月10日上午11:26:55
    	 * @version 1.0
    	 */
    	private static FlowNode getNextFlowNode(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances, int i, HistoricActivityInstance activityImpl_) {
    		// 保存后一个节点
    		FlowNode sameActivityImpl = null;
    		
    		// 如果当前节点不是用户任务节点,则取排序的下一个节点为后续节点
    		if(!"userTask".equals(activityImpl_.getActivityType())) {
    			// 是最后一个节点,没有下一个节点
    			if(i == historicActivityInstances.size()) return sameActivityImpl;
    			// 不是最后一个节点,取下一个节点为后继节点
    			sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i + 1).getActivityId());// 找到紧跟在后面的一个节点
    			// 返回
    			return sameActivityImpl;
    		}
    		
    		// 遍历后续节点,获取当前节点后续节点
    		for (int k = i + 1; k <= historicActivityInstances.size() - 1; k++) {
    			// 后续节点
    			HistoricActivityInstance activityImp2_ = historicActivityInstances.get(k);
    			// 都是userTask,且主节点与后续节点的开始时间相同,说明不是真实的后继节点
    			if("userTask".equals(activityImp2_.getActivityType()) && activityImpl_.getStartTime().getTime() == activityImp2_.getStartTime().getTime()) continue;
    			// 找到紧跟在后面的一个节点
    			sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(k).getActivityId());
    			break;
    		}
    		return sameActivityImpl;
    	}
    }
    
    

    6.4 附上DemoController完整代码

    package com.mypro.activiti.controller;
    
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.activiti.bpmn.model.BpmnModel;
    import org.activiti.engine.HistoryService;
    import org.activiti.engine.RepositoryService;
    import org.activiti.engine.RuntimeService;
    import org.activiti.engine.TaskService;
    import org.activiti.engine.history.HistoricActivityInstance;
    import org.activiti.engine.history.HistoricActivityInstanceQuery;
    import org.activiti.engine.history.HistoricProcessInstance;
    import org.activiti.engine.impl.RepositoryServiceImpl;
    import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
    import org.activiti.engine.runtime.ProcessInstance;
    import org.activiti.engine.runtime.ProcessInstanceQuery;
    import org.activiti.engine.task.Task;
    import org.activiti.image.ProcessDiagramGenerator;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import com.mypro.activiti.utils.ActivitiUtils;
    import com.mypro.activiti.utils.StreamUtils;
    
    /**
     * <p>Activiti控制器</p>
     * @author FRH
     * @time 2018年12月10日上午9:30:18
     * @version 1.0
     */
    @Controller
    @RequestMapping("/demo")
    public class DemoController {
    
    private static final Logger logger = LoggerFactory.getLogger(DemoController.class);
    	
    	/** 流程定义和部署相关的存储服务 */
    	@Autowired
    	private RepositoryService repositoryService;
    	
    	/** 流程运行时相关的服务 */
    	@Autowired
    	private RuntimeService runtimeService;
    	
    	/** 节点任务相关操作接口 */
    	@Autowired
    	private TaskService taskService;
    	
    	/** 流程图生成器 */
    	@Autowired
    	private ProcessDiagramGenerator processDiagramGenerator;
    
    	/** 历史记录相关服务接口 */
    	@Autowired
    	private HistoryService historyService;
    
    	
    	
    	/**
    	 * <p>跳转到测试主页面</p>
    	 * @return String 测试主页面
    	 * @author FRH
    	 * @time 2018年12月10日上午11:12:28
    	 * @version 1.0
    	 */
    	@RequestMapping(value="/toIndex.html")
    	public String toTestPage() {
    		return "/index";
    	}
    	
    	
    	
    	/**
    	 * <p>跳转到上级审核页面</p>
    	 * @return String 上级审核页面
    	 * @author FRH
    	 * @time 2018年12月5日下午2:31:42
    	 * @version 1.0
    	 */
    	@RequestMapping(value="/toLeave")
    	public String employeeLeave() {
    		return "/employeeLeave";
    	}
    	
    	
    	
    	/**
    	 * <p>启动请假流程(流程key即xml中定义的ID为leaveProcess)</p>
    	 * @return String 启动的流程ID
    	 * @author FRH
    	 * @time 2018年12月10日上午11:12:50
    	 * @version 1.0
    	 */
    	@RequestMapping(value="/start")
    	@ResponseBody
    	public String start() {
    		/*
    		 *  xml中定义的ID
    		 */
    		String instanceKey = "leaveProcess";
    		logger.info("开启请假流程...");
    		
    		
    		/*
    		 *  设置流程参数,开启流程
    		 */
    		Map<String,Object> map = new HashMap<String,Object>();
            map.put("jobNumber","A1001");
            map.put("busData","bus data");
    		ProcessInstance instance = runtimeService.startProcessInstanceByKey(instanceKey, map);//使用流程定义的key启动流程实例,key对应helloworld.bpmn文件中id的属性值,使用key值启动,默认是按照最新版本的流程定义启动
    		
    		logger.info("启动流程实例成功:{}", instance);
    		logger.info("流程实例ID:{}", instance.getId());
    		logger.info("流程定义ID:{}", instance.getProcessDefinitionId());
    		
    		
    		/*
    		 * 验证是否启动成功
    		 */
    	    //通过查询正在运行的流程实例来判断
    	    ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
    	    //根据流程实例ID来查询
    	    List<ProcessInstance> runningList = processInstanceQuery.processInstanceId(instance.getProcessInstanceId()).list();
    	    logger.info("根据流程ID查询条数:{}", runningList.size());
    		
    	    
    	    /*
    	     *  返回流程ID
    	     */
    		return instance.getId();
    	}
    	
    	
    	
    	/**
    	 * <p>查看当前流程图</p>
    	 * @param instanceId 流程实例
    	 * @param response void 响应
    	 * @author FRH
    	 * @time 2018年12月10日上午11:14:12
    	 * @version 1.0
    	 */
    	@ResponseBody
    	@RequestMapping(value="/showImg")
    	public void showImg(String instanceId, HttpServletResponse response) {
    		/*
    		 * 参数校验
    		 */
    		logger.info("查看完整流程图!流程实例ID:{}", instanceId);
    		if(StringUtils.isBlank(instanceId)) return;
    		
    		
    		/*
    		 *  获取流程实例
    		 */
    		HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceId).singleResult();
    		if(processInstance == null) {
    			logger.error("流程实例ID:{}没查询到流程实例!", instanceId);
    			return;
    		}
    		
    		// 根据流程对象获取流程对象模型
    		BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
    		
    		
    		/*
    		 *  查看已执行的节点集合
    		 *  获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序
    		 */
    		// 构造历史流程查询
    		HistoricActivityInstanceQuery historyInstanceQuery = historyService.createHistoricActivityInstanceQuery().processInstanceId(instanceId);
    		// 查询历史节点
    		List<HistoricActivityInstance> historicActivityInstanceList = historyInstanceQuery.orderByHistoricActivityInstanceStartTime().asc().list();
    		if(historicActivityInstanceList == null || historicActivityInstanceList.size() == 0) {
    			logger.info("流程实例ID:{}没有历史节点信息!", instanceId);
    			outputImg(response, bpmnModel, null, null);
    			return;
    		}
    		// 已执行的节点ID集合(将historicActivityInstanceList中元素的activityId字段取出封装到executedActivityIdList)
    		List<String> executedActivityIdList = historicActivityInstanceList.stream().map(item -> item.getActivityId()).collect(Collectors.toList());
    		
    		/*
    		 *  获取流程走过的线
    		 */
    		// 获取流程定义
    		ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService).getDeployedProcessDefinition(processInstance.getProcessDefinitionId());
    		List<String> flowIds = ActivitiUtils.getHighLightedFlows(bpmnModel, processDefinition, historicActivityInstanceList);
    		
    		
    		/*
    		 * 输出图像,并设置高亮
    		 */
    		outputImg(response, bpmnModel, flowIds, executedActivityIdList);
    	}
    	
    
    
    	/**
    	 * <p>员工提交申请</p>
    	 * @param request 请求
    	 * @return String 申请受理结果
    	 * @author FRH
    	 * @time 2018年12月10日上午11:15:09
    	 * @version 1.0
    	 */
    	@RequestMapping(value="/employeeApply")
    	@ResponseBody
    	public String employeeApply(HttpServletRequest request){
    		/*
    		 * 获取请求参数
    		 */
    		String taskId = request.getParameter("taskId"); // 任务ID
    		String jobNumber = request.getParameter("jobNumber"); // 工号
    		String leaveDays = request.getParameter("leaveDays"); // 请假天数
    		String leaveReason = request.getParameter("leaveReason"); // 请假原因
    		
    		
    		/*
    		 *  查询任务
    		 */
    		Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
    		if(task == null) {
    			logger.info("任务ID:{}查询到任务为空!", taskId);
    			return "fail";
    		}
    
    		
    		/*
    		 * 参数传递并提交申请
    		 */
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("days", leaveDays);
            map.put("date", new Date());
            map.put("reason", leaveReason);
            map.put("jobNumber", jobNumber);
            taskService.complete(task.getId(), map);
            logger.info("执行【员工申请】环节,流程推动到【上级审核】环节");
            
            /*
             * 返回成功
             */
    		return "success";
    	}
    	
    	
    	/**
    	 * <p>跳转到上级审核页面</p>
    	 * @return String 页面
    	 * @author FRH
    	 * @time 2018年12月5日下午2:31:42
    	 * @version 1.0
    	 */
    	@RequestMapping(value="/viewTask")
    	public String toHigherAudit(String taskId, HttpServletRequest request) {
    		/*
    		 * 获取参数
    		 */
    		logger.info("跳转到任务详情页面,任务ID:{}", taskId);
    		if(StringUtils.isBlank(taskId)) return "/higherAudit";
    		
    		
    		/*
    		 *  查看任务详细信息
    		 */
    		Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
    		if(task == null) {
    			logger.info("任务ID:{}不存在!", taskId);
    			return "/higherAudit";
    		}
    		
    		
    		/*
    		 * 完成任务
    		 */
    		Map<String, Object> paramMap = taskService.getVariables(taskId);
    		request.setAttribute("task", task);
    		request.setAttribute("paramMap", paramMap);
    		return "higherAudit";
    	}
    	
    	
    	
    	/**
    	 * <p>跳转到部门经理审核页面</p>
    	 * @param taskId 任务ID
    	 * @param request 请求
    	 * @return String 响应页面
    	 * @author FRH
    	 * @time 2018年12月6日上午9:54:34
    	 * @version 1.0
    	 */
    	@RequestMapping(value="/viewTaskManager")
    	public String viewTaskManager(String taskId, HttpServletRequest request) {
    		/*
    		 * 获取参数
    		 */
    		logger.info("跳转到任务详情页面,任务ID:{}", taskId);
    		if(StringUtils.isBlank(taskId)) return "/manageAudit";
    		
    		
    		/*
    		 *  查看任务详细信息
    		 */
    		Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
    		if(task == null) {
    			logger.info("任务ID:{}不存在!", taskId);
    			return "/manageAudit";
    		}
    		
    		
    		/*
    		 * 完成任务
    		 */
    		Map<String, Object> paramMap = taskService.getVariables(taskId);
    		request.setAttribute("task", task);
    		request.setAttribute("paramMap", paramMap);
    		return "manageAudit";
    	}
    	
    	
    
    	/**
    	 * <p>上级审核</p>
    	 * @param request 请求
    	 * @return String 受理结果
    	 * @author FRH
    	 * @time 2018年12月10日上午11:19:44
    	 * @version 1.0
    	 */
    	@ResponseBody
    	@RequestMapping(value="/higherLevelAudit")
    	public String higherLevelAudit(HttpServletRequest request) {
    		/*
    		 * 获取请求参数
    		 */
    		String taskId = request.getParameter("taskId");
    		String higherLevelOpinion = request.getParameter("sug");
    		String auditStr = request.getParameter("audit");
    		logger.info("上级审核任务ID:{}", taskId);
    		if(StringUtils.isBlank(taskId)) return "fail";
    
    		
    		/*
    		 * 查找任务
    		 */
    		Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
    		if(task == null) {
    			logger.info("审核任务ID:{}查询到任务为空!", taskId);
    			return "fail";
    		}
    		
    		
    		/*
    		 * 设置局部变量参数,完成任务
    		 */
    		Map<String, Object> map = new HashMap<String, Object>();
    		map.put("audit", "1".equals(auditStr) ? false : true);
    		map.put("higherLevelOpinion", higherLevelOpinion);
    		taskService.complete(taskId, map);
    		return "success";
    	}
    	
    	
    	
    	/**
    	 * <p>部门经理审核</p>
    	 * @param request 请求
    	 * @return String 受理结果
    	 * @author FRH
    	 * @time 2018年12月10日上午11:20:44
    	 * @version 1.0
    	 */
    	@ResponseBody
    	@RequestMapping(value="/divisionManagerAudit")
    	public String divisionManagerAudit(HttpServletRequest request) {
    		/*
    		 * 获取请求参数
    		 */
    		String taskId = request.getParameter("taskId");
    		String opinion = request.getParameter("sug");
    		String auditStr = request.getParameter("audit");
    		logger.info("上级审核任务ID:{}", taskId);
    		if(StringUtils.isBlank(taskId)) return "fail";
    
    		
    		/*
    		 * 查找任务
    		 */
    		Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
    		if(task == null) {
    			logger.info("审核任务ID:{}查询到任务为空!", taskId);
    			return "fail";
    		}
    		
    		
    		/*
    		 * 设置局部变量参数,完成任务
    		 */
    		Map<String, Object> map = new HashMap<String, Object>();
    		map.put("audit", "1".equals(auditStr) ? false : true);
    		map.put("managerOpinion", opinion);
    		taskService.complete(taskId, map);
    		return "success";
    	}
    	
    	
    	/**
    	 * <p>查看任务</p>
    	 * @param request 请求
    	 * @return String  任务展示页面
    	 * @author FRH
    	 * @time 2018年12月10日上午11:21:33
    	 * @version 1.0
    	 */
    	@RequestMapping(value="/toShowTask")
    	public String toShowTask(HttpServletRequest request) {
    		/*
    		 * 获取请求参数
    		 */
    		List<Task> taskList = taskService.createTaskQuery().list();
    		if(taskList == null || taskList.size() == 0) {
    			logger.info("查询任务列表为空!");
    			return "/task";
    		}
    		
    		
    		/*
    		 * 查询所有任务,并封装
    		 */
    		List<Map<String, String>> resultList = new ArrayList<Map<String, String>>();
    		for(Task task : taskList) {
    			Map<String, String> map = new HashMap<String, String>();
    			map.put("taskId", task.getId());
    			map.put("name", task.getName());
    			map.put("createTime", task.getCreateTime().toString());
    			map.put("assignee", task.getAssignee());
    			map.put("instanceId", task.getProcessInstanceId());
    			map.put("executionId", task.getExecutionId());
    			map.put("definitionId", task.getProcessDefinitionId());
    			resultList.add(map);
    		}
    		
    		
    		/*
    		 * 返回结果
    		 */
    		logger.info("返回集合:{}", resultList.toString());
    		request.setAttribute("resultList", resultList);
    		return "/task";
    	}
    	
    
    
    	/**
    	 * <p>输出图像</p>
    	 * @param response 响应实体
    	 * @param bpmnModel 图像对象
    	 * @param flowIds 已执行的线集合
    	 * @param executedActivityIdList void 已执行的节点ID集合
    	 * @author FRH
    	 * @time 2018年12月10日上午11:23:01
    	 * @version 1.0
    	 */
    	private void outputImg(HttpServletResponse response, BpmnModel bpmnModel, List<String> flowIds, List<String> executedActivityIdList) {
    		InputStream imageStream = null;
    		try {
    			imageStream = processDiagramGenerator.generateDiagram(bpmnModel, executedActivityIdList, flowIds, "宋体", "微软雅黑", "黑体", true, "png");
    			// 输出资源内容到相应对象
    			byte[] b = new byte[1024];
    			int len;
    			while ((len = imageStream.read(b, 0, 1024)) != -1) {
    				response.getOutputStream().write(b, 0, len);
    			}
    			response.getOutputStream().flush();
    		}catch(Exception e) {
    			logger.error("流程图输出异常!", e);
    		} finally { // 流关闭
    			StreamUtils.closeInputStream(imageStream);
    		}
    	}
    	
    
    
    	/**
    	 * <p>判断流程是否完成</p>
    	 * @param processInstanceId 流程实例ID
    	 * @return boolean 已完成-true,未完成-false
    	 * @author FRH
    	 * @time 2018年12月10日上午11:23:26
    	 * @version 1.0
    	 */
    	public boolean isFinished(String processInstanceId) {
            return historyService.createHistoricProcessInstanceQuery().finished().processInstanceId(processInstanceId).count() > 0;
        }
    	
    }
    
    

    7. 效果

    在这里插入图片描述
        看出Activiti默认使用Spring的security,添加配置,关闭安全认证,如下:

    # 关闭activiti登录验证
    security:
      basic:
        enabled: false
    

        重启后继续访问,可正常进入首页(如还是无法进入首页,请添加@EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class}),或者从pom.xml中移除Security包):
    在这里插入图片描述
        点击我要请假后,得到流程实例ID,再查看流程图,如下:

    在这里插入图片描述

        附上首页/index.html代码,其它页面相比比较简单:

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head>
    	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    	<meta name="viewport" content="width=device-width, initial-scale=1">
    	<title>首页</title>
    	<script th:src="@{/static/jquery.min.js}"></script>
    	<script type="text/javascript">
    		$(function(){
    			// 点击菜单
    			$(".show-page").bind("click", function(){
    				$(".main-body").html("");
    				$(".result-div").html("");
    				var url = $(this).attr("url");
    				
    				$.ajax({
    					async : false,
    					cache : false,
    					type : 'POST',
    					url : url,
    					dataType : "html",
    					error : function() {
    						alert('请求失败');
    					},
    					success : function(data) {
    						$(".result-div").html(data);
    					}
    				});
    			});
    			
    			// 点击我要请假,开启流程
    			$(".show-instance").bind("click", function(){
    				$(".main-body").html("");
    				$(".result-div").html("");
    				var url = $(this).attr("url");
    				
    				$.ajax({
    					async : false,
    					cache : false,
    					type : 'POST',
    					url : url,
    					dataType : "html",
    					error : function() {
    						alert('请求失败');
    					},
    					success : function(data) {
    						$("input[name='instanceId']").val(data);
    					}
    				});
    			});
    			
    			// 绑定查看流程图
    			$(".show-img").bind("click", function(){
    				var instanceId = $("input[name='instanceId']").val();
    				if(instanceId == "") {
    					alert("暂无流程!");
    					return;
    				}
    				var imgHtml = '<embed src="/demo/showImg?instanceId=' + instanceId + '" style="display:block;width:1000px;height:450px" />';
    				$(".result-div").html(imgHtml);
    			});
    			
    			// 查看任务
    			$(".show-task").bind("click", function(){
    				$.ajax({
    					async : false,
    					cache : false,
    					type : 'POST',
    					url : "/demo/toShowTask",
    					data : {"aaabbbccc":"aa"},
    					dataType : "html",
    					error : function() {
    						alert('请求失败');
    					},
    					success : function(data) {
    						$(".result-div").html(data);
    					}
    				});
    			});
    			
    		});
    		
    		/**
    		 * 员工提交申请
    		 */
    		function toLeave() {
    			$.ajax({
    				async : false,
    				cache : false,
    				type : 'POST',
    				url : "/demo/employeeApply",
    				dataType: "text",
    				data: $(".employee-leave").serialize(),
    				error : function() {
    					alert('请求失败');
    				},
    				success : function(data) {
    					alert(data);
    				}
    			});
    		}
    		
    		/**
    		 * 上级审核
    		 */
    		function higherAudit() {
    			$.ajax({
    				async : false,
    				cache : false,
    				type : 'POST',
    				url : "/demo/higherLevelAudit",
    				dataType: "text",
    				data: $(".higher-audit").serialize(),
    				error : function() {
    					alert('请求失败');
    				},
    				success : function(data) {
    					alert(data);
    				}
    			});
    		}
    		
    		/**
    		 * 部门经理审核
    		 */
    		function managerAudit() {
    			$.ajax({
    				async : false,
    				cache : false,
    				type : 'POST',
    				url : "/demo/divisionManagerAudit",
    				dataType: "text",
    				data: $(".manager-audit").serialize(),
    				error : function() {
    					alert('请求失败');
    				},
    				success : function(data) {
    					alert(data);
    				}
    			});
    		}
    		
    		/**
    		 * 上级审核
    		 */
    		function viewTask(taskId, name) {
    			var url = "/demo/viewTask";
    			if(name != "上级审核") {
    				url = "/demo/viewTaskManager";
    			}
    			
    			
    			$.ajax({
    				async : false,
    				cache : false,
    				type : 'POST',
    				url : url,
    				data : {"taskId" : taskId},
    				dataType : "html",
    				error : function() {
    					alert('请求失败');
    				},
    				success : function(data) {
    					$(".result-div").html(data);
    				}
    			});
    		}
    	</script>
    </head>
    <body>
    	<!-- 菜单栏 -->
    	<div class="main-menu">
    		<button class="show-instance" url="/demo/start">我要请假</button>
    		<button class="show-page" url="/demo/toLeave">开始填单</button>
    		<button class="show-img">查看流程图</button>
    		<button class="show-task">查看任务</button>
    	</div>
    	<br/>
    		流程实例ID:<input type="text" name="instanceId"/>
    	<br/>
    	<!-- 操作栏 -->
    	<div class="main-body">
    		
    	</div>	
    	<br/>
    	<!-- 结果栏 -->
    	<div class="result-div">
    		<embed src="/demo/showImg?instanceId=5070fd58-f859-11e8-a359-484d7ec5762d" style="display:block;width:1000px;height:450px" />
    		<br>
    		<!-- <img src="/static/leave-process.png"/> -->
    	</div>
    </body>
    </html>
    
    展开全文
  • 采用springboot+flowable快速实现工作流

    万次阅读 多人点赞 2018-04-07 23:04:21
    工作流框架大家一定不陌生,各种OA系统里我们常常用到。 对于JAVA领域来说一说起工作流框架第一浮现我在脑海中的便是大名鼎鼎的Activiti了。很久以前学习Activiti框架时我也曾记录过一篇文章。见链接:工作流框架...

    前言    

        工作流框架大家一定不陌生,各种OA系统里我们常常用到。    

        对于JAVA领域来说一说起工作流框架第一浮现我在脑海中的便是大名鼎鼎的Activiti了。很久以前学习Activiti框架时我也曾记录过一篇文章。见链接:工作流框架Activiti常用功能初探  尽管当时只是学习了一下在之后的相关工作和项目中并没有用到,通过学习后了解了下, 仅对于知识广度进行了扩宽。

    最近在一个开源项目里见到有使用另一个工做流框架:flowable 。简单用了下感觉还是挺方便的,于是乎决定还是要来使用下并在此做下记录,便于后面用到时可以“拿来主义”,哈哈!

    什么是flowable?

    对于flowable是什么以及关于此框架的具体信息可以参看此项目的官方文档:https://www.flowable.org/docs/userguide/index.html

    官网对于此项目如何使用有非常详细的描述,只是目前还没有对应的中文文档。

    Flowable is a light-weight business process engine written in Java.这是官网文档对此框架的完美解释:Flowable是一个用java语言写的轻量级工作流引擎。

    在简单了解flowable后与activiti框架相比的第一感觉就是开发方便快速,易与springBoot等各种框架快速整合。如果项目中需要快速实现一些工作流的相关功能那么用此框架是一个不错的选择。

    使用版本

        用测试方便,这里都使用springBoot和flowable最新的稳定版本

        springBoot版本:2.0.1.RELEASE

         flowable版本:6.3.0

    Flowable与springBoot项目整合

    添加依赖

    将flowable的依赖加入到POM中即可,flowable使用需要一个数据库,这里为了方便我选择mysql

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--flowable工作流依赖-->
            <dependency>
                <groupId>org.flowable</groupId>
                <artifactId>flowable-spring-boot-starter</artifactId>
                <version>6.3.0</version>
            </dependency>
            <!--mysql依赖-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.45</version>
            </dependency>
        </dependencies>        <dependency>
                <groupId>org.flowable</groupId>
                <artifactId>flowable-spring-boot-starter</artifactId>
                <version>6.3.0</version>
            </dependency>
            <!--mysql依赖-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.45</version>
            </dependency>
        </dependencies>

    flowable配置

    测试方便flowable配置为默认的即可。为了测试时方便看日志信息,我这里将flowable的定时job功能暂时关闭,其他的都用默认的

    当然记得要添加一个数据源,我这里添加的mysql,并且记得建好对应的mysql库,如果没有建就自己建一个吧

    like this:

    CREATE DATABASE  `flowable-spring-boot` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

     

    spring:
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/flowable-spring-boot?characterEncoding=UTF-8
        username: root
        password: root
    flowable:
    #关闭定时任务JOB
      async-executor-activate: false

    这样操作后,flowable与springBoot的整个就完成了! 个人非常方便!

    然后就可以运行了,初次运行时flowable会将自动执行flowable中的初始化脚本完成工作流所需要的数据表的建立,如果指定的数据库中还未创建过flowable的相关数据表的话。

    定义流程文件

    上面已经完成了flowable与springboot的整合了,接下来就可以使用此框架进行流程需要开发了!

    同样在flowable官方文档中对于流程文件它有这样的建议:

    The Flowable engine expects processes to be defined in the BPMN 2.0 format, which is an XML standard that is widely accepted in the industry. 

    flowable建议采用业界标准BPMN2.0的XML来描述需要定义的工作流。

    那么BPMN这个流程文件应该怎么写呢?

    Typically, such a process definition is modeled with a visual modeling tool, such as the Flowable Designer (Eclipse) or the Flowable Modeler (web application).

    上官方文档中有看到这样的描述后即便我不会写也不怕了。通常都是通过专门的流程建模工具来画出来的,可以用Eclipse里的流程插件来画。同时Flowable也提供了对应的web管理台可以对流程文件进行创建。详见: Flowable UI applications

    为了方便测试,这里采用一个开源项目中的流程文件,其描述如下:

    <?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:flowable="http://flowable.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.flowable.org/processdef">
        <process id="Expense" name="ExpenseProcess" isExecutable="true">
            <documentation>报销流程</documentation>
            <startEvent id="start" name="开始"></startEvent>
            <userTask id="fillTask" name="出差报销" flowable:assignee="${taskUser}">
                <extensionElements>
                    <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler">
                        <![CDATA[false]]></modeler:initiator-can-complete>
                </extensionElements>
            </userTask>
            <exclusiveGateway id="judgeTask"></exclusiveGateway>
            <userTask id="directorTak" name="经理审批">
                <extensionElements>
                    <flowable:taskListener event="create"
                                           class="com.haiyang.flowable.listener.ManagerTaskHandler"></flowable:taskListener>
                </extensionElements>
            </userTask>
            <userTask id="bossTask" name="老板审批">
                <extensionElements>
                    <flowable:taskListener event="create"
                                           class="com.haiyang.flowable.listener.BossTaskHandler"></flowable:taskListener>
                </extensionElements>
            </userTask>
            <endEvent id="end" name="结束"></endEvent>
            <sequenceFlow id="directorNotPassFlow" name="驳回" sourceRef="directorTak" targetRef="fillTask">
                <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='驳回'}]]></conditionExpression>
            </sequenceFlow>
            <sequenceFlow id="bossNotPassFlow" name="驳回" sourceRef="bossTask" targetRef="fillTask">
                <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='驳回'}]]></conditionExpression>
            </sequenceFlow>
            <sequenceFlow id="flow1" sourceRef="start" targetRef="fillTask"></sequenceFlow>
            <sequenceFlow id="flow2" sourceRef="fillTask" targetRef="judgeTask"></sequenceFlow>
            <sequenceFlow id="judgeMore" name="大于500元" sourceRef="judgeTask" targetRef="bossTask">
                <conditionExpression xsi:type="tFormalExpression"><![CDATA[${money > 500}]]></conditionExpression>
            </sequenceFlow>
            <sequenceFlow id="bossPassFlow" name="通过" sourceRef="bossTask" targetRef="end">
                <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='通过'}]]></conditionExpression>
            </sequenceFlow>
            <sequenceFlow id="directorPassFlow" name="通过" sourceRef="directorTak" targetRef="end">
                <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='通过'}]]></conditionExpression>
            </sequenceFlow>
            <sequenceFlow id="judgeLess" name="小于500元" sourceRef="judgeTask" targetRef="directorTak">
                <conditionExpression xsi:type="tFormalExpression"><![CDATA[${money <= 500}]]></conditionExpression>
            </sequenceFlow>
        </process>
        <bpmndi:BPMNDiagram id="BPMNDiagram_Expense">
            <bpmndi:BPMNPlane bpmnElement="Expense" id="BPMNPlane_Expense">
                <bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
                    <omgdc:Bounds height="30.0" width="30.0" x="285.0" y="135.0"></omgdc:Bounds>
                </bpmndi:BPMNShape>
                <bpmndi:BPMNShape bpmnElement="fillTask" id="BPMNShape_fillTask">
                    <omgdc:Bounds height="80.0" width="100.0" x="405.0" y="110.0"></omgdc:Bounds>
                </bpmndi:BPMNShape>
                <bpmndi:BPMNShape bpmnElement="judgeTask" id="BPMNShape_judgeTask">
                    <omgdc:Bounds height="40.0" width="40.0" x="585.0" y="130.0"></omgdc:Bounds>
                </bpmndi:BPMNShape>
                <bpmndi:BPMNShape bpmnElement="directorTak" id="BPMNShape_directorTak">
                    <omgdc:Bounds height="80.0" width="100.0" x="735.0" y="110.0"></omgdc:Bounds>
                </bpmndi:BPMNShape>
                <bpmndi:BPMNShape bpmnElement="bossTask" id="BPMNShape_bossTask">
                    <omgdc:Bounds height="80.0" width="100.0" x="555.0" y="255.0"></omgdc:Bounds>
                </bpmndi:BPMNShape>
                <bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
                    <omgdc:Bounds height="28.0" width="28.0" x="771.0" y="281.0"></omgdc:Bounds>
                </bpmndi:BPMNShape>
                <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
                    <omgdi:waypoint x="315.0" y="150.0"></omgdi:waypoint>
                    <omgdi:waypoint x="405.0" y="150.0"></omgdi:waypoint>
                </bpmndi:BPMNEdge>
                <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
                    <omgdi:waypoint x="505.0" y="150.16611295681062"></omgdi:waypoint>
                    <omgdi:waypoint x="585.4333333333333" y="150.43333333333334"></omgdi:waypoint>
                </bpmndi:BPMNEdge>
                <bpmndi:BPMNEdge bpmnElement="judgeLess" id="BPMNEdge_judgeLess">
                    <omgdi:waypoint x="624.5530726256983" y="150.44692737430168"></omgdi:waypoint>
                    <omgdi:waypoint x="735.0" y="150.1392757660167"></omgdi:waypoint>
                </bpmndi:BPMNEdge>
                <bpmndi:BPMNEdge bpmnElement="directorNotPassFlow" id="BPMNEdge_directorNotPassFlow">
                    <omgdi:waypoint x="785.0" y="110.0"></omgdi:waypoint>
                    <omgdi:waypoint x="785.0" y="37.0"></omgdi:waypoint>
                    <omgdi:waypoint x="455.0" y="37.0"></omgdi:waypoint>
                    <omgdi:waypoint x="455.0" y="110.0"></omgdi:waypoint>
                </bpmndi:BPMNEdge>
                <bpmndi:BPMNEdge bpmnElement="bossPassFlow" id="BPMNEdge_bossPassFlow">
                    <omgdi:waypoint x="655.0" y="295.0"></omgdi:waypoint>
                    <omgdi:waypoint x="771.0" y="295.0"></omgdi:waypoint>
                </bpmndi:BPMNEdge>
                <bpmndi:BPMNEdge bpmnElement="judgeMore" id="BPMNEdge_judgeMore">
                    <omgdi:waypoint x="605.4340277777778" y="169.56597222222223"></omgdi:waypoint>
                    <omgdi:waypoint x="605.1384083044983" y="255.0"></omgdi:waypoint>
                </bpmndi:BPMNEdge>
                <bpmndi:BPMNEdge bpmnElement="directorPassFlow" id="BPMNEdge_directorPassFlow">
                    <omgdi:waypoint x="785.0" y="190.0"></omgdi:waypoint>
                    <omgdi:waypoint x="785.0" y="281.0"></omgdi:waypoint>
                </bpmndi:BPMNEdge>
                <bpmndi:BPMNEdge bpmnElement="bossNotPassFlow" id="BPMNEdge_bossNotPassFlow">
                    <omgdi:waypoint x="555.0" y="295.0"></omgdi:waypoint>
                    <omgdi:waypoint x="455.0" y="295.0"></omgdi:waypoint>
                    <omgdi:waypoint x="455.0" y="190.0"></omgdi:waypoint>
                </bpmndi:BPMNEdge>
            </bpmndi:BPMNPlane>
        </bpmndi:BPMNDiagram>
    </definitions>

    其中的两个代理类为:

    import org.flowable.engine.delegate.TaskListener;
    import org.flowable.task.service.delegate.DelegateTask;
    
    public class ManagerTaskHandler implements TaskListener {
    
        @Override
        public void notify(DelegateTask delegateTask) {
            delegateTask.setAssignee("经理");
        }
    
    }
    public class BossTaskHandler implements TaskListener {
    
        @Override
        public void notify(DelegateTask delegateTask) {
            delegateTask.setAssignee("老板");
        }
    
    }

    为了方便,也可以去掉这两个JAVA类,将其对应的task改写为如下的形式:

    <userTask id="holidayApprovedTask" name="Holiday approved" flowable:assignee="${employee}"/>

    尽管上面的BPMN文件很长,但放心,毕竟那是通过相关的工具生成出来的,对于核心的逻辑部分也很少(主要在process 标签内) ,如需要详细了解的可自行学习下BPMN的标签即可!当然,在flowable的使用文档中也有相关的描述,详见:Creating a ProcessEngine

    如上定义好一个流程文件后,将其命令为ExpenseProcess.bpmn20.xml并将其放于项目中的resource目录下的processes(如此目录不存在自行创建)目录下就可以了。

    like this:

    这样当此框架启动的时候它会默认加载resource目录下的processes时就可以将此流程配置加载到数据库进行持久化了

    测试controller

    为了方便这里通过一个controller来完成此DEMO的快速编写

    @Controller
    @RequestMapping(value = "expense")
    public class ExpenseController {
        @Autowired
        private RuntimeService runtimeService;
        @Autowired
        private TaskService taskService;
        @Autowired
        private RepositoryService repositoryService;
        @Autowired
        private ProcessEngine processEngine;
    
    /***************此处为业务代码******************/
    }

    写一个controller,并注入由flowable框架启动时自动注册的几个bean,下面的功能将会用到!

    开始流程

        /**
         * 添加报销
         *
         * @param userId    用户Id
         * @param money     报销金额
         * @param descption 描述
         */
        @RequestMapping(value = "add")
        @ResponseBody
        public String addExpense(String userId, Integer money, String descption) {
            //启动流程
            HashMap<String, Object> map = new HashMap<>();
            map.put("taskUser", userId);
            map.put("money", money);
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("Expense", map);
            return "提交成功.流程Id为:" + processInstance.getId();
        }

    上面的代码通过接收用户的一个请求传入用户的ID和金额以及描述信息来开启一个报销流程,并返回给用户这个流程的Id

    查询流程列表,待办列表

        /**
         * 获取审批管理列表
         */
        @RequestMapping(value = "/list")
        @ResponseBody
        public Object list(String userId) {
            List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).orderByTaskCreateTime().desc().list();
            for (Task task : tasks) {
                System.out.println(task.toString());
            }
            return tasks.toArray().toString();
        }

    通过上面的代码获取出此用户需要处理的流程

    批准,同意

        /**
         * 批准
         *
         * @param taskId 任务ID
         */
        @RequestMapping(value = "apply")
        @ResponseBody
        public String apply(String taskId) {
            Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
            if (task == null) {
                throw new RuntimeException("流程不存在");
            }
            //通过审核
            HashMap<String, Object> map = new HashMap<>();
            map.put("outcome", "通过");
            taskService.complete(taskId, map);
            return "processed ok!";
        }

    通过前端传入的任务ID来对此流程进行同意处理

    拒绝,不同意

        /**
         * 拒绝
         */
        @ResponseBody
        @RequestMapping(value = "reject")
        public String reject(String taskId) {
            HashMap<String, Object> map = new HashMap<>();
            map.put("outcome", "驳回");
            taskService.complete(taskId, map);
            return "reject";
        } 

    生成当前流程图表

        /**
         * 生成流程图
         *
         * @param processId 任务ID
         */
        @RequestMapping(value = "processDiagram")
        public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) throws Exception {
            ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
    
            //流程走完的不显示图
            if (pi == null) {
                return;
            }
            Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
            //使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象
            String InstanceId = task.getProcessInstanceId();
            List<Execution> executions = runtimeService
                    .createExecutionQuery()
                    .processInstanceId(InstanceId)
                    .list();
    
            //得到正在执行的Activity的Id
            List<String> activityIds = new ArrayList<>();
            List<String> flows = new ArrayList<>();
            for (Execution exe : executions) {
                List<String> ids = runtimeService.getActiveActivityIds(exe.getId());
                activityIds.addAll(ids);
            }
    
            //获取流程图
            BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
            ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
            ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
            InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0);
            OutputStream out = null;
            byte[] buf = new byte[1024];
            int legth = 0;
            try {
                out = httpServletResponse.getOutputStream();
                while ((legth = in.read(buf)) != -1) {
                    out.write(buf, 0, legth);
                }
            } finally {
                if (in != null) {
                    in.close();
                }
                if (out != null) {
                    out.close();
                }
            }
        } 

    通过传入流程ID生成当前流程的流程图给前端,如果流程中使用到中文且生成的图片是乱码的,则需要进配置下字体:

    /**
     * @author haiyangp
     * date:  2018/4/7
     * desc: flowable配置----为放置生成的流程图中中文乱码
     */
    @Configuration
    public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
    
    
        @Override
        public void configure(SpringProcessEngineConfiguration engineConfiguration) {
            engineConfiguration.setActivityFontName("宋体");
            engineConfiguration.setLabelFontName("宋体");
            engineConfiguration.setAnnotationFontName("宋体");
        }
    }

    整体演示

    上面的代码写好后就可以演示下整体流程了

    1.先启动好此项目,然后创建一个流程:

    访问:http://localhost:8080/expense/add?userId=123&money=123321

    返回:提交成功.流程Id为:2501

     

    2.查询待办列表:

    访问:http://localhost:8080/expense/list?userId=123

    输出:Task[id=2507, name=出差报销]

     

    3.同意:

    访问:http://localhost:8080/expense/apply?taskId=2507

    返回:processed ok!

     

    4.生成流程图:

    访问:http://localhost:8080/expense/processDiagram?processId=2501

    返回如下图片:

    整体流程截图如下:

    总结

    通过springBoot与flowable的整合体验到了工作流的开发原来如此简单方便。

    给此框架点赞,向巨人们致敬!

     

    本文源码地址:https://github.com/puhaiyang/flowable-springboot

    文末推荐一个关于flowable的QQ群!

    QQ群:242771252

    点击链接加入群【flowable学习交流】:https://jq.qq.com/?_wv=1027&k=5DKcjgU

    展开全文
  • 手把手教你如何玩转Activiti工作流

    万次阅读 多人点赞 2018-01-30 19:51:36
    Activiti定义: Activiti5是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理、工作流、服务协作等领域的一个开源的、灵活的、易扩展的可执行流程语言框架。Activiti基于...
  • 你明白工作流是什么、怎么用了吗?

    万次阅读 多人点赞 2015-12-20 23:42:47
    我们可以将工作流理解为工作流程,它在IT领域不算是“新人”了,工作流思想在上世纪60年代就有人提出过;70年代就有人开始尝试,但是由于当时许多的限制,工作流一直没有成功的被实现;80年代才出现第一批成功的工作...
  • 工作流(Workflow) -- 工作流简介

    万次阅读 2018-09-08 21:05:00
    ## 工作流引擎 ProcessEngine对象 这是Activiti工作的核心。负责生成流程运行时的各种实例及数据、监控和管理流程的运行。 工作流的API所有的调用都要用到工作流引擎。 数据库 Activiti的后台是有数据库的支持,...
  • 工作流——顺序工作流

    千次阅读 热门讨论 2016-01-03 11:24:14
    之前已经介绍过工作流的基本概念,提到了工作流有两种基本类型,顺序工作流和状态机工作流。它们的区别主要是前者强调的是顺序过程,后者强调的是状态的改变。   主要区别:  顺序工作流:  顺序工作流的执行...
  • Git工作流指南:Gitflow工作流

    千次阅读 2017-05-09 06:47:14
    ...这节介绍的Gitflow工作流借鉴自在nvie的Vincent ...Gitflow工作流定义了一个围绕项目发布的严格分支模型。虽然比功能分支工作流复杂几分,但...Gitflow工作流没有用超出功能分支工作流的概念和命令,而是为不同
  • 顺序工作流: 顺序工作流的执行过程是一个连续的步骤序列,它在完成一个活动之后会自动去执行到下一个.比如用顺序工作流模拟新生报到的流程操作:第一步,点击开始新生报到;第二步,完善个人信息;第三步,填写家庭成员...
  • SharePoint 工作流实战教程

    千人学习 2017-07-30 20:20:13
    本次课程以SharePoint 2016平台为基础,为大家介绍SharePoint工作流,并介绍如何通过SharePoint Designer和Visio进行SharePoint工作流的配置。
  • activiti工作流表说明

    万次阅读 2016-06-12 15:59:45
    activiti工作流表说明
  • activiti5工作流demo

    千次下载 热门讨论 2013-08-26 11:51:49
    咖啡兔的请假工作流实例,activiti5引擎,适合工作流初学者,非maven配置的
  • Azkaban工作流调度

    万次阅读 2019-12-06 21:02:50
    工作流 1.工作流产生背景 工作流(Workflow),指“业务过程的部分或整体在计算机应用环境下的自动化”。是对工作流程及其各操作步骤之间业务规则的抽象、概括描述。工作流解决的主要问题是:为了实现某个业务目标,...
  • 工作流工作流快速入门

    千次阅读 热门讨论 2015-12-23 11:45:43
    【是什么】 工作流是一类能够完全或者部分...对比一下就可以发现,工作流程和工作流这两个概念的不同之处,工作流程是完成一件事的先后顺序,工作流是一类工作流程的集合,是对常用的业务进一步的抽象封装,并且加入了
  • Activity工作流

    千次阅读 2019-02-14 11:34:50
    Activiti 是一个针对企业用户、开发人员 、系统管理员的轻量级工作流业务管理平台,其核心是使用 java 开发的快速 、 稳定的 BPMN2.0 流程引擎 。它可以与 spring 完美集成。 1 工作流生命周期 一个完整的工作流...
  • 前言  工作流框架现在越来越流行了,... 本篇博客就根据工作流小组研究的结果,加上小编自己的理解,介绍一下工作流的基本内容。博客中的实例开发环境为VS2012,有兴趣的小伙伴可以尝试一下。 工作流是什么
  • flowable工作流

    万次阅读 2017-11-30 11:29:36
    如果你对工作流引擎有所了解,那么一定知道Java领域当前主流的工作流引擎无非就是Jboss旗下的JBPM和Alfresco旗下的Activiti。 Flowable是Activiti原班主创人员从Activiti分离出来的一套工作流引擎,是一个业务流程...
  • Activiti工作流教程

    万次阅读 多人点赞 2017-03-03 17:43:32
    1:工作流的概念说明:1) 假设:这两张图就是华谊兄弟的请假流程图2) 图的组成部分:A. 人物:范冰冰 冯小刚王中军B. 事件(动作):请假、批准、不批准 工作流(Workflow),就是“业务过程的部分或整体在计算机...
  • active 工作流

    千次阅读 2019-12-03 20:48:46
    https://github.com/Activiti/Activiti github地址 ... https://blog.csdn.net/cs_hnu_scw/article/details/79059965 创建一个简单的工作流 工作流介绍:https://www.cnblogs.c...
  • Vue集成activity工作流

    万次阅读 2019-08-17 17:40:38
    情景: 由于activiti与系统应用主题样式出入较大,协商后决定将activiti的... 通过iframe在相应的前台工作流界面引入activiti的model.html(最外层的主html,名字可能有出入)。 mounted时将this,即vuecompo...
  • Activiti工作流

    千次阅读 2018-12-18 02:02:49
    Activiti工作流 一、Activiti介绍 1、Activiti 的概述 ​ 工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或...
  • 工作流技术

    千次阅读 2018-09-07 21:33:15
    什么是工作流工作流就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 95,971
精华内容 38,388
关键字:

工作流