(1). 项目结构

camunda-spring-example/
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   └── resources
│   └── test
│       ├── java
│       │   └── help
│       │       └── lixin
│       │           └── camunda
│       │               ├── RuntimeServiceTest.java
│       │               └── ProcessEngineTest.java
│       └── resources
│           ├── application.properties
│           ├── applicationContext.xml
│           ├── logback-spring.xml
└── target

(2). ProcessEngineTest

package help.lixin.camunda;

import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.TaskService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ProcessEngineTest {
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		RepositoryService repositoryService = ctx.getBean(RepositoryService.class);
		RuntimeService runtimeService = ctx.getBean(RuntimeService.class);
		TaskService taskService = ctx.getBean(TaskService.class);
		HistoryService historyService = ctx.getBean(HistoryService.class);
		System.out.println(repositoryService);
		System.out.println(runtimeService);
		System.out.println(taskService);
		System.out.println(historyService);
	}
}

(3). RuntimeServiceTest

package help.lixin.camunda;

import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.camunda.bpm.engine.IdentityService;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.TaskService;
import org.camunda.bpm.engine.identity.Group;
import org.camunda.bpm.engine.identity.Tenant;
import org.camunda.bpm.engine.impl.identity.Authentication;
import org.camunda.bpm.engine.impl.persistence.entity.GroupEntity;
import org.camunda.bpm.engine.impl.persistence.entity.TenantEntity;
import org.camunda.bpm.engine.impl.persistence.entity.UserEntity;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.runtime.ProcessInstantiationBuilder;
import org.camunda.bpm.engine.task.Task;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class RuntimeServiceTest {

	static final String GROUP_ID = "7305184575594516AEC5A992F539A074";
	static final String USER_ID_ZHANGSAN = "zhangsan";
	static final String USER_ID_LISHI = "lishi";
	static final String USER_ID_WANGWU = "wangwu";
	static final String USER_ID_ZHAOLIU = "zhaoliu";
	static final String TENANT_ID = "4922021F02E248BF84CF3DB4AD0AD694";

	static ApplicationContext ctx = null;

	@BeforeClass
	public static void before() {
		ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
	}

	@Test
	public void testInitData() {
		IdentityService identityService = ctx.getBean(IdentityService.class);

		// 添加租户
		Tenant tenant = new TenantEntity();
		tenant.setId(TENANT_ID);
		tenant.setName("XXX科技有限公司");
		identityService.saveTenant(tenant);

		// 添加Group
		Group group = new GroupEntity();
		group.setId(GROUP_ID);
		group.setName("研发部门");
		group.setType("DEVELOPER");
		identityService.saveGroup(group);

		// 组和租户ID进行绑定
		identityService.createTenantGroupMembership(TENANT_ID, GROUP_ID);

		// zhangsan
		UserEntity zhangsan = new UserEntity();
		// id即是登录的账号
		zhangsan.setId("zhangsan");
		zhangsan.setPassword("1");
		zhangsan.setSalt("1");
		zhangsan.setEmail("zhangsan@126.com");
		zhangsan.setFirstName("zhang");
		zhangsan.setLastName("san");

		// lishi
		UserEntity lishi = new UserEntity();
		// id即是登录的账号
		lishi.setId("lishi");
		lishi.setPassword("1");
		lishi.setSalt("1");
		lishi.setEmail("lishi@126.com");
		lishi.setFirstName("li");
		lishi.setLastName("shi");

		// wangwu
		UserEntity wangwu = new UserEntity();
		// id即是登录的账号
		wangwu.setId("wangwu");
		wangwu.setPassword("1");
		wangwu.setSalt("1");
		wangwu.setEmail("wangwu@126.com");
		wangwu.setFirstName("wang");
		wangwu.setLastName("wu");

		// zhaoliu
		UserEntity zhaoliu = new UserEntity();
		// id即是登录的账号
		zhaoliu.setId("zhaoliu");
		zhaoliu.setPassword("1");
		zhaoliu.setSalt("1");
		zhaoliu.setEmail("zhaoliu@126.com");
		zhaoliu.setFirstName("zhao");
		zhaoliu.setLastName("liu");

		identityService.saveUser(zhangsan);
		identityService.saveUser(lishi);
		identityService.saveUser(wangwu);
		identityService.saveUser(zhaoliu);

		// 用户与组绑定关系
		identityService.createMembership(zhangsan.getId(), GROUP_ID);
		identityService.createMembership(lishi.getId(), GROUP_ID);
		identityService.createMembership(wangwu.getId(), GROUP_ID);
		identityService.createMembership(zhaoliu.getId(), GROUP_ID);

		// 租户与用户绑定关系
		identityService.createTenantUserMembership(TENANT_ID, zhangsan.getId());
		identityService.createTenantUserMembership(TENANT_ID, lishi.getId());
		identityService.createTenantUserMembership(TENANT_ID, wangwu.getId());
		identityService.createTenantUserMembership(TENANT_ID, zhaoliu.getId());
	}

	/**
	 * 部署流程定义
	 */
	@Test
	public void testProcessDeploy() {
		// ACT_RE_DEPLOYMENT : 流程部署.
		// insert into ACT_RE_DEPLOYMENT(ID_, NAME_, DEPLOY_TIME_, SOURCE_, TENANT_ID_)
		// values(?, ?, ?, ?, ?)

		// ACT_GE_BYTEARRAY : 流程定义二进制数据.
		// insert into ACT_GE_BYTEARRAY( ID_, NAME_, BYTES_, DEPLOYMENT_ID_, GENERATED_,
		// TENANT_ID_, TYPE_, CREATE_TIME_, REV_) values ( ?, ?, ?, ?, ?, ?, ?, ?, 1)

		// ACT_RE_PROCDEF : 流程定义
		// insert into ACT_RE_PROCDEF(ID_, CATEGORY_, NAME_, KEY_, VERSION_,
		// DEPLOYMENT_ID_, RESOURCE_NAME_, DGRM_RESOURCE_NAME_, HAS_START_FORM_KEY_,
		// SUSPENSION_STATE_, TENANT_ID_, VERSION_TAG_, HISTORY_TTL_, STARTABLE_, REV_)
		// values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1 )
		RepositoryService repositoryService = ctx.getBean(RepositoryService.class);
		// 部署流程定义
		repositoryService.createDeployment() //
				.name("请假流程") //
				.source("Test") //
				.tenantId(TENANT_ID) //
				.enableDuplicateFiltering() //
				.addClasspathResource("leave.bpmn") //
				.deploy();
	}

	
	@Test
	public void testStartProcessInstanceByKey() {
		// 流程定义 : 通过Camunda Modeler设计出来的流程定义信息.
		// 流程实例 : 从流程定义(模板),创建出来的流程实例.
		// 执行实例 : 一个流程实例包括所有的运行节点(执行实例).可通过流程实例对象来了解当前流程进度等信息.
		// 在流程运转的过程中,永远执行的是自己对应的执行实例.
		RuntimeService runtimeService = ctx.getBean(RuntimeService.class);
		IdentityService identityService = ctx.getBean(IdentityService.class);
		// 对应流程定义中声明的:id
		String processDefinitionKey = "leave";
		Map<String,Object> vars = new HashMap<String,Object>();
		vars.put("day", 4L);
		vars.put("start_time", new Date());
		vars.put("end_time", new Date());
		vars.put("reason", "go home...");
		
		// 在有租户的情况下,还真只能这样写.否则会报错(也可以禁用租户检查).
		Authentication authentication = new Authentication(USER_ID_ZHANGSAN, Arrays.asList(GROUP_ID), Arrays.asList(TENANT_ID));
		// 把当前用户信息,设置到线程上下文中.
		identityService.setAuthentication(authentication);
		try {
			ProcessInstantiationBuilder processInstantiationBuilder = runtimeService.createProcessInstanceByKey(processDefinitionKey);
			processInstantiationBuilder.setVariables(vars);
			processInstantiationBuilder.processDefinitionTenantId(TENANT_ID);
			ProcessInstance processInstance = processInstantiationBuilder.execute();
			// 不能再这样启动流程了(因为:tenant_id没地方传递进去)
			// ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey,vars);
			String format = "id: " + processInstance.getId() + "  businessKey: " + processInstance.getBusinessKey()
					+ " tenantId: " + processInstance.getTenantId() + " processDefinitionId: "
					+ processInstance.getProcessDefinitionId();
			System.out.println(format);
		}finally {
			// 记得要清除当前用户信息
			identityService.clearAuthentication();
		}
	}

	/**
	 * 查询待办任务
	 */
	@Test
	public void testQueryTask() {
//		 select distinct RES.REV_, RES.ID_, RES.NAME_, RES.PARENT_TASK_ID_, RES.DESCRIPTION_, RES.PRIORITY_, RES.CREATE_TIME_, RES.OWNER_, RES.ASSIGNEE_, RES.DELEGATION_, RES.EXECUTION_ID_, RES.PROC_INST_ID_, RES.PROC_DEF_ID_, RES.CASE_EXECUTION_ID_, RES.CASE_INST_ID_, RES.CASE_DEF_ID_, RES.TASK_DEF_KEY_, RES.DUE_DATE_, RES.FOLLOW_UP_DATE_, RES.SUSPENSION_STATE_, RES.TENANT_ID_ from ACT_RU_TASK RES WHERE ( 1 = 1 and RES.ASSIGNEE_ = ? ) order by RES.ID_ asc LIMIT ? OFFSET ? 
//       Parameters: zhangsan(String), 2147483647(Integer), 0(Integer)
		
		TaskService taskService = ctx.getBean(TaskService.class);
		List<Task> tasks = taskService.createTaskQuery() //
				.taskAssignee(USER_ID_ZHANGSAN).list();
//				.taskAssignee(USER_ID_ZHAOLIU).list();
		for (Task task : tasks) {
			// task.getTaskDefinitionKey() : 流程定义KEY
			// task.getId()                : 执行任务ID
			System.out.println("id: " + task.getId() + " key: " + task.getTaskDefinitionKey());
		}
	}
	
	
	@Test
	public void testComleteTask() {
		TaskService taskService = ctx.getBean(TaskService.class);
		// 获得最后一条数据
		String taskId = "1413";
		taskService.complete(taskId);
		// 可传递变量
		// taskService.complete(taskId, variables);
	}
	
	@Test
	public void testDeleteProcess() {
		// 删除指定的实例
		RuntimeService runtimeService = ctx.getBean(RuntimeService.class);
		runtimeService.deleteProcessInstance("501", "delete test");
		runtimeService.deleteProcessInstance("701", "delete test");
	}
}

