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

賦能轉轉回收:LiteFlow可視化編排方案設計

開發
LiteFlow是一款編排式的規則引擎框架,可以通過表達式的方式來編排組件或方法的執行流程,并且支持一些高級的流程編排。

1、引言

LiteFlow解決哪些場景的問題呢?通過下面的例子感受一下。

假設有三個組件(或方法)stepOne、stepTwo、stepThree,并且你想要按照順序打印"one"、"two"、"three",通常我們編寫代碼的方式可能是這樣的:

@Component
public class PrintService {
    /**
     * 執行業務代碼
     */
    private void doExecute() {
        stepOne();
        stepTwo();
        stepThree();
    }

    private void stepOne() {
        // 業務代碼
        System.out.println("one");
    }

    private void stepTwo() {
        // 業務代碼
        System.out.println("two");
    }

    private void stepThree() {
        // 業務代碼
        System.out.println("three");
    }
}

這樣寫最簡單粗暴,但是如果之后有調整打印順序的話,例如你想打印two、one、three,或者直接跳過two直接打印one、three,你一定需要修改代碼并且重新上線。

// 打印two、one、three
    public void doExecute() {
        stepTwo();
        stepOne();
        stepThree();
    }
    // 打印one、three
    public void doExecute() {
        stepOne();
        stepThree();
    }

對于需要動態調整執行流程的業務場景,顯然不適合將流程硬編碼在代碼中。

2、 LiteFlow簡介

LiteFlow是一款編排式的規則引擎框架,可以通過表達式的方式來編排組件或方法的執行流程,并且支持一些高級的流程編排。

上述案例如何通過更高級的方式來實現零代碼修改、無需重新上線即可編排流程了呢?我們基于LiteFlow做一些改造。

2.1 引入jar包

可以去官網根據需要選擇合適的版本,這里用的是最新版本

<dependency>
    <groupId>com.yomahub</groupId>
    <artifactId>liteflow-spring-boot-starter</artifactId>
    <version>2.12.0</version>
</dependency>

2.2 定義組件

將打印功能分別定義成一個個組件,繼承NodeComponent 這個抽象父類并實現其中的方法:

@Component
public class PrintOne extends NodeComponent {

    @Override
    public void process() throws Exception {
        // 業務代碼
        System.out.println("one");
    }
}
@Component
public class PrintTwo extends NodeComponent {

    @Override
    public void process() throws Exception {
        // 業務代碼
        System.out.println("two");
    }
}
@Component
public class PrintThree extends NodeComponent {

    @Override
    public void process() throws Exception {
        // 業務代碼
        System.out.println("three");
    }
}

2.3 執行流程

定義好組件之后,我們就開始編寫組件執行的流程表達式了,官方名稱叫EL表達式;上述案例可以這樣編寫表達式:

THEN(node("printOne"),node("printTwo"),node("pirntThree"));

并給這個流程起個名字(流程唯一標識):print_flow

根據流程名稱執行流程:

@Component
public class PrintService {

    @Autowired
    private FlowExecutor flowExecutor;

    /**
     * 執行業務代碼
     */
    public void doExecute() {
        // 開始執行流程
        LiteflowResponse response = flowExecutor.execute2Resp("print_flow");
        // 根據執行結果進行后續操作
        // ......
    }
}

一般我們會將流程放到數據庫中,如果想改變打印順序,只需要修改表達式即可,例如:打印two、one、three。

THEN(node("printTwo"),node("printOne"),node("pirntThree"));

打印two、three。

THEN(node("printTwo"),node("printThree"));

然后LiteFlow真正強大的地方遠不止如此,它不僅僅支持簡單的串行編排,還支持更加復雜的邏輯例如(并行編排)WHEN、(條件編排)IF、(選擇編排)SWITCH、(循環編排)FOR等。

2.4 官網

上述的簡單示例旨在為不熟悉LiteFlow框架的伙伴們提供一個初步的認知。要想真正基于LiteFlow將業務流程落地并運用到實際業務場景中,還需要通過官方文檔深入了解該框架的運作原理和核心機制。
https://liteflow.cc/

3、可視化編排(形態進階)

3.1 為什么要可視化

官網提供修改表達式的方式只有一個,那就是手寫!官網并沒有提供配套的可視化工具,手寫可能存在諸多問題和不便,例如:

  • 容易出錯:表達式少一個字母甚至一個逗號都不行!
  • 流程不可視:我們只能完全依賴大腦去構想這些流程,運營或產品團隊想要了解或討論流程,也只能依賴于其他畫圖工具來手動繪制和表達。
  • 節點不可配置:我們的運營會根據不同的場景對節點進行動態配置,沒有可視化界面,運營改動配置的需求則無從下手。

