工单系统需基于有限状态机(FSM)实现状态流转,区分运行时与持久化结构体,用行级锁或乐观锁保障并发安全,事件触发须幂等,核心操作应异步化。

Go 语言本身不提供工单系统,你得自己搭骨架——核心是状态机 + 事件驱动 + 持久化,不是套个框架就能跑起来。
工单状态流转必须用有限状态机(FSM)控制
直接用 string 或 int 表示状态(如 "open"、"processing")会导致非法跳转难以拦截,比如从 "closed" 直接回到 "open" 而没人报错。
- 推荐用
github.com/looplab/fsm或手写简单 FSM:定义明确的states和transitions,每次调用fsm.Event()前校验合法性 - 状态变更必须走
Transition方法,禁止直接改数据库字段或结构体字段 - 把状态迁移逻辑和业务解耦,例如
CloseTicket()内部只触发"close"事件,不写 SQL
工单数据模型要区分“运行时”和“持久化”结构
别把 struct 直接当 ORM Model 用。一个工单在内存中可能需要缓存用户信息、关联任务列表、最近操作日志;但存到数据库里只需要最小必要字段。
- 定义两个结构体:
Ticket(面向业务逻辑,含嵌套对象、方法)和TicketRow(纯字段映射,用于sqlx或gorm) - 避免在
Ticket里塞sql.NullString这类 DB 专用类型,它们会污染领域逻辑 - 时间字段统一用
time.Time,入库前通过BeforeSave钩子或手动赋值处理时区(建议全系统用 UTC 存储)
并发更新工单时容易丢状态或重复触发事件
多个协程同时调用 ticket.Transition("resolve") 可能导致两次成功提交,最终状态错乱,或者同一个事件被处理两遍(比如发了两条通知)。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 用数据库行级锁:执行状态变更前先
SELECT ... FOR UPDATE工单记录(PostgreSQL/MySQL 支持) - 或加乐观锁:在
TicketRow加version字段,UPDATE 时带WHERE version = ?,失败则重试 - 事件触发(如发邮件、Webhook)必须做成幂等操作,不能依赖“只触发一次”,而要靠唯一事件 ID + 去重表或 Redis
SETNX
不要在 HTTP handler 里做耗时工单操作
用户点击“分配给张三”就卡住 2 秒?那不是系统,是阻塞队列。工单操作常涉及通知、审批流、外部 API 调用,必须异步化。
- handler 只做参数校验 + 状态合法性检查 + 入库(事务内),然后投递消息到
redis streams或nats - 单独起 worker goroutine 消费事件,失败后进重试队列(带指数退避)
- 前端轮询或用 SSE 获取操作结果,而不是等后端同步返回所有副作用结果
真正难的不是增删改查,而是状态一致性、事件顺序、跨服务协作——这些不会因为用了 Go 就自动变简单。每加一个新状态或一条流转规则,都要重新审视所有可能的竞态路径。

















