Activiti学习(4)简单的请假流程

在前一篇文章的基础上,编写一个稍微复杂的请假流程,进一步熟悉Activiti的基本知识,并期望能够触类旁通,解决上一篇博文中没有解决的问题。实现过程中,参考了以下文章,在此向作者表示感谢。

1、activiti快速入门--简单请假例子(1)

2、Activiti 6.0 工作流入门 之HelloWorld

3、Activiti工作流框架——快速上手

4、Maven 生命周期

另外,程序猿之洞关于Activiti工作流的系列博文也为本文提供了重要的参考信息。

1、功能设计

编写控制台应用程序,创建Activiti流程引擎,创建并部署请假业务流程实例,通过控制台输入,驱动业务流程执行。请假流程如图1所示。

图1

为了熟悉不同类型任务的使用方法,在流程结束前分别加入脚本任务(ScriptTask)和服务任务(ServiceTask)显示审批结果。

2、创建项目

(1)创建Maven项目

项目命名为LeaveProcess,业务类写在src/main/java下,相应的资源文件放置在src/main/resources下。测试的业务类在src/test/java下,相应的测试资源文件放置在src/test/resources下。

(2)配置pom.xml

特别说明的是,此处加入了三个特别的依赖信息:

① guava

Guava是google维护的一种基于开源Java库,此处用于使用【com.google.common.collect.Maps】提供的Maps方法。

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>26.0-jre</version>
</dependency>

②jaxb-api

jaxb api是Java EE 的API,默认情况下,Java 9之后,Java SE中将不再包含Java EE 的Jar包。本文中在执行脚本任务时需要这个API,否则会出现如下类似错误:

java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException……

参考《How to resolve java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException in Java 9 》一文,在pom.xml文件中加入如下依赖可解决上述问题。

<!-- https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api -->
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.0</version>
</dependency>

③groovy-all

脚本任务中,由于javascrip不支持任何形式的打印或者输出的函数,因此本文采用groovy脚本编写任务,需要增加支持groovy的依赖。

<!-- https://mvnrepository.com/artifact/org.codehaus.groovy/groovy-all -->
<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>3.0.0-alpha-3</version>
    <type>pom</type>
</dependency>

有关自动执行任务和Groovy语言的基本知识,可参考《工作流Activiti的学习总结(八)Activiti自动执行的应用》、《Activiti脚本任务(ScriptTask)》和简单的土豆的博文《Groovy 语言快速入门》。

综上,完整pom.xml文件如下。 

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.seu.liuds.activiti</groupId>
	<artifactId>LeaveProcess</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
	<name>LeaveProcess</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<jdk.version>10.0.0.2</jdk.version>
		<junit.version>3.8.1</junit.version>
		<activiti.version>6.0.0</activiti.version>
		<slf4j.version>1.7.21</slf4j.version>
		<mysql.connector.version>8.0.11</mysql.connector.version>
		<guava.version>26.0-jre</guava.version>
		<jaxb-api.version>2.3.0</jaxb-api.version>
		<groovy-all.version>3.0.0-alpha-3</groovy-all.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>${guava.version}</version>
		</dependency>
		<dependency>
			<groupId>javax.xml.bind</groupId>
			<artifactId>jaxb-api</artifactId>
			<version>${jaxb-api.version}</version>
		</dependency>
		<dependency>
			<groupId>org.codehaus.groovy</groupId>
			<artifactId>groovy-all</artifactId>
			<version>${groovy-all.version}</version>
			<type>pom</type>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.activiti</groupId>
			<artifactId>activiti-engine</artifactId>
			<version>${activiti.version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${slf4j.version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>${slf4j.version}</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysql.connector.version}</version>
		</dependency>
	</dependencies>
</project>

(3)创建主程序类

Maven工程创建后,src/main/java下包含一个名为App的Java文件,内容为一个包含main函数的Java类,如下所示。这里将以此为基础,构建项目的主程序类。

package com.seu.liuds.activiti.LeaveProcess;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
    }
}

(4)创建MySQL数据库

使用MySQLWorkbench作为数据库管理工具,创建名为【leaveprocess】的数据库,用户名为【root】,密码为空。

(5)配置日志记录

在/src/main/resources/中添加log4j.properties文件,设置信息显示级别为DEBUG,文件内容如下:

log4j.rootLogger=DEBUG, ACT
 
log4j.appender.ACT=org.apache.log4j.ConsoleAppender
log4j.appender.ACT.layout=org.apache.log4j.PatternLayout
log4j.appender.ACT.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n