所以可視化對于編排流程來說意義重大,對于研發能更準確地理解和設計流程,還能讓運營能更便捷地監控和管理流程。

3.2 方案設計

網上有一些網友開源的項目,但基本都是個人維護,對于復雜流程的處理不是很好,質量也參差不齊,所以自己進行了調研和設計;支持普通節點、判斷節點、選擇節點、并行節點;循環節點目前業務不需要,有需要的可以自己拓展,掌握方案之后拓展節點類型非常簡單。完成可視化編排需要解決兩個問題:

  • 一款與用戶交互的前端畫布(推薦logicFlow,有自己熟悉的也行)
  • 將畫布數據轉化成EL表達式(手寫算法,基于DFS的遞歸)

這里重點講畫布數據轉化為EL的過程。

3.2.1 整體流程

創建流程

流程的核心在第5步,下面會重點講解。

圖片

回顯流程

解析EL成本很高,所以我選擇不解析表達式,直接將前端傳入的畫布json數據返回給前端進行回顯。

3.2.2 后端抽象語法樹設計

節點類型枚舉

public enum NodeEnum {
    // 普通節點,對應普通組件
    COMMON,
    // 并行節點,對應并行組件
    WHEN,
    // 判斷節點,對應判斷組件
    IF,
    // 選擇節點,對應選擇組件
    SWITCH,
    // 匯總節點(自定義)
    SUMMARY,
    // 開始節點(自定義)
    START,
    // 結束節點(自定義)
    END;
}

COMMON

普通節點,入度和出度都為1。

圖片

IF

判斷節點,包含一個true分支,一個false分支,入度為1,出度為2。圖片

SWITCH

根據SWITCH返回的tag,來決定執行后續哪個流程。入度為1,出度大于1。圖片

WHEN

官網沒有WHEN節點的概念,我這里自定義WHEN節點會避免很多問題。圖片

為什么要定義WHEN節點?

WHEN作為一個出度大于1的節點,和IF、SWITCH不同的是WHEN并沒有一個前置節點去驅動一個流程。

假設這樣一個流程,如果沒有WHEN節點的支持,展示到畫布上的效果很差。

THEN(
    IF(node("c"), 
        WHEN(
            node("a"),
            node("b"),
            node("d"),
            node("e")
        ).ignoreError(true)
    ),
    node("f")
)

圖片

SUMMARY

官網沒有這種節點,自定義節點,用于匯總所有分支節點,也就是WHEN、IF、SWITCH節點。入度大于1,出度為1。圖片

為什么要定義SUMMARY節點?

構建EL算法是基于遞歸實現的,參考的是深度優先遍歷算法(DFS),這種嵌套方式如果沒有一個結束標志會一直執行下去。

舉個例子:圖片

基于圖1生成EL表達式

THEN(
    node("c"),
    WHEN(
        THEN(node("b"),node("e")),
        THEN(node("d"),node("e"))
    )
)

基于圖2生成EL表達式

THEN(
    node("c"),
    WHEN(node("b"),node("d")),
    node("e")
)

可以看出來圖2的EL表達式才是我們想要的。

市面上有名的工作流引擎在畫布上處理匯總問題也是這樣設計的,比如在activiti中使用并行網關開啟會簽,也必須用并行網關在會簽結束時進行匯總,否則就會出現重復審批的問題。

START

開始節點,一個流程必須有一個開始節點,入度為0,出度為1。
END

結束節點,一個流程必須有一個結束節點,入度為1,出度為0。
上述節點類型的類定義

// 抽象父類
@Getter
public abstract class Node {

    // node的唯一id
    private final String id;

    // node名稱,對應LiteFlow的Bean名稱
    private final String name;

    // 入度
    private final List<Node> pre = Lists.newArrayList();

    // 節點類型
    private final NodeEnum nodeEnum;

    // 出度
    private final List<Node> next = Lists.newArrayList();

    protected Node(String id, String name, NodeEnum nodeEnum) {
        this.id = id;
        this.name = name;
        this.nodeEnum = nodeEnum;
    }

    public void addNextNode(Node node) {
        next.add(node);
    }

    public void addPreNode(Node preNode) {
        pre.add(preNode);
    }
}
// 普通節點
public class CommonNode extends Node {

