Apache Commons-SCXML系列之Demo:"請假流程"

首先分析自己的業務邏輯,畫好狀態圖

1、請假流程狀態圖

這裏寫圖片描述
畫圖工具EA

2、根據狀態圖編寫xml文件

<?xml version="1.0"?>
<!--
請假流程定義,狀態圖如上圖所示
-->
<scxml xmlns="http://www.w3.org/2005/07/scxml"
       version="1.0"
       datamodel="jexl"
       initial="filling">

    <!-- 請假單需要的數據-->
    <datamodel>
        <data id="applicant" expr=""></data>
        <data id="reason" expr=""></data>
        <data id="from" expr=""></data>
        <data id="to" expr=""></data>
        <data id="departmentApprove" expr="false"></data>
        <data id="personnelApprove" expr="false"></data>
    </datamodel>

    <state id="filling">
        <onentry>

        </onentry>
        <!--當填寫完了表單,將外界的數據通過,系統變量_event.data傳進來,data其實是一個map-->
        <transition event="fill.end" target="approving">
            <assign location="applicant" expr="_event.data.name"></assign>
            <assign location="reason" expr="_event.data.reason"></assign>
            <assign location="from" expr="_event.data.from"></assign>
            <assign location="to" expr="_event.data.to"></assign>
            <!--調用leaveEntity的填表函數,在這個函數裏面,我們可以操作做數據持久化-->
            <!--個人覺得這種方式和領域驅動設計很像,歡迎和我交流-->
            <script>
                leaveEntity.fillForm(applicant,reason,from,to)
            </script>

        </transition>
    </state>

    <!--這是一個複合狀態:一個大狀太-->
    <state id="approving">
        <initial>
            <transition target="departmentApproving">

            </transition>
        </initial>
        <!--部門經理審批狀態-->
        <state id="departmentApproving">
            <onentry></onentry>
            <!--如果部門經理同意,就將departmentApprove的值賦值爲true,然後我們可以執行持久化的數據操作(我這裏只是簡單的輸出)-->
            <transition event="approve" target="personnelApproving">
                <assign location="departmentApprove" expr="true"></assign>
                <script>
                    leaveEntity.departmentApprove(departmentApprove)
                </script>
            </transition>
        </state>
        <!--人事經理審批狀態-->
        <state id="personnelApproving">
            <!--如果人事經理同意,就將personnelApprove的值賦值爲true,然後我們可以執行持久化的數據操作(我這裏只是簡單的輸出)-->
            <transition event="approve" target="approveEnd">
                <assign location="personnelApprove" expr="true"></assign>
                <script>
                    leaveEntity.personnelApprove(personnelApprove)
                </script>
            </transition>
        </state>

        <final id="approveEnd"></final>

        <!--這個轉移的事件“event.state.approving”是噹噹前複合狀態到達 <final> 節點的時候框架自動生成的。-->
        <transition event="done.state.approving" target="approved" />
        <!--這個轉移事件,是隻要任何一個經理拒絕了請求,就轉向被拒絕狀態-->
        <transition event="reject" target="rejected">

        </transition>
    </state>
    <!--已同意狀態,進入狀態的時候,可以發送郵件給相應的用戶-->
    <state id="approved">
        <onentry>
            <script>
                leaveEntity.sendEmail()
            </script>
        </onentry>
        <!--一個eventless(自動轉移),執行完了<onentry>裏面的事件就轉移了-->
        <transition target="archiving" ></transition>
    </state>
    <!--被拒絕狀態,進入狀態的時候,可以發送郵件給相應的用戶-->
    <state id="rejected">
        <onentry>
            <script>
                leaveEntity.sendEmail()
            </script>
        </onentry>
        <!--申請者選擇繼續修改申請信息的時候的轉移,-->
        <transition event="goFilling" target="filling"></transition>
        <!--申請者可以取消本次請假-->
        <transition event="goEnd" target="archiving"></transition>
    </state>
    <!--歸檔狀態,進入的時候直接歸檔-->
    <state id="archiving">
        <onentry>
            <script>
                leaveEntity.archive()
            </script>
        </onentry>
        <transition target="end"/>
    </state>

    <!--結束狀態-->
    <final id="end">
    </final>
</scxml>

3、編寫程序、控制狀態變化

LeaveEntity.java

package leave;

/**
 * Created by zhengshouzi on 2015/11/24.
 *
 * 這個類是一個實體類,就想領域驅動設計裏面的思想一樣,每一個實體類不僅要有數據,而且要有實體類對應的方法。
 * 不知道什麼是領域驅動設計的,請另行百度。
 */
public class LeaveEntity {

    //請假表單信息,這些字段都可以不需要,凡事需要使用這些字段的地方,都可以選擇從狀態機裏面傳遞出來,我也剛研究,最好還是加上。
    private String appliant;
    private String reason;
    private String from ;
    private String to;
    private boolean departmentApprove;
    private boolean personnelApprove;


