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

