一直在搞工做流(activiti),總結一下關於工做流(activiti)中同時併發處理多個子流程的操做方法。node
先說下我要實現的業務:sql
一、辦公室發通知(在系統申報頁面上,勾選科室,被選中的科室執行第二步)數據庫
二、科室科員填報數據數組
三、科室科長作審覈(注意這裏的科長審覈是對應第二步的科室,本科科長去審覈本單位填報完的任務)併發
四、辦公室編制立項書,彙總數據dom
好,需求就先這樣,這裏主要是給講解一下關於子流程的使用,因此其餘的需求就不往上寫了。ide
先看一眼畫好後的流程圖:ui
能夠看到任務發起時(啓動流程實例時)就進入了子流程中,這裏須要關心的是怎麼才能生成多個子流程呢,請看:this
接下來咱們對這個子流程進行配置:spa
注:一、Collection:能夠存放集合,集合中能夠存任意值,工做流會根據你集合裏的值個數,去生成對應的子流程,
例如:我這裏存的是3個科室code,{0001,0002,0003},那麼就會生成出3個子流程,
其實這裏我簡單說明一下,若是隻傳入1個值會生成4個流程實例,
傳2個會生成出6個流程實例(多出的兩個,一個是子流程subprocess的,一個是流程中第一個任務的),以此類推。
二、Element variable:顧名思義就是節點流程變量,用於在流程圖中代替集合中表示當前子流程的變量(我這存的是科室code,因此表示的就是科室code)。
這個節點流程變量能夠在當前子流程中任意的task中使用,例如 子流程中的任務我就用到了這個變量,稍後會有圖詳細說明
三、Completion condition:顧名思義就是完成條件,這裏寫的表達式若是知足便可到(第三步:立項書編制)這個任務,關於這裏的配置,
給你們推薦一個網址介紹:http://my.oschina.net/winHerson/blog/139267?fromerr=ApnxMXj5
接下來繼續配置,個人業務需求是 選中的科室作填報,而且有這個科室的科長去審覈,那麼咱們接着去配置具體的用戶任務(userTask)
這裏簡單講一下我作的這個項目的權限控制,我這裏是經過權限點去控制顯示每一個任務的待辦的權限,假如張三 有PM10000101權限點,
他就能看到任務中配置了PM10000101的待辦,由於個人系統是三級樹權限控制,用戶--角色--權限點(功能點),
因此我在工做流Candiate groups中配置的是功能點,各位能夠根據本身系統的須要去合理配置。
順便在講一下將${candiateUser}配置到Candidate users或者Candidate groups的後果,
它會根據你集合中存的變量個數生成出任務來比較噁心的是這種形式,例如:
candiateUserList中存了{001,002,003},按照規則會生成出3個子流程(A、B、C),
可是在生成任務的時候會生成出這種A(001)、A(002)、A(003)、B(001)、B(002)、B(003)、C(001)、C(002)、C(003)
問題是我這裏不須要這麼生成任務,只須要一個流程對應1個任務就OK,因此我將${candiateUser}配置到了userTask的描述信息中
配置到了這一步基本告一段落,下面我將個人查詢待辦sql貼出來,相信你們都懂了
SELECT T.SERIAL_NUMBER, (SELECT Q.DO_USERNAME FROM ACT_HI_TASKREMARK Q WHERE Q.P_ID=T.PROC_INST_ID_ AND Q.TASK_ID IS NULL AND ROWNUM <2 ) AS CREAT_USER, T.ID_ AS ID, T.PROC_INST_ID_ AS PID, T.EXECUTION_ID_ AS EID, P.NAME_ AS TASKTYPE, T.NAME_ AS FLOWNAME, TO_CHAR(T.CREATE_TIME_, 'YYYY-MM-DD HH24:MI:SS') AS CREATE_TIME, TIMEHEADER((REPLACE(SUBSTR(TO_CHAR(NUMTODSINTERVAL((SELECT CEIL((TO_DATE(TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),'YYYY-MM-DD HH24:MI:SS') - TO_DATE(TO_CHAR(T.CREATE_TIME_, 'YYYY-MM-DD HH24:MI:SS'), 'YYYY-MM-DD HH24:MI:SS')) * 24 * 60) FROM DUAL),'MINUTE')),'12','5'),':','小時')||'分' )) STAY_TIME, DECODE((SELECT V.TEXT_ FROM ACT_RU_VARIABLE V WHERE V.PROC_INST_ID_ = T.PROC_INST_ID_ AND V.NAME_ = 'IS_URGENCY'),'2','<FONT COLOR="RED">【緊急】</FONT>','3','<FONT COLOR="PURPLE">【特急】</FONT>','<FONT COLOR="GREEN">【常規】</FONT>')||(SELECT V.TEXT_ FROM ACT_RU_VARIABLE V WHERE V.PROC_INST_ID_ = T.PROC_INST_ID_ AND V.NAME_ = 'TASKNAME') TASKNAME, (SELECT V.TEXT_ FROM ACT_RU_VARIABLE V WHERE V.PROC_INST_ID_ = T.PROC_INST_ID_ AND V.NAME_ = 'DEADLINE') DEADLINE, (SELECT COUNT(1) FROM CT_PURSUE C WHERE C.TASKID = T.ID_) URGE_TIME FROM ACT_RU_TASK T, ACT_RU_IDENTITYLINK F, ACT_RE_PROCDEF P WHERE T.ID_ = F.TASK_ID_(+) AND T.PROC_DEF_ID_ = P.ID_ AND ('當前登陸用戶名' IN (SELECT U.USERNAME FROM PERM_ROLE_FUNC R, PERM_USERS U WHERE U.ROLE LIKE '%' || R.ROLEID || '%' AND R.FUNCCODE IN (F.GROUP_ID_) GROUP BY U.USERNAME) OR '科室code' IN (T.DESCRIPTION_)) AND 1=1 AND 1=1 AND (NVL(T.DESCRIPTION_,'1')='1' OR T.DESCRIPTION_ ='科室code')
再貼一張系統圖:
接下來將啓動流程時的代碼貼出來:
//工做流 String targetFilePath = ServletActionContext.getRequest().getSession().getServletContext() .getRealPath("/") + IcmisModels.TEMP_FOLD+ "/";//獲取項目臨時文件目錄 //獲得當前系統用戶對象 UserView u = (UserView) ServletActionContext.getRequest().getSession().getAttribute(FrameConstant.SESSION_USERVIEW); Map<String, Object> map=new HashMap<String, Object>();//要在啓動流程中存的流程變量map map.put(WorkFlowConstant.START_USER, u.getUsername());//設置發起人 List<String> candiateUserList=new ArrayList<String>();//建立多個子流程用的集合 //獲取從前臺傳過來的科室code,並將其解析出來存入集合 if(result.getAddinfoDep()!=null&&!"".equals(result.getAddinfoDep())){ String strs[]=result.getAddinfoDep().split(",");//將1,2,3,4..拼接的科室code字符串解析到數組裏 for (String s : strs) { candiateUserList.add(s);//將科室code存入集合中 } }else{ candiateUserList.add("流程錯誤,無效任務"); //純粹扯淡,這種狀況根本不存在 } map.put("candiateUserList", candiateUserList);//多個子流程集合 map.put(WorkFlowConstant.TASK_NAME, result.getProjectName()+"設備清單填報");//任務名稱 map.put(WorkFlowConstant.DEADLINE,IcmisUnit.date2str(result.getDepDeadline()));//任務截止時間 map.put(WorkFlowConstant.IS_URGENCY,result.getTaskUrgency());//任務緊急度 //發起流程 String piId=wfservice.getWorkflowBaseService().startProcess("bpApply", map,targetFilePath);
public String startProcess(String processDefinitionKey, Map<String, Object> vMap,String targetFilePath) { RuntimeService rs = this.processEngine.getRuntimeService(); //獲得運行時的service //設置發起人,這個方法是經過線程判斷是一個了流程實例的 this.processEngine.getIdentityService().setAuthenticatedUserId(vMap.get(WorkFlowConstant.START_USER).toString()); //根據傳入的用戶名獲得工做流內置的用戶表對象 User user = this.processEngine.getIdentityService().createUserQuery() .userId(vMap.get(WorkFlowConstant.START_USER).toString()) .singleResult(); vMap.put("ac", processDefinitionKey);//設置流程定義key,存到流程變量中 //根據流程定義key獲得流程定義對象 ProcessDefinition processDefinition = this.getProcessDefinitionByKey(processDefinitionKey); //vMap.put(WorkFlowConstant.VIEW_URL, processDefinition.getDescription()); Map<String,Object> map=getSeqFlowByBpmn(processDefinition.getDeploymentId(),targetFilePath);//獲得全部連線駁回的任務節點的map vMap.put(WorkFlowConstant.SEQUENCEFLOW, map);//將連線駁回的任務節點map存入流程變量中 // 保存日誌 ProcessInstance instance = rs.startProcessInstanceByKey( processDefinitionKey, vMap); TaskMarkModel taskMarkModel = new TaskMarkModel(); taskMarkModel.setAuditRemark("發起任務"); //排序使用 taskMarkModel.setCreateDate(new Date(new Date().getTime()-100000)); taskMarkModel.setDoDate(new Date(new Date().getTime()-100000)); taskMarkModel.setDoResult("發起任務"); taskMarkModel.setDoUserName(user.getFirstName()); taskMarkModel.setPid(instance.getProcessInstanceId()); taskMarkModel.setpName(processDefinition.getName()); taskMarkModel.setTaskName("發起任務"); this.saveTaskMark(taskMarkModel); // 保存日誌結束 return instance.getProcessInstanceId(); }
/** * 獲取每一個任務的連線. * * @Title: getSeqFlowByBpmn * @author liufei12581@sinosoft.com.cn * @param deploymentid 部署id * @param targetFilePath 路徑 * @return */ public Map getSeqFlowByBpmn(String deploymentid,String targetFilePath){ Map<String,List<String>> map=new HashMap<String, List<String>>(); Map<String,String> taskmap=new HashMap<String, String>(); Map allmap=new HashMap(); try { InputStream in=null; List<String> list = getProcessEngine().getRepositoryService()// .getDeploymentResourceNames(deploymentid); //定義圖片資源的名稱 String resourceName = ""; if(list!=null && list.size()>0){ for(String name:list){ if(name.indexOf(".bpmn")>=0){ resourceName = name; } } } //獲取數據庫裏部署的bpmn文件 in = getProcessEngine().getRepositoryService() .getResourceAsStream(deploymentid, resourceName); //將bpmn文件另存到本地並寫入xml文件 String fileName=resourceName.substring(0,resourceName.indexOf("."))+".xml"; File file = new File(targetFilePath+fileName); // if(file.exists()){//判斷文件是否存在 System.out.println(targetFilePath); file.delete();//若是存在則先刪除 file.createNewFile(); //建立文件 OutputStream output = new FileOutputStream(file); BufferedOutputStream bufferedOutput = new BufferedOutputStream(output); bufferedOutput.write(IcmisUnit.toByteArray(in)); bufferedOutput.flush(); // } //獲取dom工廠獲得dom對象 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();//步驟1 DocumentBuilder builder = factory.newDocumentBuilder();//步驟2 //將保存的xml解析成dom樹 Document doc = builder.parse(file);//步驟3 //獲取連線的全部node NodeList sequenceFlow = doc.getElementsByTagName("sequenceFlow"); //獲取網關的全部node NodeList exclusiveGateway = doc.getElementsByTagName("exclusiveGateway"); //獲取任務的全部node NodeList userTask = doc.getElementsByTagName("userTask"); //循環任務節點 for (int i = 0; i < userTask.getLength(); i++) { //獲取任務節點的id屬性 String id=userTask.item(i).getAttributes().getNamedItem("id").getNodeValue(); //獲取任務節點的name屬性 String name=userTask.item(i).getAttributes().getNamedItem("name").getNodeValue(); taskmap.put(id, name); } //循環連線節點 for (int i = 0; i < sequenceFlow.getLength(); i++) { //獲取連線的起始節點 String sourceTask=sequenceFlow.item(i).getAttributes().getNamedItem("sourceRef").getNodeValue(); //獲取連線的目標節點 String targetTask=sequenceFlow.item(i).getAttributes().getNamedItem("targetRef").getNodeValue(); boolean bool=true;//用來判斷是不是經過的連線,下面的操做是爲了過濾描述經過的連線 //獲取連線節點下的配置信息節點,一個連線應該只有一個配置節點 NodeList nl= sequenceFlow.item(i).getChildNodes(); for (int j = 0; j <nl.getLength(); j++) { if(nl.item(j).getNodeType()==Node.ELEMENT_NODE){ //找到配置節點,並獲得配置的值 if("conditionExpression".equals(nl.item(j).getNodeName())){ //這裏要注意一下:配置經過的連線必定要寫成${flag=='1'}或"${flag==\"1\"} String flag=Util.nulltostr(nl.item(j).getFirstChild().getNodeValue()); if("${flag=='1'}".equals(flag)||"${flag==\"1\"}".equals(flag)){ //表示是經過的連線,若是是經過的連線則不作處理,這裏用到了上面的boolean變量,經過變量來控制過濾節點 bool=false; } } } } //經過變量過濾已經過的連線節點 if(bool){ //存連線的開始任務 if(map.containsKey(sourceTask)){ //表示存在 map.get(sourceTask).add(targetTask); }else{ //表示不存在 List<String> targetlist=new ArrayList<String>(); targetlist.add(targetTask); map.put(sourceTask, targetlist); } } } //默認取出來的連線針對網關記錄的是網關的節點,可是實際操做中想要獲得網關的節點id是不可能的,因此加了一步處理,獲取鏈接網關的上個節點 //這裏要說明一下,鏈接到網關的連線 也就是targetRef確定是只有一條連線,因此順藤摸瓜找到了鏈接網關的任務節點,這樣在實際項目中 //經過任務節點便可找到駁回連線的targetRef任務節點,便可展現到前臺讓客戶去選擇駁回的連線啦 //循環網關node for (int i = 0; i < exclusiveGateway.getLength(); i++) { //獲得網關id String exclusiveGatewayid=exclusiveGateway.item(i).getAttributes().getNamedItem("id").getNodeValue(); //經過循環全部的連線,比對獲得那條惟一鏈接網關的連線將數據從新防止到map當中 for (String key : map.keySet()) { for (String target : map.get(key)) { if(exclusiveGatewayid.equals(target)){ map.put(key, map.get(exclusiveGatewayid)); } } } } } catch (Exception e) { e.printStackTrace(); } allmap.put(WorkFlowConstant.SEQ, map); allmap.put(WorkFlowConstant.USERTASK, taskmap); return allmap; }