偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

告別if-else噩夢(mèng)!流程編排技術(shù)真的太香了!

開發(fā) 項(xiàng)目管理
一個(gè)方法里的 if-else 能嵌套到第 8 層,縮進(jìn)比你工資條上的數(shù)字還長(zhǎng)。改的時(shí)候大氣不敢喘,生怕動(dòng)了其中一個(gè) else,整個(gè)系統(tǒng)就跟多米諾骨牌似的全崩了?

兄弟們,咱先聊個(gè)扎心的事兒,打開公司祖?zhèn)鞯?Java 項(xiàng)目,想改個(gè)簡(jiǎn)單的業(yè)務(wù)邏輯,結(jié)果一翻代碼,好家伙!一個(gè)方法里的 if-else 能嵌套到第 8 層,縮進(jìn)比你工資條上的數(shù)字還長(zhǎng)。改的時(shí)候大氣不敢喘,生怕動(dòng)了其中一個(gè) else,整個(gè)系統(tǒng)就跟多米諾骨牌似的全崩了?

我前陣子就踩過這坑。當(dāng)時(shí)要給訂單系統(tǒng)加個(gè) “老用戶專屬折扣” 的邏輯,原代碼里訂單處理的方法長(zhǎng)這樣:

public void handleOrder(Order order) {
    // 第一步:判斷訂單類型
    if (order.getType() == OrderType.NORMAL) {
        // 普通訂單判斷支付方式
        if (order.getPayType() == PayType.WECHAT) {
            // 微信支付判斷是否滿減
            if (order.getAmount() >= 100) {
                // 滿減后判斷是否老用戶
                if (order.getUser().isOldUser()) {
                    order.setDiscount(0.9);
                } else {
                    order.setDiscount(1.0);
                }
                wechatPayService.pay(order);
            } else {
                if (order.getUser().isOldUser()) {
                    order.setDiscount(0.95);
                }
                wechatPayService.pay(order);
            }
        } else if (order.getPayType() == PayType.ALIPAY) {
            // 支付寶支付又一套判斷...
            if (order.getAmount() >= 200) {
                // 此處省略800字嵌套...
            }
        }
    } else if (order.getType() == OrderType.GROUP) {
        // 團(tuán)購訂單再來一套獨(dú)立的if-else...
    }
    // 后面還有庫存判斷、日志記錄、消息推送的嵌套...
}

改完這個(gè)邏輯,我眼睛都快成斗雞眼了,測(cè)試的時(shí)候還漏了個(gè) “新用戶用支付寶支付滿 150 減 20” 的場(chǎng)景,結(jié)果線上出了 bug,當(dāng)晚加班到凌晨?jī)牲c(diǎn) —— 這 if-else,簡(jiǎn)直就是開發(fā)者的 “職場(chǎng) PUA 神器”!后來我痛定思痛,把這套邏輯用流程編排重構(gòu)了一遍,現(xiàn)在不管加什么新規(guī)則,都是 “插拔式” 操作,再也不用在嵌套里找不著北。今天就跟大家好好嘮嘮,怎么用流程編排跟 if-else 徹底說再見。

一、先別急著罵 if-else,咱得搞懂它為啥會(huì) “癌變”

首先聲明:if-else 本身沒問題,就像菜刀能切菜也能傷人,問題出在 “用錯(cuò)地方” 和 “過度使用”。我見過很多項(xiàng)目里的 if-else,都是從 “一行簡(jiǎn)單判斷” 慢慢長(zhǎng)成 “千行嵌套怪物” 的,這個(gè)過程就叫 “邏輯癌變”。

咱先拿剛才的訂單例子分析下,if-else 為啥會(huì)越寫越爛:

1. 「邏輯耦合」:所有規(guī)則都擠在一個(gè) “垃圾桶” 里

你看原代碼里,訂單類型、支付方式、滿減規(guī)則、用戶身份判斷,全堆在handleOrder方法里。就像把衣服、鞋子、零食、化妝品全塞在一個(gè)衣柜里,剛開始還能翻找,越堆越多就徹底亂了。

后來產(chǎn)品說 “要給新用戶支付寶支付加個(gè)首單立減”,我得在支付寶支付的 else 分支里再塞一個(gè) if;再后來又說 “老用戶團(tuán)購訂單額外 9 折”,我又得在團(tuán)購訂單的分支里加判斷 —— 每加一個(gè)新規(guī)則,就是在 “垃圾桶” 里多扔一件東西,最后誰也分不清里面到底有啥。

2. 「擴(kuò)展性差」:改一行代碼,像拆一顆炸彈

有次我要把 “滿 100 減 10” 改成 “滿 100 減 15”,按理說只是改個(gè)數(shù)字,但因?yàn)檫@個(gè)判斷藏在微信支付的 if 分支里,我得先理清 “訂單類型是普通訂單→支付方式是微信→金額滿 100→用戶是新用戶” 這個(gè)鏈路,生怕改的時(shí)候碰了其他判斷條件。

更坑的是,有些判斷條件還互相依賴。比如 “是否老用戶” 的判斷,既用在普通訂單里,又用在團(tuán)購訂單里,后來產(chǎn)品改了 “老用戶定義”(從注冊(cè)滿 1 年改成滿 6 個(gè)月),我得在代碼里找遍所有用到isOldUser()的 if 分支,改漏一個(gè)就出 bug—— 這哪是改代碼,這是拆炸彈?。?/p>

3. 「可讀性為零」:新人看代碼,得先畫思維導(dǎo)圖

我同事剛接手這個(gè)項(xiàng)目的時(shí)候,看這個(gè)handleOrder方法看了一下午,最后在筆記本上畫了個(gè)思維導(dǎo)圖,才理清里面的邏輯。他跟我說:“這代碼里的 if-else,比我老家的族譜還復(fù)雜,光分支就有 12 個(gè)?!?/p>

其實(shí)這還不算最夸張的,我見過有人寫的代碼,if-else 嵌套到第 11 層,縮進(jìn)能從屏幕左邊排到右邊,中間還夾雜著各種臨時(shí)變量和魔法值 —— 這種代碼,除了寫的人自己,沒人能一次看懂。

4. 「測(cè)試噩夢(mèng)」:想覆蓋所有場(chǎng)景,得寫 100 個(gè)測(cè)試用例

因?yàn)槊總€(gè) if-else 分支都是獨(dú)立的場(chǎng)景,要保證測(cè)試覆蓋,就得把所有分支都跑一遍。剛才的訂單例子,光訂單類型(2 種)× 支付方式(2 種)× 金額區(qū)間(3 種)× 用戶身份(2 種),就有 2×2×3×2=24 種場(chǎng)景,還沒算上異常情況。

每次加新規(guī)則,測(cè)試用例就得翻倍。后來測(cè)試小姐姐跟我吐槽:“你們這訂單模塊,我測(cè)一次得花一下午,比我逛街還累?!?/p>

二、啥是流程編排?說白了就是 “給代碼找個(gè)管家”

既然 if-else 這么坑,那有沒有辦法讓代碼 “變整齊”?答案就是流程編排。可能有些兄弟覺得 “流程編排” 這詞兒聽著挺玄乎,其實(shí)特好理解 —— 就像你去餐廳吃飯,后廚不會(huì)讓一個(gè)廚師又買菜又切菜又炒菜又裝盤,而是有專門的采購、切配、掌勺、擺盤師傅,各司其職,最后把菜端到你面前。

流程編排就是給代碼做 “分工”:把一個(gè)復(fù)雜的業(yè)務(wù)流程,拆成一個(gè)個(gè)獨(dú)立的 “小步驟”(比如訂單處理里的 “支付驗(yàn)證”“滿減計(jì)算”“庫存扣減”),然后規(guī)定這些步驟的執(zhí)行順序,讓它們像流水線一樣配合工作。

咱還是拿訂單處理舉例,用流程編排重構(gòu)后,邏輯會(huì)變成這樣:

  1. 第一步:獲取訂單基礎(chǔ)信息(獨(dú)立步驟)
  2. 第二步:判斷訂單類型(獨(dú)立步驟,不同類型走不同分支)
  3. 第三步:驗(yàn)證支付方式(獨(dú)立步驟)
  4. 第四步:計(jì)算折扣(獨(dú)立步驟,根據(jù)用戶身份和金額)
  5. 第五步:扣減庫存(獨(dú)立步驟)
  6. 第六步:記錄訂單日志(獨(dú)立步驟)

