PHP订单超时关闭怎么实现

wen PHP项目 4

本文目录导读:

PHP订单超时关闭怎么实现

  1. 定时任务方案(推荐)
  2. 延时任务方案(Redis)
  3. 懒处理方案
  4. 使用消息队列
  5. 最佳实践建议
  6. 注意事项

实现PHP订单超时关闭主要有以下几种方案,我来详细介绍:

定时任务方案(推荐)

使用Cron Job定时扫描

<?php
// order_timeout.php
class OrderTimeoutHandler {
    private $db;
    private $timeoutMinutes = 30; // 超时时间(分钟)
    public function __construct($db) {
        $this->db = $db;
    }
    /**
     * 关闭超时订单
     */
    public function closeTimeoutOrders() {
        $timeLimit = date('Y-m-d H:i:s', strtotime("-{$this->timeoutMinutes} minutes"));
        try {
            // 开启事务
            $this->db->beginTransaction();
            // 查找超时订单
            $sql = "SELECT id FROM orders 
                    WHERE status = 'pending' 
                    AND created_at < ? 
                    AND created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)
                    FOR UPDATE";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$timeLimit]);
            $orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
            if (count($orders) > 0) {
                $orderIds = array_column($orders, 'id');
                // 批量更新订单状态
                $inQuery = implode(',', array_fill(0, count($orderIds), '?'));
                $updateSql = "UPDATE orders 
                              SET status = 'closed', 
                                  closed_at = NOW(),
                                  close_reason = 'timeout'
                              WHERE id IN ($inQuery)";
                $updateStmt = $this->db->prepare($updateSql);
                $updateStmt->execute($orderIds);
                // 记录日志
                $this->logTimeoutOrders($orderIds);
            }
            // 提交事务
            $this->db->commit();
            echo "成功处理 " . count($orders) . " 个超时订单\n";
        } catch (Exception $e) {
            $this->db->rollBack();
            error_log("处理订单超时失败: " . $e->getMessage());
        }
    }
    /**
     * 记录超时日志
     */
    private function logTimeoutOrders($orderIds) {
        $logData = [
            'time' => date('Y-m-d H:i:s'),
            'order_ids' => implode(',', $orderIds),
            'action' => 'auto_close'
        ];
        file_put_contents(
            '/var/log/order_timeout.log', 
            json_encode($logData) . "\n", 
            FILE_APPEND
        );
    }
}
// 定时任务入口
// crontab 设置:*/5 * * * * php /path/to/order_timeout.php
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$handler = new OrderTimeoutHandler($db);
$handler->closeTimeoutOrders();
?>

延时任务方案(Redis)

使用Redis延时队列

<?php
// Redis延时队列实现
class OrderTimeoutQueue {
    private $redis;
    private $queueKey = 'order:timeout:queue';
    public function __construct($redis) {
        $this->redis = $redis;
    }
    /**
     * 添加订单到延时队列
     */
    public function addToQueue($orderId, $timeoutSeconds) {
        $score = time() + $timeoutSeconds;
        return $this->redis->zAdd($this->queueKey, $score, $orderId);
    }
    /**
     * 处理超时订单
     */
    public function processTimeoutOrders() {
        $now = time();
        // 获取所有超时的订单
        $timeoutOrderIds = $this->redis->zRangeByScore(
            $this->queueKey, 
            0, 
            $now
        );
        if (!empty($timeoutOrderIds)) {
            // 从队列中移除
            $this->redis->zRem($this->queueKey, ...$timeoutOrderIds);
            // 批量处理订单
            $this->closeOrders($timeoutOrderIds);
            echo "处理了 " . count($timeoutOrderIds) . " 个超时订单\n";
        }
    }
    /**
     * 关闭订单
     */
    private function closeOrders($orderIds) {
        // 数据库操作
        $pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
        $inQuery = implode(',', array_fill(0, count($orderIds), '?'));
        $sql = "UPDATE orders 
                SET status = 'closed', 
                    closed_at = NOW()
                WHERE id IN ($inQuery) 
                AND status = 'pending'"; // 防止重复处理
        $stmt = $pdo->prepare($sql);
        $stmt->execute($orderIds);
        echo "订单 " . implode(',', $orderIds) . " 已关闭\n";
    }
    /**
     * 取消订单超时任务
     */
    public function cancelTimeoutTask($orderId) {
        return $this->redis->zRem($this->queueKey, $orderId);
    }
}
// 使用示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$queue = new OrderTimeoutQueue($redis);
// 创建订单时
$orderId = 12345;
$timeoutSeconds = 30 * 60; // 30分钟
$queue->addToQueue($orderId, $timeoutSeconds);
// 定期执行(可配合crontab)
// */1 * * * * php /path/to/process_timeout.php
$queue->processTimeoutOrders();
?>

