PHP项目怎样实现订单状态修改?

wen PHP项目 14

本文目录导读:

PHP项目怎样实现订单状态修改?

  1. 数据库设计
  2. 状态机设计(核心逻辑)
  3. 更新订单状态的代码实现
  4. 常见业务场景实现
  5. 最佳实践与安全注意事项
  6. 使用 Laravel 的简化示例

在PHP项目中实现订单状态修改,核心是在数据库中更新订单字段,并配合严谨的逻辑校验操作权限控制

以下是完整的实现方案,从数据库设计到代码实现,再到安全考虑。


数据库设计

通常订单表会有一个状态字段。

CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_sn` varchar(64) NOT NULL COMMENT '订单号',
  `user_id` int(11) NOT NULL COMMENT '用户ID',
  `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '订单状态: 0-待支付, 1-已支付, 2-已发货, 3-已完成, 4-已取消',
  `status_history` text COMMENT '状态变更记录 (JSON格式)',
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

关键点:

  • status 字段用 tinyintvarchar(取决于业务)。
  • status_history 字段:存储状态变更日志,方便追溯(非必须但推荐)。

状态机设计(核心逻辑)

不要允许任意状态跳转,需要定义清晰的状态流转图。

例如一个典型电商订单状态机:

待支付(0) -> 已支付(1) -> 已发货(2) -> 已完成(3)
待支付(0) -> 已取消(4)

对应的PHP代码:

class OrderStatusMachine {
    // 定义允许的状态转换
    const STATUS_FLOW = [
        0 => [1, 4], // 待支付可以变更为已支付 或 已取消
        1 => [2],    // 已支付可以变更为已发货
        2 => [3],    // 已发货可以变更为已完成
        4 => [],     // 已取消后不可变更
        3 => [],     // 已完成不可变更
    ];
    /**
     * 检查状态转换是否允许
     * @param int $currentStatus 当前状态
     * @param int $newStatus 新状态
     * @return bool
     */
    public static function canTransition(int $currentStatus, int $newStatus): bool {
        if (!isset(self::STATUS_FLOW[$currentStatus])) {
            return false;
        }
        return in_array($newStatus, self::STATUS_FLOW[$currentStatus]);
    }
}

更新订单状态的代码实现

使用 PDOMySQLi 操作数据库,这里以 PDO 为例:

<?php
class OrderService {
    private $db; // PDO 实例
    public function __construct(PDO $db) {
        $this->db = $db;
    }
    /**
     * 修改订单状态
     * @param int $orderId 订单ID
     * @param int $newStatus 新状态
     * @param int $operatorId 操作人ID (如管理员ID 或 用户ID)
     * @param string $note 备注
     * @return bool
     * @throws Exception
     */
    public function updateOrderStatus(int $orderId, int $newStatus, int $operatorId, string $note = ''): bool {
        // 1. 开启事务
        $this->db->beginTransaction();
        try {
            // 2. 查询当前订单 (加锁避免并发)
            $sql = "SELECT id, status, status_history FROM orders WHERE id = :order_id FOR UPDATE";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([':order_id' => $orderId]);
            $order = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$order) {
                throw new Exception('订单不存在');
            }
            // 3. 检查状态转换是否允许
            if (!OrderStatusMachine::canTransition($order['status'], $newStatus)) {
                throw new Exception('不允许的状态转换:从 ' . $order['status'] . ' 到 ' . $newStatus);
            }
            // 4. (可选)检查操作权限
            // 此处示例:检查操作者是否是管理员 或 订单所属用户
            // if (!$this->checkPermission($orderId, $operatorId)) {
            //     throw new Exception('无权限操作');
            // }
            // 5. 更新状态 + 记录日志
            $history = $order['status_history'] ? json_decode($order['status_history'], true) : [];
            $history[] = [
                'from'     => $order['status'],
                'to'       => $newStatus,
                'operator' => $operatorId,
                'note'     => $note,
                'time'     => date('Y-m-d H:i:s')
            ];
            $updateSql = "UPDATE orders SET status = :new_status, status_history = :history, updated_at = NOW() WHERE id = :order_id";
            $updateStmt = $this->db->prepare($updateSql);
            $updateStmt->execute([
                ':new_status'  => $newStatus,
                ':history'     => json_encode($history),
                ':order_id'    => $orderId
            ]);
            // 6. 执行后续业务逻辑 (如支付完成后发送邮件、减库存等)
            $this->afterStatusChange($orderId, $order['status'], $newStatus);
            // 7. 提交事务
            $this->db->commit();
            return true;
        } catch (Exception $e) {
            $this->db->rollBack();
            throw $e;
        }
    }
    /**
     * 状态变更后的处理逻辑
     */
    private function afterStatusChange(int $orderId, int $oldStatus, int $newStatus): void {
        switch ($newStatus) {
            case 1: // 已支付
                // 减库存、发送通知等
                // $this->deductStock($orderId);
                // $this->sendPaymentSuccessNotification($orderId);
                break;
            case 2: // 已发货
                // $this->sendShippingNotification($orderId);
                break;
            case 4: // 已取消
                // 恢复库存
                // $this->restoreStock($orderId);
                break;
        }
    }
}
// 使用示例
try {
    $orderService = new OrderService($pdo);
    $result = $orderService->updateOrderStatus(1001, 1, 1, '用户支付成功');
    echo '状态更新成功';
} catch (Exception $e) {
    echo '更新失败: ' . $e->getMessage();
}