每個(gè)步驟都是一個(gè) “小模塊”,可以單獨(dú)修改、測(cè)試、復(fù)用。比如要改折扣規(guī)則,只需要改 “計(jì)算折扣” 這個(gè)步驟,其他步驟完全不用動(dòng) —— 這就像你想換件衣服,不用把褲子、鞋子、襪子全換掉一樣。

可能有人會(huì)問:“這不就是把代碼拆成方法嗎?跟流程編排有啥區(qū)別?”

區(qū)別大了!普通的方法拆分,還是需要你在主方法里用 if-else 調(diào)用各個(gè)方法,比如:

public void handleOrder(Order order) {
    getOrderInfo(order);
    if (order.getType() == OrderType.NORMAL) {
        checkNormalPay(order);
        calculateNormalDiscount(order);
    } else if (order.getType() == OrderType.GROUP) {
        checkGroupPay(order);
        calculateGroupDiscount(order);
    }
    deductStock(order);
    logOrder(order);
}

而流程編排是 “把步驟的執(zhí)行順序也交給框架管理”,你不用寫 if-else 調(diào)用,只需要告訴框架 “第一步執(zhí)行 A,第二步執(zhí)行 B,第三步根據(jù)條件選 C 或 D”,剩下的事兒框架全幫你干了。就像你不用親自指揮后廚的每個(gè)師傅,只需要告訴餐廳 “我要一份番茄炒蛋”,餐廳的流程體系會(huì)自動(dòng)讓采購買番茄雞蛋、切配師傅切菜、掌勺師傅炒菜 —— 這才是流程編排的核心:把 “指揮邏輯” 和 “執(zhí)行邏輯” 徹底分開。

三、Java 生態(tài)里的流程編排方案:從 “輕量級(jí)” 到 “重量級(jí)”,總有一款適合你

聊完概念,咱來點(diǎn)實(shí)在的 ——Java 里到底有哪些流程編排方案?該怎么選?

我把常用的方案分成了三類:輕量級(jí)(自己用設(shè)計(jì)模式實(shí)現(xiàn))、中量級(jí)(Spring 生態(tài)工具)、重量級(jí)(專業(yè)流程引擎)。咱一個(gè)個(gè)說,每個(gè)方案都帶代碼示例,保證你看完就能用。

方案一:輕量級(jí) —— 用 “責(zé)任鏈 + 策略模式”,不用引入任何框架

如果你的業(yè)務(wù)流程不算特別復(fù)雜(比如只有 5-8 個(gè)步驟),又不想引入新框架,那用設(shè)計(jì)模式自己實(shí)現(xiàn)是最好的選擇。這里推薦 “責(zé)任鏈模式 + 策略模式” 的組合,前者負(fù)責(zé) “步驟順序”,后者負(fù)責(zé) “條件分支”。

還是拿訂單處理舉例,咱一步步實(shí)現(xiàn):

1. 第一步:定義 “流程節(jié)點(diǎn)” 接口(所有步驟都要實(shí)現(xiàn)這個(gè)接口)

先定義一個(gè)OrderProcessNode接口,里面只有一個(gè)process方法,每個(gè)步驟都是一個(gè)節(jié)點(diǎn):

// 訂單流程節(jié)點(diǎn)接口
public interface OrderProcessNode {
    // 處理訂單流程,返回是否繼續(xù)執(zhí)行下一個(gè)節(jié)點(diǎn)
    boolean process(Order order);
}

返回boolean是為了控制流程:返回true表示繼續(xù)執(zhí)行下一個(gè)節(jié)點(diǎn),返回false表示終止流程(比如支付驗(yàn)證失敗,就不用執(zhí)行后面的庫存扣減了)。

2. 第二步:實(shí)現(xiàn)各個(gè)獨(dú)立的流程節(jié)點(diǎn)

把之前嵌套在 if-else 里的邏輯,拆成一個(gè)個(gè)節(jié)點(diǎn)實(shí)現(xiàn)類:

① 獲取訂單信息節(jié)點(diǎn)

// 獲取訂單基礎(chǔ)信息
public class OrderInfoNode implements OrderProcessNode {
    @Override
    public boolean process(Order order) {
        System.out.println("第一步:獲取訂單基礎(chǔ)信息");
        // 模擬從數(shù)據(jù)庫獲取訂單詳情
        order.setProductName("iPhone 15");
        order.setAmount(5999.0);
        return true; // 繼續(xù)執(zhí)行下一個(gè)節(jié)點(diǎn)
    }
}

② 訂單類型判斷節(jié)點(diǎn)(策略模式在這里用)因?yàn)椴煌唵晤愋偷奶幚磉壿嫴煌?,這里用策略模式,先定義訂單類型處理器接口:

// 訂單類型處理器接口(策略接口)
public interface OrderTypeHandler {
    void handle(Order order);
}
// 普通訂單處理器(具體策略)
public class NormalOrderHandler implements OrderTypeHandler {
    @Override
    public void handle(Order order) {
        System.out.println("處理普通訂單邏輯");
        order.setTypeDesc("普通訂單,支持微信/支付寶支付");
    }
// 團(tuán)購訂單處理器(具體策略)
public class GroupOrderHandler implements OrderTypeHandler {
    @Override
    public void handle(Order order) {
        System.out.println("處理團(tuán)購訂單邏輯");
        order.setTypeDesc("團(tuán)購訂單,需滿3人成團(tuán)");
    }
}

然后實(shí)現(xiàn)訂單類型判斷節(jié)點(diǎn),根據(jù)訂單類型選擇對(duì)應(yīng)的處理器:

// 訂單類型判斷節(jié)點(diǎn)
public class OrderTypeNode implements OrderProcessNode {
    // 策略工廠,根據(jù)訂單類型獲取對(duì)應(yīng)的處理器
    private Map<OrderType, OrderTypeHandler> typeHandlerMap;
    // 構(gòu)造方法初始化策略工廠
    public OrderTypeNode() {
        typeHandlerMap = new HashMap<>();
        typeHandlerMap.put(OrderType.NORMAL, new NormalOrderHandler());
        typeHandlerMap.put(OrderType.GROUP, new GroupOrderHandler());
    }
    @Override
    public boolean process(Order order) {
        System.out.println("第二步:判斷訂單類型");
        // 根據(jù)訂單類型獲取處理器,執(zhí)行對(duì)應(yīng)邏輯
        OrderTypeHandler handler = typeHandlerMap.get(order.getType());
        if (handler == null) {
            System.out.println("未知訂單類型,終止流程");
            return false; // 終止流程
        }
        handler.handle(order);
        return true; // 繼續(xù)執(zhí)行下一個(gè)節(jié)點(diǎn)
    }
}

③ 支付驗(yàn)證節(jié)點(diǎn)

// 支付驗(yàn)證節(jié)點(diǎn)
public class PayCheckNode implements OrderProcessNode {
    @Override
    public boolean process(Order order) {
        System.out.println("第三步:驗(yàn)證支付方式");
        if (order.getPayType() == PayType.WECHAT || order.getPayType() == PayType.ALIPAY) {
            System.out.println("支付方式合法:" + order.getPayType());
            return true;
        } else {
            System.out.println("不支持的支付方式:" + order.getPayType() + ",終止流程");
            return false;
        }
    }
}

④ 折扣計(jì)算節(jié)點(diǎn)

// 折扣計(jì)算節(jié)點(diǎn)
public class DiscountCalculateNode implements OrderProcessNode {
    @Override
    public boolean process(Order order) {
        System.out.println("第四步:計(jì)算訂單折扣");
        double discount = 1.0;
        // 老用戶折扣
        if (order.getUser().isOldUser()) {
            discount -= 0.1; // 老用戶9折
            System.out.println("老用戶享受9折優(yōu)惠");
        }
        // 滿減折扣
        if (order.getAmount() >= 5000) {
            discount -= 0.05; // 滿5000再減5%
            System.out.println("滿5000元再減5%");
        }
        // 防止折扣低于0.5
        order.setDiscount(Math.max(discount, 0.5));
        System.out.println("最終折扣:" + order.getDiscount());
        return true;
    }
}

⑤ 庫存扣減節(jié)點(diǎn)

// 庫存扣減節(jié)點(diǎn)
publicclass StockDeductNode implements OrderProcessNode {
    private StockService stockService = new StockService(); // 模擬庫存服務(wù)

    @Override
    public boolean process(Order order) {
        System.out.println("第五步:扣減訂單庫存");
        boolean deductSuccess = stockService.deductStock(order.getProductId(), order.getQuantity());
        if (deductSuccess) {
            System.out.println("庫存扣減成功");
            returntrue;
        } else {
            System.out.println("庫存不足,終止流程");
            returnfalse;
        }
    }
}

⑥ 日志記錄節(jié)點(diǎn)

// 日志記錄節(jié)點(diǎn)
public class OrderLogNode implements OrderProcessNode {
    private LogService logService = new LogService(); // 模擬日志服務(wù)

