状态机的Go语言实现版本

news2024/10/2 12:31:38

一、状态机

1. 定义

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

2. 组成要素

  • 现态(src state):事务当前所处的状态。

  • 事件(event):事件就是执行某个操作的触发条件,当一个事件被满足,将会触发一个动作,或者执行一次状态的迁移。

  • 动作(action):事件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当事件满足后,也可以不执行任何动作,直接迁移到新状态。

  • 次态(dst state):事件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

  • 状态流转(transition):事物从现态转为次态的整个过程。

3. 优点

  • 代码抽象:将业务流程进行抽象和结构化,将复杂的状态转移图,分割成相邻状态的最小单元。这样相当于搭建乐高积木,在这套机制上可以组合成复杂的状态转移图,同时隐藏了系统的复杂度。
  • 简化流程:业务rd只需要关注当前操作的业务逻辑(状态流转过程中的业务回调函数),极大的解耦了状态和业务。
  • 易扩展:在新增状态或事件时,无需修改原有的状态流转逻辑,直接建立新的状态转移链路即可。
  • 业务建模:通过最小粒度的相邻状态拼接,最终组成了业务的整体graph。

二、代码

请添加图片描述

假设我们有要实现一个订单下单功能,上图是订单状态的流转图,方框为订单的状态,箭头旁的文字为事件。

1. database包

package database

import "fmt"

// DB 模拟数据库对象
type DB struct {
}

// Transaction 模拟事务
func (db *DB) Transaction(fun func() error) error {
	fmt.Println("事务执行开始。")
	err := fun()
	fmt.Println("事务执行结束。")
	return err
}

// Order 订单
type Order struct {
	ID    int64 // 主键ID
	State int   // 状态
}

type OrderList []*Order

// 查询所有订单
func ListAllOrder() (OrderList, error) {
	orderList := OrderList{
		&Order{1, 0},
		&Order{2, 1},
		&Order{2, 2},
	}
	return orderList, nil
}

// UpdateOrderState 更新订单状态
func UpdateOrderState(curOrder *Order, srcState int, dstState int) error {
	if curOrder.State == srcState {
		curOrder.State = dstState
	}
	fmt.Printf("更新id为 %v 的订单状态,从现态[%v]到次态[%v]\n", curOrder.ID, srcState, dstState)
	return nil
}

用来模拟数据库的操作,如数据库事务、查询所有订单、更新订单状态等。

2. fsm包

package fsm

import (
	"fmt"
	"reflect"
	"zuzhiang/database"
)

// FSMState 状态机的状态类型
type FSMState int

// FSMEvent 状态机的事件类型
type FSMEvent string

// FSMTransitionMap 状态机的状态转移图类型,现态和事件一旦确定,次态和动作就唯一确定
type FSMTransitionMap map[FSMState]map[FSMEvent]FSMDstStateAndAction

// FSMTransitionFunc 状态机的状态转移函数类型
type FSMTransitionFunc func(params map[string]interface{}, srcState FSMState, dstState FSMState) error

// FSMDstStateAndAction 状态机的次态和动作
type FSMDstStateAndAction struct {
	DstState FSMState  // 次态
	Action   FSMAction // 动作
}

// FSMAction 状态机的动作
type FSMAction interface {
	Before(bizParams map[string]interface{}) error                   // 状态转移前执行
	Execute(bizParams map[string]interface{}, tx *database.DB) error // 状态转移中执行
	After(bizParams map[string]interface{}) error                    // 状态转移后执行
}

// FSM 状态机,元素均为不可导出
type FSM struct {
	transitionMap  FSMTransitionMap  // 状态转移图
	transitionFunc FSMTransitionFunc // 状态转移函数
}

// CreateNewFSM 创建一个新的状态机
func CreateNewFSM(transitionFunc FSMTransitionFunc) *FSM {
	return &FSM{
		transitionMap:  make(FSMTransitionMap),
		transitionFunc: transitionFunc,
	}
}

