docs(rules): 新增文件级覆盖机制规则并修正索引职责互斥

- 新增 ulthon-file-override-mechanism.md:阐述 app/ 覆盖 extend/base/ 的三类机制(视图模板、include 标签、app_file_path)与不支持覆盖的反例清单
- ulthon-base-app-architecture:升格原孤立描述为独立小节"5. 文件级覆盖机制"
- ulthon-timer:在 app_file_path 覆盖说明处增加规则引用
- AGENTS.md:登记新规则到框架内置规则索引
- ulthon-rules-manager:修订索引职责(AGENTS.md 仅 ulthon-、PROJECT.md 仅 project-,互斥不重复维护);索引格式从 4 列改为 3 列与项目实际一致;维护规则补充对称约束;泛化"迁移现有内容"章节
This commit is contained in:
augushong
2026-07-19 00:30:05 +08:00
parent 073172473b
commit 46f0d62852
5 changed files with 130 additions and 14 deletions

View File

@@ -0,0 +1,94 @@
# 文件级覆盖机制app/ 覆盖 extend/base/
> 来源框架内置ulthon-
> 作用域:所有"框架默认文件 → 业务同名文件"的覆盖场景视图模板、include 标签、`app_file_path()` 加载的 PHP/配置/模板)
> 触发条件:需要替换框架内置视图/模板/跳转页/异常页/定时任务配置/CURD 模板/版本升级钩子时加载
## 规则内容
### 概述
Ulthon Admin 在"类继承覆盖"之外,还提供"文件级覆盖"能力:业务侧在 `app/` 下放置与 `extend/base/` 同路径、同名的文件,框架运行时会优先加载 app 版本。
该机制只对**特定类型**的文件生效,不是所有文件都支持。覆盖能力分三类:
- 机制一:视图模板覆盖(最核心、最隐蔽,控制器 `fetch()` 自动生效)
- 机制二:模板内 `{include}` 标签覆盖(与机制一同源)
- 机制三:`app_file_path()` 辅助函数显式覆盖(需调用方主动使用)
### 机制一:视图模板覆盖
- 实现位置:`extend/think/view/driver/Think.php:160-257``parseTemplate()` 方法
- 不走 `app_file_path`,由视图驱动自身完成三级回退查找:
1. `app/<module>/view/<template>.html`最高优先app 应用视图)
2. `extend/base/<module>/view/<template>.html`(框架兜底视图)
3. `<root>/view/<template>.html`(根目录全局视图,最低优先)
- 触发场景:所有控制器 `fetch()` 渲染视图时(经 `ViewBase::fetch()``think.View::fetch()``Think::fetch()``parseTemplate()`
- 业务侧用法:在 `app/<module>/view/` 下放同名 `.html` 即可覆盖框架视图
### 机制二:模板内 `{include}` 标签覆盖
- 实现位置:`extend/think/Template.php:1192-1248``parseTemplateFile()` 方法
- 查找逻辑:与机制一相同的三级查找(`app_path` / `base_app_path` / `view_app_path`,由 `Think.php:196-198` 注入到 Template 实例)
- 前缀规则:
- `@/` 开头:从根目录定位(三级路径都设为同一个 `view_root_path`
- `@` 开头:从控制器视图目录定位(用 `base_view_path`
- `./` 开头:相对当前模板文件目录
- 无前缀:走完整三级查找
- 触发场景:模板编译时解析 `{include file="..."}` 标签
### 机制三:`app_file_path()` 辅助函数
- 定义位置:`extend/base/helper.php:392-400`
- 加载入口:`app/common.php:6`(唯一 include 点;这是 AGENTS.md 中明确的全局辅助函数唯一特许)
- 查找层级(两级回退):
1. `app/<相对路径>`(最高优先)
2. `extend/base/<相对路径>`(兜底)
- 函数签名约束(来自源码注释):
- 第一个参数 `$file_path` 不要以 `/` 开头
- 不需要以 `app` 开头
- 函数内部会自动定位 `app/``extend/base/`
#### 已知调用点(业务侧可在 app/ 同路径下覆盖其中任一)
| 调用点 | 相对路径参数 | 文件类型 | 业务意义 |
|---|---|---|---|
| `config/app.php:35` | `common/tpl/think_exception(_debug).tpl` | 异常模板 | 调试异常页 |
| `config/app.php:37,39` | `common/tpl/dispatch_jump.tpl` | 跳转模板 | success/error 跳转页 |
| `TimerServiceBase:19,71` | `common/command/timer/config.php` | 配置数组 | 定时任务列表(可被业务定制) |
| `AdminUpdateCodeServiceBase:23` | `admin/service/adminUpdateCodeData/{version}.php` | PHP 钩子文件 | 版本升级时执行的迁移代码 |
| `MigrateBase:207` | `common/command/curd/migrate.tpl` | 视图模板 | CURD 生成迁移代码的模板 |
### 反例:以下场景不支持文件级覆盖
下列路径与机制都不走 app/base 覆盖,业务侧在 `app/` 下放同名文件**不会**生效:
- `AdminInitServiceBase::requireData()``extend/base/admin/service/AdminInitServiceBase.php:139`):用 `__DIR__` 直接定位 `extend/base/admin/service/adminInitData/`,不支持 app 覆盖
- 命令注册(`config/console.php`):空 `commands` 数组,命令通过 SPI 自动扫描 `app/common/command/`,不走覆盖
- 语言包(`config/lang.php`):标准 ThinkPHP 机制,不支持 app/base 覆盖
- 路由(`route/app.php`):空文件,无覆盖机制
- 静态资源(`public/static/`Filesystem disk 加载,无覆盖机制
- 配置目录(`config/*`):标准 ThinkPHP 配置加载,无 app/base 覆盖机制
### 与"类继承覆盖"的关系
类继承层面的 Base/App 覆盖(如 `app\<Class>` extends `base\<Class>Base`)属于**另一套机制**,详见技能:[ulthon-base-app-architecture](../skills/ulthon-base-app-architecture/SKILL.md)。本规则只讲"文件级"覆盖,不涉及类的继承重写。
### 业务侧使用范例
- 覆盖异常页样式:在 `app/common/tpl/think_exception.tpl` 放同名文件
- 覆盖定时任务配置:在 `app/common/command/timer/config.php` 放同名文件(实际项目已在用)
- 覆盖某控制器视图:在 `app/admin/view/<controller>/<action>.html` 放同名文件
- 覆盖 CURD 迁移模板:在 `app/common/command/curd/migrate.tpl` 放同名文件
## 相关文件
- `extend/base/helper.php``app_file_path()` 定义)
- `app/common.php`(辅助函数唯一加载入口)
- `extend/think/view/driver/Think.php`(视图驱动 `parseTemplate()`
- `extend/think/Template.php`(模板编译 `parseTemplateFile()`
## 相关技能
- [ulthon-base-app-architecture](../skills/ulthon-base-app-architecture/SKILL.md)
- [ulthon-timer](../skills/ulthon-timer/SKILL.md)(使用 `app_file_path` 加载定时器配置)

View File

@@ -87,13 +87,24 @@ description: "详细说明了 Base/App 双层架构的设计理念、三层结
- **辅助函数**:放在 `extend/base/helper.php`,通过 `app/common.php` 引入。
- **初始化数据**:放在 `extend/base/adminInitData/`(带 @internal-framework 注解标记)。
- **版本更新代码**:放在 `extend/base/adminUpdateCodeData/`(带 @internal-framework 注解标记)。
- 静态文件/模板/配置支持分层加载:优先加载 `app/`,不存在时再回落到框架默认实现(例如 `app_file_path`)。
### 4. 核心层维护原则
- 稳定性优先:保证向下兼容
- 通用性优先:不引入具体业务逻辑
### 5. 文件级覆盖机制
除"类继承覆盖"外,框架还支持**文件级覆盖**:在 `app/` 下放置与 `extend/base/` 同路径同名的视图、模板、配置数组或 PHP 钩子文件,运行时会优先加载 app 版本。该能力覆盖三类文件:
- 视图模板(控制器 `fetch()` 渲染时三级回退:`app/<module>/view/``extend/base/<module>/view/``<root>/view/`
- 模板内 `{include}` 标签(与视图同源的三级查找)
- `app_file_path()` 辅助函数显式加载的文件(两级回退:`app/<相对路径>``extend/base/<相对路径>`
注意:文件级覆盖只对**特定类型**的文件生效。命令、语言包、路由、`config/*``public/static/*``AdminInitServiceBase::requireData()` 等均**不支持**——业务侧在 `app/` 下放同名文件不会生效。
完整的机制说明、7 处已知调用点清单与反例,见规则:[ulthon-file-override-mechanism](../../rules/ulthon-file-override-mechanism.md)。
---
## 三、架构参考资料

View File

@@ -127,21 +127,23 @@ Rules 可包含以下类型的内容:
新增规则后,必须同步更新两处索引:
**AGENTS.md框架索引)**
在「零散规则」章节的索引表中新增一行(仅 `ulthon-` 前缀的规则)。
**AGENTS.md框架规则索引)**
在「零散规则」章节的「框架内置规则索引表中新增一行(仅 `ulthon-` 前缀的规则)。
**PROJECT.md全量索引)**
在「规则索引」章节的索引表中新增一行(所有前缀的规则)。
**PROJECT.md项目业务规则索引)**
在「规则索引」章节的索引表中新增一行(`project-` 前缀的规则)。
### 5. 迁移现有内容
两个索引职责互斥:`ulthon-` 前缀只进 AGENTS.md`project-` 前缀只进 PROJECT.md。框架更新操作 `ulthon-*`)不污染项目业务索引,项目维护(操作 `project-*`)不污染框架索引。
如果规则内容原本记录在 `PROJECT.md` 的「增量规则记录」章节,迁移后应将该章节的对应内容替换为指向规则文件的引用(而非直接删除,保留历史痕迹)。
### 5. 迁移现有内容(如适用)
如果规则内容原本以其他形式散落记录(如 AGENTS.md / PROJECT.md 的内联描述、聊天记录、issue 等),抽取为独立规则文件后,应在原位置替换为指向规则文件的引用,保留可追溯性。
## 读取规则
智能体在以下场景应主动查阅 `.agents/rules/`
1. 首次接触项目时,通过 `AGENTS.md` `PROJECT.md` 的索引了解有哪些规则
1. 首次接触项目时,先看 `AGENTS.md` 的「框架内置规则索引」了解框架规则,再看 `.agents/PROJECT.md`「规则索引了解项目业务规则
2. 涉及特定模块开发时,查找该模块是否有对应的规则文件
3. 用户提到某个模块有特殊约束时,查找对应规则
@@ -149,15 +151,21 @@ Rules 可包含以下类型的内容:
- 规则内容变更时,同步更新文件内容和索引中的说明列
- 规则过期时,标记为"已废弃"或直接删除,并从索引中移除
- 框架更新时,只操作 `ulthon-` 前缀的规则文件,不动 `project-` 前缀的文件
- 框架更新时,只操作 `ulthon-` 前缀的规则文件与 AGENTS.md 的索引,不动 `project-` 前缀的文件与 PROJECT.md 的索引
- 项目业务规则维护时,只操作 `project-` 前缀的规则文件与 PROJECT.md 的索引,不动 `ulthon-` 前缀的文件与 AGENTS.md 的索引
## 索引格式
索引表统一使用以下格式:
索引表统一使用以下格式3 列;"来源"通过文件名前缀 `ulthon-`/`project-` 已表达,不单列)
```markdown
| 规则文件 | 来源 | 作用域 | 说明 |
|---------|------|--------|------|
| ulthon-timer-multi-node.md | 框架 | 定时任务相关 | 多节点协调规则 |
| project-order-stock-lock.md | 业务 | 订单模块 | 库存锁定规则 |
| 规则文件 | 作用域 | 说明 |
|---------|--------|------|
| [ulthon-timer-multi-node.md](./.agents/rules/ulthon-timer-multi-node.md) | 定时任务相关 | 多节点协调规则 |
| [project-order-stock-lock.md](./.agents/rules/project-order-stock-lock.md) | 订单模块 | 库存锁定规则 |
```
链接路径以索引文件自身位置为基准写相对路径:
- AGENTS.md 在项目根目录,用 `./.agents/rules/...`
- PROJECT.md 在 `.agents/`,用 `./rules/...`

