You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WooCommerce批量数据端点开发:递归调用失效问题排查

问题排查与解决方案:WooCommerce批量数据同步递归执行失败

问题背景

开发WordPress插件用于WooCommerce批量数据(订单、商品等)导出/API传输,为避免影响站点性能放弃WP-Cron改用递归调用,但递归仅执行一次,仅获取250条记录。

代码展示

端点处理代码

public function getCCBulkSync( $request )
{
    $entity = $_GET['entity'];
    $startDate = $_GET['start_date'];
    $endDate = $_GET['end_date'];
    $delivery_url = $_GET['delivery_url'];
    $limit = $_GET['limit'];
    if(!$delivery_url) return 'Delivery Url cannot be null';
    include 'class-wc-cc-bulk-data-builder.php';
    $bulkBuilder = WC_CC_Bulk_Data_Builder::getInstance();
    $bulkBuilder = ($bulkBuilder === NULL || $bulkBuilder->getObjectDetails()['entity'] === NULL) ? $bulkBuilder->setObjectDetails($entity, $delivery_url, $startDate, $endDate, $limit) : $bulkBuilder;
    return 'Bulk sync initiated! Check later for the completion!!!';
}

批量数据构建类代码

class WC_Bulk_Data_Builder 
{
    private static $instance = NULL;
    protected $entity = NULL;
    protected $startDate = NULL;
    protected $endDate = NULL;
    protected $delivery_url = NULL;
    protected $dataCount = 0;
    protected $limit = 0;

    static public function getInstance()
    {
       if (self::$instance === NULL)
          self::$instance = new WC_Bulk_Data_Builder();
       return self::$instance;
    }

    protected function __construct($entity = NULL, $delivery_url = NULL, $startDate = NULL, $endDate = NULL, $limit = 1000)
    {
        $this->entity = $entity;
        $this->delivery_url = $delivery_url;
        $this->startDate = $startDate;
        $this->endDate = $endDate;
        $this->limit = $limit;
    }

    public function getObjectDetails() {
        $objData = array();
        $objData['entity'] = $this->entity;
        $objData['startDate'] = $this->startDate;
        $objData['endDate'] = $this->endDate;
        $objData['deliveryUrl'] = $this->delivery_url;
        $objData['dataCount'] = $this->dataCount;
        $objData['limit'] = $this->limit;
        return $objData;
    }

    public function setObjectDetails($entity = NULL, $delivery_url = NULL, $startDate = NULL, $endDate = NULL, $limit = 1000) {
        $this->entity = $entity;
        $this->delivery_url = $delivery_url;
        $this->startDate = $startDate;
        $this->endDate = $endDate;
        $this->limit = $limit;
        $this->bulk_data_builder();
        return self::$instance;
    }    

    function bulk_data_builder()
    {
        $entity = $this->entity;
        if($entity === 'order') {
            $this->build_order_data();
        }
        sleep(20);
        $this->bulk_data_builder();
    }

    protected function build_order_data()
    {
        $ordersArray = wc_get_orders( array(
            'limit' => 250,
            'offset' => $this->dataCount,
            'orderby' => 'ID',
            'order' => 'ASC',
        ) );
        $this->dataCount += count($ordersArray);
        $orders = array();
        foreach($ordersArray as $order_index=>$order) {
            $orders[$order_index]=$order->get_data();
            $orders[$order_index]['refunds'] = $order->get_refunds();
        }
        
        $header_args = array_keys($orders[0]);
        $output = fopen( 'order_csv_export.csv', 'a+' );
        fputcsv($output, $header_args);
        foreach($orders AS $order_item){
            foreach($header_args AS $ind=>$key) {
                if('array' === gettype($order_item[$key])) $order_item[$key] = json_encode($order_item[$key]);
            }
            fputcsv($output, $order_item);
        }
        if($dataCount>=$limit)
        {
            $dataCount = 0;
            $req_headers = array();
            $req_headers[] = 'Content-Type: text/csv; charset=utf-8';
            $req_headers[] = 'Content-Disposition: attachment; filename=order_csv_export.csv';
            $cURL = curl_init();
            $setopt_array = array(CURLOPT_URL => $delivery_url,    CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $req_headers); 
            curl_setopt_array($cURL, $setopt_array);
            $json_response_data = curl_exec($cURL);
            curl_close($cURL);
            exit;
        }
        fclose($output);
    }
}

问题原因分析

  1. 请求提前终止:端点方法getCCBulkSync调用bulk_data_builder后立即返回响应,PHP请求在响应发送后直接终止进程,导致递归无法继续执行。
  2. 变量作用域错误:build_order_data中判断if($dataCount>=$limit)时,使用的是未定义的局部变量,而非类属性$this->dataCount,永远无法触发终止条件。
  3. 递归无终止逻辑:bulk_data_builder没有设置终止判断,即使数据量达标,也会无限递归,直到进程被强制终止。
  4. 单例状态残留:单例对象会保留之前的任务状态,多次调用端点会导致数据混乱。

修复方案

1. 改用异步任务触发

PHP同步请求生命周期有限,递归无法持续执行,改用wp_remote_post发起异步请求,避免主进程被终止:

function bulk_data_builder()
{
    $entity = $this->entity;
    $continue = false;
    if($entity === 'order') {
        $continue = $this->build_order_data();
    }

    if ($continue) {
        // 发起异步请求触发下一次任务
        wp_remote_post(
            admin_url('admin-ajax.php'),
            array(
                'timeout'   => 0.01,
                'blocking'  => false,
                'sslverify' => false,
                'body'      => array(
                    'action'        => 'cc_bulk_sync_continue',
                    'entity'        => $this->entity,
                    'delivery_url'  => $this->delivery_url,
                    'start_date'    => $this->startDate,
                    'end_date'      => $this->endDate,
                    'limit'         => $this->limit,
                    'data_count'    => $this->dataCount,
                ),
            )
        );
    }
}

