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

通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 20:12:01