精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

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

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

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

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

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

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

首先聲明:if-else 本身沒問題,就像菜刀能切菜也能傷人,問題出在 “用錯(cuò)地方” 和 “過度使用”。我見過很多項(xiàng)目里的 if-else,都是從 “一行簡單判斷” 慢慢長成 “千行嵌套怪物” 的,這個(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—— 這哪是改代碼,這是拆炸彈啊!

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

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

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

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

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

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

二、啥是流程編排?說白了就是 “給代碼找個(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ú)修改、測試、復(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. 第四步:測試流程執(zhí)行效果

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

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 登場了 —— 它是 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. 第五步:測試狀態(tài)流轉(zhuǎn)

寫個(gè)測試類,模擬訂單狀態(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ù)庫,方便測試。

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. 第四步:寫接口測試流程

創(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. 第五步:測試流程完整運(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ì)用大炮打蚊子一樣,流程編排也有它的適用場景,用錯(cuò)了反而會(huì)增加復(fù)雜度。我總結(jié)了幾個(gè)項(xiàng)目里踩過的坑,大家一定要注意:

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

有些兄弟剛學(xué)會(huì)流程編排,就不管什么業(yè)務(wù)都想用。比如一個(gè)簡單的 “根據(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ù)簡單用 if-else,業(yè)務(wù)復(fù)雜用流程編排。判斷標(biāo)準(zhǔ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("庫存扣減失敗(已重試3次),觸發(fā)人工處理:" + e.getMessage());
            sendAlertToStaff(order); // 發(fā)送告警給運(yùn)營人員
            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á)式只用來做簡單的條件判斷(比如#{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é)任鏈 + 策略模式,適合簡單流程,不用引入框架
  • 中量級(jí):Spring StateMachine,適合狀態(tài)流轉(zhuǎn)類業(yè)務(wù)
  • 重量級(jí):Flowable/Camunda,適合復(fù)雜流程和可視化需求

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

其實(shí)流程編排不只是一種技術(shù),更是一種 “分而治之” 的思維 —— 把復(fù)雜的問題拆成簡單的小問題,再把小問題的解決方案按順序組合起來。這種思維不僅能用于代碼開發(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-05 13:26:00

KafkaTopicSpring

2024-01-26 07:48:10

SpringKafka提升

2025-07-21 05:00:00

if-elseV1版本

2025-08-15 12:19:08

2020-06-04 09:18:52

CTOif-else代碼

2021-01-11 08:03:30

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

2013-03-06 10:28:57

ifJava

2020-10-10 11:07:38

Java開發(fā)代碼

2021-04-13 06:39:13

代碼重構(gòu)code

2020-10-22 09:20:22

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

2023-11-07 10:36:37

2021-03-10 07:20:43

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

2022-07-11 08:16:55

策略模式if-else

2024-07-05 15:59:29

代碼if復(fù)雜性

2021-07-28 14:20:13

正則PythonFlashText

2020-12-21 07:36:15

緩存數(shù)據(jù)庫緩存層

2020-05-13 14:15:25

if-else代碼前端

2020-12-15 09:31:58

CTOif-else代碼

2021-11-04 08:53:00

if-else代碼Java
點(diǎn)贊
收藏

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

porn亚洲| 国产孕妇孕交大片孕| 日本午夜精品| 日本久久精品电影| 一区二区三区av| 精品国产av 无码一区二区三区| 亚洲视频免费| 亚洲人成电影网站色| 中文字幕 欧美日韩| jizz一区二区三区| 中文字幕精品一区二区精品绿巨人| 成人欧美在线视频| 国产一级精品视频| 无码一区二区三区视频| 亚洲精品mp4| 激情文学亚洲色图| 悠悠资源网亚洲青| 最新欧美精品一区二区三区| 亚洲高清久久网| 久久婷婷五月综合色国产香蕉| 免费成人黄色| 波多野结衣亚洲一区| 国产色综合天天综合网| 好吊操这里只有精品| 天天揉久久久久亚洲精品| 日韩精品免费在线| 精产国品一区二区三区| 成人精品电影在线| 亚洲五码中文字幕| 国产卡一卡二在线| 成黄免费在线| www亚洲一区| 99re国产视频| 国产精品一级视频| 日韩av网站在线观看| 国产69精品久久久| 欧美卡一卡二卡三| 国产精品久久久久久久久久10秀| 亚洲人成啪啪网站| yy6080午夜| 日韩中文字幕在线一区| 欧美精品日韩一区| 男女啪啪网站视频| 婷婷午夜社区一区| 欧美视频免费在线| 精品少妇一区二区三区在线| 福利在线导航136| 亚洲伦在线观看| 中国黄色录像片| 麻豆网站在线免费观看| 91麻豆高清视频| 久久精品二区| 图片区 小说区 区 亚洲五月| 成人午夜电影久久影院| 高清视频在线观看一区| 精品久久国产视频| 国产一区二区电影| 亚洲影院色在线观看免费| 91亚洲国产成人久久精品麻豆| 亚洲专区免费| 清纯唯美亚洲激情| 人人草在线观看| 久久不射网站| 国产成人拍精品视频午夜网站| 一级成人黄色片| 性一交一乱一区二区洋洋av| 日本电影亚洲天堂| 男人天堂视频网| 美女视频免费一区| 国产免费一区视频观看免费 | 福利视频999| 外国成人毛片| 日韩一区二区三区精品视频| 91精品国产高清91久久久久久| 久久五月精品中文字幕| 亚洲伦在线观看| www.夜夜爱| wwww亚洲| 岛国精品视频在线播放| 国产精品视频一区二区三区四区五区| 午夜影院一区| 91久久精品网| 日韩av在线中文| 玖玖精品一区| 欧美精品一区二区三区蜜桃视频| 日本三级日本三级日本三级极| 欧美在线关看| 亚洲电影在线免费观看| 成人动态视频| 中日韩av在线| 亚洲妇女成熟| 色偷偷88欧美精品久久久| 99视频在线免费| 美女久久久久久| 日韩欧美国产精品一区| 先锋资源av在线| 精品美女视频| 色综合久久悠悠| 亚洲乱码国产乱码精品| 日韩国产精品91| 91亚洲精品在线观看| 成人免费一级视频| 久久新电视剧免费观看| 亚洲一区三区| freexxx性亚洲精品| 欧美在线观看一区二区| 国产精品91av| 成人91在线| 97国产精品视频| 一区二区不卡视频在线观看| 99国产精品久久久久久久久久 | 日韩日韩日韩日韩日韩| 本网站久久精品| 亚洲国产成人精品久久久国产成人一区 | 国产探花视频在线| 亚洲国产午夜| 亚洲国产欧美一区二区三区久久| 无码人妻精品一区二区中文| 欧美视频二区| 国产精品视频一区国模私拍| 黄色av网站免费在线观看| 国产精品女同一区二区三区| 国产91xxx| 久久三级中文| 国产一区二区三区在线观看视频| 国产亚洲欧美久久久久| 免费人成在线不卡| 精品久久久久亚洲| 97caopron在线视频| 91官网在线观看| 你懂的在线观看网站| 亚洲视频电影在线| 国产精品久久久久久久久影视| 人妻无码一区二区三区久久99| 国产精品免费av| 91看片就是不一样| 久久夜色电影| 欧美激情极品视频| 国产三级漂亮女教师| 国产亚洲精品资源在线26u| 成人性生活视频免费看| 亚洲精品不卡在线观看 | av文字幕在线观看| 在线视频中文字幕一区二区| 亚洲黄色免费在线观看| 中文无码久久精品| 国产日本欧美一区| 97最新国自产拍视频在线完整在线看| 狠狠做深爱婷婷久久综合一区| 国产高清成人久久| 欧美午夜一区二区福利视频| 91九色在线视频| 在线观看免费黄色| 色欲综合视频天天天| 大又大又粗又硬又爽少妇毛片 | 国产精品视频免费| 九色porny91| 日本道不卡免费一区| 国产原创欧美精品| 蜜桃视频网站在线观看| 91精品国产综合久久香蕉的特点| 99热这里只有精品4| 精品综合免费视频观看| 熟妇熟女乱妇乱女网站| 视频亚洲一区二区| 欧美国产高跟鞋裸体秀xxxhd| 免费国产黄色片| 精品国产乱码久久久久久天美 | 亚洲 中文字幕 日韩 无码| 最新亚洲精品| 国产伦精品一区二区三区精品视频 | 巨胸喷奶水www久久久| 色偷偷偷亚洲综合网另类| 国产视频第一页| 亚洲一二三级电影| 不卡一区二区在线观看| 秋霞影院一区二区| 91社在线播放| 另类春色校园亚洲| 日本精品在线视频| 毛片av在线| 亚洲国产成人av在线| 国产九色91回来了| 亚洲欧美激情小说另类| 国产精品果冻传媒| 午夜在线一区二区| 这里只有精品66| 999国产精品一区| 青青久久aⅴ北条麻妃| 欧美成人xxx| 日韩精品久久久久久福利| 中文字幕第三页| 亚洲一区二区免费视频| 黄色片网站免费| 国产 欧美在线| 日本熟妇人妻xxxxx| 亚洲成人二区| 欧美日韩在线播放一区二区| 亚洲精品成a人ⅴ香蕉片| 国语自产精品视频在线看一大j8 | 7m精品国产导航在线| 国产精品久久久久久久久久东京| av网址在线免费观看| 亚洲欧美国产精品va在线观看| 国产精品久久无码一三区| 精品高清美女精品国产区| 国产小视频你懂的| 99re成人精品视频| 爱豆国产剧免费观看大全剧苏畅| 一本色道88久久加勒比精品| a级网站在线观看| 免费精品国产| 国产欧美一区二区视频 | 91免费看网站| 成人性生交大片免费看午夜| 精品毛片乱码1区2区3区| 亚洲午夜在线播放| 精品久久久久久亚洲国产300| 国产高清视频免费在线观看| 五月婷婷视频在线| 91香蕉视频污在线| 久久久国产精品久久久| 美女www一区二区| 99久久久无码国产精品6| 亚洲调教视频在线观看| 伊人久久婷婷色综合98网| 精品福利久久久| 九色综合日本| 国产精品流白浆在线观看| 成人久久久久爱| 成人在线视频播放| 欧美在线亚洲一区| 多野结衣av一区| 久久6免费高清热精品| 久久精品视频观看| 色婷婷**av毛片一区| 福利在线午夜| 亚洲人在线视频| 日本福利片高清在线观看| 亚洲精品一区二区三区香蕉 | 免费污视频在线观看| 草民午夜欧美限制a级福利片| yourporn在线观看视频| 亚洲欧美一区二区三区情侣bbw| 少妇又色又爽又黄的视频| 日韩视频在线永久播放| 国产毛片久久久久| 91精品国产综合久久久蜜臀粉嫩| 在线免费观看一区二区| 欧美三日本三级三级在线播放| 91视频久久久| 欧洲生活片亚洲生活在线观看| 91久久国产综合久久91| 色88888久久久久久影院野外| youjizz在线视频| 91福利国产精品| 看黄色一级大片| 欧美在线一二三| 国产九色91回来了| 欧美裸体bbwbbwbbw| 91久久久久国产一区二区| 91精选在线观看| 亚洲av无码片一区二区三区| 精品人在线二区三区| 四虎永久在线观看| 国产丝袜视频一区| 九色视频网站在线观看| 永久免费毛片在线播放不卡| 欧美成人hd| 欧美极品少妇与黑人| 92久久精品| 欧美一级视频一区二区| 电影久久久久久| 亚洲aⅴ日韩av电影在线观看 | 国产在线一区二区三区欧美| 欧美挤奶吃奶水xxxxx| 欧美成人dvd在线视频| 欧美系列电影免费观看| 性做爰过程免费播放| 亚洲狠狠婷婷| 欧美激情精品久久久久久小说| 麻豆一区二区三| 永久免费未满蜜桃| 久久精品一区蜜桃臀影院| 看黄色录像一级片| 夜夜精品浪潮av一区二区三区| 五月天综合激情| 欧美三区在线观看| 国产草草影院ccyycom| 亚洲精品成人久久电影| 3d成人动漫在线| 欧美精品xxx| 成人看片在线观看| caoporn国产精品免费公开| 网友自拍区视频精品| 日韩三级在线播放| 国产精品豆花视频| 亚洲 欧美 日韩系列| 成人在线一区二区三区| 少妇久久久久久久久久| 亚洲欧美激情小说另类| 免费av中文字幕| 欧美xxx久久| 午夜毛片在线| 午夜精品一区二区三区在线| 欧美美女被草| 蜜桃麻豆www久久国产精品| 欧美理论在线| 簧片在线免费看| 99国产精品久久| 天天干中文字幕| 欧美在线高清视频| 午夜视频免费在线| 不用播放器成人网| 国产美女久久| 日本不卡一二三区| 亚洲精品美女91| 日本中文字幕在线不卡| 亚洲国产精华液网站w| 久久久久久久极品| 日韩一区二区免费在线电影| 91啦中文在线| 日韩美女主播视频| 日韩美脚连裤袜丝袜在线| 青青草视频国产| 极品尤物av久久免费看| gv天堂gv无码男同在线观看| 欧美三级欧美成人高清www| www.好吊色| 久久av.com| 四虎国产精品永久在线国在线| 日本一区二区精品| 国产日韩欧美在线播放不卡| 亚洲麻豆一区二区三区| 亚洲精品国产a久久久久久| 亚洲视频久久久| 色噜噜久久综合伊人一本| 国产91欧美| 亚洲不卡一卡2卡三卡4卡5卡精品| 亚洲精品女人| 欧美无人区码suv| 亚洲第一在线综合网站| 欧美视频在线观看一区二区三区| 欧美另类高清videos| 国产一区二区三区免费观看在线| 亚洲美女自拍偷拍| 国内精品自线一区二区三区视频| 岛国片在线免费观看| 欧美日韩亚洲综合在线| 福利小视频在线观看| 国产精品久久999| 成人无号精品一区二区三区| 日日噜噜夜夜狠狠| 国产精品狼人久久影院观看方式| 一区二区精品视频在线观看| 日韩视频精品在线| 国产午夜亚洲精品一级在线| 中文字幕第50页| 成人毛片在线观看| 五月婷婷开心网| 亚洲无线码在线一区观看| 在线成人视屏| 亚洲欧洲精品一区| 精品一区二区三区免费视频| jizz亚洲少妇| 亚洲国产黄色片| 吉吉日韩欧美| 亚洲激情一区二区| 国产在线播放一区| 久久无码精品丰满人妻| 日韩经典第一页| 福利一区二区免费视频| eeuss中文| 成人app下载| 久久久精品毛片| 久久人人爽人人爽爽久久 | 欧美一区二区成人| 成年人黄色大片在线| 日韩av电影免费播放| 激情五月激情综合网| 日本亚洲欧美在线| 一本久久综合亚洲鲁鲁| 日韩欧美高清一区二区三区| 秋霞无码一区二区| 欧美高清在线视频| www.99视频| 日韩av片电影专区| 亚洲成av人片乱码色午夜| 亚洲观看黄色网| 欧美日韩精品是欧美日韩精品| 美足av综合网| 日韩av电影在线观看| 国产精品一级在线| 精品人妻一区二区色欲产成人| 成年无码av片在线| 米奇777超碰欧美日韩亚洲| 亚洲免费成人在线视频| 欧美日韩国产一区二区三区| 国产一区久久精品| 麻豆av一区二区三区|