订单状态跟踪是很多业务系统的核心需求,不管是电商下单还是服务预约,都需要清晰记录订单从创建到完结的全流程状态变化。Golang的并发特性让它在处理这类多任务状态管理场景时非常有优势,下面我们就一步步实现完整的订单状态跟踪功能。

一、先梳理订单状态与转换规则
首先我们需要明确订单支持的所有状态,以及状态之间允许转换的规则,避免出现非法状态变更,比如已取消的订单不能直接变成已发货。
常见的订单状态定义如下:
- 待支付:订单创建后的初始状态
- 已支付:用户完成支付后的状态
- 待发货:支付完成后商家未发货的状态
- 已发货:商家完成发货的状态
- 已完成:用户确认收货后的最终状态
- 已取消:用户取消或超时未支付的中间终止状态
合法的转换规则可以用表格梳理:
| 当前状态 | 允许转换的目标状态 |
|---|---|
| 待支付 | 已支付、已取消 |
| 已支付 | 待发货、已取消 |
| 待发货 | 已发货、已取消 |
| 已发货 | 已完成、已取消 |
| 已完成 | 无 |
| 已取消 | 无 |
二、定义核心数据结构
我们先定义订单和状态相关的结构体,方便后续逻辑处理。
package main
import (
"errors"
"fmt"
"sync"
"time"
)
// 订单状态枚举
type OrderStatus string
const (
StatusPendingPay OrderStatus = "pending_pay" // 待支付
StatusPaid OrderStatus = "paid" // 已支付
StatusPendingSend OrderStatus = "pending_send" // 待发货
StatusShipped OrderStatus = "shipped" // 已发货
StatusFinished OrderStatus = "finished" // 已完成
StatusCancelled OrderStatus = "cancelled" // 已取消
)
// 订单结构体
type Order struct {
ID string // 订单ID
Status OrderStatus // 当前状态
CreatedAt time.Time // 创建时间
UpdatedAt time.Time // 最后更新时间
mu sync.RWMutex // 状态修改锁,保证并发安全
}
// 状态变更记录,用于跟踪状态流转历史
type StatusChangeRecord struct {
OrderID string // 订单ID
FromStatus OrderStatus // 变更前状态
ToStatus OrderStatus // 变更后状态
ChangeTime time.Time // 变更时间
Operator string // 操作人
Reason string // 变更原因
}三、实现状态机核心逻辑
状态机需要校验状态转换是否合法,同时保证状态修改的原子性,避免并发场景下出现状态不一致。
// 合法状态转换规则映射
var statusTransRule = map[OrderStatus][]OrderStatus{
StatusPendingPay: {StatusPaid, StatusCancelled},
StatusPaid: {StatusPendingSend, StatusCancelled},
StatusPendingSend: {StatusShipped, StatusCancelled},
StatusShipped: {StatusFinished, StatusCancelled},
StatusFinished: {},
StatusCancelled: {},
}
// 判断状态转换是否合法
func isStatusTransValid(from, to OrderStatus) bool {
allowStatuses, ok := statusTransRule[from]
if !ok {
return false
}
for _, s := range allowStatuses {
if s == to {
return true
}
}
return false
}
// 订单状态变更方法
func (o *Order) ChangeStatus(to OrderStatus, operator, reason string) error {
o.mu.Lock()
defer o.mu.Unlock()
// 校验转换合法性
if !isStatusTransValid(o.Status, to) {
return errors.New(fmt.Sprintf("状态从 %s 转换到 %s 不合法", o.Status, to))
}
// 记录变更前的状态
from := o.Status
// 更新状态和时间
o.Status = to
o.UpdatedAt = time.Now()
// 这里可以添加状态变更记录持久化的逻辑,比如写入数据库
record := StatusChangeRecord{
OrderID: o.ID,
FromStatus: from,
ToStatus: to,
ChangeTime: time.Now(),
Operator: operator,
Reason: reason,
}
fmt.Printf("订单 %s 状态变更: %s -> %s, 操作人: %s, 原因: %s\n",
o.ID, from, to, operator, reason)
return nil
}四、结合Golang并发特性处理状态跟踪
实际业务中,状态变更后可能需要发送通知、更新统计数据等异步操作,我们可以用goroutine和channel来处理这些后置逻辑,避免阻塞主流程。
// 状态变更事件
type StatusChangeEvent struct {
Order *Order
Record StatusChangeRecord
}
// 初始化事件通道,缓冲大小为100,避免阻塞
var eventChan = make(chan StatusChangeEvent, 100)
// 启动事件消费协程,处理状态变更后的异步任务
func startEventConsumer() {
go func() {
for event := range eventChan {
// 这里可以处理各种后置逻辑,比如发送通知、更新统计
handleStatusChangeEvent(event)
}
}()
}
// 处理状态变更事件的具体逻辑
func handleStatusChangeEvent(event StatusChangeEvent) {
order := event.Order
record := event.Record
// 示例:已支付状态发送发货提醒
if record.ToStatus == StatusPaid {
fmt.Printf("订单 %s 已支付,提醒商家尽快发货\n", order.ID)
}
// 示例:已完成状态更新统计数据
if record.ToStatus == StatusFinished {
fmt.Printf("订单 %s 已完成,更新订单完成统计\n", order.ID)
}
}
// 带事件通知的状态变更方法
func (o *Order) ChangeStatusWithNotify(to OrderStatus, operator, reason string) error {
o.mu.Lock()
defer o.mu.Unlock()
if !isStatusTransValid(o.Status, to) {
return errors.New(fmt.Sprintf("状态从 %s 转换到 %s 不合法", o.Status, to))
}
from := o.Status
o.Status = to
o.UpdatedAt = time.Now()
record := StatusChangeRecord{
OrderID: o.ID,
FromStatus: from,
ToStatus: to,
ChangeTime: time.Now(),
Operator: operator,
Reason: reason,
}
// 发送事件到通道,异步处理
eventChan <- StatusChangeEvent{
Order: o,
Record: record,
}
return nil
}五、完整使用示例
下面我们写一个完整的示例,演示从订单创建到状态流转的全过程。
func main() {
// 启动事件消费者
startEventConsumer()
// 创建新订单
order := &Order{
ID: "ORD20240510001",
Status: StatusPendingPay,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// 模拟用户支付,状态从待支付转已支付
err := order.ChangeStatusWithNotify(StatusPaid, "user_123", "用户完成支付")
if err != nil {
fmt.Println("状态变更失败:", err)
}
// 模拟商家发货,状态从已支付转待发货
err = order.ChangeStatusWithNotify(StatusPendingSend, "merchant_456", "商家点击发货")
if err != nil {
fmt.Println("状态变更失败:", err)
}
// 等待异步事件处理完成
time.Sleep(1 * time.Second)
}六、注意事项
实际落地时还需要注意几个问题:
- 状态变更需要保证幂等性,避免重复请求导致状态多次变更
- 高并发场景下可以结合Redis等存储订单状态,减少数据库压力
- 状态变更记录建议持久化存储,方便后续对账和问题排查
- 如果涉及分布式场景,需要引入分布式锁保证状态修改的原子性