懒处理方案

用户访问时检查

<?php
class OrderTimeoutLazyHandler {
    /**
     * 检查订单是否超时(在查询订单时调用)
     */
    public function checkOrderTimeout($orderId) {
        $pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
        // 查询订单信息
        $sql = "SELECT id, status, created_at, 
                TIMESTAMPDIFF(MINUTE, created_at, NOW()) as elapsed_minutes
                FROM orders 
                WHERE id = ?";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$orderId]);
        $order = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($order && $order['status'] == 'pending') {
            // 如果超过30分钟,自动关闭
            if ($order['elapsed_minutes'] >= 30) {
                $this->closeOrder($orderId);
                return 'closed';
            }
        }
        return $order['status'] ?? 'not_found';
    }
    /**
     * 关闭订单
     */
    private function closeOrder($orderId) {
        $pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
        $sql = "UPDATE orders 
                SET status = 'closed', 
                    closed_at = NOW(),
                    close_reason = 'timeout'
                WHERE id = ? AND status = 'pending'";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$orderId]);
    }
    /**
     * 批量检查过期订单(可选)
     */
    public function cleanupExpiredOrders() {
        $pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
        $sql = "UPDATE orders 
                SET status = 'closed', 
                    closed_at = NOW(),
                    close_reason = 'timeout'
                WHERE status = 'pending' 
                AND created_at < DATE_SUB(NOW(), INTERVAL 30 MINUTE)";
        $affected = $pdo->exec($sql);
        echo "清理了 {$affected} 个过期订单\n";
    }
}
// 使用示例
$handler = new OrderTimeoutLazyHandler();
$status = $handler->checkOrderTimeout(12345);
?>

使用消息队列

RabbitMQ延时队列

<?php
// RabbitMQ延时队列实现
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Exchange\AMQPExchangeType;
class RabbitMQOrderTimeout {
    private $channel;
    public function __construct() {
        $connection = new AMQPStreamConnection('localhost', 5672, 'user', 'pass');
        $this->channel = $connection->channel();
        // 声明延时交换机
        $this->channel->exchange_declare(
            'order_timeout_exchange', 
            AMQPExchangeType::DIRECT, 
            false, 
            true, 
            false
        );
        // 声明队列
        $this->channel->queue_declare(
            'order_timeout_queue', 
            false, 
            true, 
            false, 
            false,
            false,
            [
                'x-dead-letter-exchange' => ['S', 'order_close_exchange'],
                'x-dead-letter-routing-key' => ['S', 'order.close']
            ]
        );
    }
    /**
     * 添加订单超时任务
     */
    public function addTimeoutTask($orderId, $delayMs) {
        $message = new AMQPMessage(
            json_encode(['order_id' => $orderId]),
            [
                'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
                'expiration' => $delayMs // 延时时间(毫秒)
            ]
        );
        $this->channel->basic_publish(
            $message, 
            'order_timeout_exchange', 
            'order.timeout'
        );
    }
    /**
     * 处理超时订单的消费者
     */
    public function consumeTimeoutOrders() {
        $this->channel->queue_declare(
            'order_close_queue',
            false,
            true,
            false,
            false
        );
        $callback = function($msg) {
            $data = json_decode($msg->body, true);
            $orderId = $data['order_id'];
            // 关闭订单逻辑
            $pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
            $sql = "UPDATE orders 
                    SET status = 'closed', closed_at = NOW()
                    WHERE id = ? AND status = 'pending'";
            $stmt = $pdo->prepare($sql);
            $stmt->execute([$orderId]);
            echo "订单 {$orderId} 已超时关闭\n";
        };
        $this->channel->basic_consume(
            'order_close_queue', 
            '', 
            false, 
            true, 
            false, 
            false, 
            $callback
        );
        while (count($this->channel->callbacks)) {
            $this->channel->wait();
        }
    }
}
// 使用示例
$handler = new RabbitMQOrderTimeout();
$handler->addTimeoutTask(1001, 30 * 60 * 1000); // 30分钟后处理
?>