    /**
     * 做表單數據保存操作
     * @param name
     * @param reason
     * @param from
     * @param to
     */
    public void fillForm(String name,String reason,String from,String to){

        this.appliant = name;
        this.reason = reason;
        this.from=from;
        this.to= to;
        System.out.println("請假人:"+appliant);
        System.out.println("請假原因:"+reason);
        System.out.println("開始時間:"+from);
        System.out.println("結束時間:"+to);
    }

    /**
     * 更新部門經理同意信息
     * @param b
     */
    public void departmentApprove(boolean b){
        this.departmentApprove = b;
        System.out.println("部門經理同意");
    }
    /**
     * 更新人事經理同意信息
     * @param b
     */
    public void personnelApprove(boolean b){
        this.personnelApprove =b;
        System.out.println("人事經理同意");
    }

    /**
     * 發送郵件信息
     */
    public void sendEmail(){
        if (departmentApprove && personnelApprove){
            System.out.println(appliant+":你好,你的請假已經通過了");
        }else{
            System.out.println(appliant+":你好,你的請假沒有通過,請重新填寫");
        }
    }

    /**
     * 自動執行歸檔功能
     */
    public void archive(){
        System.out.println("開始歸檔");
    }
}

LeaveFrame.java



/**
 * Created by zhengshouzi on 2015/11/24.
 */

package leave;

import org.apache.commons.scxml2.Context;
import org.apache.commons.scxml2.Evaluator;
import org.apache.commons.scxml2.SCXMLExecutor;
import org.apache.commons.scxml2.TriggerEvent;
import org.apache.commons.scxml2.env.SimpleErrorReporter;
import org.apache.commons.scxml2.env.jexl.JexlEvaluator;
import org.apache.commons.scxml2.io.SCXMLReader;
import org.apache.commons.scxml2.model.ModelException;
import org.apache.commons.scxml2.model.SCXML;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;


public class LeaveFrame extends JFrame implements ActionListener {

    private static final long serialVersionUID = 1L;

    //一系列的按鈕,標籤
    private JLabel applicant;
    private JLabel reason;
    private JLabel from;
    private JLabel to;

    private JTextField nameTest;
    private JTextField reasonTest;
    private JTextField fromTest;
    private JTextField toTest;

    private JButton submit;
    private JButton departmentApprove;
    private JButton personeelApprove;
    private JButton reject;
    private JButton continueFill;
    private JButton archive;
    private JButton start;

    private SCXMLExecutor executor=null;

    public LeaveFrame() {
        super("SCXML Leave");
        initUI();
    }

    public static void main(String[] args) {
        new LeaveFrame();
    }

    /**
     * 初始化請假流程
     */
    private void initWorkflow()  {
        //得到資源文件路徑
        final URL leaveApprovel = this.getClass().getResource("leaveApprove1.xml");
        //實例化數據模型解析器
        Evaluator evaluator = new JexlEvaluator();

        //實例化引擎
        executor = new SCXMLExecutor(evaluator, null, new SimpleErrorReporter());

        try {
            //加載資源文件,實例化到一個SCXML對象,兩者之間一一對應
            SCXML scxml = SCXMLReader.read(leaveApprovel);

            //將這樣的一個SCXML實例,作爲狀態機對象,傳入到引擎裏面。
            executor.setStateMachine(scxml);

            //設置引擎執行的根上下文
            Context rootContext = evaluator.newContext(null);
            LeaveEntity leaveEntity = new LeaveEntity();
            rootContext.set("leaveEntity", leaveEntity);
            executor.setRootContext(rootContext);

            //開始啓動流程
            executor.go();

        }catch (Exception e){
            e.printStackTrace();
        }

        System.out.println(executor.getGlobalContext().getSystemContext().get("_sessionid"));
    }