    @Override
    public boolean process(Order order) {
        System.out.println("第六步:記錄訂單日志");
        logService.recordLog("訂單" + order.getOrderId() + "處理完成,最終金額:" + order.getAmount() * order.getDiscount());
        return true;
    }
}

3. 第三步:構(gòu)建責(zé)任鏈,串聯(lián)所有節(jié)點(diǎn)

接下來需要一個(gè) “流程管理器”,把這些節(jié)點(diǎn)按順序串成一條責(zé)任鏈,然后執(zhí)行流程:

// 訂單流程管理器(責(zé)任鏈)
publicclassOrderProcessChain {
    // 用鏈表存儲(chǔ)所有節(jié)點(diǎn),保證執(zhí)行順序
    private LinkedList<OrderProcessNode> nodeList = new LinkedList<>();

    // 添加節(jié)點(diǎn)到鏈尾
    public void addNode(OrderProcessNode node) {
        nodeList.add(node);
    }

    // 執(zhí)行流程:依次調(diào)用每個(gè)節(jié)點(diǎn)的process方法
    public void execute(Order order) {
        for (OrderProcessNode node : nodeList) {
            boolean continueNext = node.process(order);
            if (!continueNext) {
                System.out.println("流程在節(jié)點(diǎn)[" + node.getClass().getSimpleName() + "]終止");
                return;
            }
        }
        System.out.println("所有流程節(jié)點(diǎn)執(zhí)行完成!");
    }
}

4. 第四步:測(cè)試流程執(zhí)行效果

最后寫個(gè)測(cè)試類,看看流程跑起來怎么樣:

public classOrderProcessTest {
    public static void main(String[] args) {
        // 1. 創(chuàng)建訂單對(duì)象
        Order order = new Order();
        order.setOrderId("ORDER_20250826_001");
        order.setType(OrderType.NORMAL); // 普通訂單
        order.setPayType(PayType.WECHAT); // 微信支付
        order.setProductId("PROD_001");
        order.setQuantity(1);
        User user = new User();
        user.setOldUser(true); // 老用戶
        order.setUser(user);

        // 2. 構(gòu)建流程鏈,按順序添加節(jié)點(diǎn)
        OrderProcessChain chain = new OrderProcessChain();
        chain.addNode(new OrderInfoNode());
        chain.addNode(new OrderTypeNode());
        chain.addNode(new PayCheckNode());
        chain.addNode(new DiscountCalculateNode());
        chain.addNode(new StockDeductNode());
        chain.addNode(new OrderLogNode());

        // 3. 執(zhí)行流程
        System.out.println("開始處理訂單:" + order.getOrderId());
        chain.execute(order);
    }
}

運(yùn)行結(jié)果如下:

開始處理訂單:ORDER_20250826_001
第一步:獲取訂單基礎(chǔ)信息
第二步:判斷訂單類型
處理普通訂單邏輯
第三步:驗(yàn)證支付方式
支付方式合法:WECHAT
第四步:計(jì)算訂單折扣
老用戶享受9折優(yōu)惠
滿5000元再減5%
最終折扣:0.85
第五步:扣減訂單庫存
庫存扣減成功
第六步:記錄訂單日志
所有流程節(jié)點(diǎn)執(zhí)行完成!

你看,現(xiàn)在要加新規(guī)則,比如 “新用戶首單支付寶支付減 200”,只需要新建一個(gè)NewUserAlipayDiscountNode,然后在流程鏈里加個(gè)節(jié)點(diǎn)就行:

// 新用戶支付寶首單折扣節(jié)點(diǎn)
publicclass NewUserAlipayDiscountNode implements OrderProcessNode {
    @Override
    public boolean process(Order order) {
        System.out.println("新增步驟:新用戶支付寶首單折扣");
        if (!order.getUser().isOldUser() && order.getPayType() == PayType.ALIPAY && order.isFirstOrder()) {
            order.setAmount(order.getAmount() - 200);
            System.out.println("新用戶首單支付寶支付,立減200元,優(yōu)惠后金額:" + order.getAmount());
        }
        returntrue;
    }
}

// 構(gòu)建流程鏈時(shí)添加這個(gè)節(jié)點(diǎn)
chain.addNode(new NewUserAlipayDiscountNode()); // 加在折扣計(jì)算節(jié)點(diǎn)前面

完全不用動(dòng)原來的任何代碼,這就是 “插拔式” 開發(fā)的爽快感!

方案二:中量級(jí) —— 用 Spring StateMachine,搞定 “狀態(tài)流轉(zhuǎn)” 類業(yè)務(wù)

如果你的業(yè)務(wù)里有很多 “狀態(tài)變化” 的邏輯(比如訂單狀態(tài):待支付→已支付→待發(fā)貨→已發(fā)貨→已完成),用上面的責(zé)任鏈模式雖然能實(shí)現(xiàn),但狀態(tài)管理會(huì)比較麻煩。這時(shí)候就該 Spring StateMachine 登場(chǎng)了 —— 它是 Spring 生態(tài)里專門處理 “狀態(tài)機(jī)” 的工具,能幫你把復(fù)雜的狀態(tài)流轉(zhuǎn)邏輯變得清晰。

咱還是拿訂單狀態(tài)流轉(zhuǎn)舉例,比如訂單有以下狀態(tài):

  • 待支付(WAIT_PAY)
  • 已支付(PAID)
  • 待發(fā)貨(WAIT_SHIP)
  • 已發(fā)貨(SHIPPED)
  • 已完成(COMPLETED)
  • 已取消(CANCELED)

狀態(tài)之間的流轉(zhuǎn)規(guī)則:

