java中的工作流流程管理和流轉思路

NO IMAGE

在做某個管理專案時,被要求實現一套流程管理,比如請假的申請審批流程等,在參考了很多資料,並和同事討論後,得到了一個自主實現的流程管理。

 
  以下提供我的設計思路,知道了思路,實現起來就簡單很多了。
 
  首先我設計了5個類來實現流程的自主設定,主要是對流程的定義和流程流轉。
 
  
 
注:這是設計的圖,並不是實現
 
Dictionary:資料字典,不多說,流程型別存在這裡面
 
Flow:流程,即流程的定義,其中包括流程名稱,描述,型別,啟用時間,備註等;目前是通過判斷某個類別的流程啟用時間來進行判斷當前流程是否啟用的。
 
    一個類別只啟用一個流程。所以只需要通過流程類別即可確定流程,並不要特定的狀態欄位。
 
FlowInfoMovingNode:流程節點,在分析流程流轉的時候,我們發現,流轉一步就相當於從一個節點跳到另一個節點,所以我們設計這個流程節點類來表示每一步。
 
    其中包括,所屬流程,節點名稱,節點描述,監聽許可權。
 
    解釋下監聽許可權是什麼. 由於我們做的大部分是稽核的流程,所以每個節點都需要有個稽核的過程才進入下一個節點,所以我們要這個handlerRole屬性來確定這個節點究竟是什麼許可權來稽核。我們也知道,稽核一般是某個人稽核,這個我們後面說。這裡是規定某個許可權,即可以稽核這個節點的許可權。
 
FlowInfoMovingRole:流程流轉規則,為了解決從節點出來的各個分支,我們設計了這個流轉規則,本來其實可以一起放到FlowInfoMovingNode中,但這樣話無論從資料上    還是管理上來說都不如加流轉規則方便清楚。FlowInfoMovingRole主要用來確定流轉規則,比如某個節點通過了應該去哪個節點,某個節點沒通過應該去哪個節點,這樣無論是分支還是單支還是迴圈都可以通過相同的方式來進行設定。transition為變換規則,參照shiro驗證許可權的方式,我們也使用純字串格式來進行判斷變換規則。
 
FlowInfoMoving:流程流轉資訊,這裡是每一步流轉資訊的存放,基本在進行流程流轉的過程中,都是通過此類,其中包括:所屬節點(得到所屬節點同時也就得到所屬流      程),申請源(因為我們不知道申請源是什麼,我們只是管流程是怎麼運轉的,其申請源跟我們沒有任何關係,我們儲存申請源的唯一標識,若是想在稽核的過程中進行檢視申請源資訊,則可以請求在Flow中監聽的Url(handlerUrl),來進行檢視,我會把申請源的唯一標識當做引數傳遞到Url中)。
 
    由於這個是操作最頻繁的,所以我來具體解釋下這個類。
 
  以下為本人具體實現的類設計,屬性欄位均有註釋進行解釋:
 
 
複製程式碼
/**
 * 流程流轉資訊
 * @author lichao *
 */
@Entity
@Table(name = FlowInfoMoving.TABLE_NAME)
public class FlowInfoMoving extends BaseAuditEntity {
 
    private static final long  serialVersionUID = 1L;
 
    public static final String TABLE_NAME       = “t_flowInfoMoving”;
 
    // 流程節點,標識此流轉資訊是流轉到了哪個節點
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = “node_id”,nullable = true)
    private FlowInfoMovingNode node;
 
    // 申請源,具體申請資訊的唯一標識
    @Column
    private Long               applyId;
 
    // 申請人,具體申請資訊的申請人
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = “applyuser_id”,nullable = false)
    private User               applyUser;
 
    // 申請資訊,具體申請資訊的簡要概述,由申請提供
    @Column(length = 50)
    @Size(max = 50)
    private String             discription;
 
    // 申請類別,即申請的流程的型別
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = “flowtype_id”,nullable = false)
    private Dictionary         applyType;
 
    // 變換的結果,即稽核通過未通過等條件,此條件同FlowInfoMovingRole種的transition,
    // 但有一些區別,因為有新增待稽核,已結束等其他資訊條件
    @Column(length = 50)
    @Size(max = 50)
    private String             transition;
 
    // 處理資訊,即稽核資訊,由稽核人新增
    @Column(length = 100)
    @Size(max = 100)
    private String             handlerInfo;
 
    // 處理人,即此流轉應該由誰來申請,由FlowInfoMovingNode中的handlerRole來進行篩選,
     //並由上一個節點的處理人來進行選擇具體稽核人。
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = “handleruser_id”,nullable = false)
    private User               handlerUser;
 
    // 此流轉資訊的狀態,用於查詢,0未稽核,1已稽核,2本申請已經結束
    @Column()
    private int                status;
 
  //getter…and setter….
 
}
複製程式碼
 
 
這樣整個流程定義就結束了,我們可以自由設定流程的流程規則,來設定流程的流轉方式。這樣無論任何複雜的流程都可以進行自定義,並且可以隨意的修改。
 
