整理大脑android开发杂识设计模式

状态机思维

2018-06-15  本文已影响227人  独钓寒江雪_520

目录


一. 背景

二. 概念

三. 状态机在软件领域的应用

四. Spring State Machine 介绍

五. 状态机是一种思维方式


一 背景

有限状态机FSM(Finite State Machine),相信有些读者听说过,或者使用过。但是了解的人似乎并不多。

在硬件领域,状态机是由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。

状态机的概念其实已经很老了,有限自动机的描述可以追溯到1943年,当时 Warren McCulloch 和 Walter Pitts 先生写了一篇关于它的论文。后来,George H.Mealy 在1955年提出了一个状态机概念,称为Mealy机。一年后的1956年,Edward F.Moore 提出了另一篇被称为Moore机的论文。后来这个概念被广泛应用于语言学、计算机科学、生物学、数学和逻辑学,甚至于哲学等各种领域。

在计算机科学中,有限状态机被广泛用于应用行为建模、硬件电路系统设计、软件工程,编译器、网络协议、和计算与语言的研究。

今天我们来聊聊状态机思维,以及它在计算机软件开发领域中的应用。

二 概念

2.1 状态机模型的概念

有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

有限状态机.png

2.2 组成要素

2.3 三个特征

2.4 执行逻辑

状态机执行逻辑

2.5 分类

2.5.1 识别器(接受器),也叫序列检测器。输入字符符号,产生一个二元输出,“是”或“否”,来回答输入是否被机器接受。

这个应用在语言学中,如果语言中的所有字词组都能为机器识别并接受,那么我们称这门语言是正则语言cf. Kleene的定理)。

再如下图识别地址的状态机:

地址识别器

2.5.2 变换器

摩尔型有限状态机(Moore机),输出只依赖于当前状态。即:


次态 = f(现态,输入),输出 = f(现态)

Moore机

米利型有限状态机(Mealy机),输出依赖于当前状态和输入。即:


次态 = f(现态,输入),输出 = f(现态,输入)

Mealy机

2.6 表示法

2.6.1 状态图

学生状态机图

2.6.2 活动图

喝饮料活动图

2.6.3 状态转移表

状态转移表.png

三 状态机在软件领域的应用

3.1 应用场景

例如:[a|b]*abb

正则表达式的NFA 词法分析的基本步骤 The TCP Finite State Machine (FSM) 角色自动关机状态图

var menu = {

    // 当前状态

    currentState: 'hide',

    // 绑定事件

    initialize: function() {

      var self = this;

      self.on("hover", self.transition);

    },

    // 状态转换

    transition: function(event){

      switch(this.currentState) {

        case "hide":

          this.currentState = 'show';

          doSomething();

          break;

        case "show":

          this.currentState = 'hide';

          doSomething();

          break;

        default:

          console.log('Invalid State!');

          break;

      }

    }

  }; 


// State.js
import React, { Component, PropTypes } from 'react';

/**
 * 使用es6语法 定义一个State组件
 */
export default class State extends Component {

  constructor(props) {
    super(props);
    this.state = { //初始化state
      countnum:0,
    };
  }

  /**
   * 点击事件方法 countnum+1
   */
  _handlerEvent(){
    this.setState({
      countnum:this.state.countnum+1,
    })
  }
  render() {
    return (<div>
      {this._renderView()}
    </div>);
  }
  /**
   * 渲染一个button组件
   */
  _renderView(){
    return(
      <div>
        <button onClick={this._handlerEvent.bind(this)}>
            点击{this.state.countnum}次
        </button>
      </div>
    );
  }
}

例如1:购入流程

购入流程状态图

小结:状态机在软件行业使用广泛,它们都有一个共通的特点,将所有的状态、事件、动作都抽离出来,对复杂的状态迁移逻辑统一管理。状态机让复杂的问题变得直观、简单、易懂、解耦、易管理。

3.2 编码中如何运用状态机

引例:空调工作机制简化后的模型,如何编码实现。

  1. 假设遥控器只有两个按钮,power电源键和cool制冷键。

  2. 空调的运行呈现3个状态,停止/Off、仅送风/FanOnly、制冷/Cool。

  3. 起始状态为Off 。

空调工作状态图.png

package com.mhc.sample;

import static com.mhc.sample.Aircon.Event.*;
import static com.mhc.sample.Aircon.State.*;

/**
 * 空调
 *
 * @author xiaolong
 * @date 18/6/11 下午5:54
 */
public class Aircon {
    /**
     * 空调当前状态
     */
    private State currentState = OFF;

    public void dispather(Event event) {
        if (currentState == OFF) {
            if(event == CLICK_POWER){
                setCurrentState(FAN_ONLY);
                doStartFan();
            }
        } else if (currentState == FAN_ONLY) {
            if(event == CLICK_POWER){
                setCurrentState(OFF);
                doStopFan();
            } else if (event == CLICK_COOL) {
                setCurrentState(COOL);
                doStartCool();
            }
        } else if(currentState == COOL){
            if(event == CLICK_POWER){
                setCurrentState(OFF);
                doStopCool();
            } else if (event == CLICK_COOL) {
                setCurrentState(FAN_ONLY);
                doStartFan();
            }
        }
    }

    private void doStartFan(){
        System.out.println("start Fan");
    }
    private void doStopFan(){
        System.out.println("stop Fan");
    }
    private void doStartCool(){
        System.out.println("start Cool");
    }
    private void doStopCool(){
        System.out.println("stop Cool");
    }

    private void setCurrentState(State currentState) {
        this.currentState = currentState;
    }

    /**
     * 空调状态枚举
     */
    public enum State {
        //关闭中状态
        OFF,
        //送风中状态
        FAN_ONLY,
        //制冷中状态
        COOL
    }

    /**
     * 空调事件枚举
     */
    public enum Event {
        //点击电源键
        CLICK_POWER,
        //点击制冷键
        CLICK_COOL
    }
}