2. 修复变量作用域与终止逻辑

修改build_order_data,使用类属性并添加终止判断,拆分文件发送逻辑:

protected function build_order_data()
{
    if ($this->dataCount >= $this->limit) {
        $this->send_csv_file();
        return false;
    }

    $ordersArray = wc_get_orders( array(
        'limit' => 250,
        'offset' => $this->dataCount,
        'orderby' => 'ID',
        'order' => 'ASC',
        'date_created' => $this->startDate . '...' . $this->endDate,
    ) );

    if (empty($ordersArray)) {
        $this->send_csv_file();
        return false;
    }

    $this->dataCount += count($ordersArray);
    $orders = array();
    foreach($ordersArray as $order_index=>$order) {
        $orders[$order_index] = $order->get_data();
        $orders[$order_index]['refunds'] = $order->get_refunds();
    }
    
    $file_path = wp_upload_dir()['path'] . '/order_csv_export.csv';
    $file_exists = file_exists($file_path);
    $output = fopen($file_path, 'a+');
    
    if (!$file_exists && !empty($orders)) {
        fputcsv($output, array_keys($orders[0]));
    }
    
    foreach($orders AS $order_item){
        foreach(array_keys($orders[0]) AS $key) {
            if(is_array($order_item[$key])) {
                $order_item[$key] = json_encode($order_item[$key]);
            }
        }
        fputcsv($output, $order_item);
    }
    
    fclose($output);

    if ($this->dataCount >= $this->limit) {
        $this->send_csv_file();
        return false;
    }

    return true;
}

protected function send_csv_file()
{
    $file_path = wp_upload_dir()['path'] . '/order_csv_export.csv';
    if (!file_exists($file_path)) return;

    $req_headers = array(
        'Content-Type: text/csv; charset=utf-8',
        'Content-Disposition: attachment; filename=order_csv_export.csv'
    );

    $cURL = curl_init();
    curl_setopt_array($cURL, array(
        CURLOPT_URL => $this->delivery_url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => $req_headers,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => file_get_contents($file_path),
    )); 

    curl_exec($cURL);
    curl_close($cURL);

    unlink($file_path);
    $this->dataCount = 0;
}

3. 注册AJAX处理端点

添加AJAX动作接收异步请求,继续执行任务:

add_action('wp_ajax_cc_bulk_sync_continue', 'cc_bulk_sync_continue_handler');
add_action('wp_ajax_nopriv_cc_bulk_sync_continue', 'cc_bulk_sync_continue_handler');

function cc_bulk_sync_continue_handler()
{
    $entity = $_POST['entity'];
    $delivery_url = $_POST['delivery_url'];
    $startDate = $_POST['start_date'];
    $endDate = $_POST['end_date'];
    $limit = (int)$_POST['limit'];
    $dataCount = (int)$_POST['data_count'];

    include 'class-wc-cc-bulk-data-builder.php';
    $bulkBuilder = WC_CC_Bulk_Data_Builder::getInstance();
    $bulkBuilder->entity = $entity;
    $bulkBuilder->delivery_url = $delivery_url;
    $bulkBuilder->startDate = $startDate;
    $bulkBuilder->endDate = $endDate;
    $bulkBuilder->limit = $limit;
    $bulkBuilder->dataCount = $dataCount;

    $bulkBuilder->bulk_data_builder();
    wp_die();
}

4. 优化端点初始化逻辑

修改getCCBulkSync,清理旧文件并重置计数器:

public function getCCBulkSync( $request )
{
    $entity = $_GET['entity'];
    $startDate = $_GET['start_date'];
    $endDate = $_GET['end_date'];
    $delivery_url = $_GET['delivery_url'];
    $limit = (int)$_GET['limit'];

    if(empty($delivery_url)) {
        return new WP_Error('missing_delivery_url', 'Delivery Url cannot be null', array('status' => 400));
    }

    $file_path = wp_upload_dir()['path'] . '/order_csv_export.csv';
    if (file_exists($file_path)) {
        unlink($file_path);
    }

    include 'class-wc-cc-bulk-data-builder.php';
    $bulkBuilder = WC_CC_Bulk_Data_Builder::getInstance();
    $bulkBuilder->setObjectDetails($entity, $delivery_url, $startDate, $endDate, $limit);

    return 'Bulk sync initiated! Check later for the completion!!!';
}

5. 修复单例状态重置

修改setObjectDetails确保每次调用重置计数器:

public function setObjectDetails($entity = NULL, $delivery_url = NULL, $startDate = NULL, $endDate = NULL, $limit = 1000) {
    $this->entity = $entity;
    $this->delivery_url = $delivery_url;
    $this->startDate = $startDate;
    $this->endDate = $endDate;
    $this->limit = $limit;
    $this->dataCount = 0;
    $this->bulk_data_builder();
    return self::$instance;
}

额外优化建议

  • 权限安全:给临时CSV文件设置合适权限,避免未授权访问
  • 日志记录:添加wc_get_orders和curl_exec的错误日志,便于排查问题
  • 并发控制:添加文件锁机制,防止同一任务被多次触发

内容的提问来源于stack exchange,提问作者Senthilkumar NJSP

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 06:01:07