常见业务场景实现

场景1:用户取消订单(API接口)

// 控制器层
public function cancelOrder(Request $request) {
    $orderId = $request->get('order_id');
    $userId = $request->user()->id; // 当前登录用户
    try {
        // 检查订单是否属于该用户(权限校验)
        $order = Order::findOrFail($orderId);
        if ($order->user_id != $userId) {
            throw new Exception('无权操作');
        }
        // 调用服务层
        $orderService = new OrderService($pdo);
        $orderService->updateOrderStatus($orderId, 4, $userId, '用户取消订单');
        return response()->json(['success' => true]);
    } catch (Exception $e) {
        return response()->json(['success' => false, 'message' => $e->getMessage()]);
    }
}

场景2:管理员后台发货

public function shipOrder(int $orderId, string $trackingNumber) {
    // 1. 更新状态
    $orderService = new OrderService($pdo);
    $orderService->updateOrderStatus($orderId, 2, $adminId, '发货,单号: ' . $trackingNumber);
    // 2. 保存物流单号到订单表其他字段
    $this->updateTrackingNumber($orderId, $trackingNumber);
}

最佳实践与安全注意事项

使用事务 (beginTransaction + commit / rollBack)

确保状态更新、日志记录、库存操作等数据一致性。

数据库行锁 (FOR UPDATE)

在查询订单时加锁,防止并发导致状态错乱(例如用户同时发起支付和取消)。

权限校验

  • 用户只能操作自己的订单(user_id 匹配)。
  • 管理员可能有更宽泛的权限(发货、退款等)。

状态机验证

  • 不要信任前端传入的new_status,后端必须做严格的转换校验。
  • 建议使用 枚举类常量 管理状态值,避免魔数。

日志记录

  • 可以用 status_history JSON字段记录,也可以单独建表 order_status_logs(更推荐,方便查询)。
  • 记录:操作人、旧状态、新状态、时间、IP、备注。

幂等性处理

  • 如果操作是异步触发的(如支付回调),要确保同一订单状态不会重复修改。
  • 可以在修改前判断当前状态是否已经是目标状态,如果是则直接返回成功。

使用框架的模型事件

如果使用 Laravel、Symfony 等框架,可以利用模型事件(如 updatingsaving)在模型层面自动校验状态转换,保持代码优雅。

抽象成服务类

将订单状态修改逻辑放在 OrderService 中,不要散落在控制器或模型里。

使用 Laravel 的简化示例

如果你使用 Laravel 框架,可以用 Eloquent 模型 + 自定义 trait:

// app/Traits/HasStatusMachine.php
trait HasStatusMachine {
    public static function bootHasStatusMachine() {
        static::updating(function ($model) {
            if ($model->isDirty('status')) {
                $oldStatus = $model->getOriginal('status');
                $newStatus = $model->status;
                if (!OrderStatusMachine::canTransition($oldStatus, $newStatus)) {
                    return false; // 阻止保存
                }
                // 自动记录日志
                $model->status_history = json_encode(
                    array_merge(
                        json_decode($model->status_history ?? '[]', true),
                        [['from' => $oldStatus, 'to' => $newStatus, 'time' => now()]]
                    )
                );
            }
        });
    }
}
// 在 Order 模型中使用
class Order extends Model {
    use HasStatusMachine;
}

实现订单状态修改的核心步骤:

  1. 设计状态流转规则(状态机)。
  2. 使用事务 + 行锁 保证数据一致性。
  3. 严格的权限校验
  4. 记录日志 用于审计。
  5. 执行后续业务逻辑(如支付成功发邮件、取消订单恢复库存)。

抱歉,评论功能暂时关闭!