  1. 待支付 → 已支付(用戶付款)
  2. 待支付 → 已取消(用戶取消訂單)
  3. 已支付 → 待發(fā)貨(商家確認(rèn)收款)
  4. 已支付 → 已取消(退款)
  5. 待發(fā)貨 → 已發(fā)貨(商家發(fā)貨)
  6. 已發(fā)貨 → 已完成(用戶確認(rèn)收貨)

如果用 if-else 寫,會(huì)是這樣:

public void changeOrderStatus(Order order, String event) {
    if (order.getStatus() == OrderStatus.WAIT_PAY) {
        if ("PAY".equals(event)) {
            order.setStatus(OrderStatus.PAID);
        } elseif ("CANCEL".equals(event)) {
            order.setStatus(OrderStatus.CANCELED);
        }
    } elseif (order.getStatus() == OrderStatus.PAID) {
        if ("CONFIRM".equals(event)) {
            order.setStatus(OrderStatus.WAIT_SHIP);
        } elseif ("REFUND".equals(event)) {
            order.setStatus(OrderStatus.CANCELED);
        }
    } elseif (order.getStatus() == OrderStatus.WAIT_SHIP) {
        if ("SHIP".equals(event)) {
            order.setStatus(OrderStatus.SHIPPED);
        }
    }
    // 還有更多狀態(tài)判斷...
}

用 Spring StateMachine 重構(gòu)后,代碼會(huì)清爽很多。

1. 第一步:引入依賴

在 Spring Boot 項(xiàng)目的 pom.xml 里加依賴:

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>3.2.0</version>
</dependency>

2. 第二步:定義訂單狀態(tài)和事件

先把訂單狀態(tài)和觸發(fā)狀態(tài)變化的事件定義成枚舉:

// 訂單狀態(tài)枚舉
publicenum OrderStatus {
    WAIT_PAY("待支付"),
    PAID("已支付"),
    WAIT_SHIP("待發(fā)貨"),
    SHIPPED("已發(fā)貨"),
    COMPLETED("已完成"),
    CANCELED("已取消");

    private final String desc;
    OrderStatus(String desc) {
        this.desc = desc;
    }
}

// 訂單事件枚舉(觸發(fā)狀態(tài)變化的動(dòng)作)
publicenum OrderEvent {
    PAY("用戶付款"),
    CANCEL("用戶取消"),
    CONFIRM("商家確認(rèn)收款"),
    REFUND("退款"),
    SHIP("商家發(fā)貨"),
    CONFIRM_RECEIVE("用戶確認(rèn)收貨");

    private final String desc;
    OrderEvent(String desc) {
        this.desc = desc;
    }
}

3. 第三步:配置狀態(tài)機(jī)

創(chuàng)建一個(gè)配置類,定義狀態(tài)機(jī)的狀態(tài)、事件和流轉(zhuǎn)規(guī)則:

@Configuration
@EnableStateMachineFactory// 啟用狀態(tài)機(jī)工廠,方便創(chuàng)建多個(gè)狀態(tài)機(jī)實(shí)例
publicclass OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderEvent> {

    // 配置狀態(tài)機(jī)的初始狀態(tài)和所有狀態(tài)
    @Override
    public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states) throws Exception {
        states
            .withStates()
            .initial(OrderStatus.WAIT_PAY) // 初始狀態(tài):待支付
            .states(EnumSet.allOf(OrderStatus.class)); // 所有狀態(tài)
    }

    // 配置狀態(tài)流轉(zhuǎn)規(guī)則(核心)
    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions) throws Exception {
        transitions
            // 1. 待支付 → 已支付:觸發(fā)事件PAY
            .withExternal()
            .source(OrderStatus.WAIT_PAY)
            .target(OrderStatus.PAID)
            .event(OrderEvent.PAY)
            .and()
            // 2. 待支付 → 已取消:觸發(fā)事件CANCEL
            .withExternal()
            .source(OrderStatus.WAIT_PAY)
            .target(OrderStatus.CANCELED)
            .event(OrderEvent.CANCEL)
            .and()
            // 3. 已支付 → 待發(fā)貨:觸發(fā)事件CONFIRM
            .withExternal()
            .source(OrderStatus.PAID)
            .target(OrderStatus.WAIT_SHIP)
            .event(OrderEvent.CONFIRM)
            .and()
            // 4. 已支付 → 已取消:觸發(fā)事件REFUND
            .withExternal()
            .source(OrderStatus.PAID)
            .target(OrderStatus.CANCELED)
            .event(OrderEvent.REFUND)
            .and()
            // 5. 待發(fā)貨 → 已發(fā)貨:觸發(fā)事件SHIP
            .withExternal()
            .source(OrderStatus.WAIT_SHIP)
            .target(OrderStatus.SHIPPED)
            .event(OrderEvent.SHIP)
            .and()
            // 6. 已發(fā)貨 → 已完成:觸發(fā)事件CONFIRM_RECEIVE
            .withExternal()
            .source(OrderStatus.SHIPPED)
            .target(OrderStatus.COMPLETED)
            .event(OrderEvent.CONFIRM_RECEIVE);
    }

    // 配置狀態(tài)機(jī)監(jiān)聽器,監(jiān)聽狀態(tài)變化事件
    @Bean
    public StateMachineListener<OrderStatus, OrderEvent> orderStateListener() {
        returnnew StateMachineListenerAdapter<OrderStatus, OrderEvent>() {
            @Override
            public void stateChanged(State<OrderStatus, OrderEvent> from, State<OrderStatus, OrderEvent> to) {
                System.out.println("訂單狀態(tài)變化:" + (from == null ? "初始狀態(tài)" : from.getId().getDesc()) 
                    + " → " + to.getId().getDesc());
            }

            @Override
            public void eventNotAccepted(Message<OrderEvent> event) {
                System.out.println("不支持的事件:" + event.getPayload().getDesc() 
                    + ",當(dāng)前訂單狀態(tài)可能不允許此操作");
            }
        };
    }

    // 注冊(cè)監(jiān)聽器
    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderStatus, OrderEvent> config) throws Exception {
        config
            .withConfiguration()
            .listener(orderStateListener());
    }
}

4. 第四步:創(chuàng)建訂單狀態(tài)服務(wù)

寫一個(gè)服務(wù)類,封裝狀態(tài)機(jī)的使用,方便業(yè)務(wù)層調(diào)用:

@Service
publicclassOrderStateService {

    @Autowired
    private StateMachineFactory<OrderStatus, OrderEvent> stateMachineFactory;

    // 線程安全:每個(gè)訂單用一個(gè)獨(dú)立的狀態(tài)機(jī)實(shí)例
    private Map<String, StateMachine<OrderStatus, OrderEvent>> orderStateMachineMap = new ConcurrentHashMap<>();

    // 初始化訂單狀態(tài)機(jī)(訂單創(chuàng)建時(shí)調(diào)用)
    public void initOrderStateMachine(String orderId) {
        StateMachine<OrderStatus, OrderEvent> stateMachine = stateMachineFactory.getStateMachine();
        // 設(shè)置訂單ID作為狀態(tài)機(jī)的ID,方便后續(xù)關(guān)聯(lián)
        stateMachine.getExtendedState().getVariables().put("orderId", orderId);
        orderStateMachineMap.put(orderId, stateMachine);
        System.out.println("訂單[" + orderId + "]狀態(tài)機(jī)初始化完成,初始狀態(tài):" + stateMachine.getState().getId().getDesc());
    }

    // 觸發(fā)訂單狀態(tài)變化(核心方法)
    public boolean sendEvent(String orderId, OrderEvent event) {
        StateMachine<OrderStatus, OrderEvent> stateMachine = orderStateMachineMap.get(orderId);
        if (stateMachine == null) {
            System.out.println("訂單[" + orderId + "]狀態(tài)機(jī)未初始化");
            returnfalse;
        }
        // 發(fā)送事件,觸發(fā)狀態(tài)變化
        return stateMachine.sendEvent(event);
    }

    // 獲取訂單當(dāng)前狀態(tài)
    public OrderStatus getCurrentState(String orderId) {
        StateMachine<OrderStatus, OrderEvent> stateMachine = orderStateMachineMap.get(orderId);
        if (stateMachine == null) {
            returnnull;
        }
        return stateMachine.getState().getId();
    }
}

5. 第五步:測(cè)試狀態(tài)流轉(zhuǎn)

寫個(gè)測(cè)試類,模擬訂單狀態(tài)變化的整個(gè)過程:

