Files
YwxAppThink/docs/guide/model_selfheal.md
T

94 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 模型自愈机制(Schema Self-Heal
YwxApp 的数据库结构自愈引擎收敛在 `ywxapp/model/BaseModel.php` 的静态方法中。
它的定位是 **「缺表 / 缺列兜底」**,不是迁移工具——旧表改名、跨前缀重整等问题仍需手动执行迁移 SQL(见 `docs/migrations/`)。
## 一、自愈引擎能力(BaseModel 静态方法)
| 方法 | 作用 | 说明 |
| --- | --- | --- |
| `ensureTable($table, $sql)` | 表不存在则 CREATE | `$table` 为已带前缀的完整表名;内部先 `SHOW TABLES LIKE` 探测 |
| `ensureTableFromInstall($prefix, $table)` | 从 `public/install/install.sql` 正则提取建表 DDL 执行 | **install.sql 是唯一事实源**`$table` 为不含前缀的表名 |
| `ensureColumn($table, $col, $def)` | 列不存在则 ALTER ADD | MySQL 不支持 `ADD COLUMN IF NOT EXISTS`,故先探测 |
| `ensureAutoIncrementPk($table, $col='id')` | 修复 id 非自增主键 | 老库 `id` 定义成 NOT NULL 但无 AUTO_INCREMENT 时,create() 会报 1364 |
| `tableExists($table)` | 探测表是否存在 | `SHOW TABLES LIKE`,异常时返回 false |
| `currentPrefix()` | 取运行时表前缀 | 优先 `config/database.php`,回退 `Db::getConfig('prefix')` |
所有方法内部都包了 `try/catch`:自愈失败(如数据库账号无 `CREATE/ALTER` 权限、`NO_BACKSLASH_ESCAPES` 导致 SQL 出错)会**被静默吞掉并记录日志**,不会阻断业务——但意味着「自愈没起作用」时不会立刻报错,需要查日志才能发现。
## 二、模型壳约定
每个需要自愈的模型都自带一个静态方法:
```php
public static function ensureSchema(): void
{
try {
$prefix = \think\facade\Db::getConfig('prefix') ?: '';
$table = $prefix . 'notice';
// 缺表建表(优先 ensureTableFromInstall,或从 install.sql 同款 DDL 兜底)
self::ensureTable($table, "CREATE TABLE IF NOT EXISTS `{$prefix}notice` (...)");
// 老库扩展列兜底(幂等)
self::ensureColumn($table, 'type', "tinyint(1) NOT NULL DEFAULT 1");
} catch (\Throwable $e) {
// 忽略自愈失败,由业务暴露
}
}
```
**幂等性**:所有自愈都先探测(SHOW TABLES / SHOW COLUMNS)再操作,已存在则跳过,重复调用安全。
## 三、触发时机(谁在什么时候调用 ensureSchema
自愈**不会定时 / 不会自动**,全部是「业务请求时按需触发」。
### 1. 核心库(每次相关请求都跑一次,开销极小)
| 入口 | 调用 | 触发场景 |
| --- | --- | --- |
| `ywxapp/library/AdminAuth.php` (`login`/`register`) | `BackendAdmin::ensureSchema()` | 后台登录 / 注册 |
| `ywxapp/controller/MemberBase.php` (`_initialize`) | `MemberUser::ensureSchema()` | 访问任意会员中心控制器 |
### 2. 业务方法内触发
| 模型 / 类 | 触发方法 | 触发场景 |
| --- | --- | --- |
| `ywxapp/model/Notice.php` | `getActiveTopNotices()` / `getList()` | 前台公告条渲染、公告列表页 |
| `ywxapp/model/Ad.php` | `getByPosition()` / `getSlots()` | 广告位渲染 |
### 3. 中间件 / 蜘蛛统计
| 入口 | 调用 | 触发场景 |
| --- | --- | --- |
| `SpiderStat` 中间件 | `ensureTables()` | 每次请求(蜘蛛日志 / 统计表) |
### 4. 插件安装 / 升级
| 插件 | 触发入口 | 触发场景 |
| --- | --- | --- |
| blog | `BlogSchema::ensure*` | 插件业务方法 |
| forum | `ForumSchema::ensure*` | 插件业务方法 |
| appmall(中心站) | `AppmallSchema::ensure*` | 开发者中心业务方法 |
| haonav / seo | `Addon.php::upgrade()` / `ensureTablesFromInstallSql()` | 插件升级 / 安装 |
| wxchat / mqttbroker / mqpay 等 | 各自 Schema 类 | 插件初始化 |
## 四、线上老库迁移不在自愈范围
自愈只负责「缺表 / 缺列兜底」,**不会做 RENAME、不会跨前缀重整**。旧库改名必须手动执行迁移 SQL:
- `docs/migrations/migrate_admin_user_to_backend_member_wxapp.sql` — admin/user → backend/member
- `docs/migrations/rename_table_prefix.sql` — 批量改前缀
- `docs/migrations/fix_missing_wxapp_notice.sql` — 自愈因权限被吞时手动补表
## 五、排错:遇到 1146 Table doesn't exist
1. 确认表是否在 `install.sql` 中有建表语句(事实源)。
2. 若是核心 / 插件表,确认对应 `ensureSchema()` 是否被调用(见第三节入口)。
3. ⚠️ **高频根因:运行时没取到表前缀**`ensureSchema()` 若用 `Db::getConfig('prefix')` 取前缀,在部分线上环境(多应用路由 / CLI / Db 配置未就绪)会返回空字符串,导致自愈建出**无前缀的表**(如 `notice`),而模型 `name` 拼前缀后查的是 `wxapp_notice` → 1146。
- **修复**:所有模型壳 / 控制器一律改用 `BaseModel::currentPrefix()` 取前缀(优先 `config/database.php`,回退 `Db::getConfig`),并已统一修复 `Notice` / `Ad``appmall` / `appmall` 控制器。
- 自查:`grep "Db::getConfig('prefix')"` 应只剩 0 处(全部换成 `currentPrefix`)。
4. 若前缀正确、`ensureSchema()` 已调用仍 1146 → **多半是线上 DB 账号无 CREATE/ALTER 权限**,自愈被静默吞掉。
- 根治:给 DB 用户授予建表 / 改表权限,自愈会自动补齐;
- 兜底:手动执行 `docs/migrations/` 下对应修复 SQL(如 `fix_missing_wxapp_notice.sql`)。
5. 若是改名类(admin_* / user_*)→ 必须手动跑迁移 SQL,自愈无法处理。