Activiti是业务流程引擎即工作流,与其相似还有JBPM,Flowable(下篇会讲解)。
该篇我将结合实际开发场景将项目中用到activiti的核心技术和其中的23张表做讲解和代码展示。
项目需求
开发一个实验签审单流程和实验审批单流程。
签审单就是通过一级一级签字通过不需要驳回如图

实验审批单流程会含有网关节点即通过和驳回。

下面我将直接演示审批流程的实现--------图2
懒人可以戳这里 https://github.com/jiajia154569836/activiti-
注意本节代码在这里

项目搭建
使用的工具是idea,管理工具maven,架构是spring mvc+mybatis,数据库mysql。
首先搭建springmvc+mybatis。搭建好之后在maven中引入activiti版本5.21.0
<properties>
<activiti.version>5.21.0</activiti.version>
</properties>
<!-- 工作流相关的jar -->
<!-- https://mvnrepository.com/artifact/javax.activation/activation -->
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.activiti/activiti-bpmn-converter -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-converter</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.activiti/activiti-bpmn-layout -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-layout</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.activiti/activiti-bpmn-model -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-model</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.activiti/activiti-diagram-rest -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-diagram-rest</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.activiti/activiti-engine -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.activiti/activiti-image-generator -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-image-generator</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.activiti/activiti-json-converter -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.activiti/activiti-modeler -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-modeler</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.activiti/activiti-process-validation -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-process-validation</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.activiti/activiti-spring -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>${activiti.version}</version>
</dependency>
在resources目录下引入 ApplicationContext-activiti.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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:aop="http://www.springframework.org/schema/aop"
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.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 使用系统数据源1 -->
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="activityFontName" value="宋体"/>
<property name="labelFontName" value="宋体"/>
<property name="annotationFontName" value="宋体"/>
<property name="databaseSchemaUpdate" value="true" /><!-- true:表不存在就自动创建,false:不创建 -->
</bean>
<!-- 独立链接数据库
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcDriver" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=utf8"></property>
<property name="jdbcUsername" value="root"></property>
<property name="jdbcPassword" value="root"></property>
<property name="databaseSchemaUpdate" value="true"></property>
</bean>
-->
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>
<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="formService" factory-bean="processEngine" factory-method="getFormService" />
<bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService" />
<bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" />
<bean id="identityService" factory-bean="processEngine" factory-method="getIdentityService" />
<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper"/>
</beans>
在web.xml中引入该xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring/ApplicationContext-main.xml,
classpath:spring/ApplicationContext-dataSource.xml,
classpath:spring/ApplicationContext-activiti.xml
</param-value>
</context-param>
一个入门例子基本了解工作流
先在resources目录下 引入activiti.cfg.xml及需要部署的流程放在bpmn文件夹下