public void dispather(Event event) {
    switch (currentState) {
        case OFF:
            switch (event) {
                case CLICK_POWER:
                    setCurrentState(FAN_ONLY);
                    doStartFan();
                    break;
            }
            break;
        case FAN_ONLY:
            switch (event) {
                case CLICK_POWER:
                    setCurrentState(OFF);
                    doStopFan();
                    break;
                case CLICK_COOL:
                    setCurrentState(COOL);
                    doStartCool();
                    break;
            }
            break;
        case COOL:
            switch (event) {
                case CLICK_POWER:
                    setCurrentState(OFF);
                    doStopCool();
                    break;
                case CLICK_COOL:
                    setCurrentState(FAN_ONLY);
                    doStartFan();
                    break;
            }
            break;
    }
}

缺点:

a. 当状态很多的时候,维护起来非常麻烦,容易出错。

b. 不容易定位错误,对于状态的理解也不清晰。

c. 这段代码没有实现有限状态机和具体事件动作的隔离。


package com.mhc.sample;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static com.mhc.sample.AirconTable.Event.*;
import static com.mhc.sample.AirconTable.State.*;

/**
 * 空调 - 状态转移表模式
 *
 * @author xiaolong
 * @date 18/6/11 下午5:54
 */
public class AirconTable {
    /**
     * 状态转移表
     */
    private List<Transfor> transforTable = new ArrayList<Transfor>() {
        private static final long serialVersionUID = 2679742264102211454L;
        {
            add(Transfor.of( OFF,      CLICK_POWER,  FAN_ONLY, () -> doStartFan() ));
            add(Transfor.of( FAN_ONLY, CLICK_POWER,  OFF,      () -> doStopFan()  ));
            add(Transfor.of( FAN_ONLY, CLICK_COOL,   COOL,     () -> doStartCool()));
            add(Transfor.of( COOL,     CLICK_POWER,  OFF,      () -> doStopCool() ));
            add(Transfor.of( COOL,     CLICK_COOL,   FAN_ONLY, () -> doStartFan() ));
        }
    };

    /**
     * 空调当前状态
     */
    private State currentState = OFF;

    public void dispather(Event event) {
        transforTable.forEach(transfor -> {
            if(transfor.startState == currentState && transfor.event == event){
                if(Objects.nonNull(transfor.doAction)){
                    transfor.doAction.run();
                    setCurrentState(transfor.nextState);
                }
            }
        });
    }

    private void doStartFan() {
        System.out.println("start Fan");
    }

    private void doStopFan() {
        System.out.println("stop Fan");
    }

    private void doStartCool() {
        System.out.println("start Cool");
    }

    private void doStopCool() {
        System.out.println("stop Cool");
    }

    private void setCurrentState(State currentState) {
        this.currentState = currentState;
    }
    /**
     * 转移
     */
    static class Transfor {
        //开始状态
        State startState;
        //事件
        Event event;
        //目标状态
        State nextState;
        //执行动作
        Runnable doAction;

        static Transfor of(State startState, Event event, State nextState, Runnable doAction) {
            Transfor transfor = new Transfor();
            transfor.startState = startState;
            transfor.nextState = nextState;
            transfor.event = event;
            transfor.doAction = doAction;
            return transfor;
        }
    }

    /**
     * 空调状态枚举
     */
    public enum State {
        //关闭中状态
        OFF,
        //送风中状态
        FAN_ONLY,
        //制冷中状态
        COOL
    }

    /**
     * 空调事件枚举
     */
    public enum Event {
        //点击电源键
        CLICK_POWER,
        //点击制冷键
        CLICK_COOL
    }
}


优点:

a. 状态机可读性比较好

b. 运行时修改状态表非常方便

c. 维护起来简单

d. 可以实现多个状态转换表,根据需要加载不同的转换表。

状态模式:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。见《状态模式》

  • 环境(Context)角色,也称上下文:定义客户端所感兴趣的接口,并且保留一个具体状态类的实例。这个具体状态类的实例给出此环境对象的现有状态。
  • 抽象状态(State)角色:定义一个接口,用以封装环境(Context)对象的一个特定的状态所对应的行为。
  • 具体状态(ConcreteState)角色:每一个具体状态类都实现了环境(Context)的一个状态所对应的行为。

类图如下:

状态模式类图
  1. 接口实现状态模式

package com.mhc.sample;

/**
 * 状态上下文
 * @author xiaolong
 * @date 18/6/12 下午12:02
 */
public class Context {
    private State state;

    public Context(State state){
        setState(state);
    }

    public void setState(State state) {
        this.state = state;
    }

    public State getState() {
        return state;
    }

    public void request(Event event){
        state.handle(this, event);
    }
}


package com.mhc.sample;

/**
 * 状态接口
 * @author xiaolong
 * @date 18/6/12 下午12:01
 */
public interface State {
    /**
     * 处理逻辑
     * @param context
     * @param event
     */
    void handle(Context context, Event event);
}


package com.mhc.sample;

/**
 * 关闭中状态
 * @author xiaolong
 * @date 18/6/12 下午12:27
 */
public class OffState implements State {
    @Override
    public void handle(Context context, Event event) {
        switch (event) {
            case CLICK_POWER:
                context.setState(new FanOnlyState());
                doStartFan();
                break;
        }
    }

    private void doStartFan() {
        System.out.println("start Fan");
    }
}


package com.mhc.sample;

/**
 * 送风中状态
 * @author xiaolong
 * @date 18/6/12 下午12:32
 */
public class FanOnlyState implements State {
    @Override
    public void handle(Context context, Event event) {
        switch (event) {
            case CLICK_POWER:
                context.setState(new OffState());
                doStopFan();
                break;
            case CLICK_COOL:
                context.setState(new CoolState());
                doStartCool();
                break;
        }
    }

    private void doStopFan(){
        System.out.println("stop Fan");
    }
    private void doStartCool(){
        System.out.println("start Cool");
    }
}

package com.mhc.sample;

/**
 * 制冷中状态
 * @author xiaolong
 * @date 18/6/12 下午12:27
 */