@SpringBootTest
publicclassOrderStateMachineTest {

    @Autowired
    private OrderStateService orderStateService;

    @Test
    public void testOrderStateFlow() {
        String orderId = "ORDER_20250826_002";

        // 1. 初始化訂單狀態(tài)機(jī)
        orderStateService.initOrderStateMachine(orderId);

        // 2. 觸發(fā)"用戶付款"事件:待支付→已支付
        boolean paySuccess = orderStateService.sendEvent(orderId, OrderEvent.PAY);
        System.out.println("觸發(fā)用戶付款事件:" + (paySuccess ? "成功" : "失敗"));
        System.out.println("當(dāng)前訂單狀態(tài):" + orderStateService.getCurrentState(orderId).getDesc() + "\n");

        // 3. 觸發(fā)"商家確認(rèn)收款"事件:已支付→待發(fā)貨
        boolean confirmSuccess = orderStateService.sendEvent(orderId, OrderEvent.CONFIRM);
        System.out.println("觸發(fā)商家確認(rèn)收款事件:" + (confirmSuccess ? "成功" : "失敗"));
        System.out.println("當(dāng)前訂單狀態(tài):" + orderStateService.getCurrentState(orderId).getDesc() + "\n");

        // 4. 觸發(fā)"商家發(fā)貨"事件:待發(fā)貨→已發(fā)貨
        boolean shipSuccess = orderStateService.sendEvent(orderId, OrderEvent.SHIP);
        System.out.println("觸發(fā)商家發(fā)貨事件:" + (shipSuccess ? "成功" : "失敗"));
        System.out.println("當(dāng)前訂單狀態(tài):" + orderStateService.getCurrentState(orderId).getDesc() + "\n");

        // 5. 觸發(fā)"用戶確認(rèn)收貨"事件:已發(fā)貨→已完成
        boolean confirmReceiveSuccess = orderStateService.sendEvent(orderId, OrderEvent.CONFIRM_RECEIVE);
        System.out.println("觸發(fā)用戶確認(rèn)收貨事件:" + (confirmReceiveSuccess ? "成功" : "失敗"));
        System.out.println("當(dāng)前訂單狀態(tài):" + orderStateService.getCurrentState(orderId).getDesc() + "\n");

        // 6. 嘗試觸發(fā)"退款"事件(已完成狀態(tài)不支持退款,會(huì)失?。?        boolean refundSuccess = orderStateService.sendEvent(orderId, OrderEvent.REFUND);
        System.out.println("觸發(fā)退款事件:" + (refundSuccess ? "成功" : "失敗"));
        System.out.println("當(dāng)前訂單狀態(tài):" + orderStateService.getCurrentState(orderId).getDesc());
    }
}

運(yùn)行結(jié)果如下:

訂單[ORDER_20250826_002]狀態(tài)機(jī)初始化完成,初始狀態(tài):待支付
訂單狀態(tài)變化:初始狀態(tài) → 待支付

觸發(fā)用戶付款事件:成功
訂單狀態(tài)變化:待支付 → 已支付
當(dāng)前訂單狀態(tài):已支付

觸發(fā)商家確認(rèn)收款事件:成功
訂單狀態(tài)變化:已支付 → 待發(fā)貨
當(dāng)前訂單狀態(tài):待發(fā)貨

觸發(fā)商家發(fā)貨事件:成功
訂單狀態(tài)變化:待發(fā)貨 → 已發(fā)貨
當(dāng)前訂單狀態(tài):已發(fā)貨

觸發(fā)用戶確認(rèn)收貨事件:成功
訂單狀態(tài)變化:已發(fā)貨 → 已完成
當(dāng)前訂單狀態(tài):已完成

不支持的事件:退款,當(dāng)前訂單狀態(tài)可能不允許此操作
觸發(fā)退款事件:失敗
當(dāng)前訂單狀態(tài):已完成

你看,所有狀態(tài)流轉(zhuǎn)規(guī)則都集中在配置類里,不用寫一行 if-else。如果要加新的狀態(tài)流轉(zhuǎn)(比如 “已發(fā)貨→已取消”,用戶拒收退款),只需要在transitions配置里加一段:

.withExternal()
.source(OrderStatus.SHIPPED)
.target(OrderStatus.CANCELED)
.event(OrderEvent.REJECT_REFUND); // 新增“拒收退款”事件

是不是比改 if-else 舒服多了?

方案三:重量級(jí) —— 用 Flowable/Camunda,應(yīng)對(duì) “可視化 + 復(fù)雜流程”

如果你的業(yè)務(wù)流程非常復(fù)雜(比如 OA 審批流程、電商售后流程),需要產(chǎn)品經(jīng)理能可視化編輯流程,或者需要支持流程暫停、重試、回滾、定時(shí)任務(wù)等高級(jí)功能,那輕量級(jí)和中量級(jí)方案就不夠用了,這時(shí)候就得上專業(yè)的流程引擎 ——Flowable 和 Camunda 是目前 Java 生態(tài)里最火的兩款。

這倆引擎都基于 BPMN 2.0 標(biāo)準(zhǔn)(業(yè)務(wù)流程建模與 notation),支持用畫圖的方式定義流程,產(chǎn)品經(jīng)理用 Flowable Modeler 或 Camunda Modeler 畫個(gè)流程圖,開發(fā)直接把圖導(dǎo)入項(xiàng)目就能用,不用手寫流程邏輯。

咱以 Flowable 為例,用 “電商售后退款流程” 來演示,流程如下:

  1. 用戶提交退款申請(qǐng)(需填寫退款原因和金額)
  2. 系統(tǒng)自動(dòng)驗(yàn)證退款金額是否合理(≤訂單金額)
  3. 驗(yàn)證通過→商家審核;驗(yàn)證失敗→駁回用戶申請(qǐng)
  4. 商家審核:通過→財(cái)務(wù)審核;不通過→駁回用戶申請(qǐng)
  5. 財(cái)務(wù)審核:通過→執(zhí)行退款;不通過→駁回用戶申請(qǐng)
  6. 執(zhí)行退款后,發(fā)送短信通知用戶

1. 第一步:引入依賴

Spring Boot 項(xiàng)目加 Flowable 依賴:

<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-spring-boot-starter</artifactId>
    <version>7.0.0.M2</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

這里用 H2 內(nèi)存數(shù)據(jù)庫,方便測(cè)試。

2. 第二步:畫 BPMN 流程圖

用 Flowable Modeler(官網(wǎng)可下載)畫流程圖,保存為after_sales_refund.bpmn20.xml,放在src/main/resources/processes目錄下(Flowable 會(huì)自動(dòng)掃描這個(gè)目錄的流程文件)。

流程圖的 XML 內(nèi)容如下(你也可以直接復(fù)制用,或用 Modeler 可視化編輯):