    public CommonNode(@NonNull String id, @NonNull String name) {
        super(id, name, NodeEnum.COMMON);
    }
}
// 并行節點
public class WhenNode extends Node {

    public WhenNode(@NonNull String id, @NonNull String name) {
        super(id, name, NodeEnum.WHEN);
    }
}
// 判斷節點
@Getter
public class IfNode extends Node {

    private Node trueNode;

    private Node falseNode;

    public IfNode(@NonNull String id, @NonNull String name) {
        super(id, name, NodeEnum.IF);
    }

    public void setTrueNode(Node trueNode) {
        this.trueNode = trueNode;
        super.addNextNode(trueNode);
    }

    public void setFalseNode(Node falseNode) {
        this.falseNode = falseNode;
        super.addNextNode(falseNode);
    }
}
// 選擇節點
@Getter
public class SwitchNode extends Node {

    private final Map<Node, String> nodeTagMap = Maps.newHashMap();

    public SwitchNode(@NonNull String id, @NonNull String name) {
        super(id, name, NodeEnum.SWITCH);
    }

    public void putNodeTag(Node node, String tag) {
        nodeTagMap.put(node, tag);
        super.addNextNode(node);
    }
}
// 開始節點
public class StartNode extends Node {

    public StartNode(@NonNull String id, @NonNull String name) {
        super(id, name, NodeEnum.START);
    }
}
// 結束節點
public class EndNode extends Node {

    public EndNode(@NonNull String id, @NonNull String name) {
        super(id, name, NodeEnum.END);
    }
}
// 匯總節點
public class SummaryNode extends Node {

    public SummaryNode(@NonNull String id, @NonNull String name) {
        super(id, name, NodeEnum.SUMMARY);
    }
}

3.2.3 畫布JSON數據設計

畫布數據最終體現在JSON語法樹,數據結構如下:

{
    "nodeEntities": [
        {
            "id": "節點的唯一id,由前端生成。必填",
            "name": "節點名稱,對應LiteFlow的節點名稱,spring的beanName。必填",
            "label": "前端節點展示名稱,到時候給前端。必填",
            "nodeType": "節點的類型,有COMMON、IF、SWITCH、WHEN、START、END和SUMMARY。必填",
            "x": "x坐標。必填",
            "y": "y坐標。必填"
        }
    ],
    "nodeEdges": [
        {
            "source": "源節點。必填",
            "target": "目標節點。必填",
            "ifNodeFlag": "if類型節點的true和false,只有ifNode時必填,其他node隨意",
            "tag": "switch類型的下層節點的tag,主機有switchNode時必填,其他node隨意"
        }
    ]
}

用戶拖動畫布節點和節點之間連線的過程,其實就是維護節點數組和邊數組的過程。

3.2.4 畫布JSON數據合法校驗

下面是針對畫布json數據的一些簡單合法性校驗,可以自己根據需要拓展,實現很簡單,最后有具體實現代碼,需要的可以下載。

  • 流程必須有一個開始節點和一個結束節點
  • 校驗節點類型,只能是IF、WHEN、COMMON、SWITCH、START、END和SUMMARY
  • IF、WHEN、SWITCH節點的數量總和與SUMMARY類型節點數量總和校驗
  • 校驗節點和邊的source和target是否能對應上
  • 校驗SWITCH的出度邊是否有tag,且tag不能為空
  • 校驗IF節點有沒有ifNodeFlag的標識,并且總有一條true分支,總有一條false分支

3.2.5 畫布JSON數據轉化為抽象語法樹

舉個簡單的例子:圖片對應的JSON語法樹如下;避免篇幅過長,這里只列舉了部分屬性。

{
    "nodeEntities": [
        {
            "id": "a",
            "label": "a",
            "nodeType": "COMMON"
        },
        {
            "id": "e",
            "label": "e",
            "nodeType": "WHEN"
        },
        {
            "id": "b",
            "label": "b",
            "nodeType": "COMMON"
        },
        ......
    ],
    "nodeEdges": [
        {
            "source": "a",
            "target": "e",
        },
        {
            "source": "e",
            "target": "b",
        },
        {
            "source": "e",
            "target": "c",
        },
        ......
    ]
}

JSON轉化為抽象語法樹,實際就是創建節點對象,并維護節點的屬性,下面是偽代碼。