activiti.cfg.xml文件为
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/activiti?characterEncoding=utf-8" />
<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="123456" />
<!--解决上传下载的中文乱码-->
<property name="activityFontName" value="宋体"/>
<property name="labelFontName" value="宋体"/>
<property name="annotationFontName" value="宋体"/>
<!-- true:表不存在就自动创建,false:不创建 -->
<property name="databaseSchemaUpdate" value="true" />
<!--保存全部历史变量与hi_detail表中-->
<property name="history" value="full"/>
</bean>
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>
<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="formService" factory-bean="processEngine" factory-method="getFormService" />
<bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService" />
<bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" />
<bean id="identityService" factory-bean="processEngine" factory-method="getIdentityService" />
<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper"/>
</beans>
请假流程(反射).bpmn20.xml内容
<?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:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.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" /">
<process id="KEY_leave" name="请假流程" isExecutable="true">
<startEvent id="start" name="开始"></startEvent>
<endEvent id="end" name="结束"></endEvent>
<userTask id="option1" name="提交申请" activiti:assignee="#{user1}" activiti:formKey="bpmn1/start.form">
<extensionElements>
<activiti:taskListener event="create" class="com.activiti.ManagerTaskHandler"></activiti:taskListener>
</extensionElements>
</userTask>
<userTask id="option2" name="审批【部门经理】" activiti:assignee="#{user2}" activiti:formKey="bpmn1/dept-leader-audit.form">
<extensionElements>
<activiti:taskListener event="create" class="com.activiti.ManagerTaskHandler"></activiti:taskListener>
</extensionElements>
</userTask>
<userTask id="option3" name="审批【总经理】" activiti:assignee="#{user3}" activiti:formKey="bpmn1/all-leader-audit.form">
<extensionElements>
<activiti:taskListener event="create" class="com.activiti.ManagerTaskHandler"></activiti:taskListener>
</extensionElements>
</userTask>
<exclusiveGateway id="pt1" name="批准or驳回"></exclusiveGateway>
<exclusiveGateway id="pt2" name="批准or驳回"></exclusiveGateway>
<sequenceFlow id="flow1" name="启动" sourceRef="start" targetRef="option1"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="option2" targetRef="pt1"></sequenceFlow>
<sequenceFlow id="flow2" name="提交" sourceRef="option1" targetRef="option2"></sequenceFlow>
<sequenceFlow id="flow6" sourceRef="option3" targetRef="pt2"></sequenceFlow>
<sequenceFlow id="flow4" name="驳回" sourceRef="pt1" targetRef="option1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${RESULT == "驳回"}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow5" name="批准" sourceRef="pt1" targetRef="option3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${RESULT == "批准"}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow8" name="批准" sourceRef="pt2" targetRef="end">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${RESULT == "批准"}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow7" name="驳回" sourceRef="pt2" targetRef="option1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${RESULT == "驳回"}]]></conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_KEY_leave">
<bpmndi:BPMNPlane bpmnElement="KEY_leave" id="BPMNPlane_KEY_leave">
<bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
<omgdc:Bounds height="30.0" width="30.0" x="30.0" y="130.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
<omgdc:Bounds height="28.0" width="28.0" x="660.0" y="231.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="option1" id="BPMNShape_QJ1">
<omgdc:Bounds height="80.0" width="100.0" x="150.0" y="105.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="option2" id="BPMNShape_QJ2">
<omgdc:Bounds height="80.0" width="100.0" x="315.0" y="15.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="option3" id="BPMNShape_QJ3">
<omgdc:Bounds height="80.0" width="100.0" x="480.0" y="105.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="pt1" id="BPMNShape_pt1">
<omgdc:Bounds height="40.0" width="40.0" x="345.0" y="125.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="pt2" id="BPMNShape_pt2">
<omgdc:Bounds height="40.0" width="40.0" x="510.0" y="225.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="365.0" y="95.0"></omgdi:waypoint>
<omgdi:waypoint x="365.0" y="125.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="200.0" y="105.0"></omgdi:waypoint>
<omgdi:waypoint x="200.0" y="55.0"></omgdi:waypoint>
<omgdi:waypoint x="315.0" y="55.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
<omgdi:waypoint x="385.0" y="145.0"></omgdi:waypoint>
<omgdi:waypoint x="480.0" y="145.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
<omgdi:waypoint x="345.0" y="145.0"></omgdi:waypoint>
<omgdi:waypoint x="250.0" y="145.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="60.0" y="145.0"></omgdi:waypoint>
<omgdi:waypoint x="150.0" y="145.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
<omgdi:waypoint x="510.0" y="245.0"></omgdi:waypoint>
<omgdi:waypoint x="200.0" y="245.0"></omgdi:waypoint>
<omgdi:waypoint x="200.0" y="185.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
<omgdi:waypoint x="530.0" y="185.0"></omgdi:waypoint>
<omgdi:waypoint x="530.0" y="225.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
<omgdi:waypoint x="550.0" y="245.0"></omgdi:waypoint>
<omgdi:waypoint x="660.0" y="245.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
对应的流程图