public class CoolState implements State {
    @Override
    public void handle(Context context, Event event) {
        switch (event) {
            case CLICK_POWER:
                context.setState(new OffState());
                doStopCool();
                break;
            case CLICK_COOL:
                context.setState(new FanOnlyState());
                doStartFan();
                break;
        }
    }

    private void doStartFan() {
        System.out.println("start Fan");
    }
    private void doStopCool(){
        System.out.println("stop Cool");
    }
}


package com.mhc.sample;

import static com.mhc.sample.Event.*;

/**
 * @author xiaolong
 * @date 18/6/12 下午12:40
 */
public class AirconMain {
    public static void main(String[] args) {
        State initState = new OffState();
        Context context = new Context(initState);

        context.request(CLICK_POWER);
        System.out.println(context.getState().toString());

        context.request(CLICK_COOL);
        System.out.println(context.getState().toString());

        context.request(CLICK_COOL);
        System.out.println(context.getState().toString());

        context.request(CLICK_POWER);
        System.out.println(context.getState().toString());
    }
}

  1. 枚举实现状态模式

package com.mhc.sample;

/**
 * 状态上下文
 * @author xiaolong
 * @date 18/6/12 下午12:02
 */
public class EnumStateContext {
    private AirconStateEnum state;

    public EnumStateContext(AirconStateEnum state){
        setState(state);
    }

    public void setState(AirconStateEnum state) {
        this.state = state;
    }

    public AirconStateEnum getState() {
        return state;
    }

    public void request(Event event){
        state.handle(this, event);
    }
}


package com.mhc.sample;

import static com.mhc.sample.Event.CLICK_COOL;
import static com.mhc.sample.Event.CLICK_POWER;

/**
 * 枚举实现状态模式
 * @author xiaolong
 * @date 18/6/12 下午1:01
 */
public enum AirconStateEnum {
    OFF {
        @Override
        void handle(EnumStateContext context, Event event) {
            switch (event) {
                case CLICK_POWER:
                    context.setState(FAN_NOLY);
                    super.doStartFan();
                    break;
            }
        }
    },
    FAN_NOLY {
        @Override
        void handle(EnumStateContext context, Event event) {
            switch (event) {
                case CLICK_POWER:
                    context.setState(OFF);
                    super.doStopFan();
                    break;
                case CLICK_COOL:
                    context.setState(COOL);
                    super.doStartCool();
                    break;
            }
        }
    },
    COOL {
        @Override
        void handle(EnumStateContext context, Event event) {
            switch (event) {
                case CLICK_POWER:
                    context.setState(OFF);
                    super.doStopCool();
                    break;
                case CLICK_COOL:
                    context.setState(FAN_NOLY);
                    super.doStartFan();
                    break;
            }
        }
    };

    abstract void handle(EnumStateContext context, Event event);
    
    private void doStartFan(){
        System.out.println("start Fan");
    }
    private void doStopFan(){
        System.out.println("stop Fan");
    }
    private void doStartCool(){
        System.out.println("start Cool");
    }
    private void doStopCool(){
        System.out.println("stop Cool");
    }


    public static void main(String[] args) {
        EnumStateContext context = new EnumStateContext(OFF);

        context.request(CLICK_POWER);
        System.out.println(context.getState().toString());

        context.request(CLICK_COOL);
        System.out.println(context.getState().toString());

        context.request(CLICK_COOL);
        System.out.println(context.getState().toString());

        context.request(CLICK_POWER);
        System.out.println(context.getState().toString());
    }
}

优点:

a. 状态维护方便

b. 扩展性强

c. 解耦

现在状态机开源框架也有不少。

  • squirrel-foundation(702stars,a year ago)
  • spring-statemachine(479stars,2 months ago)
  • stateless4j(349stars,a month ago)

这三款FSMgithub上stars top3的java状态机引擎框架。至于如何技术选型,可参考《状态机引擎选型》。因为spring家族的statemachine活跃度和星级都还不错,所以我果断选择它了。在下一章节我将详细介绍其使用方法,如果想先睹为快,请转到4.4章节Spring State Machine 例子

小结:每种方法各有利弊,具体使用请结合实际场景。

四 Spring State Machine 介绍

4.1 项目概要

该项目自2015年启动,已经3岁啦。

Spring Statemachine(SSM)是基于Spring框架的、实现了状态机概念的框架。 SSM旨在提供以下功能:

开源项目模块划分如下:

模块划分.png

4.2 使用场景

以下情况是使用状态机的理想选择:

如果你准备实现一个状态机:

4.3 要素和基本概念

Order Shipping

4.4 Spring State Machine 例子

关于状态机如何使用,官网例子有很多,我这里就不细说了,需要先睹简单例子的请移驾官网。

在这里我分享下在生产环境如何优雅的使用状态机?

引例:物流系统订单处理过程。该例子来自本人公司的Jac项目,如果您是内部员工,请移驾gitlab

订单状态机图.png 项目架构.png 状态机目录.png

package com.mhc.jac.service.core.statemachine.service.event;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 订单事件
 *
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/4/15 下午4:19
 */
@AllArgsConstructor
@Getter
public enum OrderEvent {
    INIT_STATE("INIT_STATE", "订单状态初始化"),
    RECEIVE_ORDER("RECEIVE_ORDER", "客服接单"),
    SCHEDULE("SCHEDULE", "调度接单"),
    COMPLETE("COMPLETE","完成订单"),
    CANCEL("CANCEL","取消订单"),
    CLOSE("CLOSE","关闭订单"),
    ;

    private String key;
    private String desc;
}


package com.mhc.jac.service.core.statemachine.service.state;

//import 省略

/**
 * 订单状态
 *
 * @author xiaolong
 * @Date 18/4/15 下午4:17
 */
@AllArgsConstructor
@Getter
@EnumAnnotation
public enum  OrderState implements BaseEnum {
    WAIT_SUBMIT(0,"WAIT_SUBMIT","待提交(草稿状态)"),
    WAIT_RECEIVING(5,"WAIT_RECEIVING","待接单"),
    WAIT_SCHEDULING(10,"WAIT_SCHEDULING","待调度"),
    PROCESSING(15,"PROCESSING","进行中"),
    COMPLETED(20,"COMPLETED","已完成"),
    CLOSED(99,"CLOSED","已关闭"),
    CANCELED(100,"CANCELED","已取消"),
    ;