<?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"
             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd"
             xmlns:flowable="http://flowable.org/bpmn"
             id="Definitions_1"
             targetNamespace="http://flowable.org/bpmn">

    <process id="afterSalesRefundProcess" name="電商售后退款流程" isExecutable="true">
        <!-- 開始事件:用戶提交退款申請(qǐng) -->
        <startEvent id="startEvent1" name="用戶提交退款申請(qǐng)">
            <extensionElements>
                <!-- 定義流程變量:訂單ID、退款金額、退款原因、用戶ID -->
                <flowable:formProperty id="orderId" name="訂單ID" type="string" required="true"/>
                <flowable:formProperty id="refundAmount" name="退款金額" type="double" required="true"/>
                <flowable:formProperty id="refundReason" name="退款原因" type="string" required="true"/>
                <flowable:formProperty id="userId" name="用戶ID" type="string" required="true"/>
            </extensionElements>
        </startEvent>

        <!-- 服務(wù)任務(wù):系統(tǒng)驗(yàn)證退款金額 -->
        <serviceTask id="validateRefundAmountTask" name="驗(yàn)證退款金額" flowable:delegateExpression="${validateRefundAmountDelegate}"/>

        <!-- 排他網(wǎng)關(guān):驗(yàn)證結(jié)果判斷 -->
        <exclusiveGateway id="validateGateway" name="驗(yàn)證結(jié)果"/>
        <sequenceFlow id="flow1" sourceRef="startEvent1" targetRef="validateRefundAmountTask"/>
        <sequenceFlow id="flow2" sourceRef="validateRefundAmountTask" targetRef="validateGateway"/>

        <!-- 服務(wù)任務(wù):駁回用戶申請(qǐng) -->
        <serviceTask id="rejectTask" name="駁回用戶申請(qǐng)" flowable:delegateExpression="${rejectRefundDelegate}"/>
        <!-- 結(jié)束事件:流程結(jié)束 -->
        <endEvent id="endEvent1" name="流程結(jié)束"/>
        <sequenceFlow id="flow3" sourceRef="rejectTask" targetRef="endEvent1"/>

        <!-- 用戶任務(wù):商家審核 -->
        <userTask id="merchantAuditTask" name="商家審核" flowable:assignee="merchant">
            <extensionElements>
                <!-- 商家審核結(jié)果:通過/不通過 -->
                <flowable:formProperty id="merchantAuditResult" name="審核結(jié)果" type="enum" required="true">
                    <flowable:value id="pass" name="通過"/>
                    <flowable:value id="reject" name="不通過"/>
                </flowable:formProperty>
                <flowable:formProperty id="merchantAuditComment" name="審核意見" type="string"/>
            </extensionElements>
        </userTask>
        <!-- 排他網(wǎng)關(guān):商家審核結(jié)果判斷 -->
        <exclusiveGateway id="merchantAuditGateway" name="商家審核結(jié)果"/>
        <sequenceFlow id="flow4" sourceRef="validateGateway" targetRef="merchantAuditTask">
            <!-- 條件:驗(yàn)證通過 -->
            <conditionExpression xsi:type="tFormalExpression">#{validateResult == 'pass'}</conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="flow5" sourceRef="validateGateway" targetRef="rejectTask">
            <!-- 條件:驗(yàn)證失敗 -->
            <conditionExpression xsi:type="tFormalExpression">#{validateResult == 'reject'}</conditionExpression>
        </sequenceFlow>

        <!-- 用戶任務(wù):財(cái)務(wù)審核 -->
        <userTask id="financeAuditTask" name="財(cái)務(wù)審核" flowable:assignee="finance">
            <extensionElements>
                <!-- 財(cái)務(wù)審核結(jié)果:通過/不通過 -->
                <flowable:formProperty id="financeAuditResult" name="審核結(jié)果" type="enum" required="true">
                    <flowable:value id="pass" name="通過"/>
                    <flowable:value id="reject" name="不通過"/>
                </flowable:formProperty>
                <flowable:formProperty id="financeAuditComment" name="審核意見" type="string"/>
            </extensionElements>
        </userTask>
        <sequenceFlow id="flow6" sourceRef="merchantAuditTask" targetRef="merchantAuditGateway"/>
        <sequenceFlow id="flow7" sourceRef="merchantAuditGateway" targetRef="financeAuditTask">
            <!-- 條件:商家審核通過 -->
            <conditionExpression xsi:type="tFormalExpression">#{merchantAuditResult == 'pass'}</conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="flow8" sourceRef="merchantAuditGateway" targetRef="rejectTask">
            <!-- 條件:商家審核不通過 -->
            <conditionExpression xsi:type="tFormalExpression">#{merchantAuditResult == 'reject'}</conditionExpression>
        </sequenceFlow>

        <!-- 排他網(wǎng)關(guān):財(cái)務(wù)審核結(jié)果判斷 -->
        <exclusiveGateway id="financeAuditGateway" name="財(cái)務(wù)審核結(jié)果"/>
        <!-- 服務(wù)任務(wù):執(zhí)行退款 -->
        <serviceTask id="executeRefundTask" name="執(zhí)行退款" flowable:delegateExpression="${executeRefundDelegate}"/>
        <!-- 服務(wù)任務(wù):發(fā)送退款通知 -->
        <serviceTask id="sendNotifyTask" name="發(fā)送退款通知" flowable:delegateExpression="${sendRefundNotifyDelegate}"/>

        <sequenceFlow id="flow9" sourceRef="financeAuditTask" targetRef="financeAuditGateway"/>
        <sequenceFlow id="flow10" sourceRef="financeAuditGateway" targetRef="executeRefundTask">
            <!-- 條件:財(cái)務(wù)審核通過 -->
            <conditionExpression xsi:type="tFormalExpression">#{financeAuditResult == 'pass'}</conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="flow11" sourceRef="financeAuditGateway" targetRef="rejectTask">
            <!-- 條件:財(cái)務(wù)審核不通過 -->
            <conditionExpression xsi:type="tFormalExpression">#{financeAuditResult == 'reject'}</conditionExpression>
        </sequenceFlow>

        <sequenceFlow id="flow12" sourceRef="executeRefundTask" targetRef="sendNotifyTask"/>
        <sequenceFlow id="flow13" sourceRef="sendNotifyTask" targetRef="endEvent1"/>
    </process>
</definitions>

3. 第三步:實(shí)現(xiàn)流程任務(wù)的業(yè)務(wù)邏輯

Flowable 里的serviceTask需要用Delegate類實(shí)現(xiàn)具體業(yè)務(wù)邏輯,這里實(shí)現(xiàn) 5 個(gè)任務(wù)的 Delegate:

① 驗(yàn)證退款金額 Delegate

