通过Web管理界面启动去背景脚本致网站卡顿的解决方案咨询
解决PHP CodeIgniter中图片去背景脚本阻塞网站的方案
目前你的问题核心是同步执行耗时脚本导致PHP进程被占用,引发网站卡顿,结合不能给SSH权限、无法用Cronjob的限制,以下是几种可行的异步处理方案:
方案1:直接将脚本后台执行(最简单)
修改CodeIgniter的remove_bg控制器方法,不等待脚本执行完成,而是将脚本放到系统后台运行,立刻给AJAX返回响应。
后端代码修改
public function remove_bg() { // 验证CSRF和用户权限(原有逻辑保留) $file_id = $this->input->post('file_id'); $file = $this->db->where('id', $file_id)->get('files')->row(); if (!$file) { echo json_encode(['status' => 'error', 'msg' => '文件不存在']); return; } // 转义文件路径,避免命令注入 $safe_file_path = escapeshellarg($file->path); $script_path = '/path/to/your/remove_bg_script.sh'; // 用nohup将脚本后台执行,重定向输出避免PHP阻塞 shell_exec("nohup $script_path $safe_file_path > /dev/null 2>&1 &"); // 更新数据库任务状态为「处理中」 $this->db->where('id', $file_id)->update('files', ['status' => 'processing']); echo json_encode(['status' => 'success', 'msg' => '任务已启动,处理中']); }
前端AJAX优化
提交任务后不立刻刷新页面,改为轮询任务状态:
$(document).on('click', '.btn-remove-bg', function () { var $btn = $(this); var file_id = $btn.attr('data-file-id'); var data = { "file_id": file_id }; data[csfr_token_name] = $.cookie(csfr_cookie_name); $btn.prop('disabled', true).text('处理中...'); $.ajax({ type: "POST", url: base_url + "file_controller/remove_bg", data: data, success: function (response) { // 每2秒轮询一次任务状态 var pollTimer = setInterval(function() { $.get(base_url + "file_controller/check_task_status", {file_id: file_id}, function(statusRes) { if (statusRes.status === 'done') { clearInterval(pollTimer); location.reload(); // 处理完成后刷新页面 } else if (statusRes.status === 'error') { clearInterval(pollTimer); $btn.prop('disabled', false).text('去背景(重试)'); alert('处理失败,请重试'); } }); }, 2000); }, error: function() { $btn.prop('disabled', false).text('去背景'); alert('提交失败,请重试'); } }); });
新增状态检查接口
public function check_task_status() { $file_id = $this->input->get('file_id'); $file = $this->db->where('id', $file_id)->get('files')->row(); if (!$file) { echo json_encode(['status' => 'error']); return; } // 脚本执行完成后,需要自行更新数据库status为'done'(比如在shell脚本末尾调用PHP接口) echo json_encode(['status' => $file->status]); }
方案2:使用队列+常驻Worker进程(最可靠)
适合任务量较大、需要控制并发的场景,核心是把任务存入队列,用独立的CLI进程处理任务,不占用Web请求进程。
1. 创建任务队列
用Redis或数据库实现简单队列,这里以Redis为例:
public function remove_bg() { // 验证权限和文件存在性(同上) $file_id = $this->input->post('file_id'); $task_data = json_encode(['file_id' => $file_id]); // 将任务推入Redis队列 $this->load->driver('cache', ['adapter' => 'redis']); $this->cache->redis->rpush('remove_bg_queue', $task_data); // 更新数据库状态 $this->db->where('id', $file_id)->update('files', ['status' => 'pending']); echo json_encode(['status' => 'success', 'msg' => '任务已加入队列']); }
2. 编写CLI Worker脚本
创建application/cli/Task_worker.php:
<?php defined('BASEPATH') OR exit('No direct script access allowed'); class Task_worker extends CI_Controller { public function index() { $this->load->driver('cache', ['adapter' => 'redis']); $this->load->database(); // 常驻循环监听队列 while (true) { // 从队列头部取出任务 $task_json = $this->cache->redis->lpop('remove_bg_queue'); if ($task_json) { $task = json_decode($task_json, true); $file_id = $task['file_id']; $file = $this->db->where('id', $file_id)->get('files')->row(); if ($file) { $this->db->where('id', $file_id)->update('files', ['status' => 'processing']); // 执行去背景脚本 $safe_path = escapeshellarg($file->path); exec("/path/to/remove_bg_script.sh $safe_path"); // 更新状态为完成 $this->db->where('id', $file_id)->update('files', ['status' => 'done']); } } sleep(1); // 每秒检查一次,避免占用过多资源 } } }
3. 启动并守护Worker进程
用命令启动Worker并让它常驻后台:
nohup php index.php task_worker index > /dev/null 2>&1 &
如果担心进程意外退出,可以用supervisor工具管理Worker,配置自动重启。
方案3:使用PCNTL创建子进程(需扩展支持)
如果服务器开启了pcntl扩展,可以通过创建子进程执行脚本,父进程立刻返回响应:
public function remove_bg() { $file_id = $this->input->post('file_id'); $file_path = $this->get_file_path_by_id($file_id); $pid = pcntl_fork(); if ($pid == -1) { echo json_encode(['status' => 'error', 'msg' => '无法启动任务']); } elseif ($pid == 0) { // 子进程:执行脚本并更新状态 exec("/path/to/remove_bg_script.sh " . escapeshellarg($file_path)); $this->db->where('id', $file_id)->update('files', ['status' => 'done']); exit(); // 子进程必须退出 } else { // 父进程:立刻返回 $this->db->where('id', $file_id)->update('files', ['status' => 'processing']); echo json_encode(['status' => 'success']); } }
注意:子进程会复制父进程的数据库连接,可能导致连接数异常,需要在子进程中重新初始化数据库连接。
关键注意事项
- 权限控制:确保Web服务器用户(如
www-data)有执行脚本、读写图片文件的权限。 - 安全防护:所有传入脚本的参数必须用
escapeshellarg转义,防止命令注入;严格验证用户操作权限。 - 状态跟踪:数据库中必须保留任务状态字段(如
pending/processing/done/error),方便前端和后台跟踪进度。
内容的提问来源于stack exchange,提问作者sohapply
相关产品推荐
相关产品推荐