    private Integer code;
    private String key;
    private String desc;
}


package com.mhc.jac.service.core.statemachine.service.config.machine;

//import 省略

/**
 * 订单状态机配置
 * @author xiaolong
 * @date 18/4/15 下午4:15
 */
@Configuration
@EnableStateMachineFactory(name="orderStateMachineFactory",contextEvents = false)
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {
      //订单行为动作(需要做的业务)
    @Autowired
    private OrderAction orderAction;
    //订单状态机监听器(也可以做相关的业务)
    @Autowired
    private OrderStateMachineListener listener;
    //日志监听器
    @Autowired
    private LogStateMachineListener<OrderState, OrderEvent> logStateMachineListener;
    //状态机运行时持久化配置
    @Autowired
    private StateMachineRuntimePersister<OrderState, OrderEvent,String> stateMachineRuntimePersister;

    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderState, OrderEvent> config)
            throws Exception {
        config
                .withConfiguration()
                        //注册监听器
                    .listener(listener)
                    .listener(logStateMachineListener)
        ;

        config.withPersistence()
                            //配置运行时持久化对象
                    .runtimePersister(stateMachineRuntimePersister);
    }

    @Override
    public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
        states
                .withStates()
                        //初始化订单状态
                    .initial(OrderState.WAIT_SUBMIT)
                    //有限订单状态集合
                    .states(EnumSet.allOf(OrderState.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions)
            throws Exception {
        transitions
                .withExternal()
                    //待提交 -> 待接单
                    .source(OrderState.WAIT_SUBMIT).target(OrderState.WAIT_RECEIVING)
                    //订单状态初始化事件
                    .event(OrderEvent.INIT_STATE)
                    .and()
                .withExternal()
                    //待接单 -> 待调度
                    .source(OrderState.WAIT_RECEIVING).target(OrderState.WAIT_SCHEDULING)
                    //客服接单事件
                    .event(OrderEvent.RECEIVE_ORDER)
                    //接单业务
                    .action(Actions.withException(orderAction::agentAccept))
                    .and()
                .withExternal()
                    //待接单 -> 已取消
                    .source(OrderState.WAIT_RECEIVING).target(OrderState.CANCELED)
                    //取消订单事件
                    .event(OrderEvent.CANCEL)
                    //取消订单业务
                    .action(Actions.withException(orderAction::cancelOrder))
                    .and()
                .withExternal()
                    //待调度 -> 已取消
                    .source(OrderState.WAIT_SCHEDULING).target(OrderState.CANCELED)
                    //取消订单事件
                    .event(OrderEvent.CANCEL)
                    //取消订单业务
                    .action(orderAction::cancelOrder)
                    .and()
                .withExternal()
                    //待调度 -> 进行中
                    .source(OrderState.WAIT_SCHEDULING).target(OrderState.PROCESSING)
                    //调度接单事件
                    .event(OrderEvent.SCHEDULE)
                    //调度接单业务
                    .action(Actions.withException(orderAction::dispatcherAccept))
                    .and()
                .withExternal()
                    //进行中 -> 已关闭
                    .source(OrderState.PROCESSING).target(OrderState.CLOSED)
                    //关闭订单事件
                    .event(OrderEvent.CLOSE)
                    //关闭订单业务
                    .action(orderAction::closeOrder)
                    .and()
                .withExternal()
                     //进行中 -> 已完成
                    .source(OrderState.PROCESSING).target(OrderState.COMPLETED)
                    //完成订单事件
                    .event(OrderEvent.COMPLETE);
    }
}



package com.mhc.jac.service.core.bus.subscriber.statemachine;
//import 省略

/**
 * 订单状态机订阅者
 *
 * @author xiaolong
 * @date 18/5/22 下午2:02
 */
@Component
@Slf4j
public class OrderStateMachineSubscriber {
     //自定义的状态机服务
    @Autowired
    private CustomStateMachineService<OrderState, OrderEvent> stateMachineService;

    /**
     * 订单业务数据初始化完成时,初始化订单状态
     *
     * @param orderInitFinishEvent
     */
    @Subscribe
    @AllowConcurrentEvents
    public void initOrderStateMachine(OrderInitFinishEvent orderInitFinishEvent) {

        //获取订单状态机
        StateMachine<OrderState, OrderEvent> stateMachine =
                stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, orderInitFinishEvent.getOrderId());