有关log4j.properties的功能和设定可参考《Maven项目中添加log4j.properties实现日志功能工具》 一文。

3、创建流程引擎

流程引擎的创建过程可以直接在程序中用Java语句配置引擎的相关参数,也可以通过读取配置文件完成,具体方法参考萝卜地里的兔子的博文《Activiti5第八弹,ProcessEngineConfiguration和ProcessEngine》。之前采用了Java语句直接设置引擎参数配置,此处采用xml配置文件创建流程引擎。

(1)创建流程引擎配置文件

创建名为【activiti-cfg.xml】配置文件,存放到【src/main/resources】

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
	http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

	<!-- processEngine Configuration -->
	<bean id="processEngineConfiguration"
		class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">

		<property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver" />
		<property name="jdbcUrl"
			value="jdbc:mysql://localhost:3306/leaveprocess?useSSL=false&amp;serverTimezone=GMT%2B8" />
		<property name="jdbcUsername" value="root" />
		<property name="jdbcPassword" value="" />
		<property name="databaseSchemaUpdate" value="true" />
	</bean>
</beans>

需要注意以下两个问题:

① &符号在 xml 中是作为实体字符形式存在的,因此需要通过html的实体名称来代替。此处&应为【&amp】,并用【;】分隔。常用的实体名称参见《HTML字符实体引用》一文。

②此前的一个例子在MySQL服务器上创建了一个名为activiti的数据库,新建的名为leaveprocess的数据库在初始化过程中出现了以下错误。

### …… Cause: java.sql.SQLSyntaxErrorException: Table 'leaveprocess.act_ge_property' doesn't exist
### The error may exist in org/activiti/db/mapping/entity/Property.xml
### The error may involve org.activiti.engine.impl.persistence.entity.PropertyEntityImpl.selectProperty-Inline
### The error occurred while setting parameters
### SQL: select * from ACT_GE_PROPERTY where NAME_ = ?
……

参考icezcity的博文《一个MySql实例自动创建多个Activiti数据库问题》,将名为activiti的数据库删除以后解决,具体的技术细节需要进一步学习。

(2)加载配置文件创建流程引擎

在App.java文件中创建方法【getProcessEngine()】,加载【activiti-cfg.xml】创建流程引擎,并在main函数中调用该方法。代码如下。

package com.seu.liuds.activiti.LeaveProcess;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;

public class App {
	public static void main(String[] args) {
		String strNowTime = CurrentTime();
		System.out.println(strNowTime + " --Start application-----");

		// Create Process Engine
		ProcessEngine processEngine = getProcessEngine();

		strNowTime = CurrentTime();
		System.out.println(strNowTime + " --End application-----");
	}

	private static ProcessEngine getProcessEngine() {
		String strNowTime = CurrentTime();
		System.out.println(strNowTime + " --Start building a process engine---");

		ProcessEngineConfiguration cfg = ProcessEngineConfiguration
				.createProcessEngineConfigurationFromResource("activiti-cfg.xml");
		ProcessEngine processEngine = cfg.buildProcessEngine();

		String engineName = processEngine.getName();
		String engineVersion = ProcessEngine.VERSION;
		strNowTime = CurrentTime();
		System.out.println(strNowTime + " --Process engine [" + engineName + "] v" + engineVersion + " was built---");
		return processEngine;
	}

	private static String CurrentTime() {
		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// set date format
		String strCurrentTime = df.format(new Date());// new Date(): get current system time
		return strCurrentTime;
	}
}

App.java的运行结果如下:

2018-08-10 14:21:13 --Start application-----
2018-08-10 14:21:13 --Start building a process engine---
……
2018-08-10 14:21:17 --Process engine [default] v6.0.0.4 was built---
2018-08-10 14:21:17 --End application-----

4、部署流程定义文件

(1)创建流程定义文件

使用Activiti BPMN2.0 Designer设计流程定义文件,方法可参见《Activiti学习文档(三)之画流程图并部署流程》一文。由于文件篇幅较大,上传后提供下载链接demo.bpmn20.xml。系统设定下载所需资源最少为1积分/C币,如果需要请在留言里提供邮箱。

① 定义脚本任务

在Activiti Designer中选中脚本任务图标,按照如图2所示设置将要执行的groovy脚本。

图2

②定义服务任务

编写需要执行的任务类,本例中只在控制台输出请假审批信息,代码如下。

package com.seu.liuds.activiti.LeaveProcess;

