日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

flowable笔记 - 简单的通用流程

發布時間:2024/9/27 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 flowable笔记 - 简单的通用流程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

簡介
通用流程可以用于一些基本的申請,例如請假、加班。
大致過程是:

1. 創建申請 2. 分配給審批人(需要審批人列表,當前審批人) -> 有下一個審批人 -> 3 -> 無 -> 4 3. 審批人審批 -> 同意 -> 2 -> 拒絕 -> 5 4. 存儲數據,發送通知 5. 結束

比較簡單,唯一的難點就是動態設置審批人或者審批組,下面開始代碼部分。

bpmn20文件

...<!-- standardRequest用來開始流程,在flowable里稱為processDefinitionKey --><process id="standardRequest" name="標準申請流程" isExecutable="true"><!-- 第一步:開始流程,創建申請 --><startEvent id="startEvent" name="創建申請"/><sequenceFlow sourceRef="startEvent" targetRef="assignToAuditor"/><!-- 第二步:分配審批人 --><!-- 這個AssignToAuditorDelegate類就是解決動態設置審批人的Java類 --><!-- 審批人列表需要從外部傳入或者根據當前流程id來從數據庫獲取 --><serviceTask id="assignToAuditor" name="分配審批人" flowable:class="me.xwbz.flowable.delegate.AssignToAuditorDelegate"/><sequenceFlow sourceRef="assignToAuditor" targetRef="auditorExist"/><!-- 唯一網關:類似于switch,只能通過一個序列流 --><!-- 這里就是要么存在,要么不存在 --><!-- 使用default屬性定義默認序列流,在其他序列流條件都不滿足的情況下使用 --><exclusiveGateway id="auditorExist" name="審批人是否存在" default="auditorNotExistFlow"/><sequenceFlow sourceRef="auditorExist" targetRef="approveTask"><!-- auditMethod是Spring里的一個bean,下面有提到 --><!-- execution是flowable內部變量,類型是org.flowable.engine.delegate.DelegateExecution,也就是serviceTask里的代理方法拿到的 --><conditionExpression xsi:type="tFormalExpression"><![CDATA[${auditMethod.existAuditor(execution)}]]></conditionExpression></sequenceFlow><sequenceFlow id="auditorNotExistFlow" sourceRef="auditorExist" targetRef="agreeDelegate" /><!-- 第三步:審批人審批 --><userTask id="approveTask" name="等待審批"flowable:candidateGroups="${auditMethod.getCandidateGroups(execution)}"flowable:candidateUsers="${auditMethod.getCandidateUsers(execution)}"/><sequenceFlow sourceRef="approveTask" targetRef="decision"/><!-- 唯一網關:一個審批一個審批人 --><exclusiveGateway id="decision" default="rejectFlow"/><sequenceFlow sourceRef="decision" targetRef="assignToAuditor"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${auditMethod.isApproved(execution)}]]></conditionExpression></sequenceFlow><sequenceFlow id="rejectFlow" sourceRef="decision" targetRef="rejectDelegate" /><!-- 第四步:同意后存儲數據,發送通知 --><serviceTask id="agreeDelegate" name="數據存儲"flowable:class="me.xwbz.flowable.delegate.StandardRequestAgreeDelegate"/><sequenceFlow sourceRef="agreeDelegate" targetRef="approveEnd"/><serviceTask id="rejectDelegate" name="回復拒絕消息"flowable:class="me.xwbz.flowable.delegate.BaseRejectDelegate"/><sequenceFlow sourceRef="rejectDelegate" targetRef="rejectEnd"/><!-- 第五步:結束 --><endEvent id="approveEnd" name="已同意"/><endEvent id="rejectEnd" name="已駁回"/></process>...

常量部分
這次沒有另外存儲數據,所以變量都是直接存儲到flowable自帶的變量表里 強烈建議大家另外存儲,自帶的查詢起來非常麻煩!

審批人列表:AUDITOR_LIST_KEY = "AUDITOR_LIST"; 當前審批人:AUDITOR_KEY = "AUDITOR"; 當前審批人下標:AUDITOR_IDX_KEY = "AUDITOR_IDX"; 是否已審批:APPROVED_KEY = "AUDIT_APPROVED"; 申請類型:AUDIT_TYPE_KEY = "AUDIT_TYPE"; 申請狀態:AUDIT_STATUS_KEY = "AUDIT_STATUS"; 其他參數:AUDIT_PARAMS_KEY = "AUDIT_PARAMS"; 申請狀態 public enum AuditStatus {/** 待審批 */WAIT_AUDIT,/** 已同意申請 */AGREE_AUDIT,/** 已拒絕申請 */REJECT_AUDIT,/** 已取消 */CANCEL }