這樣設計的結果首先是可以任意的自定義流程,其次是申請源不需要去管流程的流轉了,只需要提交一份申請,其他的事情均由流程進行操作實現。
 
那麼我們來看看一個流程是如何進行運轉的。
 
 
 
1.首先通過一個介面來傳遞具體的一些申請資訊。
 
2.通過介面中的getType()來確定是哪個流程
 
3.查詢到開始的流程節點。
 
4.將開始的資訊儲存到流轉資訊中,並等待處理
 
5.由稽核人處理,指定下一個稽核人
 
6.通過處理結果來獲得下一個流程節點
 
7.查詢下一個節點
 
8.將這次的處理資訊和下一個節點的資訊儲存到流轉資訊中,並等待處理
 
9.由稽核人處理,這樣一直迴圈知道流程結束
 
10.通過稽核資訊查詢的下一個節點為null,則表示此流程已經結束,
 
11.將結束的資訊儲存到流程流轉資訊中
 
12.將整個流程流轉的狀態改為已結束。
 
 
 
首先是Applyable是怎麼設計的:其實一個介面,用於提供一些申請的資訊
 
 
複製程式碼
public interface Applyable {
 
    // 得到申請的id,與類別進行聯合查詢,用於確定具體是哪個流程
    Long getId();
 
    // 得到申請的類別,用於確定具體是哪類流程
    String getApplyType();
 
    // 得到變換條件,用於確定申請後的第一個步驟
    String getTransition();
 
    // 得到此次申請的描述
    String getDiscription();
 
    // 得到審批人,即第一個步驟由誰審批
    User getHandlerUser();
 
}
複製程式碼
 
 
然後是主要的控制流程流轉和申請的介面
 
 
複製程式碼
/**
 * 
 *@Description:處理流程業務的Service
 *@Author:lichao
 *@Since:Oct 10, 201412:02:39 PM
 */
public interface FlowMainService {
 
    /**
     * 
     *@Description: 用於申請
     *@Author: lichao
     *@Since: Oct 10, 20143:19:07 PM
     *@param applyable
     *@return
     * @throws Exception 
     */
    public void applyFlow(Applyable applyable) throws Exception;
 
    /**
     * 
     *@Description: 查詢當前所處的流轉資訊
     *@Author: medees
     *@Since: Oct 13, 20148:56:00 AM
     *@param applyid 申請源
     *@param applytype 申請類別
     *@return
     */
    public FlowInfoMoving findNowMoving(Long applyid,Dictionary applyType);
 
    /**
     * 
     *@Description: 查詢某一流程下的所有走過的流程 按建立時間升序排序
     *@Author: lichao
     *@Since: Oct 13, 20141:46:54 PM
     *@param applyid 申請源
     *@param applytype 申請類別
     *@return
     */
    public List<FlowInfoMoving> findAllMoving(Long applyid,Dictionary applyType);
 
    /**
     * 
     *@Description: 獲取所有變換條件
     *@Author: lichao
     *@Since: Oct 13, 20144:27:41 PM
     *@param nodeid 所處節點的id
     *@return
     */
    public List<String> findNextTransition(Long nodeid);
 
    /**
     * 
     *@Description: 得到此節點可以稽核的所有使用者
     *@Author: lichao
     *@Since: Oct 14, 201410:22:50 AM
     *@param nodeid 節點id
     *@param applyid 申請源
     *@param applyUserId 申請人id
     *@return
     */
    public List<User> findNextAppUser(Long nodeid,String transition,Long applyUserId);
 
    /**
     * 
     *@Description: 分頁查詢自己所審批的流程
     *@Author: lichao
     *@Since: Oct 14, 20141:39:50 PM
     *@return
     */
    public Page<FlowInfoMoving> findMyMoving(Pageable pageRequest);
 
    /**
     * 
     *@Description: 進行稽核的方法
     *@Author: lichao
     *@Since: Oct 15, 20142:11:18 PM
     *@param moving
     * @throws Exception 如果稽核出錯則丟擲異常
     */
    public boolean approval(FlowInfoMoving moving) throws Exception;
 
    /**
     * 
     *@Description: 查詢一個流程的所有流轉
     *@Author: lichao
     *@Since: Oct 16, 201411:04:03 AM
     *@param applyid
     *@param applytype
     *@return
     */
    public Page<FlowInfoMoving> findAllMoving(Long applyid,Long applytype,Pageable Pageable);
 
    /**
     * 
     *@Description: 重新申請
     *@Author: lichao
     *@Since: Oct 17, 20141:43:03 PM
     *@return
     */
    public void resetApply(Applyable applyable) throws Exception;
 
    /**
     * 
     *@Description: 是否需要重新申請
     *@Author: lichao
     *@Since: Oct 17, 20141:43:03 PM
     *@return
     */
    public boolean isNeedReset(Long applyid,Dictionary dic);
 
}