状态机 - meetbill/chi GitHub Wiki

状态机

https://github.com/smallnest/gofsm

华为云的 Agent 中使用了 gofsm 做状态机
https://github.com/huaweicloud/telescope

example

package main

import (
	"errors"
	"fmt"
	"log"
	"math/rand"
	"time"

	fsm "github.com/smallnest/gofsm"
)

type Turnstile struct {
	ID         uint64
	EventCount uint64
	CoinCount  uint64
	PassCount  uint64
	State      string
	States     []string
}

// TurnstileEventProcessor is used to handle turnstile actions.
type TurnstileEventProcessor struct{}

func (p *TurnstileEventProcessor) OnExit(fromState string, args []interface{}) {
	t := args[0].(*Turnstile)
	if t.State != fromState {
		panic(fmt.Errorf("转门 %v 的状态与期望的状态 %s 不一致,可能在状态机外被改变了", t, fromState))
	}

	log.Printf("转门 %d 从状态 %s 改变", t.ID, fromState)
}

func (p *TurnstileEventProcessor) Action(action string, fromState string, toState string, args []interface{}) error {
	t := args[0].(*Turnstile)
	t.EventCount++

	switch action {
	case "pass": //用户通过的action
		t.PassCount++
	case "check", "repeat-check": //刷卡或者投币的action
		if t.CoinCount > 0 { // repeat-check
			return errors.New("转门暂时故障")
		}

		t.CoinCount++
	default: //其它action
	}

	return nil
}

func (p *TurnstileEventProcessor) OnEnter(toState string, args []interface{}) {
	t := args[0].(*Turnstile)
	t.State = toState
	t.States = append(t.States, toState)

	log.Printf("转门 %d 的状态改变为 %s ", t.ID, toState)
}

func (p *TurnstileEventProcessor) OnActionFailure(action string, fromState string, toState string, args []interface{}, err error) {
	t := args[0].(*Turnstile)

	log.Printf("转门 %d 的状态从 %s to %s 改变失败, 原因: %v", t.ID, fromState, toState, err)
}

func main() {
	rand.Seed(time.Now().UnixNano())

	ts := &Turnstile{
		ID:     1,
		State:  "Locked",
		States: []string{"Locked"},
	}
	fsm := initFSM()

	//推门
	//没刷卡/投币不可进入
	err := fsm.Trigger(ts.State, "Push", ts)
	if err != nil {
		// t.Errorf("trigger err: %v", err)
		fmt.Errorf("trigger err: %v", err)
	}

	//推门
	//没刷卡/投币不可进入
	err = fsm.Trigger(ts.State, "Push", ts)
	if err != nil {
		fmt.Errorf("trigger err: %v", err)
	}

	//刷卡或者投币
	//不容易啊,终于解锁了
	err = fsm.Trigger(ts.State, "Coin", ts)
	if err != nil {
		fmt.Errorf("trigger err: %v", err)
	}

	//刷卡或者投币
	//无用的投币, 测试Action执行失败
	err = fsm.Trigger(ts.State, "Coin", ts)
	if err != nil {
		fmt.Errorf("trigger err: %v", err)
	}

	//推门
	//这时才能进入,进入后闸门被锁
	err = fsm.Trigger(ts.State, "Push", ts)
	if err != nil {
		fmt.Errorf("trigger err: %v", err)
	}

	//推门
	//无法进入,闸门已锁
	err = fsm.Trigger(ts.State, "Push", ts)
	if err != nil {
		fmt.Errorf("trigger err: %v", err)
	}

	lastState := Turnstile{
		ID:         1,
		EventCount: 6,
		CoinCount:  1,
		PassCount:  1,
		State:      "Locked",
		States:     []string{"Locked", "Unlocked", "Locked"},
	}

	if !compareTurnstile(&lastState, ts) {
		fmt.Errorf("Expected last state: %+v, but got %+v", lastState, ts)
	} else {
		fmt.Printf("最终的状态: %+v", ts)
	}

	fsm.Export("state.png")
}

func compareTurnstile(t1 *Turnstile, t2 *Turnstile) bool {
	if t1.ID != t2.ID || t1.CoinCount != t2.CoinCount || t1.EventCount != t2.EventCount || t1.PassCount != t2.PassCount ||
		t1.State != t2.State {
		return false
	}

	return fmt.Sprint(t1.States) == fmt.Sprint(t2.States)
}

func initFSM() *fsm.StateMachine {
	delegate := &fsm.DefaultDelegate{P: &TurnstileEventProcessor{}}

	transitions := []fsm.Transition{
		{From: "Locked", Event: "Coin", To: "Unlocked", Action: "check"},
		{From: "Locked", Event: "Push", To: "Locked", Action: "invalid-push"},
		{From: "Unlocked", Event: "Push", To: "Locked", Action: "pass"},
		{From: "Unlocked", Event: "Coin", To: "Unlocked", Action: "repeat-check"},
	}

	return fsm.NewStateMachine(delegate, transitions...)
}