申請人類型

public enum CandidateType{/** 候選人 */USER,/** 候選組 */GROUP }

審批使用的方法定義
一個普通的Java類

package me.xwbz.flowable.method;import com.alibaba.fastjson.JSONObject; import org.flowable.engine.delegate.DelegateExecution;/*** 審批相關的方法** 用于flowable流程使用*/ public class AuditMethod {/*** 是否存在審批者* <sequenceFlow sourceRef="decision" targetRef="assignToAuditor">* <conditionExpression xsi:type="tFormalExpression">* <![CDATA[* ${auditMethod.existAuditor(execution)}* ]]>* </conditionExpression>* </sequenceFlow>*/public boolean existAuditor(DelegateExecution execution){return execution.hasVariable(AUDITOR_KEY);}/*** 獲取當前審批者*/public JSONObject getCurrentAuditor(DelegateExecution execution){return JSONObject.parseObject((String)execution.getVariable(AUDITOR_KEY));}/*** 獲取當前候選組*/public String getCandidateGroups(DelegateExecution execution){JSONObject candidate = getCurrentAuditor(execution);if(candidate.getIntValue("type") == CandidateType.GROUP.ordinal()) {return candidate.getString("id");}return null;}public String getCandidateUsers(DelegateExecution execution){JSONObject candidate = getCurrentAuditor(execution);if(candidate.getIntValue("type") == CandidateType.USER.ordinal()) {return candidate.getString("id");}return null;}/*** 獲取當前審批者id* <userTask id="approveTask" name="等待審批" flowable:assignee="${auditMethod.getCurrentAuditorId(execution)}" />*/public String getCurrentAuditorId(DelegateExecution execution){JSONObject auditor = getCurrentAuditor(execution);return JSONObject.toJavaObject(auditor, User.class).getId();}/*** 是否同意申請*/public boolean isApproved(DelegateExecution execution){Boolean approved = execution.getVariable(APPROVED_KEY, Boolean.class);return approved != null && approved;} }

流程結束處理

下面是同意處理,主要是更改狀態。“拒絕處理”同理

可以根據業務增加消息通知,保存數據等。

package me.xwbz.flowable.delegate;import lombok.extern.slf4j.Slf4j; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.JavaDelegate;@Slf4j public class BaseAgreeDelegate implements JavaDelegate {@Overridepublic void execute(DelegateExecution execution) {log.info("{}已被同意", execution.getVariables());execution.setVariable(AUDIT_STATUS_KEY, AuditStatus.AGREE_AUDIT.toString());} }

flowable結合Spring可以直接使用Spring里的bean。

像下面這樣定義,然后直接${auditMethod.isApproved(execution)}就可以調用auditMethod里的isApproved方法。

import me.xwbz.flowable.method.AuditMethod; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class FlowableConfig {@Bean(name = "auditMethod")public AuditMethod auditMethod(){return new AuditMethod();} }

動態設置審批人
這個是配置在serviceTask里的,所以需要實現JavaDelegate接口