        //发送初始化状态事件
        stateMachine.sendEvent(OrderEvent.INIT_STATE);
    }

    /**
     * 尝试客服接单
     *
     * @param tryAgentAcceptEvent
     */
    @Subscribe
    @AllowConcurrentEvents
    public void tryAgentAccept(TryAgentAcceptEvent tryAgentAcceptEvent) {

        //获取订单状态机
        StateMachine<OrderState, OrderEvent> stateMachine =
                stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, tryAgentAcceptEvent.getOrderId());

        //给状态机发送客服接单事件
        Message<OrderEvent> message = MessageBuilder
                .withPayload(OrderEvent.RECEIVE_ORDER)
                .setHeader(ORDER_ID_T_LONG, tryAgentAcceptEvent.getOrderId())
                .build();

        stateMachine.sendEvent(message);

    }

    /**
     * 尝试调度接单
     *
     * @param tryDispatcherAcceptEvent
     */
    @Subscribe
    @AllowConcurrentEvents
    public void tryDispatcherAccept(TryDispatcherAcceptEvent tryDispatcherAcceptEvent) {

        //获取订单状态机
        StateMachine<OrderState, OrderEvent> stateMachine =
                stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, tryDispatcherAcceptEvent.getOrderId());

        //给状态机发送调度接单事件
        Message<OrderEvent> message = MessageBuilder
                .withPayload(OrderEvent.SCHEDULE)
                .setHeader(ORDER_ID_T_LONG, tryDispatcherAcceptEvent.getOrderId())
                .build();

        stateMachine.sendEvent(message);
            //异常处理
        Exception exception = StateMachineUtils.getExtraStateVariable(
                stateMachine,
                KeyConstant.STATE_MACHINE_ACTION_EXCEPTION_T_EXCEPTION
        );

        if (Objects.nonNull(exception)){
            tryDispatcherAcceptEvent.setCallbackException(BaseEvent.CallbackException.of(exception));
        }
    }

    /**
     * 尝试取消订单
     *
     * @param tryCancelEvent
     */
    @Subscribe
    @AllowConcurrentEvents
    public void tryCancelOrder(TryCancelEvent tryCancelEvent) {

        //获取订单状态机
        StateMachine<OrderState, OrderEvent> stateMachine =
                stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, tryCancelEvent.getOrderId());

        //给状态机发送取消订单事件
        Message<OrderEvent> message = MessageBuilder
                .withPayload(OrderEvent.CANCEL)
                .setHeader(ORDER_ID_T_LONG, tryCancelEvent.getOrderId())
                .build();

        stateMachine.sendEvent(message);
    }

    /**
     * 尝试关闭订单
     *
     * @param tryCloseEvent
     */
    @Subscribe
    @AllowConcurrentEvents
    public void tryCloseOrder(TryCloseEvent tryCloseEvent) {

        //获取订单状态机
        StateMachine<OrderState, OrderEvent> stateMachine =
                stateMachineService.getStateMachine(StateMachineTypeEnum.ORDER, tryCloseEvent.getOrderId());

        //给状态机发送关闭订单事件
        Message<OrderEvent> message = MessageBuilder
                .withPayload(OrderEvent.CLOSE)
                .setHeader(ORDER_ID_T_LONG, tryCloseEvent.getOrderId())
                .build();

        stateMachine.sendEvent(message);
    }
}


package com.mhc.jac.service.core.statemachine.service.action;

//import 省略

/**
 * 订单任务
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/4/16 下午11:35
 */
@Action
@Slf4j
public class OrderAction {
    @Autowired
    private OrderService orderService;
    @Autowired
    private EventBus eventBus;

    public void changeStateAction2(StateContext<OrderState, OrderEvent> context) {
        log.info("OrderAction changeStateAction2");
    }

    /**
     * 客服接单
     * @param context
     */
    public void agentAccept(StateContext<OrderState, OrderEvent> context) {
        MessageHeaders messageHeaders = context.getMessage().getHeaders();
        Long orderId = CommonUtils.getByKey(messageHeaders, ORDER_ID_T_LONG);

        orderService.agentAcceptOrder(orderId);
    }

    /**
     * 调度接单
     * @param context
     */
    public void dispatcherAccept(StateContext<OrderState, OrderEvent> context) {
        log.info("do action dispatcherAccept");
        MessageHeaders messageHeaders = context.getMessage().getHeaders();
        Long orderId = CommonUtils.getByKey(messageHeaders, ORDER_ID_T_LONG);

        orderService.dispatcherAcceptOrder(orderId);
    }

    /**
     * 取消订单
     * @param context
     */
    public void cancelOrder(StateContext<OrderState, OrderEvent> context) {
        MessageHeaders messageHeaders = context.getMessage().getHeaders();
        Long orderId = CommonUtils.getByKey(messageHeaders, ORDER_ID_T_LONG);

        //订单取消成功后,发送订单取消任务执行完成事件
        if(orderService.cancelOrder(orderId)){
            CancelCompleteEvent cancelCompleteEvent = CancelCompleteEvent.builder()
                    .orderId(orderId)
                    .build();
            cancelCompleteEvent.setFrom(OperatingEventEnum.ORDER_CANCEL.getDesc());
            cancelCompleteEvent.setSendTime(LocalDateTime.now());

            eventBus.post(cancelCompleteEvent);
        }
    }

    /**
     * 关闭订单
     * @param context
     */
    public void closeOrder(StateContext<OrderState, OrderEvent> context) {
        MessageHeaders messageHeaders = context.getMessage().getHeaders();
        Long orderId = CommonUtils.getByKey(messageHeaders, ORDER_ID_T_LONG);

        orderService.closeOrder(orderId);
    }
}


package com.mhc.jac.service.core.statemachine.service.listener;

//import 省略

import java.util.Objects;

/**
 * 订单状态机监听器
 *
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @ate 18/4/16 下午11:20
 */
@Listener
@Slf4j
public class OrderStateMachineListener extends StateMachineListenerAdapter<OrderState,OrderEvent> {
    @Override
    public void stateChanged(State<OrderState,OrderEvent> from, State<OrderState,OrderEvent> to) {
        log.info("OrderStateMachineListener stateChanged,source:{},target:{}",from,to);
    }

    @Override
    public void stateEntered(State<OrderState,OrderEvent> state) {
        log.info("OrderStateMachineListener stateEntered,state:{}",state.getId());
    }

    @Override
    public void stateExited(State<OrderState,OrderEvent> state) {
        log.info("OrderStateMachineListener stateExited,state:{}",state.getId());
    }

    @Override
    public void eventNotAccepted(Message<OrderEvent> event) {
        log.info("OrderStateMachineListener eventNotAccepted,,event:{}",event.getPayload());
    }

    @Override
    public void transition(Transition<OrderState,OrderEvent> transition) {
        log.info("OrderStateMachineListener transition,source:{},target:{}",transition,transition.getTarget().getId());
    }

    @Override
    public void transitionStarted(Transition<OrderState,OrderEvent> transition) {
        log.info("OrderStateMachineListener transitionStarted,source:{},target:{}",transition,transition.getTarget().getId());
    }

    @Override
    public void transitionEnded(Transition<OrderState,OrderEvent> transition) {
        log.info("OrderStateMachineListener transitionEnded,source:{},target:{}",
                transition.getSource(),Objects.nonNull(transition.getTarget()) ? transition.getTarget().getId() : "");
    }

    @Override
    public void stateMachineStarted(StateMachine<OrderState,OrderEvent> stateMachine) {
        log.info("OrderStateMachineListener stateMachineStarted");
    }

