如何提前返回JSON成功消息并后台持续执行邮件发送任务?
异步批量发送邮件:立即返回响应,后台持续执行
问题根源
你的代码虽然先输出了JSON响应,但PHP默认的输出缓冲机制会把响应内容暂存,直到整个脚本执行完成(所有邮件发送完毕)才发送给浏览器。同时,若用户关闭页面,HTTP连接断开后,PHP可能终止脚本,导致未发送的邮件中断。
解决方案步骤
1. 立即发送响应并断开HTTP连接
修改响应输出部分,确保浏览器立刻收到JSON,同时让脚本在后台继续执行:
// 清空所有输出缓冲,避免响应被滞留 while (ob_get_level()) { ob_end_clean(); } // 设置响应头 header('Content-Type: application/json'); header('Cache-Control: no-cache, no-store, must-revalidate'); header('Pragma: no-cache'); header('Expires: 0'); // 输出成功响应 echo json_encode(["status" => "success"]); // 强制将输出发送到浏览器 flush(); ob_flush(); // 核心设置:即使客户端断开连接,脚本继续执行 ignore_user_abort(true); // 取消脚本执行时间限制(根据实际发送量调整,0表示无限制) set_time_limit(0); // 断开与客户端的连接(不同环境适配) if (function_exists('fastcgi_finish_request')) { // 推荐:PHP-FPM/Nginx环境下使用此函数,直接结束HTTP请求 fastcgi_finish_request(); } else { // Apache/其他环境:通过关闭连接头实现 header('Connection: Close'); }
2. 修正邮件发送逻辑(修复原代码的计数bug)
原代码中$total_sent的计数逻辑存在错误,会导致多发送一封邮件,同时优化等待逻辑:
$mail = new PHPMailer(true); try { $mail->isSMTP(); $mail->Host = 'smtp.example.net'; $mail->SMTPAuth = true; $mail->Username = 'it.example@example.com'; $mail->Password = 'your_smtp_password'; // 替换为真实SMTP密码 $mail->SMTPSecure = 'TLS'; $mail->Port = 587; $mail->CharSet = 'utf-8'; $mail->setFrom('it.example@example.com', 'IT example'); $limit = 120; $total_sent = 0; // 去重避免重复发送 $to_emails = array_unique($to_emails); foreach ($to_emails as $email) { // 达到发送限制则终止循环 if ($total_sent >= $limit) { break; } $mail->addAddress($email); $mail->Subject = "example"; $mail->Body = "请填写实际邮件内容"; // 替换为真实邮件内容 $mail->isHTML(true); $mail->send(); $mail->clearAddresses(); $total_sent++; // 仅在还有未发送邮件且未达限制时等待 if ($total_sent < count($to_emails) && $total_sent < $limit) { sleep(21); } } } catch (Exception $e) { // 后台脚本无法向浏览器输出错误,需记录到日志文件 $log_path = '/var/log/mail_send_errors.log'; // 替换为你的日志路径 file_put_contents( $log_path, date('Y-m-d H:i:s') . " 发送失败: " . $e->getMessage() . " 邮箱: " . $email . "\n", FILE_APPEND ); }
3. 重要注意事项
- 输出缓冲必须清空:如果遗漏这一步,响应仍会被PHP滞留,直到脚本结束。
- 服务器环境适配:
fastcgi_finish_request()是PHP-FPM环境下的最优方案,若使用Apache mod_php,Connection: Close头也能生效,但可靠性稍差。 - 错误日志必须配置:后台运行的脚本无法向用户反馈错误,一定要将错误信息写入日志文件,方便排查问题。
- 超时配置检查:确保服务器全局配置(如PHP.ini的
max_execution_time、FPM的request_terminate_timeout)允许脚本长时间运行。 - 邮箱去重:原代码可能从不同分组查询到重复邮箱,添加
array_unique($to_emails)避免重复发送。
内容的提问来源于stack exchange,提问作者Emir Karakutuk
相关产品推荐
相关产品推荐