// SetTransitionMap 设置状态机的状态转移图
func (fsm *FSM) SetTransitionMap(srcState FSMState, event FSMEvent, dstState FSMState, action FSMAction) {
	if int(srcState) < 0 || len(event) <= 0 || int(dstState) < 0 {
		panic("现态|事件|次态非法。")
		return
	}
	transitionMap := fsm.transitionMap
	if transitionMap == nil {
		transitionMap = make(FSMTransitionMap)
	}
	if _, ok := transitionMap[srcState]; !ok {
		transitionMap[srcState] = make(map[FSMEvent]FSMDstStateAndAction)
	}
	if _, ok := transitionMap[srcState][event]; !ok {
		dstStateAndAction := FSMDstStateAndAction{
			DstState: dstState,
			Action:   action,
		}
		transitionMap[srcState][event] = dstStateAndAction
	} else {
		fmt.Printf("现态[%v]+事件[%v]+次态[%v]已定义过,请勿重复定义。\n", srcState, event, dstState)
		return
	}
	fsm.transitionMap = transitionMap
}

// Push 状态机的状态迁移
func (fsm *FSM) Push(tx *database.DB, params map[string]interface{}, currentState FSMState, event FSMEvent) error {
	// 根据现态和事件从状态转移图获取次态和动作
	transitionMap := fsm.transitionMap
	events, eventExist := transitionMap[currentState]
	if !eventExist {
		return fmt.Errorf("现态[%v]未配置迁移事件", currentState)
	}
	dstStateAndAction, ok := events[event]
	if !ok {
		return fmt.Errorf("现态[%v]+迁移事件[%v]未配置次态", currentState, event)
	}
	dstState := dstStateAndAction.DstState
	action := dstStateAndAction.Action

	// 执行before方法
	if action != nil {
		fsmActionName := reflect.ValueOf(action).String()
		fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], [%v].before\n", currentState, event, dstState, fsmActionName)
		if err := action.Before(params); err != nil {
			return fmt.Errorf("现态[%v]+迁移事件[%v]->次态[%v]失败, [%v].before, err: %v", currentState, event, dstState, fsmActionName, err)
		}
	}

	// 事务执行execute方法和transitionFunc
	if tx == nil {
		tx = new(database.DB)
	}
	transactionErr := tx.Transaction(func() error {
		fsmActionName := reflect.ValueOf(action).String()
		fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], [%v].execute\n", currentState, event, dstState, fsmActionName)
		if action != nil {
			if err := action.Execute(params, tx); err != nil {
				return fmt.Errorf("状态转移执行出错:%v", err)
			}
		}

		fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], transitionFunc\n", currentState, event, dstState)
		if err := fsm.transitionFunc(params, currentState, dstState); err != nil {
			return fmt.Errorf("执行状态转移函数出错: %v", err)
		}
		return nil
	})
	if transactionErr != nil {
		return transactionErr
	}

	// 执行after方法
	if action != nil {
		fsmActionName := reflect.ValueOf(action).String()
		fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], [%v].after\n", currentState, event, dstState, fsmActionName)
		if err := action.After(params); err != nil {
			return fmt.Errorf("现态[%v]+迁移事件[%v]->次态[%v]失败, [%v].before, err: %v", currentState, event, dstState, fsmActionName, err)
		}
	}
	return nil
}

状态机包含的元素有两个:状态转移图和状态转移函数,为了防止包外直接调用,这两个元素都设为了不可导出的。状态转移图说明了状态机的状态流转情况,状态转移函数定义了在状态转移的过程中需要做的事情,在创建状态时指定,如更新数据库实体(order)的状态。

而状态转移图又包含现态、事件、次态和动作,一旦现态和事件确定,那么状态流转的唯一次态和动作就随之确定。

状态机的动作又包含三个:Before、Execute和After。Before操作在是事务前执行,由于此时没有翻状态,所以该步可能会被重复执行。Execute操作是和状态转移函数在同一事务中执行的,同时成功或同时失败。After操作是在事务后执行,因为在执行前状态已经翻转,所以最多会执行一次,在业务上允许执行失败或未执行。

状态机的主要方法有两个,SetTransitionMap方法用来设置状态机的状态转移图,Push方法用来根据现态和事件推动状态机进行状态翻转。Push方法中会先执行Before动作,再在同一事务中执行Execute动作和状态转移函数,最后执行After动作。在执行Push方法的时候会将params参数传递给状态转移函数。