// 創建節點對象
        List<Node> nodes = Lists.newArrayList();
        for (NodeEntity nodeEntity : nodeEntities) {
            Node node = null;
            switch (nodeEntity.getNodeType()) {
                case NodeEnum.COMMON;
                    node = new CommonNode("節點的id", "節點的label");
                    break;
                case NodeEnum.WHEN;
                    node = new WhenNode("節點的id", "節點的label");
                    break;
                case NodeEnum.SUMMARY;
                    node = new SummaryNode("節點的id", "節點的label");
                    break;
                default:
                    throw new RuntimeException("未知的節點類型!");
            }
            nodes.add(node);
        }
        // 構建nodeId和node的map
        Map<String, Node> nodeIdAndNodeMap = nodes.stream()
        .collect(Collectors.toMap(Node::getId, Function.identity()));
        // 維護節點間關系
        for (NodeEdge nodeEdge : nodeEdges) {
            Node sourceNode = nodeIdAndNodeMap.get(nodeEdge.getSource());
            Node targetNode = nodeIdAndNodeMap.get(nodeEdge.getTarget());
            sourceNode.addNextNode(targetNode);
            targetNode.addPreNode(sourceNode);
            ......
        }

疑問:為什么要設計JSON和AST(抽象語法樹)兩種數據結構?

根據上述JSON數據可以發現,用戶編輯畫布時,前端只需要維護節點和邊兩個數組即可;而生成EL表達式的操作在后端,生成方法是利用遞歸實現的深度優先遍歷算法(DFS),顯然JSON是不滿足遞歸需求的,所以JSON轉換為AST。

總之設計JSON和AST就是為了方便前后端去各自維護數據。

3.2.6 抽象語法樹生成EL表達式

整個流程的核心就在這里,AST生成EL表達式

同樣用上面的例子來模擬生成EL表達式過程,該流程只涉及THEN和WHEN,我們約定把THEN和WHEN當成數組來處理,例如THEN(node("a"),node("b"))對應數組[node("a"),node("b")],同理WHEN。

  1. 流程必須以一個數組開始。圖片
[
    node("a")
]

  1. 遇見WHEN分支節點e,創建一個新數組,并加入上一層數組。

圖片

[
    node("a"),
    [
    ]
]

  1. 分支節點之后的每一個分支都要創建一個數組,并且加入到分支節點的數組中。圖片
[
    node("a"),
    [
        [
            node("b")
        ]
    ]
]

  1. 正常的串行,節點直接加入最內層數組。

圖片

[
    node("a"),
    [
        [
            node("b"),
            node("d")
        ]
    ]
]

  1. 遇見匯總節點,什么也不處理。圖片
[
    node("a"),
    [
        [
            node("b"),
            node("d")
        ]
    ]
]

  1. 繼續向下,將f節點加入WHEN節點所在的數組,到達遞歸的出口。

圖片

[
    node("a"),
    [
        [
            node("b"),
            node("d")
        ]
    ],
    node("f")
]

這可能有疑問,程序是如何定位到WHEN所在的數組在哪呢?

利用棧,遇到WHEN節點的時候會將WHEN節點所在的數組壓棧,等遇到匯總節點時將數組出棧,那么可以確定f節點應該加入出棧時的數組了。


  1. 因為是從e節點開始有分支流程的,以b節點開頭的分支已經執行完,回溯到另一條分支;同樣c節點屬于e的一條分支,分支節點之后的每一個分支都要創建一個數組,并且加入到分支節點的數組中。圖片
[
    node("a"),
    [
        [
            node("b"),
            node("d")
        ],
        [
            node("c")
        ]
    ],
    node("f")
]

  1. 到了匯總節點,因為遍歷以b節點開頭的分支時已經訪問了該匯總節點,這次不處理,到達遞歸的出口。
    圖片
[
    node("a"),
    [
        [
            node("b"),
            node("d")
        ],
        [
            node("c")
        ]
    ],
    node("f")
]

如何判斷匯總節點是否訪問過?

用Set,訪問過的匯總節點加入Set中,下次再訪問先判斷Set中有沒有該匯總節點,有就不往下執行,到達遞歸出口。

結束!


根據上面簡單示例,下面是用遞歸實現DFS的偽代碼;文末有全量源碼,感興趣的可以下載參考一下。