测试实例
public class LeaveActivitiTest {
/**
* 会默认按照Resources目录下的activiti.cfg.xml创建流程引擎
*/
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
/**
* 根据配置文件activiti.cfg.xml创建ProcessEngine
*/
@Test
public void testCreateProcessEngineByCfgXml() {
ProcessEngineConfiguration cfg = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
ProcessEngine engine = cfg.buildProcessEngine();
}
/**
* 发布流程
* RepositoryService
*/
@Test
public void deployProcess() {
RepositoryService repositoryService = processEngine.getRepositoryService();
DeploymentBuilder builder = repositoryService.createDeployment();
builder.addClasspathResource("bpmn/请假流程(反射).bpmn20.xml");
builder.deploy();
}
/**
* 启动流程
* RuntimeService
*/
@Test
public void startProcess() {
RuntimeService runtimeService = processEngine.getRuntimeService();
//可根据id、key、message启动流程
String trialId = "001";
Map<String, Object> map = new HashMap<String, Object>();
map.put("trialId", trialId); //当前实验id 保存变量
map.put("user1", "chenyue"); //当前用户的姓名
ProcessInstance pi = processEngine.getRuntimeService().startProcessInstanceByKey("KEY_leave", map);
System.out.println("流程实例ID:" + pi.getId()); //流程实例ID
System.out.println("流程定义ID:" + pi.getProcessDefinitionId()); //流程定义ID
System.out.println("存入实验id" + trialId + "流程实例ID" + pi.getId());
}
/**
* 查看任务
* TaskService
*/
@Test
public void queryTask() {
TaskService taskService = processEngine.getTaskService();
//根据assignee(代理人)查询任务
String assignee = "chenyue";
List<Task> tasks = taskService.createTaskQuery().taskAssignee(assignee).list();
int size = tasks.size();
for (int i = 0; i < size; i++) {
Task task = tasks.get(i);
}
for (Task task : tasks) {
System.out.println("taskId:" + task.getId() +
",taskName:" + task.getName() +
",assignee:" + task.getAssignee() +
",createTime:" + task.getCreateTime());
}
}
/**
* 办理任务
*/
@Test
public void handleTask() {
TaskService taskService = processEngine.getTaskService();
//根据上一步生成的taskId执行任务
String taskId = "25004";
Map<String, Object> map = new HashMap<String, Object>();
map.put("RESULT", "批准");
taskService.complete(taskId, map);
}
/**
* 判断流程结束
*/
@Test
public void ifEnd() {
String processInstanceId = "17501";
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance pi = runtimeService.createProcessInstanceQuery()//
.processInstanceId(processInstanceId)//使用流程实例ID查询
.singleResult();
if (pi == null) {
System.out.println("流程结束");
} else {
System.out.println("未结束");
}
}
/**
* 删除流程
*/
@Test
public void deleteDeployment() {
String deploymentId = "37501";
//获取仓库服务对象
RepositoryService repositoryService = processEngine.getRepositoryService();
//普通删除,如果当前规则下有正在执行的流程,则抛异常
repositoryService.deleteDeployment(deploymentId);
//级联删除,会删除和当前规则相关的所有信息,正在执行的信息,也包括历史信息
//repositoryService.deleteDeployment(deploymentId, true);
System.out.println("删除成功" + deploymentId);
}
}
注意:
依次顺序执行
在初次执行时用mysql数据库会出现以下错误
org.activiti.engine.ActivitiException: couldn't create db schema: create index ACT_IDX_HI_PROCVAR_NAME_TYPE on ACT_HI_VARINST(NAME_, VAR_TYPE_)
需要在 act_hi_procinst表中将tenant_id__,name_ 字段长度改成100(原来是255)
在执行startProcess()后输出

说明:
流程实例ID 将贯穿整个流程(80001),流程定义ID与部署流程有关下面讲,
其中map.put("user1", "chenyue");对应#{user1}即第一个节点的指派人这里的指派人就是chenyue

接着查询chenyue的任务 执行queryTask()
注意这里的 String assignee = "chenyue";执行后如图

这里有一条记录说明chenyue有一条任务需要完成
接着完成chenyue的任务 执行handleTask()
注意这里的String taskId = "80008";上图的taskId
并设置map.put("user2", "chenyue1");
此时第二个指派人为chenyue1执行如图

表明完成任务
接着查看chenyue1的任务 执行queryTask()
此时 String assignee = "chenyue1";

chenyue1(部门经理)同样有一条记录此时taskId是92506
完成chenyue1的任务 执行handleTask();
@Test
public void handleTask() {
TaskService taskService = processEngine.getTaskService();
//根据上一步生成的taskId执行任务
String taskId = "92506";
Map<String, Object> map = new HashMap<String, Object>();
map.put("RESULT", "批准");
map.put("user3", "chenyue2");
taskService.complete(taskId, map);
}
说明 :taskId还是上面执行的结果得到的 ,user3是指派下一个节点的执行人
RESULT顾名思义是本次执行的结果是批准还是驳回RESULT是在请假流程(反射).bpmn20.xml这个配置文件中配置的
<sequenceFlow id="flow8" name="批准" sourceRef="pt2" targetRef="end">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${RESULT == "批准"}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow7" name="驳回" sourceRef="pt2" targetRef="option1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${RESULT == "驳回"}]]></conditionExpression>
接着查询chenyue2并执行重复上面的操作


至此请假流程走完 最后执行ifEnd() 并将processid改为。。忘了请查看我们一开始执行startProcess()的图片 好吧是80001
String processInstanceId = "80001";

到此基本的操作结束,流程走完我想应该对工作流有了基本的认识
接下来看一下数据库中activiti的25张表,如图