import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;

public class PrintServiceTask implements JavaDelegate {
	private final String taskName = PrintServiceTask.class.getName();

	public void execute(DelegateExecution execution) {
		System.out.println("variavles=" + execution.getVariables());
		execution.setVariable("审批意见", "不同意。");
		System.out.println(taskName);
	}
}

在Activiti Designer中选中服务任务图标,按照图3所示设置将要执行的类名。

图3

(2)部署流程定义文件

将此前设计好的流程定义文件部署在流程引擎上。在App.java文件中创建方法【getProcessDefinition(ProcessEngine processEngine)】,并在main函数中调用该方法。核心代码如下(限于篇幅,import导入语句和前文已经出现的代码段落不再详细罗列)。

package com.seu.liuds.activiti.LeaveProcess;

……

public class App {
	public static void main(String[] args) throws ParseException {

		String strNowTime = CurrentTime();
		System.out.println(strNowTime + " --Start application-----");

		// Create Process Engine
		ProcessEngine processEngine = getProcessEngine();

		// Deploy Process Definition
		ProcessDefinition processDefinition = getProcessDefinition(processEngine);

		strNowTime = CurrentTime();
		System.out.println(strNowTime + " --End application-----");
	}

	private static ProcessDefinition getProcessDefinition(ProcessEngine processEngine) {
		String strNowTime = CurrentTime();
		System.out.println(strNowTime + " --Start deploying process definition file---");

		RepositoryService repositoryService = processEngine.getRepositoryService();
		DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
		deploymentBuilder.addClasspathResource("demo.bpmn20.xml");
		Deployment deployment = deploymentBuilder.deploy();
		String deploymentId = deployment.getId();
		ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
				.deploymentId(deploymentId).singleResult();

		String processName = processDefinition.getName();
		strNowTime = CurrentTime();
		System.out.println(strNowTime + " --Process definition file [" + processName + "] was deployed");
		return processDefinition;
	}

	private static ProcessEngine getProcessEngine() {
		……		
	}

	private static String CurrentTime() {
		……
	}
}

App.java的运行结果如下:

2018-08-10 14:55:59 --Start application-----
2018-08-10 14:55:59 --Start building a process engine---
……
2018-08-10 14:56:02 --Process engine [default] v6.0.0.4 was built---
2018-08-10 14:56:02 --Start deploying process definition file---
2018-08-10 14:56:03 --Process definition file [LeaveApproveProcess] was deployed
2018-08-10 14:56:03 --End application-----

5、启动运行流程

启动已经部署在流程引擎上的流程定义文件。在App.java文件中创建方法【getProcessDefinition(ProcessEngine processEngine)】,并在main函数中调用该方法。核心代码如下。

package com.seu.liuds.activiti.LeaveProcess;

……

public class App {
	public static void main(String[] args) throws ParseException {
		String strNowTime = CurrentTime();
		System.out.println(strNowTime + " --Start application-----");

		// Create Process Engine
		ProcessEngine processEngine = getProcessEngine();

		// Deploy Process Definition
		ProcessDefinition processDefinition = getProcessDefinition(processEngine);

		// Run the Process
		ProcessInstance processInstance = getProcessInstance(processEngine, processDefinition);

		strNowTime = CurrentTime();
		System.out.println(strNowTime + " --End application-----");
	}

	private static ProcessInstance getProcessInstance(ProcessEngine processEngine,
			ProcessDefinition processDefinition) {
		String strNowTime = CurrentTime();
		System.out.println(strNowTime + " --Start running process instance---");

		RuntimeService runtimeService = processEngine.getRuntimeService();
		ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId());

		strNowTime = CurrentTime();
		System.out.println(
				strNowTime + " --Process instance [" + processInstance.getProcessDefinitionKey() + "] started---");
		return processInstance;
	}

	private static ProcessDefinition getProcessDefinition(ProcessEngine processEngine) {
		……
	}

	private static ProcessEngine getProcessEngine() {
		……
	}

	private static String CurrentTime() {
		……
	}
}

App.java的运行结果如下:

2018-08-10 15:02:31 --Start application-----
2018-08-10 15:02:31 --Start building a process engine---
……
2018-08-10 15:02:34 --Process engine [default] v6.0.0.4 was built---
2018-08-10 15:02:34 --Start deploying process definition file---
2018-08-10 15:02:35 --Process definition file [LeaveApproveProcess] was deployed
2018-08-10 15:02:35 --Start running process instance---
2018-08-10 15:02:35 --Process instance [leaveApprove] started---
2018-08-10 15:02:35 --End application-----

