環境:Springboot2.2.11.RELEASE + Activiti7.1.0.M6 + MySQL
環境說明:
不要透過如下方式引包:
<dependencyManagement> <dependencies> <dependency> <groupId>org.activiti.dependencies</groupId> <artifactId>activiti-dependencies</artifactId> <version>7.1.0.M6</version> <scope>import</scope> <type>pom</type> </dependency> </dependencies></dependencyManagement>
如果透過上面的方式引入會有各種問題。
正確方式:
<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_HI_*: 'HI’表示history。這些表包含歷史資料,比如歷史流程例項,遍歷,任務等等。
ACT_GE_*: 'GE’表示general。通用資料,用於不同場景。
資料表分類通用資料(act_ge_*)
流程定義(act_re_*)
執行例項(act_ru_*)
歷史流程(act_hi_*)
其他
核心類ProcessEngine流程引擎的抽象,可以透過此類獲取需要的所有服務。
透過ProcessEngine獲取,Activiti將不同生命週期的服務封裝在不同Service中,包括定義、部署、執行。透過服務類可獲取相關生命週期中的服務資訊。
TaskService
流程執行過程中,每個任務節點的相關操作介面,如complete,delete,delegate等。RepositoryService
流程定義和部署相關的儲存服務。RuntimeService
流程執行時相關的服務,如根據流程好啟動流程例項startProcessInstanceByKey。HistoryService
歷史記錄相關服務介面。
關於eclipse中安裝外掛就不說了,我是把外掛下載下來安裝的,線上安裝不上。
設計請假流程在src/main/resources下新建processes資料夾,springboot下預設的流程檔案定義路徑字首及檔案字尾如下:
這裡可以在application.yml配置檔案中更改。
設計一個請假的流程holiday.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" 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?serverTimezone=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的日誌輸出級別,該屬性有以下四個值:
none:不儲存任何的歷史資料,因此,在流程執行過程中,這是最高效的。activity:級別高於none,儲存流程例項與流程行為,其他資料不儲存。audit:除activity級別會儲存的資料外,還會儲存全部的流程任務及其屬性。audit為history的預設值。full:儲存歷史資料的最高級別,除了會儲存audit級別的資料外,還會儲存其他全部流程相關的細節資料,包括一些流程引數等。spring.activiti.check-process-definitions:如果不設定為true,那麼流程定義必須手動進行部署。
sprint security配置,放行任何請求:
@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/**") .permitAll() ; } }
服務工具類HolidayService
@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) ; }}
HolidayController介面
@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
查詢制定key的流程審批介面:/holidays啟動請假審批流程介面:/holidays/start引數:processDefinitionId:流程定義中的ID。userId:要處理使用者的id。
查看錶資訊:
這時候就為使用者id為:10000的生成了一個要處理的任務,填寫審批單。
查詢指派給我需要處理的任務介面:/holidays/tasks填寫審批單介面:/holidays/apply引數:mgr:指定下一個節點處理人。explain:請假原因。days:請假天數。
這裡根據自己的業務需要去設定。
再次呼叫查詢介面userId=10002
流程已經到了部門經理。
再次呼叫/holidays/apply介面。
引數:top:指明總經理節點需要處理的userId。
再次呼叫/holidays/apply介面。(總經理處理)
查詢對應的任務表資訊,已經沒有資料了。
查詢表:act_hi_actinst
到此一個流程就走完了。下篇 檢視流程圖。
完畢!!!