    @Override
    public void stateMachineStopped(StateMachine<OrderState,OrderEvent> stateMachine) {
        log.info("OrderStateMachineListener stateMachine");
    }

    @Override
    public void stateMachineError(StateMachine<OrderState,OrderEvent> stateMachine, Exception exception) {
        log.info("OrderStateMachineListener stateMachineError",exception);
    }

    @Override
    public void extendedStateChanged(Object key, Object value) {
        log.info("OrderStateMachineListener extendedStateChanged");
    }

    @Override
    public void stateContext(StateContext<OrderState,OrderEvent> stateContext) {
        //log.info("OrderStateMachineListener stateContext");
    }
}


package com.mhc.jac.service.core.statemachine.service.config.base;

//import 省略

/**
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @Date 18/4/21 下午10:19
 */
@Configuration
public class OrderStateMachineBaseConfig extends StateMachineBaseConfig<OrderState, OrderEvent> {
    @Autowired
    private OrderManager orderManager;
    
    @Override
    public StateMachineTypeEnum supplierStateMachineType() {
        return StateMachineTypeEnum.ORDER;
    }

    @Override
    public void saveBizState(CustomStateMachineContext<OrderState,OrderEvent> context){
        Order o = new Order();
        o.setOrderId(context.getBizId());
        o.setOrderStatus(context.getState().getCode());

        orderManager.updateById(o);
    }

    @Bean("orderLogStateMachineListener")
    @Override
    public LogStateMachineListener<OrderState,OrderEvent> logStateMachineListener(){
        return super.logStateMachineListener();
    }

    @Bean("orderStateMachineRuntimePersister")
    @Override
    public StateMachineRuntimePersister<OrderState, OrderEvent,String> stateMachineRuntimePersister(
            JpaStateMachineRepository jpaStateMachineRepository){
        return super.stateMachineRuntimePersister(jpaStateMachineRepository);
    }

    @Bean("orderCustomStateMachineService")
    @Override
    public CustomStateMachineService<OrderState, OrderEvent> stateMachineService(StateMachineFactory<OrderState, OrderEvent> stateMachineFactory, StateMachineRuntimePersister<OrderState, OrderEvent,String> stateMachineRuntimePersister){
        return super.stateMachineService(stateMachineFactory,stateMachineRuntimePersister);
    }
}

- 状态机基础配置,抽象类(StateMachineBaseConfig)


package com.mhc.jac.service.core.statemachine.base.config;

//import 省略

/**
 * 状态机基础配置
 * @Author wangxiaolong <xiaolong@maihaoche.com>
 * @Date 18/4/18 下午1:58
 */
public abstract class StateMachineBaseConfig<S extends Enum<S>, E extends Enum<E>> {

    /**
     * 获取状态机业务类型
     * @return
     */
    protected abstract StateMachineTypeEnum supplierStateMachineType();

    /**
     * 保存业务状态
     * @param context
     */
    protected abstract void saveBizState(CustomStateMachineContext<S,E> context);

    /**
     * 保存业务状态配置
     * @return
     */
    protected BizStatePersistingConfig<S, E> bizStatePersistingConfig() {
        return BizStatePersistingConfig.<S, E>builder()
                .saveBizSate(this::saveBizState)
                .stateMachineType(supplierStateMachineType())
                .build();
    }

    /**
     * 状态机器监听器记录日志
     * @return
     */
    protected LogStateMachineListener<S,E> logStateMachineListener(){
        return new LogStateMachineListener<>();
    }

    /**
     * 状态机运行时持久化
     * @param jpaStateMachineRepository
     * @return
     */
    protected StateMachineRuntimePersister<S,E,String> stateMachineRuntimePersister(
            JpaStateMachineRepository jpaStateMachineRepository) {

        BizStatePersistingConfig<S,E> bizStatePersistingConfig = bizStatePersistingConfig();

        if(Objects.nonNull(bizStatePersistingConfig)){
            return new CustomStateMachineRuntimePersister<>(jpaStateMachineRepository,bizStatePersistingConfig);
        }

        return new CustomStateMachineRuntimePersister<>(jpaStateMachineRepository);
    }

    /**
     * 状态机器交互统一服务
     * @param stateMachineFactory
     * @param stateMachineRuntimePersister
     * @return
     */
    protected CustomStateMachineService<S,E> stateMachineService(
            StateMachineFactory<S,E> stateMachineFactory,
            StateMachineRuntimePersister<S,E,String> stateMachineRuntimePersister) {

        return new CustomStateMachineService<>(stateMachineFactory, stateMachineRuntimePersister);
    }
}


package com.mhc.jac.service.core.statemachine.base.config;

//import 省略

/**
 * 业务状态持久化配置
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @Date 18/4/20 下午3:34
 */
@Getter
@Builder
public class BizStatePersistingConfig<S,E> {
    /**
     * 保存业务状态的方法
     */
    private Consumer<CustomStateMachineContext<S,E>> saveBizSate;
    /**
     * 状态机业务类型
     */
    private StateMachineTypeEnum stateMachineType;
}

),该类继承父类JpaPersistingStateMachineInterceptor,本质上是状态机拦截器,当状态改变时将状态机上下文和业务状态持久化到数据库。


package com.mhc.jac.service.core.statemachine.base.custom;

//import 省略

/**
 * 自定义状态机运行时持久化
 *
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/4/20 下午3:20
 */
@Slf4j
public class CustomStateMachineRuntimePersister<S,E,T> extends JpaPersistingStateMachineInterceptor<S,E,T> {
    /**
     * 保存业务状态的配置
     */
    private BizStatePersistingConfig<S,E> bizStatePersistingConfig;

    public CustomStateMachineRuntimePersister(JpaStateMachineRepository jpaStateMachineRepository) {
        super(jpaStateMachineRepository);
    }

    public CustomStateMachineRuntimePersister(JpaStateMachineRepository jpaStateMachineRepository,BizStatePersistingConfig<S,E> bizStatePersistingConfig) {
        this(jpaStateMachineRepository);
        this.bizStatePersistingConfig = bizStatePersistingConfig;
    }

