必须采用“预加载+路由注册+运行时兜底”三层机制:启动时从数据库读取活跃slug映射并注册为显式路由;未命中时才查库兜底;路由变更后需清除缓存、重建并写回routes.php。

要在 CodeIgniter 4 中实现动态生成路由且避免通配符覆盖静态路由、数据库查询失败导致的 404 泛滥、缓存不一致等常见问题,必须绕过单纯依赖 $routes->get('(:any)') 的粗放模式,转而采用“预加载 + 路由注册 + 运行时兜底”三层机制。
启动时预加载并注册全部有效路由
在应用启动阶段(而非每次请求时)从数据库读取所有活跃的 slug → controller/method 映射,直接注册为显式路由,确保 CI4 路由匹配器原生支持、零额外查询、无正则开销。
第一步:在 app/Config/Routes.php 开头引入数据库服务并查询动态路径:
use CodeIgniter\Database\Database;
在 $routes 实例创建后、任何 get() 调用前插入:
$db = \Config\Database::connect();<br>$query = $db->query("SELECT slug, controller, method FROM routes WHERE status = 'active' ORDER BY priority DESC");<br>$dynamicRoutes = $query->getResultArray();
第二步:遍历结果,逐条注册为显式 GET 路由:
foreach ($dynamicRoutes as $route) {<br> $uri = '/' . $route['slug'];<br> $handler = $route['controller'] . '::' . $route['method'];<br> $routes->get($uri, $handler);<br>}
【关键前提】数据库表 routes 必须包含 slug(唯一、URL 安全)、controller(如 BlogController)、method(如 show)、status(active/inactive)和 priority(整型,数值越大越优先匹配)字段;否则注册将失效或引发类名解析错误。
第三步:确保该段代码位于所有手动定义的静态路由之前——因为 CI4 按顺序匹配,高优先级动态路由需先注册,才能压住后续可能冲突的通配符规则。
运行时兜底:仅对未命中路由启用数据库查询
当用户访问一个未被预加载路由覆盖的路径(例如新发布但尚未触发路由重建的 slug),才触发一次性数据库查询,避免每次请求都扫表。
方法一:启用 $routes->set404Override() 并指向专用兜底控制器
在 Routes.php 末尾添加:$routes->set404Override('FallbackController::handle');
方法二:在 FallbackController::handle() 中执行安全校验与精确查询
先验证 URI 格式是否符合 slug 规范(仅含小写字母、数字、短横线):
if (! preg_match('/^[a-z0-9\-]+$/i', $this->request->getUri()->getPath())) {<br> throw new \CodeIgniter\Exceptions\PageNotFoundException();<br>}
再查库,强制要求 status = 'active' 且 slug 字段严格等于当前路径(不含前导斜杠):
$slug = ltrim($this->request->getUri()->getPath(), '/');<br>$post = $model->where(['slug' => $slug, 'status' => 'active'])->first();
若查到,立即调用对应控制器方法(使用 new $controller() + call_user_func_array()),不要重定向——重定向会丢失原始 HTTP 方法及 POST 数据。
路由变更后自动重建缓存
每次新增、编辑或下线动态路由记录时,必须强制刷新已写入 Routes.php 的预加载路由列表,否则新路由永不生效。
在管理后台保存路由记录后,执行以下操作:
① 删除 writable/cache/routes/ 下所有缓存文件(CI4 路由缓存默认存放于此);
② 执行命令行重建:php spark cache:clear && php spark routes;
③ 【不可逆操作】同步更新数据库中该记录的 updated_at 时间戳,并触发一个自定义事件(如 dynamic_route_updated),监听器可自动调用 write_dynamic_routes_to_config() 函数,将最新结果写回 Routes.php 头部注释区下方——此步骤跳过即导致预加载内容陈旧。