(4). applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:activiti="http://www.activiti.org/schema/spring/components"
       xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="dataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
        <property name="targetDataSource">
        <!-- 这里也只是利用了Spring产生的DataSource -->
            <bean class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
                <property name="driverClass" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/camunda?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </bean>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="processEngineConfiguration" class="org.camunda.bpm.engine.spring.SpringProcessEngineConfiguration">
        <property name="dataSource" ref="dataSource"/>
        <property name="history" value="full"/>
        <property name="transactionManager" ref="transactionManager"/>
        <property name="databaseSchemaUpdate" value="false"/>
        <!-- <property name="databaseSchemaUpdate" value="create-drop"/> -->
        <property name="jobExecutorActivate" value="false"/>
        <property name="dbMetricsReporterActivate" value="false" />
        <property name="telemetryReporterActivate" value="false" />
    </bean>

    <!-- using ManagedProcessEngineFactoryBean allows registering the ProcessEngine with the BpmPlatform -->
    <bean id="processEngine" class="org.camunda.bpm.engine.spring.container.ManagedProcessEngineFactoryBean">
        <property name="processEngineConfiguration" ref="processEngineConfiguration"/>
    </bean>
	
	<bean id="formService" factory-bean="processEngine" factory-method="getFormService"/>
	<bean id="externalTaskService" factory-bean="processEngine" factory-method="getExternalTaskService"/>
	<bean id="authorizationService" factory-bean="processEngine" factory-method="getAuthorizationService"/>
	<bean id="identityService" factory-bean="processEngine" factory-method="getIdentityService"/>
    <bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService"/>
    <bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService"/>
    <bean id="taskService" factory-bean="processEngine" factory-method="getTaskService"/>
    <bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService"/>
    <bean id="managementService" factory-bean="processEngine" factory-method="getManagementService"/>