    @Override
    public void write(StateMachineContext<S, E> context, T contextObj) throws Exception {
        //回写业务
        if(Objects.nonNull(bizStatePersistingConfig) && Objects.nonNull(bizStatePersistingConfig.getSaveBizSate())){

            CustomStateMachineContext<S,E> customStateMachineContext = new CustomStateMachineContext<>(
                    context.getState(),
                    context.getEvent(),
                    context.getEventHeaders(),
                    context.getExtendedState(),
                    getBizId(context.getId(),bizStatePersistingConfig.getStateMachineType()),
                    bizStatePersistingConfig.getStateMachineType()
            );

            bizStatePersistingConfig.getSaveBizSate().accept(customStateMachineContext);
        }

        //回写状态机
        super.write(context, contextObj);

        log.info("[Interceptor] Custom state machine runtime persister is success.");
    }

    private Long getBizId(String stateMachineId, StateMachineTypeEnum stateMachineType) {
        String bizIdStr = stateMachineId.replace(stateMachineType.getCode()+"_","");
        return Long.valueOf(bizIdStr);
    }
}

package com.mhc.jac.service.core.statemachine.base.custom;

//import 省略

/**
 * 自定义状态机上下文
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @Date 18/4/20 下午4:30
 */
@Data
public class CustomStateMachineContext<S,E> extends DefaultStateMachineContext<S, E> {
    private Long bizId;
    private StateMachineTypeEnum stateMachineType;

    public CustomStateMachineContext(S state, E event, Map<String, Object> eventHeaders, ExtendedState extendedState,Long bizId,StateMachineTypeEnum stateMachineType) {
        super(state, event, eventHeaders, extendedState);
        this.bizId = bizId;
        this.stateMachineType = stateMachineType;
    }
}

package com.mhc.jac.service.core.statemachine.base.custom;

//import 省略

/**
 * 状态机管理服务 (包括状态机的获取和释放)
 *
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/4/20 上午10:43
 */
@Slf4j
public class CustomStateMachineService<S,E> extends DefaultStateMachineService<S,E> {
    /**
     * 状态机本地缓存
     */
    private final Map<String, StateMachine<S, E>> machines = new ConcurrentReferenceHashMap<>(16,ConcurrentReferenceHashMap.ReferenceType.WEAK);

    private StateMachinePersist<S, E, String> stateMachinePersist;

    private final StateMachineFactory<S, E> stateMachineFactory;

    public CustomStateMachineService(StateMachineFactory<S, E> stateMachineFactory, StateMachineRuntimePersister<S, E, String> stateMachineRuntimePersister) {
        super(stateMachineFactory, stateMachineRuntimePersister);
        this.stateMachinePersist = stateMachineRuntimePersister;
        this.stateMachineFactory = stateMachineFactory;
    }

    public StateMachine<S, E> getStateMachine(StateMachineTypeEnum stateMachineType, Long bizId) {
        Assert.notNull(stateMachineType,"状态机类型不能为空");
        Assert.notNull(bizId,"业务ID不能为空");

        String machineId = stateMachineType.getCode().concat("_").concat(String.valueOf(bizId));
        return acquireStateMachine(machineId);
    }

    @Override
    public StateMachine<S, E> acquireStateMachine(String machineId) {
        //尝试释放无效缓存
        tryReleaseStateMachine(machineId);

        return acquireStateMachine(machineId, true);
    }

    private void tryReleaseStateMachine(String machineId) {
        StateMachine<S,E> stateMachine = machines.get(machineId);
        if(Objects.isNull(stateMachine)) {
            return;
        }
        //从数据库获取内容上下文
        StateMachineContext<S, E> stateMachineContext = getStateMachineContextFromDB(machineId);

        //缓存失效
        if(isInvalidCache(stateMachine, stateMachineContext)){
            //释放缓存
            releaseStateMachine(machineId,true);
        }
    }

    private boolean isInvalidCache(StateMachine<S, E> stateMachine, StateMachineContext<S, E> stateMachineContext) {
        return Objects.nonNull(stateMachineContext) && !stateMachine.getState().getId().toString().equals(stateMachineContext.getState().toString());
    }

    private StateMachineContext<S, E> getStateMachineContextFromDB(String machineId) {
        StateMachineContext<S, E> stateMachineContext = null;
        if (Objects.nonNull(stateMachinePersist)) {
            try {
                stateMachineContext = stateMachinePersist.read(machineId);
            } catch (Exception e) {
                log.error("Error handling context", e);
                throw new StateMachineException("Unable to read context from store", e);
            }
        }
        return stateMachineContext;
    }

    @Override
    public StateMachine<S, E> acquireStateMachine(String machineId, boolean start) {
        log.info("Acquiring machine with id " + machineId);
        StateMachine<S,E> stateMachine = machines.get(machineId);
        if (stateMachine == null) {
            log.info("Getting new machine from factory with id " + machineId);
            stateMachine = stateMachineFactory.getStateMachine(machineId);
            if (stateMachinePersist != null) {
                try {
                    StateMachineContext<S, E> stateMachineContext = stateMachinePersist.read(machineId);
                    stateMachine = restoreStateMachine(stateMachine, stateMachineContext);
                } catch (Exception e) {
                    log.error("Error handling context", e);
                    throw new StateMachineException("Unable to read context from store", e);
                }
            }
            machines.put(machineId, stateMachine);
        }

        return handleStart(stateMachine, start);
    }

    @Override
    public void releaseStateMachine(String machineId) {
        log.info("Releasing machine with id " + machineId);
        StateMachine<S, E> stateMachine = machines.remove(machineId);
        if (stateMachine != null) {
            log.info("Found machine with id " + machineId);
            stateMachine.stop();
        }
    }

    @Override
    public void releaseStateMachine(String machineId, boolean stop) {
        log.info("Releasing machine with id " + machineId);
        StateMachine<S, E> stateMachine = machines.remove(machineId);
        if (stateMachine != null) {
            log.info("Found machine with id " + machineId);
            handleStop(stateMachine, stop);
        }
    }

