环境:Springboot2.2.11.RELEASE + Activiti7.1.0.M6 + MySQL
环境说明:
<dependencies> <dependency> <groupId>org.activiti.dependencies</groupId> <artifactId>activiti-dependencies</artifactId> <version>7.1.0.M6</version> <type>pom</type> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>7.1.0.M6</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency></dependencies>
不知为何activit7中要吧这security强关联。整合。工作。流引
以上是整合pom.xml中所要引入的依赖。
所有的表:
图片
表结构说明:
ACT_RE_*: 'RE’表示repository。这个前缀的流引表包含了流程定义和流程 静态资源(图片、规则等等)
ACT_RU_*: 'RU’表示runtime。整合这些运行时的工作表,包含流程实例,流引认为,变量,异步任务等运行中的数据。Activiti只在流程实例执行过程中保持这些数据,在流程结束时就会删除这些记录。这样运行时表可以一直很小速度很快。
ACT_HI_*: 'HI’表示history。这些表包含历史数据,比如历史流程实例,遍历,任务等等。
ACT_GE_*: 'GE’表示general。通用数据,用于不同场景。
通用数据(act_ge_*)
图片
流程定义(act_re_*)
图片
运行实例(act_ru_*)
图片
历史流程(act_hi_*)
图片
其他
图片
来源网络
流程引擎的抽象,可以通过此类获取需要的所有服务。
通过ProcessEngine获取,Activiti将不同生命周期的服务封装在不同Service中,包括定义、部署、运行。通过服务类可获取相关生命周期中的服务信息。
TaskService
流程运行过程中,每个任务节点的相关操作接口,如complete,delete,delegate等。
RepositoryService
流程定义和部署相关的存储服务。
RuntimeService
流程运行时相关的服务,如根据流程好启动流程实例startProcessInstanceByKey。
HistoryService
历史记录相关服务接口。
关于eclipse中安装插件就不说了,我是把插件下载下来安装的,在线安装不上。
设计请假流程
在src/main/resources下新建processes文件夹,springboot下默认的流程文件定义路径前缀及文件后缀如下:
图片
这里可以在application.yml配置文件中更改。
设计一个请假的流程holiday.bpmn
图片
<?xml versinotallow="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" targetNamespace="http://www.pack.org"> <process id="holiday" name="holiday" isExecutable="true"> <startEvent id="startevent1" name="Start"></startEvent> <endEvent id="endevent1" name="End"></endEvent> <userTask id="usertask1" name="部门经理审批" activiti:assignee="${ mgr}"></userTask> <userTask id="usertask2" name="总经理审批" activiti:assignee="${ top}"></userTask> <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow> <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow> <userTask id="usertask3" name="填写审批单" activiti:assignee="${ assignee}"></userTask> <sequenceFlow id="flow4" sourceRef="startevent1" targetRef="usertask3"></sequenceFlow> <sequenceFlow id="flow5" sourceRef="usertask3" targetRef="usertask1"></sequenceFlow> </process> <bpmndi:BPMNDiagram id="BPMNDiagram_holiday"> <bpmndi:BPMNPlane bpmnElement="holiday" id="BPMNPlane_holiday"> <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1"> <omgdc:Bounds height="35.0" width="35.0" x="505.0" y="60.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1"> <omgdc:Bounds height="35.0" width="35.0" x="505.0" y="550.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1"> <omgdc:Bounds height="55.0" width="105.0" x="470.0" y="290.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2"> <omgdc:Bounds height="55.0" width="105.0" x="470.0" y="420.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3"> <omgdc:Bounds height="55.0" width="105.0" x="470.0" y="170.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2"> <omgdi:waypoint x="522.0" y="345.0"></omgdi:waypoint> <omgdi:waypoint x="522.0" y="420.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3"> <omgdi:waypoint x="522.0" y="475.0"></omgdi:waypoint> <omgdi:waypoint x="522.0" y="550.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4"> <omgdi:waypoint x="522.0" y="95.0"></omgdi:waypoint> <omgdi:waypoint x="522.0" y="170.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5"> <omgdi:waypoint x="522.0" y="225.0"></omgdi:waypoint> <omgdi:waypoint x="522.0" y="290.0"></omgdi:waypoint> </bpmndi:BPMNEdge> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram></definitions>
上面的每一个节点(任务)都动态的指派了用户执行。
填写审批单:${ assignee};
部门经理审批: ${ mgr};
总经理审批:${ top};
每一个节点执行完成时都需要指明下一个节点的执行人。
配置文件
mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.pack.domainserver: port: 8080spring: activiti: check-process-definitions: true db-history-used: true history-level: full database-schema-update: true datasource: driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/activiti?serverTimeznotallow=GMT%2B8 username: root password: xxxxxx type: com.zaxxer.hikari.HikariDataSource hikari: minimumIdle: 10 maximumPoolSize: 200 autoCommit: true idleTimeout: 30000 poolName: MasterDatabookHikariCP maxLifetime: 1800000 connectionTimeout: 30000 connectionTestQuery: SELECT 1
spring.activiti.db-history-used:表示是用历史表,如果不设置为true那么只会生成17张表,只有设置为true后才会生成25张表。如果不生成历史表那么,流程图及运行节点无法展示。
spring.activiti.history-level:对于历史数据,保存到何种粒度,Activiti提供了history-level属性对其进行配置。history-level属性有点像log4j的日志输出级别,该属性有以下四个值:
spring.activiti.check-process-definitions:如果不设置为true,那么流程定义必须手动进行部署。
@Servicepublic class HolidayService { private static final Logger logger = LoggerFactory.getLogger(HolidayService.class); @Resource private ProcessEngine processEngine; @Resource private RepositoryService repositoryService ; @Resource private RuntimeService runtimeService ; @Resource private TaskService taskService ; /** * <p> * 流程定义的部署 activiti表有哪些? * act_re_deployment 流程定义部署表,记录流程部署信息 * act_re_procdef 流程定义表,记录流程定义信息 * act_ge_bytearray 资源表(bpmn文件及png文件) * </p> * <p>时间:2021年1月22日-下午3:33:00</p> * @author xg * @return void */ public void createDeploy() { Deployment deployment = repositoryService.createDeployment() .addClasspathResource("processes/holiday.bpmn") .addClasspathResource("processes/holiday.png") .name("请假申请单流程") .key("holiday") .category("InnerP") .deploy(); logger.info("流程部署id: { }", deployment.getId()); logger.info("流程部署名称: { }", deployment.getName()); } // 注意这里这个方法是当我们没有开启自动部署流程定义时,就需要手动部署。 /** * <p> * 流程定义查询 * </p> * <p>时间:2021年1月22日-下午3:45:02</p> * @author xg * @param processDefinition * @return void */ public List<ProcessDefinition> queryProcessDefinitionByKey(String processDefinition) { // 查询流程定义 ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery(); List<ProcessDefinition> list = processDefinitionQuery.processDefinitionKey(processDefinition).list(); list.forEach(pd -> { logger.info("------------------------------------------------"); logger.info("流程部署id:{ }", pd.getDeploymentId()); logger.info("流程定义id:{ }", pd.getId()); logger.info("流程定义名称:{ }", pd.getName()); logger.info("流程定义key:{ }", pd.getKey()); logger.info("流程定义版本:{ }", pd.getVersion()); logger.info("------------------------------------------------"); }); return list ; } /** * <p> * 删除流程 * </p> * <p>时间:2021年1月22日-下午4:21:40</p> * @author xg * @return void */ public void deleteDeployment(String deploymentId) { // 设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式,如果流程 repositoryService.deleteDeployment(deploymentId, true); } /** * <p> * 启动流程实例(比如用户根据定义好的流程发起一个流程的实例(这里的请假流程申请)) * <p>时间:2021年1月22日-下午4:54:56</p> * @author xg * @return void */ public void startProcessInstanceById(String processDefinitionId) { ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId) ; logger.info("流程定义ID: { }", processInstance.getProcessDefinitionId()); logger.info("流程实例ID: { }", processInstance.getId()); } /** * <p> * 启动流程实例,指定业务Key(方便关联业务数据)(比如用户根据定义好的流程发起一个流程的实例(这里的请假流程申请)) * Businesskey(业务标识) 启动流程实例时,指定的businesskey,就会在act_ru_execution #流程实例的执行表中存储businesskey。 Businesskey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据。 比如:请假流程启动一个流程实例,就可以将请假单的id作为业务标识存储到activiti中, 将来查询activiti的流程实例信息就可以获取请假单的id从而关联查询业务系统数据库得到请假单信息。 * <p>时间:2021年1月22日-下午4:54:56</p> * @author xg * @return void */ public void startProcessInstanceToBussinessKey(String processDefinitionId, String bussinessKey) { ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, bussinessKey); logger.info("流程定义ID: { }", processInstance.getProcessDefinitionId()); logger.info("流程实例ID: { }", processInstance.getId()); logger.info("BussinessKey: { }", processInstance.getBusinessKey()) ; } /** * <p> * 设置assignee的取值,用户可以在界面上设置流程的执行人 * </p> * <p>时间:2021年1月22日-下午8:30:39</p> * @author xg * @param processDefinitionId * @return void */ public void startProcessInstanceAssignVariables(String processDefinitionId, Map<String, Object> variables) { ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, variables); logger.info("流程定义ID: { }", processInstance.getProcessDefinitionId()); logger.info("流程实例ID: { }", processInstance.getId()); logger.info("BussinessKey: { }", processInstance.getBusinessKey()) ; } /** * <p> * 查询指派关联的用户任务 * </p> * <p>时间:2021年1月23日-上午11:39:56</p> * @author xg * @param assignee 关联用户 * @return List<Task> */ public List<Task> queryTasks(String assignee) { TaskQuery query = taskService.createTaskQuery() ; return query.taskAssignee(assignee).orderByTaskCreateTime().asc().list() ; } public void executionTask(Map<String, Object> variables, String instanceId) { Task task = taskService.createTaskQuery().processInstanceId(instanceId).singleResult() ; if (task == null) { logger.error("任务【{ }】不存在", instanceId) ; throw new RuntimeException("任务【" + instanceId + "】不存在") ; } taskService.complete(task.getId(), variables) ; }}
@RestController@RequestMapping("/holidays")public class HolidayController { @Resource private HolidayService holidayService ; /** * <p>查询制定key的流程审批</p> * <p>时间:2021年1月23日-上午11:17:02</p> * @author xg * @param key ProcessDefinitionKey * @return R */ @GetMapping("") public R lists(String key) { return R.success(holidayService.queryProcessDefinitionByKey(key)) ; } /** * <p>创建请假流程审批(私有)</p> * <p>时间:2021年1月23日-上午10:31:47</p> * @author xg * @return R */ @GetMapping("/_deploy") public R createDeploy() { holidayService.createDeploy(); return R.success() ; } /** * <p>启动请假审批流程</p> * <p>时间:2021年1月23日-上午10:32:55</p> * @author xg * @param userId * @param processDefinitionId 流程定义Id * @return R */ @GetMapping("/start") public R startProcess(String userId, String processDefinitionId) { Map<String, Object> variables = new HashMap<>() ; variables.put("assignee", userId) ; holidayService.startProcessInstanceAssignVariables(processDefinitionId, variables) ; return R.success() ; } /** * <p> * 查询指派给我的任务 * </p> * <p>时间:2021年1月23日-上午11:41:21</p> * @author xg * @param userId 用户Id * @return R */ @GetMapping("/tasks") public R myTasks(String userId) { List<Task> list = holidayService.queryTasks(userId) ; // 注意这里需要我们自己组装下数据,不然会报错。 List<Map<String, Object>> result = list.stream().map(task -> { Map<String, Object> res = new HashMap<String, Object>() ; res.put("id", task.getId()) ; res.put("assignee", task.getAssignee()) ; res.put("createTime", task.getCreateTime()) ; res.put("bussinessKey", task.getBusinessKey()) ; res.put("category", task.getCategory()) ; res.put("dueDate", task.getDueDate()) ; // 到期日期 res.put("desc", task.getDescription()) ; res.put("name", task.getName()) ; res.put("owner", task.getOwner()) ; res.put("instanceId", task.getProcessInstanceId()) ; res.put("variables", task.getProcessVariables()) ; return res ; }).collect(Collectors.toList()) ; return R.success(result) ; } /** * <p> * 填写审批单 * </p> * <p>时间:2021年1月23日-上午11:57:30</p> * @author xg * @param Map取值如下 * @param days 请假天数 * @param explain 审批单说明 * @param instanceId 流程实例ID * @param assignee 指定下一个流程执行人 * @return R */ @GetMapping("/apply") public R fillApply(@RequestParam Map<String, Object> variables) { String instanceId = (String) variables.remove("instanceId") ; if (StringUtils.isEmpty(instanceId)) { return R.failure("未知任务") ; } holidayService.executionTask(variables, instanceId); return R.success() ; } }
测试:
1、启动服务
这里放在processes中的流程定义文件已经被自动部署上了。查看表act_re_procdef
图片
图片
参数:
processDefinitionId:流程定义中的ID。
userId:要处理用户的id。
查看表信息:
图片
这时候就为用户id为:10000的生成了一个要处理的任务,填写审批单。
图片
图片
参数:
mgr:指定下一个节点处理人。
explain:请假原因。
days:请假天数。
这里根据自己的业务需要去设置。
再次调用查询接口userId=10002
图片
流程已经到了部门经理。
再次调用/holidays/apply接口。
图片
参数:
top:指明总经理节点需要处理的userId。
图片
再次调用/holidays/apply接口。(总经理处理)
图片
查询对应的任务表信息,已经没有数据了。
图片
查询表:act_hi_actinst
图片
到此一个流程就走完了。下篇 查看流程图。
完毕!!!
责任编辑:武晓燕 来源: 实战案例锦集 Activiti部署(责任编辑:知识)
A股市场年内已发生556起公司并购事件 未来聚焦产业链专业化整合
评价结果显示:零售业务对商业银行收益可持续能力的贡献不断增强
航新科技(300424.SZ):“航新转债”自1月28日起开始转股 债券代码“123061”
保利物业(06049.HK)遭GIC Private Limited减持42.1万股 涉资约2835.3万港元
开山股份(300257.SZ):印尼当局要求SMGP公司临时停工 时长尚未确定