</beans>

(5). application.properties

logging.config=classpath:logback-spring.xml

(6). logback-spring.xml

<configuration>
	<appender name="STDOUT"
		class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
			</pattern>
		</encoder>
	</appender>
	<logger name="org.apache.ibatis" level="debug" />
	<logger name="javax.activation" level="info" />
	<logger name="org.springframework" level="info" />
	<logger name="org.mortbay.log" level="info" />
	<logger name="org.camunda" level="info" />
	<logger name="org.camunda.bpm.engine.test" level="debug" />
	<root level="all">
		<appender-ref ref="STDOUT" />
	</root>
</configuration>

(7). pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>help.lixin.camunda</groupId>
	<artifactId>camunda-spring-example</artifactId>
	<packaging>jar</packaging>
	<version>1.1.0</version>
	<name>camunda-spring-example-${project.version}</name>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		<java.version>1.8</java.version>
		<spring.version>5.2.8.RELEASE</spring.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.camunda.bpm</groupId>
			<artifactId>camunda-engine-spring</artifactId>
			<version>7.14.0</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.49</version>
		</dependency>
		<dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-core</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-beans</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-context</artifactId>  
            <version>${spring.version}</version>  
        </dependency>
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-jdbc</artifactId>  
            <version>${spring.version}</version>  
        </dependency> 
		<dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>  
        </dependency> 
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.2.3</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>