    /**
     * 監聽器裏面的方法,根據界面上的事件來觸發對應的轉移
     * @param event
     */
    public void actionPerformed(ActionEvent event) {
        String command = event.getActionCommand();
        try {
            if ("submit".equals(command)) {

                setEnabledAndDisabled(new JComponent[]{reject,departmentApprove}, new JComponent[]{submit});

                //添加請假的內容進去
                Map<String, String> payloadData = new HashMap<String, String>();
                payloadData.put("name",nameTest.getText());
                payloadData.put("reason",reasonTest.getText());
                payloadData.put("from", fromTest.getText());
                payloadData.put("to", toTest.getText());
                //生成表單填完的事件,攜帶外部數據進去,然後觸發
                executor.triggerEvent(new TriggerEvent("fill.end", TriggerEvent.SIGNAL_EVENT,payloadData));

            }else if ("departmentApprove".equals(command)){


                setEnabledAndDisabled(new JComponent[]{personeelApprove}, new JComponent[]{departmentApprove});

                executor.triggerEvent(new TriggerEvent("approve", TriggerEvent.SIGNAL_EVENT));

            }else if ("personeelApprove".equals(command)){

                setEnabledAndDisabled(new JComponent[]{}, new JComponent[]{personeelApprove,reject,departmentApprove});
                executor.triggerEvent(new TriggerEvent("approve", TriggerEvent.SIGNAL_EVENT));


            }else if ("reject".equals(command)){

                setEnabledAndDisabled(new JComponent[]{continueFill,archive}, new JComponent[]{submit,personeelApprove,reject,departmentApprove});

                executor.triggerEvent(new TriggerEvent("reject", TriggerEvent.SIGNAL_EVENT));

            }else if ("continueFill".equals(command)){

                setEnabledAndDisabled(new JComponent[]{submit}, new JComponent[]{departmentApprove, personeelApprove, reject, continueFill, archive});

                executor.triggerEvent(new TriggerEvent("goFilling", TriggerEvent.SIGNAL_EVENT));

            }else if ("archive".equals(command)){


                setEnabledAndDisabled(new JComponent[]{}, new JComponent[]{departmentApprove, personeelApprove, reject, continueFill, archive, submit});
                executor.triggerEvent(new TriggerEvent("goEnd", TriggerEvent.SIGNAL_EVENT));
            }else if ("start".equals(command)){

                setEnabledAndDisabled(new JComponent[]{submit}, new JComponent[]{departmentApprove, personeelApprove, reject, continueFill, archive, start});
                setEnabledAndDisabled(new JComponent[]{nameTest,reasonTest,fromTest,toTest}, new JComponent[]{});

                initWorkflow();

            }
        } catch (ModelException e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化界面
     */
    private void initUI() {

        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BorderLayout());

        JPanel contentPanel = new JPanel();

        contentPanel.setLayout(new GridLayout(8,2));

        applicant = new JLabel("申請人:");
        nameTest = new JTextField(10);

        reason = new JLabel("原因:");
        reasonTest = new JTextField(50);


        from = new JLabel("開始時間:");
        fromTest = new JTextField(10);


        to = new JLabel("結束時間:");
        toTest = new JTextField(10);

        start = createButton("start","請假");
        submit = createButton("submit","Submit");
        departmentApprove= createButton("departmentApprove","部門同意");
        personeelApprove= createButton("personeelApprove","人事同意");
        reject = createButton("reject","拒絕");
        continueFill = createButton("continueFill","繼續填寫");
        archive= createButton("archive","結束");



        setEnabledAndDisabled(new JComponent[]{start}, new JComponent[]{departmentApprove, personeelApprove, reject, continueFill, archive, submit});
        setEnabledAndDisabled(new JComponent[]{}, new JComponent[]{nameTest, reasonTest, fromTest, toTest});


        gridLayoutAdd(contentPanel,new JComponent[]{applicant,nameTest,reason,reasonTest,from,fromTest,to,toTest,start,submit,departmentApprove,personeelApprove,reject,continueFill,archive});

        mainPanel.add(contentPanel, BorderLayout.CENTER);

        setContentPane(mainPanel);

        setLocation(200, 200);
        setSize(400, 400);

        setResizable(true);
        setVisible(true);

        setDefaultCloseOperation(EXIT_ON_CLOSE);


    }

    private void gridLayoutAdd(JPanel content, JComponent[] components){

        for (int i = 0; i < components.length; i++) {
            content.add(components[i]);
        }

    }


    private JButton createButton(final String command, final String text) {
        JButton button = new JButton(text);
        button.setActionCommand(command);
        button.addActionListener(this);
        return button;
    }
    private void setEnabledAndDisabled(JComponent[] enabled,JComponent[] disabled){

        for (int i=0;i<enabled.length;i++){
            enabled[i].setEnabled(true);
        }
        for (int i=0;i<disabled.length;i++){
            disabled[i].setEnabled(false);
        }
    }
}

4、程序分析

1、首先打開程序

這裏寫圖片描述

2、然後點擊請假,發起一個請假流程,圖中的一串字符是當前會話的id。

這裏寫圖片描述

3、一旦提交表單,就輪到部門經理審批,部門經理可以選擇同意或者拒絕。

這裏寫圖片描述

4、這裏先演示部門同意,接下來就該到人事經理了。

這裏寫圖片描述

5、這裏演示人事經理拒絕,那麼按照我們之前話的狀態圖,這時候應該到“被拒絕“狀態,發起人可以更改信息,重新提交或者放棄請假。

這裏寫圖片描述
6、這裏假設我們繼續填寫,點擊提交,又回到了部門經理審批,然後人事經理審批環節,假設兩者都同意,然後整個流程就結束了。

這裏寫圖片描述

5、總結

用狀態圖只要控制好event 和condition 表達複雜的行爲很方便。SCXML框架只有控制狀態變化的能力,如果我們能夠添加上任務分派功能,那就是一個很強大的狀態機工作流了。

更多文章請移步:ThinerZQ’s Blog

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章