3. order包

(1) order

package order

import (
	"fmt"
	"zuzhiang/database"
	"zuzhiang/fsm"
)

var (
	// 状态
	StateOrderInit          = fsm.FSMState(0) // 初始状态
	StateOrderToBePaid      = fsm.FSMState(1) // 待支付
	StateOrderToBeDelivered = fsm.FSMState(2) // 待发货
	StateOrderCancel        = fsm.FSMState(3) // 订单取消
	StateOrderToBeReceived  = fsm.FSMState(4) // 待收货
	StateOrderDone          = fsm.FSMState(5) // 订单完成

	// 事件
	EventOrderPlace      = fsm.FSMEvent("EventOrderPlace")      // 下单
	EventOrderPay        = fsm.FSMEvent("EventOrderPay")        // 支付
	EventOrderPayTimeout = fsm.FSMEvent("EventOrderPayTimeout") // 支付超时
	EventOrderDeliver    = fsm.FSMEvent("EventOrderDeliver")    // 发货
	EventOrderReceive    = fsm.FSMEvent("EventOrderReceive")    // 收货
)

var orderFSM *fsm.FSM

// orderTransitionFunc 订单状态转移函数
func orderTransitionFunc(params map[string]interface{}, srcState fsm.FSMState, dstState fsm.FSMState) error {
	// 从params中解析order参数
	key, ok := params["order"]
	if !ok {
		return fmt.Errorf("params[\"order\"]不存在。")
	}
	curOrder := key.(*database.Order)
	fmt.Printf("order.ID: %v, order.State: %v\n", curOrder.ID, curOrder.State)

	// 订单状态转移
	if err := database.UpdateOrderState(curOrder, int(srcState), int(dstState)); err != nil {
		return err
	}
	return nil
}

// Init 状态机的状态转移图初始化
func Init() {
	orderFSM = fsm.CreateNewFSM(orderTransitionFunc)
	orderFSM.SetTransitionMap(StateOrderInit, EventOrderPlace, StateOrderToBePaid, PlaceAction{})                  // 初始化+下单 -> 待支付
	orderFSM.SetTransitionMap(StateOrderToBePaid, EventOrderPay, StateOrderToBeDelivered, PayAction{})             // 待支付+支付 -> 待发货
	orderFSM.SetTransitionMap(StateOrderToBePaid, EventOrderPayTimeout, StateOrderCancel, nil)                     // 待支付+支付超时 -> 订单取消
	orderFSM.SetTransitionMap(StateOrderToBeDelivered, EventOrderDeliver, StateOrderToBeReceived, DeliverAction{}) // 待发货+发货 -> 待收货
	orderFSM.SetTransitionMap(StateOrderToBeReceived, EventOrderReceive, StateOrderDone, ReceiveAction{})          // 待收货+收货 -> 订单完成
}

// ExecOrderTask 执行订单任务,推动状态转移
func ExecOrderTask(params map[string]interface{}) error {
	// 从params中解析order参数
	key, ok := params["order"]
	if !ok {
		return fmt.Errorf("params[\"order\"]不存在。")
	}
	curOrder := key.(*database.Order)

	// 初始化+下单 -> 待支付
	if curOrder.State == int(StateOrderInit) {
		if err := orderFSM.Push(nil, params, StateOrderInit, EventOrderPlace); err != nil {
			return err
		}
	}
	// 待支付+支付 -> 待发货
	if curOrder.State == int(StateOrderToBePaid) {
		if err := orderFSM.Push(nil, params, StateOrderToBePaid, EventOrderPay); err != nil {
			return err
		}
	}
	// 待支付+支付超时 -> 订单取消
	if curOrder.State == int(StateOrderToBePaid) {
		if err := orderFSM.Push(nil, params, StateOrderToBePaid, EventOrderPayTimeout); err != nil {
			return err
		}
	}
	// 待发货+发货 -> 待收货
	if curOrder.State == int(StateOrderToBeDelivered) {
		if err := orderFSM.Push(nil, params, StateOrderToBeDelivered, EventOrderDeliver); err != nil {
			return err
		}
	}
	// 待收货+收货 -> 订单完成
	if curOrder.State == int(StateOrderToBeReceived) {
		if err := orderFSM.Push(nil, params, StateOrderToBeReceived, EventOrderReceive); err != nil {
			return err
		}
	}
	return nil
}