View File

@@ -138,6 +138,8 @@ class MyQueueTask extends TimerController
- 优先覆盖:`app/common/command/timer/config.php`(一旦存在,`app_file_path(...)` 将优先读取此文件)
- 框架默认:`extend/base/common/command/timer/config.php`(当 app 未提供时回落到该文件)
> 该覆盖行为是框架"文件级覆盖机制"的实例之一视图模板、include 标签、`app_file_path` 共三类)。完整说明与不支持覆盖的反例清单详见规则:[文件级覆盖机制](../../rules/ulthon-file-override-mechanism.md)。
字段说明(兜底默认值由 Base 层的 `initConfigItem()` 提供):
- `name`:任务唯一名称(用于 Cache key不可重复

View File

@@ -100,6 +100,7 @@
| [ulthon-controller-url.md](./.agents/rules/ulthon-controller-url.md) | 控制器路由 | URL 与控制器/方法的映射规则 |
| [ulthon-deploy-environment.md](./.agents/rules/ulthon-deploy-environment.md) | 部署与命令执行 | 部署栈模式与 Docker/宿主机命令判断 |
| [ulthon-source-directory.md](./.agents/rules/ulthon-source-directory.md) | source/ 目录 | 子项目/多端代码的目录约定与安全要求 |
| [ulthon-file-override-mechanism.md](./.agents/rules/ulthon-file-override-mechanism.md) | 文件加载机制 | app/ 覆盖 extend/base/ 的三类文件级覆盖机制与反例 |
| [ulthon-timer-multi-node.md](./.agents/rules/ulthon-timer-multi-node.md) | 定时任务相关 | 多节点协调规则与设计 |
| [ulthon-testing.md](./.agents/rules/ulthon-testing.md) | 测试规范 | 测试设计哲学、4层测试模型、该不该写测试、测试库安全、fixture 原则 |