package me.xwbz.flowable.delegate;import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.JavaDelegate;/*** delegate - 分配審批人*/ public class AssignToAuditorDelegate implements JavaDelegate {@Overridepublic void execute(DelegateExecution execution) {// 初始化變量,清空臨時變量execution.removeVariable(APPROVED_KEY);execution.removeVariable(AUDITOR_KEY);execution.setVariable(AUDIT_STATUS_KEY, AuditStatus.WAIT_AUDIT.toString());// 拿到審批人列表JSONArray auditorList = JSON.parseArray(execution.getVariable(AUDITOR_LIST_KEY).toString());// 當前審批人在審批人列表的下標Integer auditorIdx = execution.getVariable(AUDITOR_IDX_KEY, Integer.class);if (auditorIdx == null) {// 第一次分配,初始化為第一個auditorIdx = 0;} else if (auditorIdx + 1 >= auditorList.size()) {// 所有審批人審批完成,結束分配return;} else {// 下一個auditorIdx++;}JSONObject auditor = auditorList.getJSONObject(auditorIdx);execution.setVariable(AUDITOR_KEY, auditor.toJSONString());execution.setVariable(AUDITOR_IDX_KEY, auditorIdx);} }

開始流程
使用runtimeService#startProcessInstanceByKey開始這個流程,記得開始之前要使用identityService#setAuthenticatedUserId設置當前用戶編號,這個是綁定到線程的,單線程環境下設置一次就行了。

Map<String, Object> vars = new HashMap<>(); // 放入申請類型 vars.put(AUDIT_TYPE_KEY, param.getType()); // 放入審批人人列表 vars.put(AUDITOR_LIST_KEY, JSONObject.toJSONString(param.getAuditors())); // 放入其他參數 vars.put(AUDIT_PARAMS_KEY, param.getParams()); // 放入審批狀態 vars.put(AUDIT_STATUS_KEY, AuditStatus.WAIT_AUDIT.toString());logger.debug("當前用戶id: {} ", Authentication.getAuthenticatedUserId()); // 設置發起人 // identityService.setAuthenticatedUserId(user.getId()); ProcessInstance pro = runtimeService.startProcessInstanceByKey("standardRequest", 生成的編號, vars); // 文件材料 if (param.getFiles() != null && !param.getFiles().isEmpty()) {// 上傳附件,可以直接上傳字節流(不建議)param.getFiles().forEach(file ->taskService.createAttachment("application/octet-stream", null,pro.getId(), file.getName(), null, file.getId())); }

查看待我審批的任務
taskService.createTaskQuery()只能查詢到正在進行的任務

要是想既能查詢到正在進行的,也要結束的可以使用下面的語句:

TaskInfoQueryWrapper taskInfoQueryWrapper = runtime ? new TaskInfoQueryWrapper(taskService.createTaskQuery()) : new TaskInfoQueryWrapper(historyService.createHistoricTaskInstanceQuery());

也就是說你要先確定是要那種。

TaskQuery query = taskService.createTaskQuery()// or() 和 endOr()就像是左括號和右括號,中間用or連接條件// 指定是我審批的任務或者所在的組別審批的任務// 實在太復雜的情況,建議不使用flowable的查詢.or().taskAssignee(user.getId()).taskCandidateUser(user.getId()).taskCandidateGroup(user.getGroup()).endOr();// 查詢自定義字段if (StringUtils.isNotEmpty(auditType)) {query.processVariableValueEquals(AUDIT_TYPE_KEY, auditType);}if(auditStatus != null){query.processVariableValueEquals(AUDIT_STATUS_KEY, auditStatus.toString());}// 根據創建時間倒序 query.orderByTaskCreateTime().desc()// 分頁.listPage(0, 10).stream().map(t -> {// 拿到這個任務的流程實例,用于顯示流程開始時間、結束時間、業務編號HistoricProcessInstance p = historyService.createHistoricProcessInstanceQuery().processInstanceId(t.getProcessInstanceId()).singleResult();return new Process(p).withTask(t) // 拿到任務編號和任務名稱// 拿到創建時和中途加入的自定義參數.withVariables(taskService.getVariables(t.getId())).withFiles(taskService.getProcessInstanceAttachments(p.getId())); }).collect(Collectors.toList()

查看我已審批的任務
任務審批后就走下一個序列流,這里只能從歷史紀錄里獲取已審批的。

當前設置歷史紀錄(HistoryLevel)粒度為audit,這是默認的。

注意這里不能篩選自定義參數,所以要么自定義sql,要么另外存儲。

// 如果不需要篩選自定義參數 if(auditStatus == null && StringUtils.isEmpty(auditType)){return historyService.createHistoricActivityInstanceQuery()// 我審批的.taskAssignee(assignee)// 按照結束時間倒序.orderByHistoricActivityInstanceEndTime().desc()// 已結束的(其實就是判斷有沒有結束時間).finished()// 分頁.listPage(firstIdx, pageSize); } // 否則需要自定義sql // managementService.getTableName是用來獲取表名的(加上上一篇提到的liquibase,估計flowable作者對數據表命名很糾結) // 這里從HistoricVariableInstance對應的表里找到自定義參數 // 篩選對象類型不支持二進制,存儲的時候盡量使用字符串、數字、布爾值、時間,用來比較的值也有很多限制,例如null不能用like比較。 String sql = "SELECT DISTINCT RES.* " +"FROM " + managementService.getTableName(HistoricActivityInstance.class) + " RES " +"INNER JOIN " + managementService.getTableName(HistoricVariableInstance.class) + " var " +"ON var.PROC_INST_ID_ = res.PROC_INST_ID_ " +"WHERE RES.ASSIGNEE_ = #{assignee} " +"AND RES.END_TIME_ IS NOT NULL "; if(auditStatus != null && StringUtils.isNotEmpty(auditType)){sql += "AND ((var.name_ = #{typeKey} AND var.TEXT_ = #{typeValue}) OR (var.name_ = #{statusKey} AND var.TEXT_ = #{statusValue}))"; } else if(auditStatus != null){sql += "AND var.name_ = #{statusKey} AND var.TEXT_ = #{statusValue}"; } else {sql += "AND var.name_ = #{typeKey} AND var.TEXT_ = #{typeValue}"; } sql += " ORDER BY RES.END_TIME_ DESC";return historyService.createNativeHistoricActivityInstanceQuery().sql(sql)// 參數用#{assignee}占位后,再調用parameter("assignee", assignee)填入值// 參數值可以多出來沒用到的,比hibernate好多了.parameter("assignee", assignee).parameter("typeKey", AUDIT_TYPE_KEY).parameter("typeValue", auditType).parameter("statusKey", AUDIT_STATUS_KEY).parameter("statusValue", auditStatus == null ? null : auditStatus.toString()).listPage(firstIdx, pageSize);

后續獲取詳細和自定義參數

list.stream().map(a -> {// 同上面的拿到這個任務的流程實例HistoricProcessInstance p = historyService.createHistoricProcessInstanceQuery().processInstanceId(a.getProcessInstanceId()).singleResult();// 因為任務已結束(我看到有提到刪除任務TaskHelper#completeTask),所以只能從歷史里獲取Map<String, Object> params = historyService.createHistoricVariableInstanceQuery().processInstanceId(a.getProcessInstanceId()).list()// 拿到的是HistoricVariableInstance對象,需要轉成原來存儲的方式.stream().collect(Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue));return new Process(p).withActivity(a).withVariables(params); }).collect(Collectors.toList())

查看我創建的任務
這個比較方便拿到,但是當前最新的任務比較難拿到,有時還不準確

// startedBy:創建任務時設置的發起人 HistoricProcessInstanceQuery instanceQuery = historyService.createHistoricProcessInstanceQuery().startedBy(user.getId()); // 自定義參數篩選 if (StringUtils.isNotEmpty(auditType)) {instanceQuery.variableValueEquals(AUDIT_TYPE_KEY, auditType); } if(auditStatus != null){instanceQuery.variableValueEquals(AUDIT_STATUS_KEY, auditStatus.toString()); }instanceQuery.orderByProcessInstanceStartTime().desc().listPage(firstIdx, pageSize).stream()// 獲取其中的詳細和自定義參數.map(this::convertHostoryProcess).collect(Collectors.toList())

獲取其中的詳細和自定義參數

private Process convertHostoryProcess(HistoricProcessInstance p) {// 不管流程是否結束,到歷史里查,最方便Map<String, Object> params = historyService.createHistoricVariableInstanceQuery().processInstanceId(p.getId()).list().stream().collect(Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue));// 獲取最新的一個userTask,也就是任務活動紀錄List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery().processInstanceId(p.getId()).orderByHistoricActivityInstanceStartTime().desc().orderByHistoricActivityInstanceEndTime().asc().listPage(0, 1);Process data = new Process(p);if (!activities.isEmpty()) {data.withActivity(activities.get(0));}return data.withVariables(params);}

撤銷流程實例(標記刪除)
撤銷后,流程直接中斷,除了用戶不能操作和多了結束時間、刪除理由外,其他停留在撤銷前的狀態。

ProcessInstance process = runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult(); if (process == null) {throw new RuntimeException("該流程不在運行狀態"); } Task task = taskService.createTaskQuery().processInstanceId(id).singleResult(); runtimeService.setVariable(task.getExecutionId(), AUDIT_STATUS_KEY, AuditStatus.CANCEL.toString()); runtimeService.deleteProcessInstance(id, "用戶撤銷");

用戶操作(同意、拒絕)

注意:如果beforeAgreeOrReject與taskService.complete在同一個事物里,且beforeAgreeOrReject里跟AssignToAuditorDelegate有更新數據庫數據,會導致事物異常。

是否有審批權限需要自己判斷。

操作后進入下一序列流,再次拿這個taskId會獲取不到這個Task,所以上傳審批意見和附件什么的要在操作前。

if(!isAssigneeOrCandidate(user, taskId)){throw new RuntimeException("無法操作"); } // 同意前設置,上傳審批意見和附件 beforeAgreeOrReject(user, taskId, AuditStatus.AGREE_AUDIT, "同意", reason); // 拒絕前設置,上傳審批意見和附件 // beforeAgreeOrReject(user, taskId, AuditStatus.REJECT_AUDIT, "拒絕", reason); // 同意 taskService.complete(taskId, Collections.singletonMap(APPROVED_KEY, true)); // 拒絕 // taskService.complete(taskId, Collections.singletonMap(APPROVED_KEY, false));

判斷是否有權限

public boolean isAssigneeOrCandidate(User user, String taskId){long count = taskService.createTaskQuery().taskId(taskId).or().taskAssignee(user.getId()).taskCandidateUser(user.getId()).taskCandidateGroup(user.getGroup()).endOr().count();return count > 0; }

操作前設置,上傳審批意見和附件

附件一般都另外存儲,這里存一個id就行了 這里是存在ftp文件服務器里的

public void beforeAgreeOrReject(User user, String taskId, AuditStatus auditStatus, String operate, ReasonParam reason){// 組成員操作后方便查詢taskService.setAssignee(taskId, user.getId());if(StringUtils.isNotEmpty(reason.getText())){// 審批意見taskService.addComment(taskId, null, null, reason.getText());}if (reason.getFiles() != null && !reason.getFiles().isEmpty()) {files.forEach(file ->// 上傳附件,可以直接上傳字節流(不建議)taskService.createAttachment("application/octet-stream", taskId,null, file.getName(), null, file.getId()));} }

更新日志
2018-12-17
支持審批組,遇到的坑提醒

2019-01-05
代碼上傳到github,github地址,https://github.com/xwbz2017/flowable-demo
修改之前錯誤的地方,當然建議是看看上方的github代碼

原文地址:https://www.cnblogs.com/xwbz/p/9886696.html

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的flowable笔记 - 简单的通用流程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 国产欧美精品久久久 | www.一区二区三区四区 | 国产日本在线播放 | 91久久久久久久久久久久久 | 豆花视频在线播放 | 嫩草国产 | 1024欧美| 日干夜干天天干 | 一区视频在线免费观看 | 污污视频在线免费观看 | 四虎国产成人精品免费一女五男 | 青青草视频在线免费观看 | 日韩国产精品一区二区 | 国产一级高清视频 | 中文字幕一区二区三区四区视频 | 亚洲精品7777| 视频二区在线观看 | 午夜h视频 | 韩国三级在线看 | 日韩av一区二区三区在线观看 | 91在线观看网站 | 亚洲成人一级 | 欧美日韩在线观看视频 | 国产又黄又粗的视频 | 制服.丝袜.亚洲.中文.综合懂 | 青青青国内视频在线观看软件 | 亚洲高清在线免费观看 | 欧美日韩一区二区三 | 免费看a级黄色片 | 国产伦精品一区二区三区免费 | 精品人人人 | 91激情| 亚洲欧美强伦一区二区 | 噜噜色av| 被各种性器调教到哭vk | 性色av蜜臀av浪潮av老女人 | 国产69精品久久久久久久久久 | 欧美性生交大片免费看 | 伊人手机视频 | 久热精品免费视频 | 91在线欧美| 噜噜噜在线视频 | 久久精品一区二区在线观看 | 欧美大片在线看免费观看 | 九九在线观看视频 | 少妇高潮一区二区三区在线 | 少妇脱了内裤让我添 | 国产日产欧洲无码视频 | 亚洲欧美韩国 | 国产精品一区二区免费视频 | 黄色一级一片免费播放 | 国产又粗又黄又爽视频 | 亚洲天堂网站在线 | 亚洲午夜电影网 | 女女同性女同一区二区三区九色 | 国产超91| 国产一二三精品 | 欧美日韩一区二区三区电影 | 日韩黄色在线播放 | 亚洲av无码久久精品色欲 | 亚洲黄色av | 精品国产制服丝袜高跟 | 色网站免费看 | 一区二区三区小说 | 久久视频热 | 在线亚洲+欧美+日本专区 | 边打电话边做 | 精品久久99 | 国产在线18| 少妇高潮一69aⅹ | 美女啪啪网址 | 可以看的av网站 | av不卡在线播放 | 五月天婷婷视频 | 九九热最新网址 | 亚洲五月网 | 动漫av在线| 国产午夜电影在线观看 | 伊人96| 男人午夜影院 | 成人xxx| 免费看麻豆| 六月婷婷中文字幕 | 带aaa级的网名 | 欧美日韩一区不卡 | 国产主播av | 久草日韩| av资源网在线观看 | av天天网| 韩国裸体网站 | 国产精品久久久久久无人区 | 成年人在线播放视频 | 欧美一级黄色片 | 日本xxxx色| 国产日批| 久久久久爱 | 免费一区二区三区四区 | 亚洲综合a | 色屋永久 |