@Component("validateRefundAmountDelegate")
publicclass ValidateRefundAmountDelegate implements JavaDelegate {

    // 模擬訂單服務(wù),獲取訂單金額
    @Autowired
    private OrderService orderService;

    @Override
    public void execute(DelegateExecution execution) {
        System.out.println("開始驗(yàn)證退款金額");
        // 獲取流程變量
        String orderId = (String) execution.getVariable("orderId");
        Double refundAmount = (Double) execution.getVariable("refundAmount");

        // 從訂單服務(wù)獲取訂單金額
        Double orderAmount = orderService.getOrderAmount(orderId);
        System.out.println("訂單[" + orderId + "]金額:" + orderAmount + ",申請(qǐng)退款金額:" + refundAmount);

        // 驗(yàn)證邏輯:退款金額≤訂單金額
        if (refundAmount <= orderAmount && refundAmount > 0) {
            execution.setVariable("validateResult", "pass");
            System.out.println("退款金額驗(yàn)證通過");
        } else {
            execution.setVariable("validateResult", "reject");
            execution.setVariable("rejectReason", "退款金額不合法(需大于0且≤訂單金額)");
            System.out.println("退款金額驗(yàn)證失?。? + execution.getVariable("rejectReason"));
        }
    }
}

② 駁回退款 Delegate

@Component("rejectRefundDelegate")
publicclass RejectRefundDelegate implements JavaDelegate {

    @Autowired
    private RefundService refundService;

    @Override
    publicvoid execute(DelegateExecution execution) {
        System.out.println("開始處理駁回退款申請(qǐng)");
        String orderId = (String) execution.getVariable("orderId");
        String rejectReason = (String) execution.getVariable("rejectReason");

        // 更新退款單狀態(tài)為“已駁回”
        refundService.updateRefundStatus(orderId, RefundStatus.REJECTED, rejectReason);
        System.out.println("訂單[" + orderId + "]退款申請(qǐng)已駁回,原因:" + rejectReason);
    }
}

③ 執(zhí)行退款 Delegate

@Component("executeRefundDelegate")
publicclass ExecuteRefundDelegate implements JavaDelegate {

    @Autowired
    private RefundService refundService;

    @Override
    public void execute(DelegateExecution execution) {
        System.out.println("開始執(zhí)行退款操作");
        String orderId = (String) execution.getVariable("orderId");
        Double refundAmount = (Double) execution.getVariable("refundAmount");

        // 調(diào)用支付網(wǎng)關(guān)執(zhí)行退款
        boolean refundSuccess = refundService.executeRefund(orderId, refundAmount);
        if (refundSuccess) {
            // 更新退款單狀態(tài)為“已退款”
            refundService.updateRefundStatus(orderId, RefundStatus.REFUNDED, "退款成功");
            execution.setVariable("refundResult", "success");
            System.out.println("訂單[" + orderId + "]退款執(zhí)行成功,金額:" + refundAmount);
        } else {
            // 更新退款單狀態(tài)為“退款失敗”
            refundService.updateRefundStatus(orderId, RefundStatus.REFUND_FAILED, "支付網(wǎng)關(guān)退款失敗");
            execution.setVariable("refundResult", "fail");
            System.out.println("訂單[" + orderId + "]退款執(zhí)行失敗");
        }
    }
}

④ 發(fā)送退款通知 Delegate

@Component("sendRefundNotifyDelegate")
publicclass SendRefundNotifyDelegate implements JavaDelegate {

    @Autowired
    private SmsService smsService;

    @Override
    publicvoid execute(DelegateExecution execution) {
        System.out.println("開始發(fā)送退款通知");
        String userId = (String) execution.getVariable("userId");
        String orderId = (String) execution.getVariable("orderId");
        Double refundAmount = (Double) execution.getVariable("refundAmount");

        // 獲取用戶手機(jī)號(hào)(模擬)
        String phone = smsService.getUserPhone(userId);
        // 發(fā)送短信通知
        String content = "【電商平臺(tái)】您的訂單" + orderId + "已成功退款" + refundAmount + "元,請(qǐng)注意查收。";
        smsService.sendSms(phone, content);
        System.out.println("已向用戶[" + userId + "]的手機(jī)號(hào)[" + phone + "]發(fā)送退款通知:" + content);
    }
}

4. 第四步:寫接口測(cè)試流程

創(chuàng)建 Controller,提供接口讓前端調(diào)用,觸發(fā)流程和處理審核:

@RestController
@RequestMapping("/refund")
publicclass RefundController {

    @Autowired
    private RuntimeService runtimeService; // Flowable的運(yùn)行時(shí)服務(wù),用于啟動(dòng)流程
    @Autowired
    private TaskService taskService; // Flowable的任務(wù)服務(wù),用于處理用戶任務(wù)(審核)

    // 1. 用戶提交退款申請(qǐng)(啟動(dòng)流程)
    @PostMapping("/apply")
    publicString applyRefund(@RequestBody RefundApplyDTO applyDTO) {
        // 設(shè)置流程變量
        Map<String, Object> variables = new HashMap<>();
        variables.put("orderId", applyDTO.getOrderId());
        variables.put("refundAmount", applyDTO.getRefundAmount());
        variables.put("refundReason", applyDTO.getRefundReason());
        variables.put("userId", applyDTO.getUserId());

        // 啟動(dòng)流程實(shí)例
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("afterSalesRefundProcess", variables);
        System.out.println("退款流程已啟動(dòng),流程實(shí)例ID:" + processInstance.getId());
        return"退款申請(qǐng)?zhí)峤怀晒?,流程?shí)例ID:" + processInstance.getId();
    }

    // 2. 商家審核退款申請(qǐng)
    @PostMapping("/merchant/audit")
    publicString merchantAudit(@RequestBody MerchantAuditDTO auditDTO) {
        // 根據(jù)流程實(shí)例ID和任務(wù)負(fù)責(zé)人(商家)查詢?nèi)蝿?wù)
        Task task = taskService.createTaskQuery()
                .processInstanceId(auditDTO.getProcessInstanceId())
                .taskAssignee("merchant") // 商家的用戶ID
                .singleResult();

        if (task == null) {
            return"未找到待審核的任務(wù)";
        }

        // 設(shè)置審核結(jié)果變量
        Map<String, Object> variables = new HashMap<>();
        variables.put("merchantAuditResult", auditDTO.getAuditResult()); // pass/reject
        variables.put("merchantAuditComment", auditDTO.getAuditComment());

        // 如果審核不通過,設(shè)置駁回原因
        if ("reject".equals(auditDTO.getAuditResult())) {
            variables.put("rejectReason", "商家審核不通過:" + auditDTO.getAuditComment());
        }

        // 完成任務(wù)(提交審核結(jié)果)
        taskService.complete(task.getId(), variables);
        return"商家審核已提交,結(jié)果:" + auditDTO.getAuditResult();
    }

    // 3. 財(cái)務(wù)審核退款申請(qǐng)
    @PostMapping("/finance/audit")
    publicString financeAudit(@RequestBody FinanceAuditDTO auditDTO) {
        // 根據(jù)流程實(shí)例ID和任務(wù)負(fù)責(zé)人(財(cái)務(wù))查詢?nèi)蝿?wù)
        Task task = taskService.createTaskQuery()
                .processInstanceId(auditDTO.getProcessInstanceId())
                .taskAssignee("finance") // 財(cái)務(wù)的用戶ID
                .singleResult();

        if (task == null) {
            return"未找到待審核的任務(wù)";
        }

        // 設(shè)置審核結(jié)果變量
        Map<String, Object> variables = new HashMap<>();
        variables.put("financeAuditResult", auditDTO.getAuditResult()); // pass/reject
        variables.put("financeAuditComment", auditDTO.getAuditComment());

        // 如果審核不通過,設(shè)置駁回原因
        if ("reject".equals(auditDTO.getAuditResult())) {
            variables.put("rejectReason", "財(cái)務(wù)審核不通過:" + auditDTO.getAuditComment());
        }

        // 完成任務(wù)(提交審核結(jié)果)
        taskService.complete(task.getId(), variables);
        return"財(cái)務(wù)審核已提交,結(jié)果:" + auditDTO.getAuditResult();
    }

    // 4. 查詢流程狀態(tài)
    @GetMapping("/status/{processInstanceId}")
    publicString getProcessStatus(@PathVariableString processInstanceId) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();

        if (processInstance == null) {
            // 流程已結(jié)束,查詢歷史狀態(tài)
            HistoricProcessInstance historicProcessInstance = runtimeService.createHistoricProcessInstanceQuery()
                    .processInstanceId(processInstanceId)
                    .singleResult();
            return"流程已結(jié)束,結(jié)束時(shí)間:" + historicProcessInstance.getEndTime() + ",狀態(tài):" + historicProcessInstance.getState();
        } else {
            // 流程未結(jié)束,查詢當(dāng)前任務(wù)
            Task currentTask = taskService.createTaskQuery()
                    .processInstanceId(processInstanceId)
                    .singleResult();
            String currentTaskName = currentTask != null ? currentTask.getName() : "無當(dāng)前任務(wù)";
            return"流程運(yùn)行中,當(dāng)前狀態(tài):" + processInstance.getState() + ",當(dāng)前任務(wù):" + currentTaskName;
        }
    }
}

5. 第五步:測(cè)試流程完整運(yùn)行

用 Postman 或 curl 調(diào)用接口,模擬整個(gè)退款流程:

① 提交退款申請(qǐng)

  • 請(qǐng)求 URL:http://localhost:8080/refund/apply
  • 請(qǐng)求體:
{
    "orderId": "ORDER_20250826_003",
    "refundAmount": 100.0,
    "refundReason": "商品質(zhì)量問題",
    "userId": "USER_001"
}
  • 響應(yīng):退款申請(qǐng)?zhí)峤怀晒Γ鞒虒?shí)例ID:12501

② 商家審核通過

  • 請(qǐng)求 URL:http://localhost:8080/refund/merchant/audit
  • 請(qǐng)求體:
{
    "processInstanceId": "12501",
    "auditResult": "pass",
    "auditComment": "同意退款"
}
  • 響應(yīng):商家審核已提交,結(jié)果:pass

③ 財(cái)務(wù)審核通過

  • 請(qǐng)求 URL:http://localhost:8080/refund/finance/audit
  • 請(qǐng)求體:
{
    "processInstanceId": "12501",
    "auditResult": "pass",
    "auditComment": "同意退款,已安排打款"
}
  • 響應(yīng):財(cái)務(wù)審核已提交,結(jié)果:pass

④ 查詢流程狀態(tài)

  • 請(qǐng)求 URL:http://localhost:8080/refund/status/12501
  • 響應(yīng):流程已結(jié)束,結(jié)束時(shí)間:2025-08-26T15:30:45.123+08:00,狀態(tài):COMPLETED

后臺(tái)日志會(huì)打印整個(gè)流程的執(zhí)行過程,從驗(yàn)證金額到商家審核、財(cái)務(wù)審核、執(zhí)行退款、發(fā)送通知,一步不差。如果產(chǎn)品經(jīng)理想改流程(比如加個(gè) “客服初審” 步驟),只需要在 BPMN 圖里加個(gè)用戶任務(wù),不用改任何 Java 代碼 —— 這就是流程引擎的強(qiáng)大之處。

四、流程編排不是 “銀彈”,這些坑你得避開

講了這么多流程編排的好處,不是說它能解決所有問題。就像你不會(huì)用大炮打蚊子一樣,流程編排也有它的適用場(chǎng)景,用錯(cuò)了反而會(huì)增加復(fù)雜度。我總結(jié)了幾個(gè)項(xiàng)目里踩過的坑,大家一定要注意:

1. 「過度設(shè)計(jì)」:簡(jiǎn)單業(yè)務(wù)用了復(fù)雜流程,純屬自找麻煩

有些兄弟剛學(xué)會(huì)流程編排,就不管什么業(yè)務(wù)都想用。比如一個(gè)簡(jiǎn)單的 “根據(jù)用戶等級(jí)返回折扣” 的邏輯,用一行 if-else 就能搞定:

public double getDiscount(User user) {
    if (user.getLevel() == Level.VIP1) return 0.95;
    else if (user.getLevel() == Level.VIP2) return 0.9;
    else return 1.0;
}