6、处理流程任务

按照已经运行的流程定义处理各个流程任务,并返回处理结果。本文的例子中,请假的申请和审批都是手工操作,显示请假处理结果则依靠脚本任务和服务任务分别实现。在App.java文件中创建方法【processTask(ProcessEngine processEngine, ProcessInstance processInstance)】,并在main函数中调用该方法。核心代码如下。

package com.seu.liuds.activiti.LeaveProcess;

……

public class App {
	public static void main(String[] args) throws ParseException {
		String strNowTime = CurrentTime();
		System.out.println(strNowTime + " --Start application-----");

		// Create Process Engine
		ProcessEngine processEngine = getProcessEngine();

		// Deploy Process Definition
		ProcessDefinition processDefinition = getProcessDefinition(processEngine);

		// Run the Process
		ProcessInstance processInstance = getProcessInstance(processEngine, processDefinition);

		// Deal with Process Tasks
		processTask(processEngine, processInstance);

		strNowTime = CurrentTime();
		System.out.println(strNowTime + " --End application-----");
	}

	private static void processTask(ProcessEngine processEngine, ProcessInstance processInstance)
			throws ParseException {
		Scanner scanner = new Scanner(System.in);
		while (processInstance != null && !processInstance.isEnded()) {
			TaskService taskService = processEngine.getTaskService();
			List<Task> list = taskService.createTaskQuery().list();
			System.out.println("--Number of to-do tasks: " + list.size());
			for (Task task : list) {
				System.out.println("--To-do task: " + task.getName());
				Map<String, Object> variables = getMap(processEngine, scanner, task);
				taskService.complete(task.getId(), variables);
				processInstance = processEngine.getRuntimeService().createProcessInstanceQuery()
						.processInstanceId(processInstance.getId()).singleResult();
			}
		}
		scanner.close();
	}

	private static Map<String, Object> getMap(ProcessEngine processEngine, Scanner scanner, Task task)
			throws ParseException {
		FormService formService = processEngine.getFormService();
		TaskFormData taskFormData = formService.getTaskFormData(task.getId());
		List<FormProperty> formProperties = taskFormData.getFormProperties();
		Map<String, Object> variables = Maps.newHashMap();
		for (FormProperty property : formProperties) {
			String line = null;
			if (StringFormType.class.isInstance(property.getType())) {
				System.out.println(">> Please input " + property.getName());
				line = scanner.nextLine();
				variables.put(property.getId(), line);
			} else if (DateFormType.class.isInstance(property.getType())) {
				System.out.println(">> Please input " + property.getName() + "(yyyy-MM-dd)");
				SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
				Date date = null;
				while (date == null) {
					line = scanner.nextLine();
					try {
						date = dateFormat.parse(line);
					} catch (Exception e) {
						System.out.println(">> Please input " + property.getName() + "(yyyy-MM-dd)");
						continue;
					}
				}
				variables.put(property.getId(), date);
			} else {
				System.out.println(">> Incompatible with " + property.getType() + " type");
			}
		}
		return variables;
	}

	private static ProcessInstance getProcessInstance(ProcessEngine processEngine,
			ProcessDefinition processDefinition) {
		……
	}

	private static ProcessDefinition getProcessDefinition(ProcessEngine processEngine) {
		……
	}

	private static ProcessEngine getProcessEngine() {
		……
	}

	private static String CurrentTime() {
		……
	}
}

运行App.java,按照提示输入信息,查看流程的执行情况。结果如下:

……
--Number of to-do tasks: 1
--To-do task: 请假申请
>> Please input 申请人姓名
Jerry
>> Please input 请假原因
调研
>> Please input 提交时间(yyyy-MM-dd)
2018-08-10
>> Please input 确认申请
Y
--Number of to-do tasks: 1
--To-do task: 部门审批
>> Please input 主管审批结果
Y
>> Please input 主管备注
同意
--Number of to-do tasks: 1
--To-do task: 人事审批
>> Please input 人事审批结果
Y
>> Please input 人事审批备注
同意
申请者姓名:Jerry
请假原因:调研
2018-08-10 15:26:44 --End application-----

当然,输入数据过程中也可以选择其他路径执行。

最后,推荐一篇不错的博文《activiti使用实践案例》。

本文的主程序代码文件下载链接。同上,需要的可以留邮箱。

猜你喜欢

转载自blog.csdn.net/Liu_desheng/article/details/81489659