    @Override
    protected void doStop() {
        log.info("Entering stop sequence, stopping all managed machines");
        ArrayList<String> machineIds = new ArrayList<>(machines.keySet());
        for (String machineId : machineIds) {
            releaseStateMachine(machineId, true);
        }
    }
}

package com.mhc.jac.service.core.statemachine.base;

//import 省略

/**
 * Actions
 *
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/5/25 下午9:24
 */
@Slf4j
public class Actions {

    /**
     * 全局异常Action
     * @param <S>
     * @param <E>
     * @return
     */
    public static <S extends Enum<S>, E extends Enum<E>> Action<S, E> globalException(){
        return stateContext -> {
            log.warn("[stateMachine]: action exception", stateContext.getException());
            //todo 异常预警通知等,或消息队列处理
        };
    }

    /**
     * 构建带有异常回执的Action
     * @param <S>
     * @param <E>
     * @return
     */
    public static <S extends Enum<S>, E extends Enum<E>> Action<S, E> withException(Action<S, E> rawAction){
        return stateContext -> {
            try {
                rawAction.execute(stateContext);
            }
            catch (Exception e) {
                log.warn("[stateMachine]: callback action exception,回执异常", stateContext.getException());
                //通过扩展属性回执异常
                stateContext.getExtendedState()
                        .getVariables()
                        .put(KeyConstant.STATE_MACHINE_ACTION_EXCEPTION_T_EXCEPTION,e);

                throw e;
            }
        };

    }
}

- 状态机工具类(StateMachineUtils)


package com.mhc.jac.service.core.statemachine.base;

//import 省略

/**
 * 状态机工具箱
 *
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/5/25 下午10:08
 */
public class StateMachineUtils {
    private StateMachineUtils(){}

    /**
     * 获取扩展状态
     * @param stateMachine
     * @param key
     * @param <S>
     * @param <T>
     * @param <R>
     * @return
     */
    public static <S,T,R> R getExtraStateVariable(StateMachine<S,T> stateMachine, String key){
        Object variable = stateMachine.getExtendedState()
                .getVariables()
                .get(key);

        if (Objects.isNull(variable)) {
            return null;
        }

        return (R)variable;
    }
}


/**
 * 状态机任务定义
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/4/23 下午2:22
 */

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Action {
}

/**
 * 状态机警卫定义
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/4/23 下午2:22
 */

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Guard {
}

/**
 * 状态机监听器定义
 * @author wangxiaolong <xiaolong@maihaoche.com>
 * @date 18/4/23 下午2:22
 */

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Listener {
}


<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-data-jpa</artifactId>
    <version>1.2.11.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-starter</artifactId>
    <version>2.0.1.RELEASE</version>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator-autoconfigure</artifactId>
        </exclusion>

    </exclusions>
</dependency>

4.5 基本原理

  • StateMachineStateConfigurer:状态配置。
  • StateMachineTransitionConfigurer:迁移配置,可以定义状态迁移接受的事件,以及相应的action。
  • StateMachineConfigurationConfigurer:状态机系统配置,包括action执行器(spring statemachine实例可以配置多个event,存储在内部queue中,并通过sync/async executor执行)、listener(事件监听器)等。
  • StateMachineListener:事件监听器(通过Spring的event机制实现),监听stateEntered(进入状态)、stateExited(离开状态)、eventNotAccepted(事件无法响应)、transition(转换)、transitionStarted(转换开始)、transitionEnded(转换结束)、stateMachineStarted(状态机启动)、stateMachineStopped(状态机关闭)、stateMachineError(状态机异常)等事件,借助listener可以追踪状态迁移过程。
  • StateMachineInterceptor:状态拦截器,不同于StateMachineListener被动监听,interceptor拥有可以改变状态变化链的能力,主要在preEvent(事件预处理)、preStateChange(状态变更的前置处理)、postStateChange(状态变更的后置处理)、preTransition(转化的前置处理)、postTransition(转化的后置处理)、stateMachineError(异常处理)等执行点生效,内部的PersistingStateChangeInterceptor(状态持久化)等都是基于这个扩展协议生效的。
  • StateMachine 状态机实例,spring statemachine支持单例、工厂模式两种方式创建,每个statemachine有一个独有的machineId用于标识machine实例;需要注意的是statemachine实例内部存储了当前状态机等上下文相关的属性,因此这个实例不能够被多线程共享。
SSM工作原理.jpeg 转态机状态迁移过程.png

4.6 使用状态机基本原则

4.7 值得思考

五 状态机是一种思维方式

瞧,对于我们日常所用的命令式编程,那些复杂的、冗长的if-else业务,难以维护和扩展,每次业务变更修改代码时总是如履薄冰,为什么会这样呢?

无非几点:

那你是否能从复杂的if-else中进行分析、抽象,抽象出状态事件动作的概念,然后对它们统一管理,包装出一个全新的概念-状态机

从小的角度来说,状态机是一种对象行为建模的工具。使用对象有一个明确并且复杂的生命流(3个以上状态),并且状态变迁存在不同的触发条件和处理行为。

从大的角度来说,这其实是一种全新的编程范式-面向状态机编程。将状态机提升到框架纬度,整个系统是由N台状态机组成,每台状态机订阅着自己感兴趣的事件,管理着自己的状态和行为动作,各司其职。它们之间通过事件相互驱动各自的流转,整个业务就在流转中完成。

从宏观角度来说,整个宇宙就是一台巨大的状态机,人类探索宇宙的奥秘,其实是在探索这台机器的运行机制。万事万物皆是状态机,小到细胞的新陈代谢,大脑中神经元的交互,大到地球的生态圈,风云变幻......

亲,你Get到了吗?

参考资料

https://zh.wikipedia.org/wiki/%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E6%9C%BA

https://blog.csdn.net/napoay/article/details/78071286

http://www.ruanyifeng.com/blog/2013/09/finite-state_machine_for_javascript.html

上一篇下一篇

猜你喜欢

热点阅读