結(jié)果非要用責(zé)任鏈模式,拆成Vip1DiscountNode、Vip2DiscountNode、NormalUserDiscountNode,還建個(gè)流程鏈 —— 這就是 “為了用技術(shù)而用技術(shù)”,反而增加了代碼量和維護(hù)成本。記住:業(yè)務(wù)簡(jiǎn)單用 if-else,業(yè)務(wù)復(fù)雜用流程編排。判斷標(biāo)準(zhǔn)很簡(jiǎn)單:如果你的 if-else 嵌套超過 3 層,或者業(yè)務(wù)規(guī)則經(jīng)常變動(dòng),再考慮流程編排。

2. 「忽略異常處理」:流程斷了沒人管,線上出問題才慌

我之前重構(gòu)訂單流程時(shí),忘了給 “庫存扣減” 節(jié)點(diǎn)加異常處理,結(jié)果有次庫存服務(wù)超時(shí),流程卡在了庫存扣減步驟,訂單狀態(tài)一直是 “待庫存扣減”,用戶付了錢卻看不到訂單狀態(tài)更新,投訴了一大堆。

后來我在每個(gè)節(jié)點(diǎn)都加了異常重試和降級(jí)邏輯:

// 改進(jìn)后的庫存扣減節(jié)點(diǎn)
publicclass StockDeductNode implements OrderProcessNode {
    private StockService stockService = new StockService();
    private RetryTemplate retryTemplate; // 重試模板

    public StockDeductNode() {
        // 初始化重試模板:重試3次,每次間隔1秒
        retryTemplate = new RetryTemplate();
        retryTemplate.setRetryPolicy(new SimpleRetryPolicy(3));
        retryTemplate.setBackOffPolicy(new FixedBackOffPolicy() {{
            setBackOffPeriod(1000);
        }});
    }

    @Override
    public boolean process(Order order) {
        try {
            // 重試3次庫存扣減
            return retryTemplate.execute(context -> {
                boolean deductSuccess = stockService.deductStock(order.getProductId(), order.getQuantity());
                if (!deductSuccess) {
                    thrownew RuntimeException("庫存扣減失敗,重試中...");
                }
                System.out.println("庫存扣減成功");
                returntrue;
            });
        } catch (Exception e) {
            // 重試失敗后降級(jí):記錄日志,觸發(fā)人工介入
            System.out.println("庫存扣減失?。ㄒ阎卦?次),觸發(fā)人工處理:" + e.getMessage());
            sendAlertToStaff(order); // 發(fā)送告警給運(yùn)營(yíng)人員
            returnfalse;
        }
    }
}

不管用哪種流程編排方案,都要考慮 “節(jié)點(diǎn)失敗了怎么辦”,是重試、降級(jí)還是終止流程,必須提前定義好。

3. 「流程可視化過度依賴」:把所有邏輯都畫在圖里,調(diào)試起來想死

用 Flowable/Camunda 的時(shí)候,有些團(tuán)隊(duì)喜歡把所有業(yè)務(wù)邏輯都用 BPMN 的網(wǎng)關(guān)和表達(dá)式實(shí)現(xiàn),比如在條件表達(dá)式里寫復(fù)雜的判斷:

<sequenceFlow id="flow4" sourceRef="validateGateway" targetRef="merchantAuditTask">
    <conditionExpression xsi:type="tFormalExpression">
        #{validateResult == 'pass' && refundAmount > 100 && userLevel == 'VIP' && orderCreateTime > '2025-08-01'}
    </conditionExpression>
</sequenceFlow>

這種表達(dá)式寫多了,調(diào)試起來特別麻煩 —— 流程走到這一步?jīng)]按預(yù)期走,你得去看 BPMN 圖里的表達(dá)式有沒有寫錯(cuò),還得查流程變量的值,比看 Java 代碼還費(fèi)勁。正確的做法是:復(fù)雜邏輯寫在 Delegate 類里,BPMN 圖只負(fù)責(zé)流程走向。表達(dá)式只用來做簡(jiǎn)單的條件判斷(比如#{validateResult == 'pass'}),這樣調(diào)試的時(shí)候,直接看 Delegate 類的日志就行。

4. 「不考慮性能」:流程節(jié)點(diǎn)太多,響應(yīng)時(shí)間變慢

流程編排本質(zhì)上是把一個(gè)方法拆成多個(gè)方法執(zhí)行,節(jié)點(diǎn)越多,方法調(diào)用次數(shù)越多,性能開銷也越大。我之前見過一個(gè)流程有 20 多個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)都要查數(shù)據(jù)庫,結(jié)果整個(gè)流程執(zhí)行下來要 5 秒多,用戶直接吐槽 “比蝸牛還慢”。

后來我們做了優(yōu)化:

  • 合并相似節(jié)點(diǎn):把 “查詢用戶信息” 和 “查詢用戶等級(jí)” 兩個(gè)節(jié)點(diǎn)合并成一個(gè),減少數(shù)據(jù)庫查詢次數(shù)
  • 異步執(zhí)行非關(guān)鍵節(jié)點(diǎn):把 “記錄日志”“發(fā)送通知” 這些不影響主流程的節(jié)點(diǎn)改成異步執(zhí)行
  • 緩存常用數(shù)據(jù):把用戶等級(jí)、商品價(jià)格這些高頻訪問的數(shù)據(jù)緩存起來,避免重復(fù)查詢

優(yōu)化后流程執(zhí)行時(shí)間降到了 1 秒以內(nèi),用戶體驗(yàn)明顯提升。所以設(shè)計(jì)流程的時(shí)候,一定要考慮 “每個(gè)節(jié)點(diǎn)的執(zhí)行時(shí)間”,別讓流程變成 “性能瓶頸”。

五、總結(jié):從 “if-else 地獄” 到 “流程編排天堂”,就差這一步

回顧一下這篇文章,咱從 if-else 的痛點(diǎn)說起,講了流程編排的核心思想,還演示了三種不同量級(jí)的實(shí)現(xiàn)方案:

  • 輕量級(jí):責(zé)任鏈 + 策略模式,適合簡(jiǎn)單流程,不用引入框架
  • 中量級(jí):Spring StateMachine,適合狀態(tài)流轉(zhuǎn)類業(yè)務(wù)
  • 重量級(jí):Flowable/Camunda,適合復(fù)雜流程和可視化需求

最后還提醒了大家要避開的坑,希望能幫你徹底告別 if-else 的噩夢(mèng)。

其實(shí)流程編排不只是一種技術(shù),更是一種 “分而治之” 的思維 —— 把復(fù)雜的問題拆成簡(jiǎn)單的小問題,再把小問題的解決方案按順序組合起來。這種思維不僅能用于代碼開發(fā),在日常工作中也很有用,比如你寫一篇技術(shù)文章,也可以拆成 “列大綱→寫初稿→改內(nèi)容→校對(duì)錯(cuò)” 這幾個(gè)步驟,一步步來,效率會(huì)高很多。

責(zé)任編輯:武曉燕 來源: 石杉的架構(gòu)筆記
相關(guān)推薦

2023-06-02 07:30:24

If-else結(jié)構(gòu)流程控制

2024-01-26 07:48:10

SpringKafka提升

2024-01-05 13:26:00

KafkaTopicSpring

2025-07-21 05:00:00

if-elseV1版本

2025-08-15 12:19:08

2021-01-11 08:03:30

阿里中臺(tái)項(xiàng)目

2020-06-04 09:18:52

CTOif-else代碼

2013-03-06 10:28:57

ifJava

2020-10-10 11:07:38

Java開發(fā)代碼

2021-07-28 14:20:13

正則PythonFlashText

2023-11-07 10:36:37

2022-07-11 08:16:55

策略模式if-else

2021-03-10 07:20:43

if-else靜態(tài)代碼

2021-04-13 06:39:13

代碼重構(gòu)code

2020-10-22 09:20:22

SQLNoSQL 數(shù)據(jù)庫

2024-07-05 15:59:29

代碼if復(fù)雜性

2025-01-09 11:24:59

線程池美團(tuán)動(dòng)態(tài)配置中心

2025-09-04 09:33:29

2023-11-09 08:01:41

Spring緩存注解

2025-09-04 09:13:22

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)