乍一看是不是觉得很复杂...别慌其实常用的不到10张表
先看一下把大致这么几类hi(history),id(identity),re(repository),ru(run)等其实常用的就hi和ru
在执行完上面的流程时我们先看一下act_hi_actinst(常用字段)

这7条记录就是我们执行上面流程的全部流程实例信息
start->option1->pt1->option2->pt2->option3->end
所以查询流程历史信息及展示时用到这张表如图


在来看一下act_hi_detail(常用字段)

注意:NAME_ 对应的TEXT_ 将会得到审批人和操作即同意和驳回这里的TEXT显然是用二进制存起来了,这些信息项目中会用到
还有就是act_hi_comment 这张表的意思就是审批完后会有审批意见在这张表里会有记录因为我们在操作的时候并没有添加 如图:

comment代码如下
// 添加审批意见
taskService.addComment(task.getId() ,task.getProcessInstanceId(),opinion);
//得到审批意见
String opinion = "";
List<Comment> commentlist = taskService.getTaskComments(hai.getTaskId());
if (commentlist.size() > 0) {
opinion = commentlist.get(0).getFullMessage();
}
进阶操作
问题来了刚刚如果忘记了processinstanceid怎么办?
@Test
public void searchHistoryInfo() {
//根据历史变量获取processInstanceId
String trialId="001";
HistoryService historyService = processEngine.getHistoryService();
String processInstanceId = historyService.createHistoricVariableInstanceQuery()
.variableValueEquals("trialId", trialId).singleResult().getProcessInstanceId();
}
这个trialId是我们一开始执行startProcess()方法时注入的变量时唯一的
但是...有的人可能会得到processInstanceId(第一次执行的可以)

说明我这里的trialId不是唯一的可以看到有11个,再实际项目中id一定是唯一的所以这是一种获取办法
还有一种获取办法是正在执行的流程时可以执行以下代码
//根据taskId获取processInstanceId 注意必须是正在执行的流程
String taskId = "95006";
String processId=taskService.createTaskQuery().taskId(taskId).singleResult()
.getProcessInstanceId();
在工作流中经常用到的查询五大类
/**
* activiti是不能直接识别xml格式的流程图的,流程图文件必须被部署到activiti中才能被activiti识别并使用。 而这个过程就是使用repositoryService来完成的,流程图被导入后,会放到act_re_打头的几个表中。 repositoryService提供了若干个接口,既可以部署xml文件到数据库中,也可以从数据库检索特定流程图供处理。
*/
@Autowired
private RepositoryService repositoryService;
/**
* activiti最重要的一个服务,基本上所有的关于流程的操作都是通过此服务来执行的。例如启动流程、审批、会签等等。
*/
@Autowired
private RuntimeService runtimeService;
/**
* 任务是activiti的核心功能之一,所有涉及到任务的操作都是通过此服务来完成的。例如任务的查询、分配、认领、完成等。
*/
@Autowired
private TaskService taskService;
/**
* 所有流程实例的信息都会被保存的历史信息中,当一个流程实例结束之前, 它是被保存在runtime和history两个地方,当它结束后,就只有history里了。
*/
@Autowired
private HistoryService historyService;
/**
* 工作流的设计思路之一就是将每个节点需要显示的数据直接绑定到此节点。 而formService就是专门为此服务的,使用formService可以获取某个节点绑定的表单数据。 当然,如果没有表单绑定到此节点,此服务就没有任何用处。
*/
@Autowired
private FormService formService;
获得这些实体类需要 activiti的核心ProcessEngine 如下
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
先来看一下taskService

可以看到查询task的条件是processInstancesId,taskDefinitionKey,taskAssignee等.........
所以想要查询Task需要具备以上条件,当一个调条件不能确定唯一时可以迭代多个条件(一般两个即可)
Task task = taskService.createTaskQuery().taskAssignee("")
.processInstanceId("").singleResult();
再来看一下HistoryService

可以看到这个很常用可以查询历史流程实例,活动实例,变量实例,Detail查询等

可以看到查询历史信息的条件也是需要processInstancesId,taskDefinitionKey,taskAssignee等.........当然一定要记着排序也有orderby的接口
再来讲一下如何获取当前节点的的下一个节点的信息

如图:我们接下来要做的就是活的K节点下面的三个节点ABC
这是一种带网关的获取即 K 下面由网关连接ABC
还有就是获得A节点下面的D节点这种事不带网关的
该测试代码在这里:

老规矩先的在resources目录下引入bpmn文件