order包中做的事情主要有:

  • 定义订单状态机的状态和事件;
  • 创建一个状态机,并设置状态转移函数和状态转移图;
  • 执行订单任务,推动状态转移。

(2) order_action_place

package order

import (
	"fmt"
	"zuzhiang/database"
)

type PlaceAction struct {
}

// Before 事务前执行,业务上允许多次操作
func (receiver PlaceAction) Before(bizParams map[string]interface{}) error {
	fmt.Println("执行下单的Before方法。")
	return nil
}

// Execute 事务中执行,与状态转移在同一事务中
func (receiver PlaceAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {
	fmt.Println("执行下单的Execute方法。")
	return nil
}

// After 事务后执行,业务上允许执行失败或未执行
func (receiver PlaceAction) After(bizParams map[string]interface{}) error {
	fmt.Println("执行下单的After方法。")
	return nil
}

(2) ~ (5)是订单不同动作的声明和实现。

(3) order_action_pay

package order

import (
	"fmt"
	"zuzhiang/database"
)

type PayAction struct {
}

// Before 事务前执行,业务上允许多次操作
func (receiver PayAction) Before(bizParams map[string]interface{}) error {
	fmt.Println("执行支付的Before方法。")
	return nil
}

// Execute 事务中执行,与状态转移在同一事务中
func (receiver PayAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {
	fmt.Println("执行支付的Execute方法。")
	return nil
}

// After 事务后执行,业务上允许执行失败或未执行
func (receiver PayAction) After(bizParams map[string]interface{}) error {
	fmt.Println("执行支付的After方法。")
	return nil
}

(4) order_action_deliver

package order

import (
	"fmt"
	"zuzhiang/database"
)

type DeliverAction struct {
}

// Before 事务前执行,业务上允许多次操作
func (receiver DeliverAction) Before(bizParams map[string]interface{}) error {
	fmt.Println("执行发货的Before方法。")
	return nil
}

// Execute 事务中执行,与状态转移在同一事务中
func (receiver DeliverAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {
	fmt.Println("执行发货的Execute方法。")
	return nil
}

// After 事务后执行,业务上允许执行失败或未执行
func (receiver DeliverAction) After(bizParams map[string]interface{}) error {
	fmt.Println("执行发货的After方法。")
	return nil
}

(5) order_action_receive

package order

import (
	"fmt"
	"zuzhiang/database"
)

type ReceiveAction struct {
}

// Before 事务前执行,业务上允许多次操作
func (receiver ReceiveAction) Before(bizParams map[string]interface{}) error {
	fmt.Println("执行收货的Before方法。")
	return nil
}

// Execute 事务中执行,与状态转移在同一事务中
func (receiver ReceiveAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {
	fmt.Println("执行收货的Execute方法。")
	return nil
}

// After 事务后执行,业务上允许执行失败或未执行
func (receiver ReceiveAction) After(bizParams map[string]interface{}) error {
	fmt.Println("执行收货的After方法。")
	return nil
}

4. main包

package main

import (
	"fmt"
	"zuzhiang/database"
	"zuzhiang/order"
)

func main() {
	order.Init()
	orderList, dbErr := database.ListAllOrder()
	if dbErr != nil {
		return
	}
	for _, curOrder := range orderList {
		params := make(map[string]interface{})
		params["order"] = curOrder
		if err := order.ExecOrderTask(params); err != nil {
			fmt.Printf("执行订单任务出错:%v\n", err)
		}
		fmt.Println("\n\n")
	}
}

最后在main包里先初始化一个订单状态机,查询所有订单,并使用状态机执行订单任务,推动订单状态转移。注意多个订单可以用同一个状态机来进行状态的迁移。

三、个人总结

为什么要使用状态机,我想主要是它可以对一个复杂的业务流程进行模块化拆分,使得代码更为易读。并且扩展性更好,如果后续有新状态加入,只需要在原来的基础上进行扩展即可,甚至不需要了解整个业务流程。

其次,它将数据库实体的状态流转进行了模范化,避免了不同的开发人员在写更新数据库实体状态代码时可能导致的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/397663.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java程序中空指针异常的最佳实践

1、空指针问题 NullPointerException 是 Java 代码中最常见的异常&#xff0c;将其最可能出现的场景归为以下 5 种&#xff1a; 参数值是 Integer 等包装类型&#xff0c;使用时因为自动拆箱出现了空指针异常&#xff1b;字符串比较出现空指针异常&#xff1b;诸如 Concurren…

新建vite+vue3+ts项目,以及解决过程中遇到的问题

目录 一、新建vitevue3ts项目 二、解决过程中遇到的问题 解决报错&#xff1a;Module ‘“xx.vue“‘ has no default export. 解决报错&#xff1a;Error [ERR_MODULE_NOT_FOUND]: Cannot find package ‘uuid’ imported from xxx的解决 解决报错&#xff1a;[plugin:vi…

如何选择一款数据库?

1主流数据库技术介绍常见的数据库模型主要分为SQL关系型数据库和NoSQL非关系型数据库。其中关系型数据库分为传统关系数据库和大数据数据库&#xff0c;非关系型数据库分为键值存储数据库、列存储数据库、面向文档数据库、图形数据库、时序数据库、搜索引擎存储数据库及其他&am…

Java集合类

在了解Java集合类之前&#xff0c;我们必须首先了解java.util包&#xff0c;这个包提供了所有集合相关的接口和类。 集合是用来存储数据类型的一种数据结构&#xff0c;提到存储数据类型的数据结构我们最先想到的应该是数组&#xff0c;接下来我们简单回顾下Java中集合和数组的…

Xilinx ZYNQ 7000 HDMI

High-Definition Multimedia Interface (HDMI) 参考xilinx application note XAPP460 HDMI来自High-Definition Multimedia Interface 高分辨率多媒体接口&#xff0c;多媒体一般包含图像和声音。 Transition Minimized Differential Signaling (TMDS) 是HDMI的物理层。 TMDS…

春招冲刺(十一):前端面试之网络总结

网络总结 Q1: GET和POST的请求的区别 应用场景&#xff1a;Get是一个幂等请求&#xff0c;一般用于请求资源。post不是幂等请求&#xff0c;一般用于修改资源。缓存&#xff1a;Get请求一般缓存&#xff0c;Post一般不缓存报文格式&#xff1a;Get请求体一般为空&#xff0c;…

【Linux】linux | 修改dns | 修改网卡dns

一、说明1、业务需要&#xff0c;需要修改服务的dns的地址2、改成阿里的二、操作查询需要修改dns的网卡ifconfig说明1&#xff1a; 留意ens192&#xff0c;即网卡名称查看网卡配置文件信息cd /etc/sysconfig/network-scripts ll备份cp /etc/sysconfig/network-scripts/ifcfg-en…

Advantest爱德万直流电源维修电压电流发生器ADCMT系列

Advantest爱德万电源维修常见型号&#xff1a;ADCMT6240A/B&#xff1b;R6243/R6246&#xff1b;ADCMT系列。 维修品牌&#xff1a; 安捷伦Agilent/HP、泰克Tektronix、安立Anritsu、罗德施瓦茨、爱德万Advantest、艾法斯/马可尼IFR/Marconi、吉时利Keithley、日立Hitachi、 松…

【BOOST C++】组件编程(2)-- 组件的设计原理

GitHub - ros2/demos at foxy 一、说明 为了研究ROS2的组件编程&#xff0c;首先要理解如何何为组件。组件本是微软的发明物体&#xff0c;但是在ubuntu上需要自己从底层实现&#xff0c;就说ROS2不用你写&#xff0c;但是就能看明白也是需要一点理论功底的。本篇按照COM内幕的…

【2223sW3】LOG1

写在前面 好好学习&#xff0c;走出宿舍&#xff0c;走向毕设&#xff01; 一些心路历程记录&#xff0c;很少有代码出现 因为鬼知道哪条代码到时候变成毕设的一部分了咧&#xff0c;还是不要给自己的查重挖坑罢了 23.3.6 lammps代码修改 因为学姐要中期答辩了&#xff0c;…

用Biome-BGC模型如何模拟水循环过程

在Biome-BGC模型中&#xff0c;对于碳的生物量积累&#xff0c;采用光合酶促反应机理模型计算出每天的初级生产力(GPP)&#xff0c;将生长呼吸和维持呼吸减去后的产物分配给叶、枝条、干和根。生物体的碳每天都按一定比例以凋落方式进入凋落物碳库&#xff1b;对于水份输运过程…

2023/3/8集合之TreeSet HashSet简介 不含代码

TreeSet : 底层是由TreeMap维护的 无序的,不可重的 底层结构 : 红黑树(平衡二叉树) 特点 : 查询效率高,默认升序排序引用场景 : 适合应用在存储多个单个值的数据的集合,去重的,自动升序排序的场景新增方法:新增了一些与比较大小相关的方法 遍历方式 1)foreach 2)iterator 1测试…

【C语言学习笔记】:图解指针变量

1 指针变量的基本操作基本操作 int a,*iptr,*jptr,*kptr;iptr &a;jptr iptr;*jptr 100;kptr NULL; 图解&#xff1a; 1.1 己址和己空间 指针变量也是一个变量&#xff0c;对应一块内存空间&#xff0c;对应一个内存地址&#xff0c;指针名就是己址。这空内存空间多大…

42-Golang中的单元测试

Golang中的单元测试需求传统方法基本介绍单元测试快速入门总结综合案例需求 在工作中&#xff0c;我们会遇到这样的情况&#xff0c;就是去确认一个函数&#xff0c;或者一个模块的结果是否正确 传统方法 在main函数中&#xff0c;调用addUpper函数&#xff0c;看看实际输出…

322. 零钱兑换 279.完全平方数

322. 零钱兑换 完全背包问题&#xff0c;需要注意的是数组的初始值。 class Solution {public int coinChange(int[] coins, int amount) {int[] dp new int[amount1];for(int i 0;i < amount;i){dp[i] Integer.MAX_VALUE;}dp[0] 0;for(int i 0;i < coins.length;i…

使用 create-react-app 脚手架搭建React项目

❀官网 1、安装脚手架&#xff1a;npm install -g create-react-app 2、查看版本&#xff1a;create-react-app -V &#xff01;&#xff01;&#xff01;注意 Node版本必须是14以上&#xff0c;不然会报以下错误。 3、创建react项目&#xff08;项目名不能包含大写字母&…

Windows上一款特别好用的画图软件

安装 废话不多说&#xff0c;打开windows的应用商店&#xff0c;搜索draw.io&#xff0c;点击获取即可。 画图 draw.io的布局左边是各种图形组件&#xff0c;中间是画布&#xff0c;右边是属性设置&#xff0c;文件扩展名是.drawio。 点击左边列表中的图形可以将它添加到画…

设计模式(十七)----行为型模式之模板方法模式

行为型模式用于描述程序在运行时复杂的流程控制&#xff0c;即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务&#xff0c;它涉及算法与对象间职责的分配。 行为型模式分为类行为模式和对象行为模式&#xff0c;前者采用继承机制来在类间分派行为&…

二、JVM内存结构

文章目录运行时数据区子系统程序计数器&#xff08;PC寄存器&#xff09;虚拟机栈本地方法接口和本地方法栈堆运行时数据区子系统 1、Java虚拟机定义了若干程序运行期间使用到的运行时数据区&#xff0c;其中有一些会随着虚拟机启动而创建&#xff0c;随着虚拟机推出而销毁。另…

SpringBoot接口 - 如何对参数进行校验

在以SpringBoot开发Restful接口时, 对于接口的查询参数后台也是要进行校验的&#xff0c;同时还需要给出校验的返回信息放到上文我们统一封装的结构中。那么如何优雅的进行参数的统一校验呢&#xff1f;什么是不优雅的参数校验后端对前端传过来的参数也是需要进行校验的&#x…