5.4 KiB
5.4 KiB
模型自愈机制(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 出错)会被静默吞掉并记录日志,不会阻断业务——但意味着「自愈没起作用」时不会立刻报错,需要查日志才能发现。
二、模型壳约定
每个需要自愈的模型都自带一个静态方法:
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/memberdocs/migrations/rename_table_prefix.sql— 批量改前缀docs/migrations/fix_missing_wxapp_notice.sql— 自愈因权限被吞时手动补表
五、排错:遇到 1146 Table doesn't exist
- 确认表是否在
install.sql中有建表语句(事实源)。 - 若是核心 / 插件表,确认对应
ensureSchema()是否被调用(见第三节入口)。 - ⚠️ 高频根因:运行时没取到表前缀。
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)。
- 修复:所有模型壳 / 控制器一律改用
- 若前缀正确、
ensureSchema()已调用仍 1146 → 多半是线上 DB 账号无 CREATE/ALTER 权限,自愈被静默吞掉。- 根治:给 DB 用户授予建表 / 改表权限,自愈会自动补齐;
- 兜底:手动执行
docs/migrations/下对应修复 SQL(如fix_missing_wxapp_notice.sql)。
- 若是改名类(admin_* / user_*)→ 必须手动跑迁移 SQL,自愈无法处理。