如何在CodeIgniter HMVC中基于子域名实现多租户多数据库的SaaS架构
我之前在CodeIgniter HMVC架构下做过类似的多租户SaaS项目,给你梳理一套可行的实现流程,分步骤来操作:
1. 本地开发:子域名Hosts配置
首先得让本地能访问这些子域名,需要修改系统Hosts文件:
- Linux/Mac 用户打开
/etc/hosts,Windows 用户打开C:\Windows\System32\drivers\etc\hosts - 添加以下三条记录:
127.0.0.1 www.client_1.localhost.com 127.0.0.1 www.client_2.localhost.com 127.0.0.1 www.client_3.localhost.com
保存后,本地访问这些子域名就能指向你的CI项目了。
2. 数据库配置文件改造
在 application/config/database.php 里,除了默认的数据库配置,新增每个客户端的专属数据库配置:
// 默认 fallback 数据库(子域名识别失败时使用) $db['default'] = array( 'dsn' => '', 'hostname' => 'localhost', 'username' => 'root', 'password' => 'your_password', 'database' => 'saas_fallback', 'dbdriver' => 'mysqli', // 其他默认配置(如dbprefix、pconnect等) ); // 客户端1数据库配置 $db['client_1'] = array( 'hostname' => 'localhost', 'username' => 'client1_user', 'password' => 'client1_pass', 'database' => 'client_1_db', 'dbdriver' => 'mysqli', // 同步其他默认配置 ); // 客户端2数据库配置 $db['client_2'] = array( 'hostname' => 'localhost', 'username' => 'client2_user', 'password' => 'client2_pass', 'database' => 'client_2_db', 'dbdriver' => 'mysqli', ); // 客户端3数据库配置 $db['client_3'] = array( 'hostname' => 'localhost', 'username' => 'client3_user', 'password' => 'client3_pass', 'database' => 'client_3_db', 'dbdriver' => 'mysqli', );
注意:生产环境建议给每个客户端分配独立的数据库账号,只授予对应数据库的读写权限,避免权限过大带来的风险。
3. 核心逻辑:子域名解析+自动切换数据库
这里推荐两种方案,选适合你项目的就行:
方案一:使用CodeIgniter钩子(推荐)
钩子能在请求生命周期的特定阶段执行代码,适合全局统一处理:
- 先打开
application/config/config.php,开启钩子:$config['enable_hooks'] = TRUE; - 在
application/config/hooks.php中注册一个post_controller_constructor钩子:$hook['post_controller_constructor'] = array( 'class' => 'DbSwitcher', 'function' => 'switchClientDatabase', 'filename' => 'DbSwitcher.php', 'filepath' => 'hooks', 'params' => array() ); - 创建
application/hooks/DbSwitcher.php文件,编写切换逻辑:class DbSwitcher { public function switchClientDatabase() { $CI =& get_instance(); // 获取当前访问的域名 $currentHost = $_SERVER['HTTP_HOST']; // 解析子域名:从www.client_1.localhost.com中提取client_1 $hostParts = explode('.', $currentHost); // 这里根据你的域名结构调整索引,比如hostParts[1]就是客户端标识 $clientKey = $hostParts[1]; // 检查是否存在对应客户端的数据库配置 if (isset($CI->db->database[$clientKey])) { // 切换到客户端专属数据库 $CI->db = $CI->load->database($clientKey, TRUE); } else { // 切换到默认fallback数据库,也可以抛出404错误 $CI->db = $CI->load->database('default', TRUE); } } }
方案二:HMVC核心控制器继承
如果你的项目所有模块控制器都继承自自定义的核心控制器(比如MY_Controller),可以把切换逻辑写在核心控制器的构造函数里:
class MY_Controller extends CI_Controller { public function __construct() { parent::__construct(); // 初始化时自动切换数据库 $this->switchClientDb(); } private function switchClientDb() { $currentHost = $_SERVER['HTTP_HOST']; $hostParts = explode('.', $currentHost); $clientKey = $hostParts[1]; if (isset($this->db->database[$clientKey])) { $this->db = $this->load->database($clientKey, TRUE); } else { $this->db = $this->load->database('default', TRUE); } } }
这样所有继承MY_Controller的HMVC模块控制器,都会自动使用当前客户端的数据库连接。
4. HMVC模块中的数据库使用
模块里的控制器只要继承了MY_Controller(或者通过钩子全局切换后),直接用$this->db就能操作当前客户端的数据库,和普通CI项目的用法完全一致,不需要额外修改模块代码,保证了代码库的统一性。
5. 生产环境进阶优化
如果后续要新增大量客户端,硬编码数据库配置会很麻烦,建议做以下优化:
- 新建一个主数据库(比如
saas_master_db),专门存储客户端信息(子域名、数据库配置、租户信息等) - 在切换数据库时,先连接主数据库,根据子域名查询对应客户端的数据库配置,再动态加载:
public function switchClientDatabase() { $CI =& get_instance(); // 先连接主数据库 $masterDb = $CI->load->database('master', TRUE); // 获取当前子域名 $currentHost = $_SERVER['HTTP_HOST']; $hostParts = explode('.', $currentHost); $subdomain = $hostParts[1]; // 查询客户端数据库配置 $clientInfo = $masterDb->get_where('tenants', ['subdomain' => $subdomain])->row(); if ($clientInfo) { // 动态构建数据库配置数组 $clientDbConfig = array( 'hostname' => $clientInfo->db_host, 'username' => $clientInfo->db_user, 'password' => $clientInfo->db_pass, 'database' => $clientInfo->db_name, 'dbdriver' => 'mysqli' ); // 加载客户端数据库 $CI->db = $CI->load->database($clientDbConfig, TRUE); } else { $CI->db = $CI->load->database('default', TRUE); } }
这样新增客户端时,只需要在主数据库添加一条记录,不需要修改代码或配置文件,扩展性更强。
内容的提问来源于stack exchange,提问作者user7485162
相关产品推荐
相关产品推荐