<?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:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.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" /">
<process id="Cy1" name="cc" isExecutable="true">
<startEvent id="start" name="开始"></startEvent>
<userTask id="user0" name="K"></userTask>
<exclusiveGateway id="git1" name="网关"></exclusiveGateway>
<sequenceFlow id="sid-C67B7379-222E-4BA5-A46E-9E9D068B1189" sourceRef="start" targetRef="user0"></sequenceFlow>
<sequenceFlow id="flow1" sourceRef="user0" targetRef="git1"></sequenceFlow>
<userTask id="user1" name="A"></userTask>
<userTask id="user2" name="B"></userTask>
<userTask id="user3" name="C"></userTask>
<userTask id="user4" name="D"></userTask>
<endEvent id="end" name="结束"></endEvent>
<sequenceFlow id="sid-7C08C697-77B7-4B61-83A7-BB1FC06DE1A9" sourceRef="user1" targetRef="user4"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="user4" targetRef="end"></sequenceFlow>
<sequenceFlow id="sid-4D6165F1-26A4-46B6-81DC-A4E7C614F264" sourceRef="user2" targetRef="end"></sequenceFlow>
<sequenceFlow id="sid-AD9AE457-3C6A-4AC0-BCF4-7494CB46B64D" sourceRef="user3" targetRef="end"></sequenceFlow>
<sequenceFlow id="sid-DD9C3A54-E5E4-46B5-9C3A-51F974FCED45" sourceRef="git1" targetRef="user1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${git1 == "1"}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-13638719-3809-4E37-97F7-86ABA125EB46" sourceRef="git1" targetRef="user2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${git1 == "2"}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-35E6A330-8E71-4837-896A-64DDFB7C030B" sourceRef="git1" targetRef="user3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${git1 == "3"}]]></conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_Cy">
<bpmndi:BPMNPlane bpmnElement="Cy1" id="BPMNPlane_Cy">
<bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
<omgdc:Bounds height="30.0" width="30.0" x="285.0" y="30.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="user0" id="BPMNShape_user0">
<omgdc:Bounds height="30.0" width="30.0" x="285.0" y="70.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="git1" id="BPMNShape_git1">
<omgdc:Bounds height="40.0" width="40.0" x="280.0" y="123.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="user1" id="BPMNShape_user1">
<omgdc:Bounds height="80.0" width="100.0" x="60.0" y="195.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="user4" id="BPMNShape_user4">
<omgdc:Bounds height="80.0" width="100.0" x="20.0" y="215.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="user2" id="BPMNShape_user2">
<omgdc:Bounds height="80.0" width="100.0" x="225.0" y="180.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="user3" id="BPMNShape_user3">
<omgdc:Bounds height="80.0" width="100.0" x="390.0" y="150.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
<omgdc:Bounds height="28.0" width="28.0" x="255.0" y="315.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-AD9AE457-3C6A-4AC0-BCF4-7494CB46B64D" id="BPMNEdge_sid-AD9AE457-3C6A-4AC0-BCF4-7494CB46B64D">
<omgdi:waypoint x="390.7913669064748" y="230.0"></omgdi:waypoint>
<omgdi:waypoint x="279.86365246378955" y="320.1693117399605"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-DD9C3A54-E5E4-46B5-9C3A-51F974FCED45" id="BPMNEdge_sid-DD9C3A54-E5E4-46B5-9C3A-51F974FCED45">
<omgdi:waypoint x="286.98936170212767" y="149.98936170212767"></omgdi:waypoint>
<omgdi:waypoint x="160.0" y="210.98425196850394"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-35E6A330-8E71-4837-896A-64DDFB7C030B" id="BPMNEdge_sid-35E6A330-8E71-4837-896A-64DDFB7C030B">
<omgdi:waypoint x="314.75" y="148.25"></omgdi:waypoint>
<omgdi:waypoint x="390.0" y="173.33333333333331"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-4D6165F1-26A4-46B6-81DC-A4E7C614F264" id="BPMNEdge_sid-4D6165F1-26A4-46B6-81DC-A4E7C614F264">
<omgdi:waypoint x="272.79816513761466" y="260.0"></omgdi:waypoint>
<omgdi:waypoint x="269.76947730701096" y="315.0211622559672"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-13638719-3809-4E37-97F7-86ABA125EB46" id="BPMNEdge_sid-13638719-3809-4E37-97F7-86ABA125EB46">
<omgdi:waypoint x="295.5" y="158.5"></omgdi:waypoint>
<omgdi:waypoint x="288.3333333333333" y="180.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-7C08C697-77B7-4B61-83A7-BB1FC06DE1A9" id="BPMNEdge_sid-7C08C697-77B7-4B61-83A7-BB1FC06DE1A9">
<omgdi:waypoint x="160.0" y="264.55974842767296"></omgdi:waypoint>
<omgdi:waypoint x="256.94853781834865" y="321.87523619449547"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-C67B7379-222E-4BA5-A46E-9E9D068B1189" id="BPMNEdge_sid-C67B7379-222E-4BA5-A46E-9E9D068B1189">
<omgdi:waypoint x="300.0761411510132" y="59.99980674959255"></omgdi:waypoint>
<omgdi:waypoint x="300.3979591836735" y="123.39795918367346"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
代码如下:
package activiti;
import org.activiti.engine.*;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.javax.el.ExpressionFactory;
import org.activiti.engine.impl.javax.el.ValueExpression;
import org.activiti.engine.impl.juel.ExpressionFactoryImpl;
import org.activiti.engine.impl.juel.SimpleContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.task.TaskDefinition;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GitwayTest {
/**
* 会默认按照Resources目录下的activiti.cfg.xml创建流程引擎
*/
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
/**
* 根据配置文件activiti.cfg.xml创建ProcessEngine
*/
@Test
public void testCreateProcessEngineByCfgXml() {
ProcessEngineConfiguration cfg = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
ProcessEngine engine = cfg.buildProcessEngine();
}
/**
* 发布流程
* RepositoryService
*/
@Test
public void deployProcess() {
RepositoryService repositoryService = processEngine.getRepositoryService();
DeploymentBuilder builder = repositoryService.createDeployment();
builder.addClasspathResource("bpmn3/Cy.bpmn20.xml");
builder.deploy();
}
/**
* 启动流程
* RuntimeService
*/
@Test
public void startProcess() {
RuntimeService runtimeService = processEngine.getRuntimeService();
//可根据id、key、message启动流程
String trialId = "001";
Map<String, Object> map = new HashMap<String, Object>();
map.put("trialId", trialId); //当前实验id
// map.put("user2","王善军"); //当前用户的姓名
//当前用户的姓名
ProcessInstance pi = processEngine.getRuntimeService().startProcessInstanceByKey("Cy1", map);
System.out.println("流程实例ID:" + pi.getId()); //流程实例ID
System.out.println("流程定义ID:" + pi.getProcessDefinitionId()); //流程定义ID
System.out.println("存入实验id" + trialId + "流程实例ID" + pi.getId());
List<Task> tasks = taskService.createTaskQuery().processInstanceId(pi.getId()).list();
int size = tasks.size();
for (int i = 0; i < size; i++) {
Task task = tasks.get(i);
}
for (Task task : tasks) {
System.out.println("taskId:" + task.getId() +
",taskName:" + task.getName() +
",assignee:" + task.getAssignee() +
",createTime:" + task.getCreateTime());
}
// TaskDefinition a= getNextTaskInfo();
}
// 流程实例ID:75001
// 流程定义ID:Cy1:1:72504
// 存入实验id001流程实例ID75001
// taskId:75006,taskName:K,assignee:null,createTime:Tue Aug 21 10:06:43 CST 2018
/**
* 办理任务根据id
*/
@Test
public void handleTask() {
TaskService taskService = processEngine.getTaskService();
//根据上一步生成的taskId执行任务
String taskId = "75006";
String taskName = "K";
String processId = "75001";
Task task = taskService.createTaskQuery().processInstanceId(processId).taskDefinitionKey(taskName).singleResult();
Map<String, Object> map = new HashMap<String, Object>();
map.put("git1", "1");
taskService.complete(taskId, map);
}
/**
* 办理任务根据key
*/
@Test
public void handle1Task() {
TaskService taskService = processEngine.getTaskService();
//根据上一步生成的taskId执行任务
String taskId = "62506";
String taskName = "A";
String taskKey = "user1";
String processId = "75001";
Task task = taskService.createTaskQuery().taskDefinitionKey(taskKey).singleResult();
System.out.println("taskid"+task.getId());
Map<String, Object> map = new HashMap<String, Object>();
map.put("git1", "1");
taskService.complete(task.getId(), map);
}
/**
* 查看任务
* TaskService
*/
@Test
public void queryTask() {
TaskService taskService = processEngine.getTaskService();
//根据processid
String processid = "75001";
List<Task> tasks = taskService.createTaskQuery().processInstanceId(processid).list();
int size = tasks.size();
for (int i = 0; i < size; i++) {
Task task = tasks.get(i);
}
for (Task task : tasks) {
System.out.println("taskId:" + task.getId() +
",taskName:" + task.getName() +
",assignee:" + task.getAssignee() +
",createTime:" + task.getCreateTime());
}
}
/**
* 判断流程结束
*/
@Test
public void ifEnd() {
String processInstanceId = "55001";
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance pi = runtimeService.createProcessInstanceQuery()//
.processInstanceId(processInstanceId)//使用流程实例ID查询
.singleResult();
if (pi == null) {
System.out.println("流程结束");
} else {
System.out.println("未结束");
}
}
/*
*输出下一节点的key
* */
@Test
public void searchTask() throws Exception {
String taskId = "77505";
String processId = "75001" ;
String processDefId = "Cy1:1:72504";
List<TaskDefinition> nextTaskGroup = getNextTaskInfo(taskId);
if (nextTaskGroup.size() > 0) {
for (TaskDefinition aa : nextTaskGroup) {
System.out.println(aa.getKey());
//查出taskname
System.out.println(aa.getNameExpression());
}
}
}
//获取下一节点办理人
//TaskDefinition a = getNextTaskInfo();
/**
* 获取下一个用户任务信息
*
* @param String taskId 任务Id信息
* @return 下一个用户任务用户组信息
* @throws Exception
*/
public List<TaskDefinition> getNextTaskInfo(String taskId) throws Exception {
ProcessDefinitionEntity processDefinitionEntity = null;
String id = null;
List<TaskDefinition> tasks = new ArrayList<TaskDefinition>();
//获取流程实例Id信息
String processInstanceId = taskService.createTaskQuery().taskId(taskId).singleResult().getProcessInstanceId();
//获取流程发布Id信息
String definitionId = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult().getProcessDefinitionId();
processDefinitionEntity = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
.getDeployedProcessDefinition(definitionId);
ExecutionEntity execution = (ExecutionEntity) runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
//当前流程节点Id信息
String activitiId = execution.getActivityId();
//获取流程所有节点信息
List<ActivityImpl> activitiList = processDefinitionEntity.getActivities();
//遍历所有节点信息
for (ActivityImpl activityImpl : activitiList) {
id = activityImpl.getId();
System.out.println("id=" + id);
if (activitiId.equals(id)) {
//获取下一个节点信息
tasks = nextTaskDefinition(activityImpl, activityImpl.getId(), null, processInstanceId);
break;
}
}
return tasks;
}
/**
* 下一个任务节点信息,
* <p>
* 如果下一个节点为用户任务则直接返回,
* <p>
* 如果下一个节点为排他网关, 获取排他网关Id信息, 根据排他网关Id信息和execution获取流程实例排他网关Id为key的变量值,
* 根据变量值分别执行排他网关后线路中的el表达式, 并找到el表达式通过的线路后的用户任务
*
* @param ActivityImpl activityImpl 流程节点信息
* @param String activityId 当前流程节点Id信息
* @param String elString 排他网关顺序流线段判断条件
* @param String processInstanceId 流程实例Id信息
* @return
*/
private List<TaskDefinition> nextTaskDefinition(ActivityImpl activityImpl, String activityId, String elString, String processInstanceId) {
PvmActivity ac = null;
List<TaskDefinition> tasks = new ArrayList<TaskDefinition>();
List<TaskDefinition> taskss = new ArrayList<TaskDefinition>();
Object s = null;
// 如果遍历节点为用户任务并且节点不是当前节点信息
if ("userTask".equals(activityImpl.getProperty("type")) && !activityId.equals(activityImpl.getId())) {
// 获取该节点下一个节点信息
TaskDefinition taskDefinition = ((UserTaskActivityBehavior) activityImpl.getActivityBehavior())
.getTaskDefinition();
tasks.add(taskDefinition);
return tasks;
} else {
// 获取节点所有流向线路信息
List<PvmTransition> outTransitions = activityImpl.getOutgoingTransitions();
List<PvmTransition> outTransitionsTemp = null;
for (PvmTransition tr : outTransitions) {
ac = tr.getDestination(); // 获取线路的终点节点
// 如果流向线路为排他网关
if ("exclusiveGateway".equals(ac.getProperty("type"))) {
outTransitionsTemp = ac.getOutgoingTransitions();
// 如果网关路线判断条件为空信息
if (StringUtils.isEmpty(elString)) {
// 获取流程启动时设置的网关判断条件信息
elString = getGatewayCondition(ac.getId(), processInstanceId);
System.out.println("el=" + elString);
}
// 如果排他网关只有一条线路信息
if (outTransitionsTemp.size() == 1) {
System.out.println("1条");
return nextTaskDefinition((ActivityImpl) outTransitionsTemp.get(0).getDestination(), activityId,
elString, processInstanceId);
} else if (outTransitionsTemp.size() > 1) { // 如果排他网关有多条线路信息
System.out.println("多条");
for (PvmTransition tr1 : outTransitionsTemp) {
s = tr1.getProperty("conditionText"); // 获取排他网关线路判断条件信息
System.out.println("s=" + s.toString());
// 判断el表达式是否成立
// if (isCondition(ac.getId(), StringUtils.trim(s.toString()), elString)) {
System.out.println("成立");
taskss.add(nextTaskDefinition((ActivityImpl) tr1.getDestination(), activityId, elString,
processInstanceId).get(0));
// }
}
return taskss;
}
} else if ("userTask".equals(ac.getProperty("type"))) {
taskss.add(((UserTaskActivityBehavior) ((ActivityImpl) ac).getActivityBehavior()).getTaskDefinition());
return taskss;
} else {
}
}
return null;
}
}
/**
* 查询流程启动时设置排他网关判断条件信息
*
* @param String gatewayId 排他网关Id信息, 流程启动时设置网关路线判断条件key为网关Id信息
* @param String processInstanceId 流程实例Id信息
* @return
*/
public String getGatewayCondition(String gatewayId, String processInstanceId) {
Execution execution = runtimeService.createExecutionQuery().processInstanceId(processInstanceId).singleResult();
Object object = runtimeService.getVariable(execution.getId(), gatewayId);
return object == null ? "" : object.toString();
}
/**
* 根据key和value判断el表达式是否通过信息
*
* @param String key el表达式key信息
* @param String el el表达式信息
* @param String value el表达式传入值信息
* @return
*/
public boolean isCondition(String key, String el, String value) {
ExpressionFactory factory = new ExpressionFactoryImpl();
SimpleContext context = new SimpleContext();
context.setVariable(key, factory.createValueExpression(value, String.class));
ValueExpression e = factory.createValueExpression(context, el, boolean.class);
return (Boolean) e.getValue(context);
}
}
按照步骤依次执行然后查看searchTask()方法
这里我们得到的对象是TaskDefinition 当然我们打印的只是name和key

