Woocommerce发送PDF门票代码评估及并发购买问题咨询
代码规范分析
整体代码逻辑清晰,但存在不少可以优化的点以及潜在bug:
- 变量命名可读性:部分变量命名可以更语义化,比如
$pdf_count可改为$total_required_tickets,$pdf_attachments改为$selected_ticket_paths,提升代码维护性。 - 边界处理缺失:
- 未检查
Tickets和UsedTickets文件夹是否存在、是否有读写权限,若文件夹缺失会直接报错。 - 未处理
glob()返回空数组的情况(即Tickets文件夹无PDF),后续数组索引操作会触发致命错误。 - 订单商品数量判断
$order->get_item_count()冗余,即使订单无商品,后续循环也不会执行,反而可能误过滤有非目标商品的订单。
- 未检查
- 索引计算bug:原代码中
$pdf_files[ $pdf_count - $item->get_quantity() + $i % count( $pdf_files ) ]的括号位置错误,$i % count($pdf_files)会先运算,导致索引可能超出数组长度,正确写法应为$pdf_files[ ($pdf_count - $item->get_quantity() + $i) % count( $pdf_files ) ]。 - 附件逻辑漏洞:无论文件移动是否成功,都将
$new_path加入附件列表,若移动失败,该路径不存在会导致邮件附件缺失。
多用户并发下单的问题
会出现严重的重复发券或发券失败问题:
当多个用户同时下单时,两个请求会同时执行glob()获取相同的PDF文件列表;在第一个请求还未完成文件移动时,第二个请求已经选中了同一份PDF;最终导致两个订单发送同一张门票,第二个请求的rename()操作会因文件已被移走而失败,且邮件会添加无效的附件路径。
本质原因是代码没有实现原子性操作,选取文件和移动文件的过程未加锁,无法保证同一时间只有一个请求能操作某份PDF资源。
优化方案
- 修复索引计算bug
// 替换原索引计算代码 $index = ($pdf_count - $item->get_quantity() + $i) % count($pdf_files); $pdf_path = $pdf_files[$index];
- 添加边界检查
$tickets_dir = get_template_directory() . '/Tickets/'; $used_dir = get_template_directory() . '/UsedTickets/'; // 检查文件夹是否存在 if (!is_dir($tickets_dir) || !is_dir($used_dir)) { error_log('Tickets或UsedTickets文件夹不存在'); return $attachments; } $pdf_files = glob($tickets_dir . '*.pdf'); // 检查是否有可用PDF if (empty($pdf_files)) { error_log('无可用PDF门票'); return $attachments; }
- 解决并发问题(文件锁方案)
通过排他锁确保同一时间只有一个请求能操作门票文件:
$lock_file = $tickets_dir . 'ticket_lock.txt'; $lock_handle = fopen($lock_file, 'w'); // 尝试获取排他锁,失败则等待后重试 if (!flock($lock_handle, LOCK_EX | LOCK_NB)) { if (!flock($lock_handle, LOCK_EX)) { error_log('无法获取门票操作锁'); fclose($lock_handle); return $attachments; } } // 此处执行选取PDF、移动文件的逻辑 // 操作完成后释放锁 flock($lock_handle, LOCK_UN); fclose($lock_handle);
- 优化附件添加逻辑
仅在文件移动成功时,才将路径加入附件:
if (rename($pdf_path, $new_path)) { error_log('PDF已移至UsedTickets文件夹: ' . $pdf_filename); $attachments[] = $new_path; } else { error_log('移动PDF失败: ' . $pdf_filename); }
内容的提问来源于stack exchange,提问作者Beze
相关产品推荐
相关产品推荐

