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

Codeigniter多租户SaaS应用:CLI定时任务动态数据库选择方案咨询

适配Web与CLI的CodeIgniter多租户动态数据库选择方案

我之前做CodeIgniter多租户SaaS的时候也碰到过这个坑——CLI模式下拿不到HTTP_HOST,导致没法自动切换租户数据库。给你几个亲测有效的方案,能完美兼顾Web和CLI场景:

方案1:CLI传参指定租户,Web用域名自动识别

这个方案适合需要单独触发某个租户定时任务的场景,比如只给某客户执行数据备份。

实现步骤:

  1. CLI运行时传租户参数:
    执行Cron任务时通过命令行参数指定租户域名,比如:

    php index.php cron_runner run --tenant=customer1.yourdomain.com
    
  2. 修改数据库配置逻辑:
    在database.php里判断当前是否为CLI模式,分别获取租户标识:

    $ci = get_instance();
    
    // 区分Web和CLI模式获取租户标识
    if (is_cli()) {
        // 从CLI参数读取租户域名
        $tenant_host = $ci->input->get('tenant');
        if (empty($tenant_host)) {
            die("Error: 请通过--tenant参数指定租户域名\n");
        }
    } else {
        // Web模式下读取HTTP_HOST
        $tenant_host = $ci->input->server('HTTP_HOST');
    }
    
    // 从主数据库查询该租户对应的数据库配置
    // 假设主数据库有个`tenants`表,存储域名与数据库信息的映射
    $ci->db->where('domain', $tenant_host);
    $tenant_config = $ci->db->get('tenants')->row();
    
    // 配置租户数据库连接
    $db['default'] = array(
        'dsn' => '',
        'hostname' => $tenant_config->db_host,
        'username' => $tenant_config->db_username,
        'password' => $tenant_config->db_password,
        'database' => $tenant_config->db_name,
        'dbdriver' => 'mysqli',
        'dbprefix' => '',
        'pconnect' => FALSE,
        'db_debug' => (ENVIRONMENT !== 'production'),
        'cache_on' => FALSE,
        'cachedir' => '',
        'char_set' => 'utf8',
        'dbcollat' => 'utf8_general_ci',
        'swap_pre' => '',
        'encrypt' => FALSE,
        'compress' => FALSE,
        'stricton' => FALSE,
        'failover' => array(),
        'save_queries' => TRUE
    );
    

方案2:批量遍历所有活跃租户执行任务

如果你的定时任务需要给所有租户统一执行(比如每日数据统计),这个方案更高效——一次Cron调用处理所有租户,不用逐个传参。

实现步骤:

  1. 修改CronRunner逻辑:
    先从主数据库获取所有活跃租户,然后循环切换数据库执行任务:

    class CronRunner {
        var $ci;
        var $ci_cron_db;
        var $ci_cron_table;
        // 新增主数据库连接属性,避免被租户连接覆盖
        var $main_db;
    
        public function __construct() {
            $this->ci = & get_instance();
            $this->ci->load->helper('url');
            $this->ci->load->library('user_agent');
            // CLI模式下不需要加载session,避免无效初始化
            if (!is_cli()) {
                $this->ci->load->library('session');
            }
            // 先连接主数据库(单独配置一个main连接,存储租户信息)
            $this->main_db = $this->ci->load->database('main', TRUE);
            $this->ci_cron_table = 'crn_crons';
        }
    
        private function calculateNextRun($obj) {
            return (time() + $obj->interval_sec);
        }
    
        // 新增切换租户数据库的方法
        private function switch_to_tenant_db($tenant) {
            $db_config = array(
                'hostname' => $tenant->db_host,
                'username' => $tenant->db_username,
                'password' => $tenant->db_password,
                'database' => $tenant->db_name,
                'dbdriver' => 'mysqli',
                'dbprefix' => '',
                'pconnect' => FALSE,
                'db_debug' => (ENVIRONMENT !== 'production'),
                'char_set' => 'utf8',
                'dbcollat' => 'utf8_general_ci'
            );
            // 初始化租户数据库连接
            $this->ci_cron_db = $this->ci->load->database($db_config, TRUE);
        }
    
        public function run() {
            // 从主数据库获取所有活跃租户
            $this->main_db->where('is_active', 1);
            $tenants = $this->main_db->get('tenants')->result();
    
            foreach ($tenants as $tenant) {
                // 切换到当前租户的数据库
                $this->switch_to_tenant_db($tenant);
    
                // 查询当前租户需要执行的定时任务
                $query = $this->ci_cron_db->where('is_active', 1)
                                          ->where('now() >= next_run_at OR next_run_at IS NULL', '', false)
                                          ->from($this->ci_cron_table)
                                          ->get();
    
                if ($query->num_rows() > 0) {
                    $env = getenv('CI_ENV');
                    foreach ($query->result() as $row) {
                        $cmd = "export CI_ENV={$env} && {$row->command}";
                        // 更新下次执行时间
                        $this->ci_cron_db->set('next_run_at', 'FROM_UNIXTIME(' . $this->calculateNextRun($row) . ')', false)
                                         ->where('id', $row->id)
                                         ->update($this->ci_cron_table);
                        // 执行任务
                        $output = shell_exec($cmd);
                        // 更新最后执行时间
                        $this->ci_cron_db->set('last_run_at', 'now()', false)
                                         ->where('id', $row->id)
                                         ->update($this->ci_cron_table);
                    }
                }
            }
        }
    }
    
  2. 配置主数据库连接:
    在database.php里新增一个main连接,专门用来存储租户信息:

    $db['main'] = array(
        'dsn' => '',
        'hostname' => '你的主数据库地址',
        'username' => '主数据库用户名',
        'password' => '主数据库密码',
        'database' => '主数据库名称',
        'dbdriver' => 'mysqli',
        // 其他默认配置...
    );
    

方案3:通过环境变量传递租户信息

如果不想用命令行参数,也可以用环境变量传递租户标识,更隐蔽。

实现步骤:

  1. Cron表达式中设置环境变量:

    * * * * * CI_TENANT=customer1.yourdomain.com php /path/to/your/app/index.php cron_runner run
    
  2. 代码中读取环境变量:
    在database.php里修改租户标识获取逻辑:

    $ci = get_instance();
    
    if (is_cli()) {
        $tenant_host = getenv('CI_TENANT');
        if (empty($tenant_host)) {
            die("Error: 请设置CI_TENANT环境变量指定租户域名\n");
        }
    } else {
        $tenant_host = $ci->input->server('HTTP_HOST');
    }
    
    // 后续查询租户配置、设置数据库连接的逻辑和方案1一致
    

额外注意事项:

  • 主数据库连接隔离:一定要把存储租户信息的主数据库和租户业务数据库分开配置,避免切换租户时覆盖主连接。
  • CLI模式下的无用库加载:像session这类Web专属的库,在CLI模式下不要加载,避免出现初始化错误。
  • 错误处理:不管用哪个方案,都要加参数/环境变量的校验,避免无租户标识时任务异常。

内容的提问来源于stack exchange,提问作者Daniel Felix

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:23:07