最佳实践建议

综合方案配置

<?php
// 综合解决方案:结合多种机制
class OrderTimeoutManager {
    /**
     * 创建订单时启动超时管理
     */
    public function createOrder($orderData) {
        $timeoutConfig = [
            'type' => $this->getTimeoutType(), // 根据业务选择
            'duration' => 30 * 60 // 30分钟
        ];
        // 1. 在订单记录中存储状态
        $orderId = $this->saveOrder($orderData);
        // 2. 根据系统配置选择超时处理方式
        switch ($timeoutConfig['type']) {
            case 'cron':
                $this->handleWithCron($orderId);
                break;
            case 'redis':
                $this->handleWithRedis($orderId, $timeoutConfig['duration']);
                break;
            case 'lazy':
                // 懒处理不需要额外操作
                break;
        }
        return $orderId;
    }
    /**
     * 根据订单状态决定是否需要关闭
     */
    public function getOrderStatus($orderId) {
        $pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
        // 查询订单信息
        $sql = "SELECT orders.*, 
                TIMESTAMPDIFF(MINUTE, orders.created_at, NOW()) as elapsed,
                CASE 
                    WHEN orders.status = 'pending' 
                         AND TIMESTAMPDIFF(MINUTE, orders.created_at, NOW()) > 30 
                    THEN 'auto_closed'
                    ELSE orders.status 
                END as actual_status
                FROM orders 
                WHERE id = ?";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$orderId]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }
}
// 定时任务状态同步
class OrderStatusSync {
    public function syncStatus() {
        // 定时检查dirty订单状态
        $pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
        // 批量同步
        $sql = "UPDATE orders 
                SET status = 'closed', closed_at = NOW()
                WHERE status IN ('pending', 'processing')
                AND created_at < DATE_SUB(NOW(), INTERVAL 30 MINUTE)
                AND (close_attempted = 0 OR close_attempted IS NULL)";
        $affected = $pdo->exec($sql);
        // 记录同步日志
        if ($affected > 0) {
            echo "同步了 {$affected} 个订单状态\n";
        }
    }
}
?>

注意事项

性能和可靠性考虑

<?php
// 性能优化和可靠性保障
class OrderTimeoutOptimizer {
    /**
     * 批量处理优化
     */
    public function batchProcess() {
        $limit = 1000; // 每次处理数量
        while (true) {
            $orders = $this->getTimeoutOrders($limit);
            if (empty($orders)) {
                break;
            }
            // 处理订单
            $this->processBatch($orders);
            // 防止无限循环
            usleep(100000); // 100ms
        }
    }
    /**
     * 订单状态变更通知
     */
    public function notifyStatusChange($orderId) {
        $message = [
            'type' => 'order_timeout',
            'order_id' => $orderId,
            'timestamp' => time()
        ];
        // 推送通知给用户
        $this->sendNotification($orderId, '订单超时未支付,已自动关闭');
        // 记录日志
        $this->logAction("订单 {$orderId} 超时关闭");
    }
    /**
     * 异常处理机制
     */
    public function handleException($orderId, $error) {
        // 记录错误日志
        error_log("处理订单 {$orderId} 超时时发生错误: " . $error);
        // 重试机制
        $retryCount = 0;
        while ($retryCount < 3) {
            try {
                $this->closeOrder($orderId);
                break;
            } catch (Exception $e) {
                $retryCount++;
                sleep(5);
            }
        }
    }
    /**
     * 订单超时通知模板
     */
    private function sendNotification($orderId, $message) {
        $users = $this->getOrderUsers($orderId);
        foreach ($users as $user) {
            // 发送微信、短信、邮件等通知
        }
    }
}
?>

推荐使用定时任务方案,因为:

  • 实现简单,维护方便
  • 批量处理效率高
  • 可以记录详细的处理日志
  • 容易监控和调试

对于高并发场景,可以使用Redis延时队列,支持更精确的控制,根据具体业务需求,也可以结合多种方案使用。

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