public static String ast2El(Node head) {
        if (head == null) {
            return null;
        }
        // 用于存放when節點List
        Deque<List> stack = new ArrayDeque<>();
        // 用于標記是否處理過summary節點了
        Set<String> doneSummary = Sets.newHashSet();
        List list = tree2El(head, new ArrayList(), stack, doneSummary);
        // 將list生成EL,你可以認為框架有對應的方法
        return toEL(list);
    }

    private static List tree2El(Node currentNode,
                                List currentThenList,
                                Deque<List> stack,
                                Set<String> doneSummary) {
        switch (currentNode.getNodeEnum()) {
            case COMMON:
                currentThenList.add(currentNode.getId());
                for (Node nextNode : currentNode.getNext()) {
                    tree2El(nextNode, currentThenList, stack, doneSummary);
                }
            case WHEN:
                stack.push(currentThenList);
                List whenELList = new ArrayList<>();
                currentThenList.add(whenELList);
                for (Node nextNode : currentNode.getNext()) {
                    List thenELList = new ArrayList<>();
                    whenELList.add(thenELList);
                    tree2El(nextNode, thenELList, stack, doneSummary);
                }
            case SUMMARY:
                if (!doneSummary.contains(currentNode.getId())) {
                    doneSummary.add(currentNode.getId());
                    // 這種節點只有0個或者1個nextNode
                    for (Node nextNode : currentNode.getNext()) {
                        tree2El(nextNode, stack.pop(), stack, doneSummary);
                    }
                }
            default:
                throw new RuntimeException("未知的節點類型!");
        }
        return currentThenList;
    }


3.2.7 校驗EL表達式的合法性

這是生成EL表達式的最后一步;框架有本身有支持校驗EL合法性的方法,在生成EL之后進行校驗。

// 校驗是否符合EL語法
Boolean isValid = LiteFlowChainELBuilder.validate(el);

進行完最后一步,EL表達式就可以入庫了。

3.3 推拉結合刷新流程

流程入庫之后并不是立即生效,進行以下操作后生效。

3.3.1 拉

框架會定期從數據庫(或通過配置指定的任何數據源)中同步最新流程,并將這些流程緩存在內存中;新流程同步和緩存的過程是平滑進行的,不會干擾或打斷現有流程的執行;該框架還允許用戶根據實際需求配置數據刷新的時間間隔(默認1分鐘),具體配置方法可參照官方文檔進行詳細了解。

3.3.2 推

如果我們希望改動的EL表達式立即生效而不是等待框架被動刷新,我們可以通過官方提供的api進行主動刷新:

flowExecutor.reloadRule();

需要注意的的是,官方提供的方法只是刷新單個實例節點的流程;如果是集群環境,我們需要借助消息隊列以達到通知整個集群的效果。

3.4 源碼

目前這套設計方案已在實際業務場景落地并使用;自己進行過很多復雜流程的驗證,基于這種規則能百分百保證生成EL表達式的正確性。圖片

自己的寫的demo,可以借鑒一下思路;里面有一個構造好的復雜流程案例,通過調接口的方式自己感受。

https://dl.zhuanstatic.com/fecommon/liteFlow-el.zip

4 效果收益&未來規劃

通過引入流程的可視化編排,結合LiteFlow框架的支持,顯著提升了流程設計的直觀性和開發效率,為項目帶來了更為順暢和高效的開發體驗。

4.1 效果收益:

  1. 開發人員只需要專注于核心的業務流程設計,而無需在語法規則上耗費過多精力。
  2. 通過直觀的可視化流程界面,產品和研發團隊之間的溝通變得更為高效,復雜的業務邏輯能夠清晰展現,避免了不必要的溝通。
  3. 運營能夠實時編輯流程節點,并快速了解節點的屬性配置;例如“黑名單校驗”節點中配置了哪些用戶,從而更加靈活地管理業務流程。

4.2 未來規劃

痛點

  1. 流程編排只是針對現有節點,對于新的業務節點,依然需要開發。
  2. 對外提供服務可能需要調用方提供較為詳盡的參數信息。

規劃

  1. 希望未來借助動態腳本,實現全新業務流程的快速搭建,無需進行任何開發工作。
  2. 引入數據字典的概念,將常用的參數整合為數據字典,例如只需要一個訂單號,便能根據數據字典獲取該流程想要的參數,從而降低調用方的開發成本。
責任編輯:龐桂玉 來源: 轉轉技術
相關推薦

2024-06-13 07:51:08

2024-06-06 08:18:42

回收業務

2023-07-12 08:33:34

引擎LiteFlow編排

2021-01-12 09:38:02

微服務服務組合編排

2021-03-25 07:30:24

代碼開發數據

2020-03-11 14:39:26

數據可視化地圖可視化地理信息