这是得到A节点下面的D节点
我们其实还是可以看到TaskDefinition这个对象可以得到很多节点信息

当前节点指派人,节点名称,key,节点描述等等
到此常用操作也就说完了
高级操作
引入流程设计器
因为我们的需求除了实现流程之外还要教会用户使用并自己设计流程。

1.将editor目录引入到你的项目中如图:

2.将stencilset.json文件放入resources文件夹下

3.将activiti-editor文件夹下的的两个目录和一个html文件放在webapp下

4.导入我的maven将不再需要引入其它的controller
5.测试
@Test
public void create(/*@RequestParam("name") String name, @RequestParam("key") String key,@RequestParam("description") String description, HttpServletRequest request, HttpServletResponse response*/
) {
HttpServletRequest request = null;
HttpServletResponse response = null;
RepositoryService repositoryService = processEngine.getRepositoryService();
String name = "test";
String key = "test";
String description = "测试";
try {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode editorNode = objectMapper.createObjectNode();
editorNode.put("id", "canvas");
editorNode.put("resourceId", "canvas");
ObjectNode stencilSetNode = objectMapper.createObjectNode();
stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
editorNode.put("stencilset", stencilSetNode);
Model modelData = repositoryService.newModel();
ObjectNode modelObjectNode = objectMapper.createObjectNode();
modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, name);
modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
description = StringUtils.defaultString(description);
modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
modelData.setMetaInfo(modelObjectNode.toString());
modelData.setName(name);
modelData.setKey(StringUtils.defaultString(key));
repositoryService.saveModel(modelData);
repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
System.out.println(modelData.getId());
// response.sendRedirect(request.getContextPath() + "activiti-editor/modeler.html?modelId=" + modelData.getId());
} catch (Exception e) {
// logger.error("创建模型失败:", e);
System.out.println("创建模型失败");
}
}
执行完之后查看数据库act_ge_bytearray,act_re_model这两招表将添加新的数据。act_ge_bytearray表中的BYTES_字段下保存的就是我们绘制的流程图信息以二进制保存在其中类型为BLOB。
结论
model表中保存的是模型ID等基础信息,bytearray与其关联,保存的是流程图的主要内容。
引入流程图,添加自定义表单,打包流程xml和png,zip部署等
因为要开始学习人工智能方面的东西上面的就不在详解了 git里都有的。
网友评论