数据库结构演进统一使用版本化迁移(
app/Services/Migrator.php+migrations/*.sql)。
目录结构
migrations/baseline.sql—— 安装基线,内容与app/install/schema.sql保持一致(当前两者逐行相同)。基线只用于全新安装和正式基线登记,不作为日常增量重跑。migrations/*.sql—— 增量迁移,按文件名排序后依次应用;新结构变更应新增独立迁移文件,不再复制到upgrade.php。migrations/*.php—— 需要数据转换的增量迁移,必须返回可调用函数,接收PDO;与 SQL 文件一起按文件名排序,因此文件名同样要带稳定的语义前缀。schema_migrations表 —— 记录已应用的迁移版本(version主键 +applied_at)。
三条应用路径(殊途同归)
场景 | 路径 |
|---|---|
全新安装 |
|
存量库升级 | 每个请求在控制器查询前调用 |
在线更新 | 更新包可保留 |
任何时刻调用 Migrator::run() 都是幂等的:已应用的跳过,未应用的按序执行并记录。迁移器用数据库级 GET_LOCK 串行化,同一数据库的并发请求不会同时执行迁移;拿不到锁时直接报错,由请求重试。
迁移器还会在执行前做“目标结构已存在”的快速判定:例如 post_templates 只检查 posts.template 列是否存在,app_tokens 等表存在即视为已完成。这样从旧版本升级时,已被基线覆盖的增量迁移不会重复执行 DDL。
排序规则
迁移器使用 sort() 按文件名字节序应用,不是自然排序。为了让顺序不受数字宽度影响,新文件名使用小写字母、数字和下划线,并保持稳定前缀:
核心结构变更:
语义名.sql,例如content_controls.sql。需要排在最后、且不能与既有名字冲突的迁移:使用
zz_、zzz_前缀,例如zz_extension_storage.sql。
新增迁移的步骤
在
migrations/下新建语义名.sql(小写字母、数字、下划线;版本号即文件名去掉.sql后缀,按文件名字节序决定应用顺序)。内容只包含 MySQL 语句(无存储过程;
--行注释会被忽略,分号拆分为独立语句执行)。每个文件只放一个目的:MySQL DDL 可能隐式提交,迁移器会在执行
ALTER TABLE ... ADD COLUMN前检查目标列是否已存在,因此中断后可安全重试;失败时该迁移不记录,修复后重跑即可。只有必须执行跨表数据转换、且 SQL 不足以表达时才新增
migrations/*.php;普通结构迁移应直接放入migrations/*.sql。upgrade.php只剩兼容旧在线更新流程的作用,执行后会被自动删除。
在线更新的迁移与回滚
在线更新(/admin/upgrade)流程:
从 Gitee Release 检查并下载更新包 → SHA-256 校验 → 结构校验(zip slip / 顶层目录 / 关键文件)。
整站备份(排除
runtime/、backups/与public/uploads/;旧根目录config.php会保留)。清空并解压新版本。
执行包内
upgrade.php(若存在),应用启动时仍会再次幂等确认Migrator::run()。任一步失败 → 自动回滚:从备份整体恢复,站点回到旧版本。
当前在线更新固定从 Gitee Release 检查下载,后台不提供本地上传开发包的入口;升级前请先备份数据库与上传目录。
手动回滚:runtime/.upgrade-bak-*/ 是升级失败时保留的备份;回滚失败时备份保留在 runtime/,按备份目录内结构手动覆盖回根目录即可。站点根目录中的 .well-known/ 由证书工具管理,升级和回滚必须保留,不能当作系统文件删除。
结构变更的回退边界
ALTER TABLE、CREATE INDEX、DROP TABLE等 DDL 不能整体事务回滚;大库会占用空间并可能等待元数据锁,升级前应备份并安排低峰执行。回退应用代码通常可以保留新增的兼容索引;确需回退结构时,先核对当前索引/列定义,再手工反向执行对应 DDL,不要盲目覆盖整站数据库。
涉及退役表(例如验证码表)的迁移必须与代码配套回退:单独恢复旧代码会重新查询已删除的表。
发布前检查清单
每次发布前逐项确认,避免发布包结构、静态资源地址和在线替换逻辑脱节:
版本号在
app/Core/Version.php与composer.json一致;包名为pafish-php-v{version}.zip,ZIP 内所有文件位于唯一pafish/顶层目录。ZIP 至少包含
pafish/index.php、pafish/app/bootstrap.php、pafish/app/Services/Upgrade.php、pafish/composer.json、依赖vendor/和当前模板引用的public/CSS/JS;不包含站点config.php、运行日志、备份、上传内容和本地开发工具。静态资源路径按两种部署形态核对:项目根目录作为 Web 根时使用
/public/...;public/本身作为 Web 根时使用/...。必须对照 Nginx/Apache 示例,并验证后台 CSS、JS、favicon 和主题资源。在线更新清理目录采用保留清单:必须保留
config.php、runtime/、backups/、.well-known/、public/uploads/,正常升级还保留已安装的themes/、plugins/。新增运行期目录必须同步加入清理和备份排除规则。更新删除逻辑逐项确认符号链接不会跟随到站点根目录之外;清理或覆盖失败时错误应包含具体路径,回滚失败时保留文件备份路径,且不能清除唯一可恢复副本。
发布前用干净的旧版本副本执行一次升级,并确认首页、后台、媒体资源、文章密码、主题图片上传、分页和
.well-known/acme-challenge均可访问;失败演练后确认旧文件和数据库备份均可恢复。