codeigniter可通过tenant_id贯穿模型、路由与会话实现多商户系统。需在basecontroller注入tenant_id,所有model查询强制where绑定,权限校验实时查库,子域名或路径前缀路由动态映射商户,并确保日志、缓存、url等全链路携带tenant_id。

CodeIgniter 本身不内置多商户或多租户支持,但通过合理设计数据库结构、路由隔离和会话作用域,完全能支撑稳定运行的多商户入驻系统。关键不在“有没有功能”,而在“怎么把 tenant_id 绑定到每层操作”。
商户数据隔离:tenant_id 必须贯穿 Model 和 Query
多商户系统最常踩的坑是「查到别人的数据」——根源往往是 Model 层漏掉了 WHERE tenant_id = ? 条件。
- 所有涉及商户资源的 Model 方法(如
get_products()、get_orders())必须显式接收或从会话中读取当前tenant_id - 不要依赖全局变量或配置项硬编码
tenant_id;推荐在 BaseController 中统一提取并注入:$this->tenant_id = $this->session->userdata('tenant_id'); - 数据库查询务必使用 CI4 的
where()链式调用绑定,避免拼接 SQL 字符串;CI3 用户注意$this->db->escape()不能替代参数化查询 - 如果用数据库驱动做租户隔离(如按 schema 分库),需重写
DB_driver类的database属性切换逻辑,但文件/Redis 会话驱动不受影响
子账号权限控制:别只靠 session userdata
子账号(如商户后台员工)登录后,仅存 user_id 和 tenant_id 远不够——必须实时校验其角色与操作权限,否则越权访问风险极高。
- 每次请求进 Controller 前,在
__construct()或beforeFilter()(CI4)中调用权限检查方法,例如:if (!$this->auth->hasPermission('product:edit')) { throw new \CodeIgniter\Exceptions\PageNotFoundException(); } - 权限判断不能只查缓存或 session;建议每次验证时查一次
user_permissions关联表(或 Redis 中预热的权限集),防止管理员修改权限后延迟生效 - 子账号的
sess_cookie_name应与主账号区分开(如设为ci_subsession),避免同域下 Cookie 冲突导致误登 - 登出子账号时,必须调用
$this->session->sess_destroy(),且清空关联的flashdata和自定义 session key(如last_login_ip)
URL 路由与子域名适配:/m/{slug} 还是 m.example.com?
CI 框架默认不解析子域名,但可通过 $_SERVER['HTTP_HOST'] 提取并映射到对应商户,这是低成本实现多租户的关键入口。
- 若用路径前缀(如
/m/acme-inc/dashboard),需在routes.php中加通配符路由:$routes->group('m/(:any)', ['namespace' => 'App\Controllers\Merchant'], function($routes) { $routes->add('', 'Dashboard::index'); });并在 BaseController 中解析$slug查库获取tenant_id - 若用子域名(如
acme-inc.example.com),在app/Config/App.php中设置$baseURL为动态值,并在中间件中根据HTTP_HOST查询商户记录;注意 Apache/Nginx 需开启泛域名解析 - 无论哪种方式,生成跳转链接时必须用
site_url()+ 动态前缀,禁止硬编码 URL 字符串,否则生成的链接会丢失商户上下文
真正难的不是写完入驻流程,而是确保每个 API 响应、每条日志、每次缓存键都带上 tenant_id ——少一处,就可能泄露数据。上线前务必用非生产商户账号跑一遍全链路,重点测跨商户的搜索、导出、通知类操作。