2020-08-21 16:08:18

NVIDIA

2025-09-17 18:49:55

2022-06-29 08:28:58

數據可視化數據可視化平臺

2017-10-14 13:54:26

數據可視化數據信息可視化

2022-08-26 09:15:58

Python可視化plotly

2009-04-21 14:26:41

可視化監控IT管理摩卡

2021-06-09 18:52:05

方案設計庫存數

2022-01-14 07:56:38

流布局設計拖拽

2021-01-09 09:48:10

可視化自然流布局 LowCode

2015-08-20 10:06:36

可視化

2021-07-12 17:23:47

零設計可視化引擎

2019-07-17 13:57:06

智慧城市數據可視化場景

2013-07-10 09:56:02

軟件定義網絡SDN
點贊
收藏

51CTO技術棧公眾號

黄色网址在线免费播放| 国产一卡二卡三卡| 日韩三级网址| 五月天激情综合| 欧美日韩一区二 | 亚洲国产一区二区在线播放| 国产在线精品一区二区三区》| 国产精品100| 欧美电影一区| 亚洲第一精品自拍| 色综合天天色综合| 欧美bbbxxxxx| 国产色产综合色产在线视频| 成人精品一区二区三区电影黑人| 亚洲va国产va欧美va观看| www.成人av.com| 成年人视频在线免费看| 久久激情电影| 亚洲国产精品va在线看黑人| 久久久久国产一区| 岛国片av在线| 亚洲欧洲成人自拍| 国产综合第一页| 在线免费看av的网站| 伊人影院久久| 久久精品久久久久久| 无码精品一区二区三区在线播放| 伊人久久大香| 色又黄又爽网站www久久| 黄黄视频在线观看| 国产精品一级伦理| 99久久99久久精品国产片果冻| 国产精品视频xxxx| 国产成人精品片| 亚洲欧美亚洲| 色哟哟入口国产精品| 女女调教被c哭捆绑喷水百合| 欧美国产日韩电影| 精品日韩中文字幕| 国产在线视频在线| 国产成人在线视频免费观看| 日本一区二区在线不卡| 国产综合av一区二区三区| 国产手机av在线| 日韩av一二三| 日韩美女在线看| 久久精品视频久久| 欧美激情五月| 精品久久久999| 久久久久亚洲AV成人无在| 日韩激情啪啪| 亚洲黄色有码视频| 伦理片一区二区| 伊人精品久久| 欧美xxxxxxxx| 日韩av成人网| 一区二区三区自拍视频| 欧美大片免费久久精品三p| 国产探花在线看| 日韩成人在线一区| 欧美日韩国产123区| 无限资源日本好片| 精品国产黄a∨片高清在线| 欧美亚洲国产一区二区三区va| 日本三级免费观看| 日韩成人影音| 欧美亚洲动漫精品| 亚洲黄色小视频在线观看| 欧美va在线观看| 欧美亚洲日本国产| 91网址在线观看精品| 伊人久久大香线蕉综合影院首页| 91精品国产综合久久香蕉麻豆 | 精品丝袜在线| 精品二区三区线观看| 男人日女人bb视频| 成人看片网站| 欧美日韩卡一卡二| 国产亚洲色婷婷久久| 一本色道69色精品综合久久| 亚洲国产精品久久久久秋霞不卡| 欧美深性狂猛ⅹxxx深喉 | 亚洲精品tv久久久久久久久| 91看片在线观看| 中文字幕在线免费不卡| 日韩一级免费看| 成人女同在线观看| 懂色aⅴ精品一区二区三区蜜月| 97在线免费公开视频| 国产精品亚洲一区二区三区在线观看| 在线精品视频免费观看| 欧美成人乱码一二三四区免费| 麻豆一二三区精品蜜桃| 精品国产91久久久久久久妲己| 泷泽萝拉在线播放| 91日韩在线| 欧美精品videos| 国产成人精品亚洲| 国产99久久久久久免费看农村| 久久艳妇乳肉豪妇荡乳av| av在线电影播放| 亚洲黄色免费网站| 成年人在线看片| 日韩电影免费观看高清完整版在线观看| 欧美一级午夜免费电影| 一本色道综合久久欧美日韩精品| 欧美好骚综合网| 久久免费视频在线观看| 中文在线最新版天堂| 成人小视频在线| 亚洲福利av在线| 国产偷倩在线播放| 欧美亚洲综合色| 日韩无码精品一区二区| 久久视频在线| 欧美又大粗又爽又黄大片视频| 国产三级伦理片| 久久久久久久久97黄色工厂| 黄色成人在线免费观看| 成人免费在线观看视频| 亚洲激情在线观看| 日韩一区二区三区四区在线| 人人狠狠综合久久亚洲| 精品91免费| 日日夜夜天天综合入口| 欧美色中文字幕| 鲁大师私人影院在线观看| 欧美国产综合| 国产精品一二三在线| 欧洲伦理片一区 二区 三区| 亚洲夂夂婷婷色拍ww47| 五月天激情播播| 精品视频亚洲| 91po在线观看91精品国产性色| 国产口爆吞精一区二区| 国产精品入口麻豆九色| 国产男女无遮挡| 欧美a一欧美| 欧美黑人性猛交| 国产又爽又黄免费软件| 中文欧美字幕免费| 午夜精品久久久内射近拍高清| 秋霞在线一区| 97国产在线视频| 东京干手机福利视频| 亚洲嫩草精品久久| 美女在线视频一区二区| 精品国精品国产自在久国产应用| 日韩**中文字幕毛片| 五月婷婷久久久| 亚洲成av人片观看| 国产大尺度视频| 合欧美一区二区三区| 亚洲一区二区中文字幕| 超碰在线观看免费| 日韩一区二区三区三四区视频在线观看| 国产又黄又粗又猛又爽的| 日韩精品午夜视频| 日韩欧美第二区在线观看| 丝袜美腿诱惑一区二区三区| 亚洲欧洲成视频免费观看| 一级aaa毛片| 成人av午夜电影| av高清在线免费观看| 日韩高清影视在线观看| 日本成人精品在线| 成人77777| 欧美日韩大陆一区二区| 国产稀缺精品盗摄盗拍| 国产麻豆视频一区二区| 亚洲精品少妇一区二区| 4438全国亚洲精品观看视频| 国自在线精品视频| 日韩精品系列| 欧美性猛交一区二区三区精品| 美国美女黄色片| 精品一区二区三区av| 日本xxx免费| 麻豆精品av| 国产精品99久久久久久久久久久久| 成人动漫在线播放| 日韩欧美高清一区| 国产一级片毛片| 中文字幕欧美国产| 色婷婷狠狠18禁久久| 国产美女一区| 中文字幕黄色大片| 日韩mv欧美mv国产网站| 国产精品视频免费观看www| 国产美女在线观看| 亚洲精品电影在线观看| 天天干天天插天天射| 亚洲精品日日夜夜| 免费观看av网站| 九九在线精品视频| 男人添女人下面高潮视频| 成人a'v在线播放| 国产 高清 精品 在线 a| 欧洲一级精品| 欧美激情亚洲一区| 97视频精彩视频在线观看| 精品精品欲导航| а中文在线天堂| 一区二区三区日韩在线观看| 一道本在线观看| 国产精品一区三区| 啊啊啊国产视频| 91久久综合| 在线视频亚洲自拍| 亚州av日韩av| 国产91一区二区三区| 日韩制服诱惑| 91av在线免费观看视频| 羞羞视频在线免费国产| 夜夜嗨av色一区二区不卡| 亚洲大尺度视频| 欧美影院精品一区| 日韩精品在线免费视频| 一区二区三区av电影 | 精品久久美女| 国产精品theporn88| 欧美成人一二区| 欧美在线播放视频| 福利在线导航136| 久久伊人色综合| 爱久久·www| 亚洲久久久久久久久久久| 亚洲黄色小说网| 91精品国产免费| 亚洲中文字幕一区二区| 欧美日韩亚洲一区二| 国产真实的和子乱拍在线观看| 亚洲欧美色一区| 日本不卡一区视频| 欧美国产一区二区| 中文字幕免费看| 99久久精品国产导航| 中文字幕乱妇无码av在线| 激情图区综合网| 亚洲欧美自拍另类日韩| 视频一区欧美精品| 大肉大捧一进一出好爽视频| 99国产精品| av免费观看国产| 亚洲激情国产| 青青草国产免费| 一区二区亚洲| 国产精品久久久久久久乖乖| 欧美日韩1080p| 国产激情在线看| 欧美视频久久| 久久精品xxx| 国产日韩亚洲| av天堂永久资源网| 日韩综合在线视频| 人妻有码中文字幕| 久久午夜精品一区二区| 欧美伦理片在线看| 美女精品一区二区| 亚洲18在线看污www麻豆| 国产自产v一区二区三区c| 国产农村妇女精品久久| 国产一区二区免费视频| 久久综合桃花网| 成人午夜视频免费看| 一级特黄a大片免费| 久久众筹精品私拍模特| 手机免费看av| 国产精品福利av| 青青草偷拍视频| 欧美日韩国产丝袜美女| 自拍偷拍校园春色| 欧美日韩免费观看一区三区| 国产精品一区二区黑人巨大| 日韩欧美一区二区三区在线| 国产成人三级在线观看视频| 亚洲欧美一区二区三区情侣bbw| 第三区美女视频在线| 久久影院中文字幕| 91吃瓜在线观看| 国产精品 欧美在线| 成人综合日日夜夜| 狠狠色噜噜狠狠色综合久| 最新国产精品视频| 日本黄色播放器| 亚洲国产日本| 日本人视频jizz页码69| 国产精品亚洲午夜一区二区三区 | 国产精品国产| 欧美自拍资源在线| 久久久久国产精品| 欧美视频在线播放一区| 另类小说欧美激情| 亚洲av无码一区东京热久久| 国产免费观看久久| 精品无码一区二区三区电影桃花| 在线视频一区二区免费| 草草视频在线播放| 亚洲欧洲高清在线| 美足av综合网| 国产精品一久久香蕉国产线看观看| 97超碰成人| 亚洲一区三区视频在线观看| 亚洲少妇在线| 亚洲国产日韩在线一区| 国产调教视频一区| 久久午夜鲁丝片午夜精品| 欧美日韩一区久久| 亚洲av成人无码久久精品老人| 日韩中文字幕网| 不卡一二三区| 国产精品久久久一区二区三区| 国产精品久久久久久久| 成人久久久久久久久| 国产成人久久精品77777最新版本| 蜜桃久久精品成人无码av| 亚洲成在人线在线播放| 97在线播放免费观看| 尤物九九久久国产精品的特点| 成入视频在线观看| 亚洲淫片在线视频| 欧美视频网址| 精品99在线视频| 成人av午夜电影| 欧美日韩一级在线观看| 欧美另类z0zxhd电影| 国产三级视频在线看| 欧美一级大片在线观看| 九九热hot精品视频在线播放| 国产女人18毛片| 精品一区二区成人精品| 波多野结衣片子| 欧美视频裸体精品| 污污视频在线免费看| 欧美激情在线观看| 91国内精品| 久久久无码中文字幕久...| 精品综合免费视频观看| 成年人视频软件| 欧美三级韩国三级日本三斤| 欧美日韩国产中文字幕在线| 91av成人在线| 日韩精选在线| 内射国产内射夫妻免费频道| av亚洲精华国产精华| 五月天婷婷丁香| 亚洲国产精品中文| 欧美aaaaa性bbbbb小妇| 久久国产精品免费一区| 国产精品日韩久久久| 久久国产精品影院| 欧美性猛xxx| 国产在线中文字幕| 国产91久久婷婷一区二区| 国产免费久久| 国产免费又粗又猛又爽| 国产精品高潮呻吟久久| 97超碰人人草| 毛片精品免费在线观看| 在线精品国产亚洲| 欧美午夜小视频| 91色porny在线视频| 中文字幕在线日本| 综合网日日天干夜夜久久| 亚洲精品一区二区在线播放∴| 麻豆md0077饥渴少妇| 国产成人亚洲综合a∨猫咪| 国产一级片免费| 亚洲欧美日韩中文在线| 久久精品嫩草影院| 男人j进女人j| 99热在这里有精品免费| 欧美性猛交bbbbb精品| 日韩在线观看网站| 91精品短视频| 好男人www社区| 亚洲蜜臀av乱码久久精品蜜桃| 殴美一级特黄aaaaaa| 日本aⅴ大伊香蕉精品视频| 日韩在线观看一区| 亚洲av午夜精品一区二区三区| 欧美午夜无遮挡| 久久久久久久久免费视频| 成人女人免费毛片| 久久一区亚洲| 免费一级a毛片夜夜看| 亚洲精品午夜精品| 96sao精品免费视频观看| 一区二区传媒有限公司| 国产精品天干天干在观线| 国内老熟妇对白xxxxhd| 555www成人网| 中文在线播放一区二区| 亚洲人成人无码网www国产| 91麻豆精品国产91久久久| 日本不卡1234视频| 男女啪啪的视频| 久久人人爽爽爽人久久久| av中文字幕免费| 国产成一区二区|