状态机的Go语言实现版本
一、状态机
1. 定义
有限状态机(Finite-state machine, FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
2. 组成要素
-
现态(src state):事务当前所处的状态。
-
事件(event):事件就是执行某个操作的触发条件,当一个事件被满足,将会触发一个动作,或者执行一次状态的迁移。
-
动作(action):事件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当事件满足后,也可以不执行任何动作,直接迁移到新状态。
-
次态(dst state):事件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
-
状态流转(transition):事物从现态转为次态的整个过程。
3. 优点
- 代码抽象:将业务流程进行抽象和结构化,将复杂的状态转移图,分割成相邻状态的最小单元。这样相当于搭建乐高积木,在这套机制上可以组合成复杂的状态转移图,同时隐藏了系统的复杂度。
- 简化流程:业务rd只需要关注当前操作的业务逻辑(状态流转过程中的业务回调函数),极大的解耦了状态和业务。
- 易扩展:在新增状态或事件时,无需修改原有的状态流转逻辑,直接建立新的状态转移链路即可。
- 业务建模:通过最小粒度的相邻状态拼接,最终组成了业务的整体graph。
二、代码

假设我们有要实现一个订单下单功能,上图是订单状态的流转图,方框为订单的状态,箭头旁的文字为事件。
1. database包
package databaseimport "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 // 主键IDState 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 fsmimport ("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.transitionMapif 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.transitionMapevents, 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.DstStateaction := 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方法和transitionFuncif 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 orderimport ("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 orderimport ("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 orderimport ("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 orderimport ("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 orderimport ("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 mainimport ("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"] = curOrderif err := order.ExecOrderTask(params); err != nil {fmt.Printf("执行订单任务出错:%v\n", err)}fmt.Println("\n\n")}
}
最后在main包里先初始化一个订单状态机,查询所有订单,并使用状态机执行订单任务,推动订单状态转移。注意多个订单可以用同一个状态机来进行状态的迁移。
三、个人总结
为什么要使用状态机,我想主要是它可以对一个复杂的业务流程进行模块化拆分,使得代码更为易读。并且扩展性更好,如果后续有新状态加入,只需要在原来的基础上进行扩展即可,甚至不需要了解整个业务流程。
其次,它将数据库实体的状态流转进行了模范化,避免了不同的开发人员在写更新数据库实体状态代码时可能导致的问题。
相关文章:
状态机的Go语言实现版本
一、状态机 1. 定义 有限状态机(Finite-state machine, FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。 2. 组成要素 现态(src state):事务当前所处的状…...
第2章 线程安全与共享资源竞争
第2章 线程安全与共享资源竞争 2.1 synchronized同步介绍 synchronized要解决的是共享资源冲突的问题。当共享资源被任务使用时,要对资源提前加锁。所有任务都采用抢占模式,即某个任务会抢先对共享资源加上第一把锁。如果这是一个排他锁,…...
77. writerows写入多行
文章目录1. 目标任务2. 准备工作3. writerow单行写入4. writerows多行写入5. a以追加的模式写入值6. 总结1. 目标任务 新建【各班级成绩】文件夹; 在该文件夹下新建一个【1班成绩单.csv】文件; 在该文件中写入下面的内容: 成绩 姓名 刘一…...
STM32MP157-Linux输入设备应用编程-多点触摸屏编程
文章目录前言多点触摸屏tslib库简介tslib库移植tslib库函数使用打开触摸屏设备配置触摸屏设备打开并配置触摸屏设备读取触摸屏设备多点触摸屏程序编写触点数据结构体定义事件定义计算触点数量判断单击、双击判断长按、移动判断放大、缩小外部调用代码流程图(草图&am…...
mybatis-plus的一般实现过程(超详细)
MyBatis-Plus 是 MyBatis 的增强工具,在 MyBatis 的基础上提供了许多实用的功能,如分页查询、条件构造器、自动填充等。下面是 MyBatis-Plus 的完整代码实现流程: ①、引入 MyBatis-Plus 依赖 在 Maven 中,可以通过以下方式引入 …...
Spark(5):RDD概述
目录 0. 相关文章链接 1. 什么是RDD 2. RDD核心属性 3. 执行原理 0. 相关文章链接 Spark文章汇总 1. 什么是RDD RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是 Spark 中最基本的数据处理模型。代码中是一个抽象类&#x…...
面向对象 - 继承
Hello , 各位同学朋友大家好啊, 今天给大家分享的技术呢, 是面向对象三大特征之一的继承,我们今天主要按照以下几个点, 展开继承的讲解。目录 :* 继承的介绍* 继承的好处和弊端* 继承中成员访问特点 - 成员变量* 继承中成员访问特点 - 成员方法* 方法重写* 继承中成…...
计算机网络的166个概念你知道几个 第十二部分
计算机网络安全安全通信的四大要素:机密性、保温完整性、端点鉴别和运行安全性。机密性:报文需要在一定程度上进行加密,用来防止窃听者截取报文。报文完整性:在报文传输过程中,需要确保报文的内容不会发生改变。端点鉴…...
【RabbitMQ】RabbitMQ各版本的兼容性与技术支持时限
今天在研究RabbitMQ的监控时,发现这个消息队列软件的版本真的很令人崩溃,版本众多,且组件之间还存在版本的兼容性,此外各个组件还对操作系统存在兼容性关系。为了帮大家节省一些查阅官方文档的时间,我把官方文档里面涉…...
【Git】P5 Git 远程仓库(3)pull 发生冲突
pull 发生冲突冲突在什么场景下发生?为什么要先 pull 再 push构建一个冲突场景初始开始操作:程序员2:程序员1:程序员2:发生冲突:查看冲突:解决冲突:冲突在什么场景下发生?…...
关于世界坐标系,相机坐标系,图像坐标系,像素坐标系的一些理解
关于世界坐标系,相机坐标系,图像坐标系,像素坐标系的一些理解前言一、各坐标系的含义二、坐标系转换1.世界坐标系与相机坐标系(旋转与平移)2.相机坐标系与图像坐标系(透视)3.图像坐标系与像素坐…...
企业防护ddos的注意事项,你知道吗?
DDoS,分布式拒绝服务攻击,是指处于不同位置的多个攻击者同时向一个或数个目标发动攻击,或者一个攻击者控制了位于不同位置的多台机器并利用这些机器对受害者同时实施攻击。在当下,DDoS 攻击是非常常见的一种攻击方式,大…...
RocketMQ如何测试
RocketMQ如何测试MQ简介RocketMQRocketMQ测试点MQ简介 MQ:Message Queue,即消息队列,是一种应用程序之间的消息通信,简单理解就是A服务不断的往队列里发布信息,另一服务B从队列中读取消息并执行处理,消息发…...
SpringBoot中的bean注入方式和原理介绍
Spring Boot是一个非常流行的Java框架,它可以帮助开发者快速地构建高效、健壮的应用程序。其中一个重要的功能就是依赖注入,也就是将一个对象注入到另一个对象中,以便它们可以相互协作。在Spring Boot中,依赖注入是通过bean实现的…...
ESP32设备驱动-RFID-RC522模块驱动
RFID-RC522模块驱动 文章目录 RFID-RC522模块驱动1、RFID-RC522介绍2、硬件准备3、软件准备4、驱动实现1、RFID-RC522介绍 基于 NXP 的 MFRC522 IC 的 RC522 RFID 模块通常带有一个 RFID 卡标签和具有 1KB 内存的密钥卡标签。 最重要的是,它可以写一个标签,这样你就可以在里…...
SMETA认证有些客户是需要做窗口期的
【SMETA认证有些客户是需要做窗口期的】SMETA审核是常见的社会责任审核标准之一,中文全称为“Sedex 会员道德贸易审核”,英文为“Sedex Members Ethical Trade Audit”. SEDEX 官网:网页链接Sedex 作为目前市场流行的CSR审核标准,…...
面向对象设计模式:创建型模式之原型模式
文章目录一、引入二、代理模式,Prototype Pattern2.1 Intent 意图2.2 Applicability 适用性2.3 类图2.4 应用实例:使用下划线或消息框展示字符串2.4 应用实例:JDK java.lang.Object java.lang.Cloneable一、引入 二、代理模式,Pr…...
三维重建(单目、双目、多目、点云、SFM、SLAM)
1 相机几何与标定1.1 相机模型中的坐标系1.2 四种坐标系之间的转换1.3 相机内参1.4 相机标定2 单目三维重建2.1 NeuralRecon三维重建定义 在计算机视觉中, 三维重建是指根据单视图或者多视图的图像重建三维信息的过程. 由于单视频的信息不完全,因此三维重建需要利用经验知识. 而…...
Java中的final和权限修饰符
目录 final 常量 细节: 权限修饰符 Java权限修饰符用于控制类、方法、变量的访问范围。Java中有四种权限修饰符: 权限修饰符的使用场景: final 方法 表明该方法是最终方法,不能被重写。类 表明该类是最终类,不能被继…...
MySQL的基本语句(SELECT型)
基本MySQL语句SELECTSELECT FROM 列的别名去除重复行空值着重号算术运算符加法( )减法( - )乘法( * )除法( / 或DIV)求模( % 或MOD)比较运算符等于( )安全等于( <> )不等于( ! 或 <…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
