
NocoBase Migration API 深度解析插件升级时的数据库结构变更与数据迁移【免费下载链接】nocobaseNocoBase is an open-source AI no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobaseNocoBase 的 Migration 是nocobase/server提供的数据迁移基类用于在应用升级时按版本、按执行时机自动处理数据库结构变更DDL和数据订正DML。本文基于官方 API 文档与仓库源码完整讲清 Migration 的on执行时机、appVersion版本控制语义、全部实例属性与方法、三类典型迁移脚本写法以及nocobase create-migration命令的底层生成逻辑——读完你能够独立编写可运行、可版本控制的迁移脚本并理解框架在 upgrade 流程中如何调度它们。Migration 基类与最小示例Migration 是 NocoBase 的数据迁移基类用于在插件升级时处理数据库结构变更和数据迁移从nocobase/server导入。最小可用示例import { Migration } from nocobase/server; export default class extends Migration { on afterLoad; appVersion 1.0.0; async up() { // 升级逻辑 } }从源码结构看这一继承链分为两层底层基类nocobase/database导出的Migration见 packages/core/database/src/migration.ts持有运行时上下文context: { db, queryInterface, sequelize }并在构造函数中注入暴露db、sequelizecontext.db.sequelize和queryInterfacecontext.db.sequelize.getQueryInterface()三个 getter以及空实现的up()/down()方法应用层基类nocobase/server的Migration见 packages/core/server/src/migration.ts继承自底层基类声明了三个类属性默认值export class Migration extends DbMigration { appVersion ; pluginVersion ; on afterLoad; get app() { return this.context.app as Application; } get pm() { return this.context.app.pm as PluginManager; } get plugin() { return this.context.plugin as Plugin; } }可以看到on的默认值afterLoad正是由这个类属性给出的同时源码中还存在一个文档未展开的pluginVersion 属性用于插件级版本判断。框架如何发现并实例化迁移文件可以看 packages/core/server/src/application.ts 中的loadMigrations()它用 glob 扫描指定目录下*.{js,ts}文件忽略.d.ts逐个importModule动态导入然后以new Migration({ app: this, db: this.db, ...context })构造实例并按m.on || afterLoad分桶到beforeLoad/afterSync/afterLoad三个数组中migration 的name被赋值为${文件名去扩展名}/${namespace}。这解释了两个细节on不写时框架不会报错而是落入默认桶migration 名称由“文件名 命名空间”构成而 core migration 的命名空间为nocobase/server由loadCoreMigrations()指定目录src/migrations传入。类属性on 与 appVersionon控制执行时机on: beforeLoad | afterSync | afterLoad;on控制 migration 在 upgrade 流程中的执行时机默认afterLoad。值执行时机适用场景beforeLoad插件加载之前底层 DDL 操作比如添加列、添加约束此时不能使用 Repository APIafterSyncdb.sync()之后、插件 upgrade 之前需要新表结构但不依赖插件逻辑的数据迁移afterLoad所有插件加载完成之后默认值大多数 migration 用这个。可以使用完整的 Repository API从源码看三种时机对应loadCoreMigrations()返回的三个 up 钩子每个桶各自调用this.db.createMigrator({ migrations: migrations[时机] })并await migrator.up()三者分别挂到应用启动流程的 beforeLoad、afterSync、afterLoad 阶段。换言之同一次 upgrade 中你的多个 migration 会按on被切分到三条流水线中执行同桶内再按文件名排序依次执行。appVersion版本门控appVersion: string;appVersion是 semver 范围字符串决定该 migration 在哪些版本的应用上执行。框架用semver.satisfies()判断只有当前应用版本满足该范围时migration 才会执行。// 只有从低于 1.0.0 的版本升级时才执行 appVersion 1.0.0; // 只有从低于 0.21.0-alpha.13 的版本升级时才执行 appVersion 0.21.0-alpha.13; // 留空则每次 upgrade 都执行 appVersion ;这个判断在源码中逐字对应packages/core/server/src/application.ts 的loadMigrations()const appVersion await this.version.get(); // ... if (!m.appVersion || semver.satisfies(appVersion, m.appVersion, { includePrerelease: true })) { m.name ${filename}/${namespace}; migrations[m.on || afterLoad].push(m); }两个要点值得注意留空即“每次执行”——!m.appVersion为真时直接放行这也是基类默认值appVersion 的语义。由于不符合范围的 migration 在加载阶段就被过滤掉所以appVersion是“是否执行”的开关而不是记录“是否已执行”的状态已执行过但留空的 migration 会在每次 upgrade 重新运行务必让up()逻辑幂等。includePrerelease: true——比较时显式开启预发布版本匹配因此0.21.0-alpha.13这类带 pre-release 的范围才能正确生效。实例属性基类与子类共同提供了访问应用各模块的 getter全部迁移逻辑都通过它们完成。appApplication 实例get app(): ApplicationNocoBase Application 实例通过它可以访问应用的各个模块async up() { // 获取应用版本 const version this.app.version; // 获取日志 this.app.log.info(Migration started); }dbDatabase 实例get db(): DatabaseNocoBase Database 实例可以用来获取 Repository、执行查询等async up() { const repo this.db.getRepository(users); await repo.update({ filter: { status: inactive }, values: { status: disabled }, }); }plugin当前插件实例get plugin(): Plugin当前插件实例。仅在插件级 migration 中可用core migration 中为undefined。async up() { const pluginName this.plugin.name; }源码中该 getter 返回this.context.plugin——只有插件加载流程在构造上下文时注入了plugincore migration如nocobase/server自带的src/migrations/上下文里没有这个键因此为undefined。sequelize执行原始 SQLget sequelize(): SequelizeSequelize 实例可以直接执行原始 SQLasync up() { await this.sequelize.query(UPDATE users SET status active WHERE status IS NULL); }queryInterface执行 DDLget queryInterface(): QueryInterfaceSequelize QueryInterface用于执行 DDL 操作添加/删除列、添加约束、修改列类型等async up() { const { DataTypes } require(nocobase/database); // 添加列 await this.queryInterface.addColumn(users, nickname, { type: DataTypes.STRING, }); // 添加唯一约束 await this.queryInterface.addConstraint(users, { type: unique, fields: [email], }); }pm插件管理器get pm(): PluginManager插件管理器。通过this.pm.repository可以查询和修改插件元数据async up() { const plugins await this.pm.repository.find(); for (const plugin of plugins) { // 批量修改插件记录 } }实例方法up() 与 down()up()async up(): Promisevoid升级时执行。子类必须 override 此方法编写迁移逻辑。底层基类中up()是空实现migrator 遍历分桶后的 migration 实例并逐个 await 调用。down()async down(): Promisevoid回滚时执行。大多数 migration 留空。如果需要支持回滚在这里编写反向操作。数据库层的迁移器同样按实例收集down()回调用于降级场景。完整示例三种时机的迁移脚本以下三个示例覆盖最常用的迁移场景全部继承原文档并可直接作为插件src/server/migrations/下的参考模板。示例一使用 Repository API 更新数据afterLoad最常见的场景——在所有插件加载完成后用 Repository API 批量更新数据import { Migration } from nocobase/server; export default class extends Migration { appVersion 1.0.0; async up() { const repo this.db.getRepository(roles); await repo.update({ filter: { $or: [{ allowConfigure: true }, { name: root }], }, values: { snippets: [ui.*, pm, pm.*], allowConfigure: false, }, }); } async down() {} }注意这里没有显式写on依赖默认值afterLoad——与源码中migrations[m.on || afterLoad]的行为一致。示例二使用 QueryInterface 修改表结构beforeLoad在插件加载之前执行底层 DDL——比如给表添加新列和唯一约束import { DataTypes } from nocobase/database; import { Migration } from nocobase/server; export default class extends Migration { on beforeLoad; appVersion 0.14.0-alpha.2; async up() { const tableName this.pm.collection.getTableNameWithSchema(); const field this.pm.collection.getField(packageName); // 先检查字段是否已存在 const exists await field.existsInDb(); if (exists) return; await this.queryInterface.addColumn(tableName, field.columnName(), { type: DataTypes.STRING, }); await this.queryInterface.addConstraint(tableName, { type: unique, fields: [field.columnName()], }); } }这个示例展示了两点 best practice通过pm.collection拿到带 schema 的表名与字段元信息避免硬编码表名以及迁移前先field.existsInDb()做幂等检查——因为如前所述appVersion只负责“是否执行”不替你记录“是否已执行”。示例三使用原始 SQL / 模型批量处理数据afterSync在表结构同步完成后用原始 SQL 或逐条模型保存做数据迁移import { Migration } from nocobase/server; export default class extends Migration { on afterSync; appVersion 1.0.0-alpha.3; async up() { const items await this.pm.repository.find(); for (const item of items) { if (item.name.startsWith(nocobase/plugin-)) { item.set(name, item.name.substring(nocobase/plugin-.length)); await item.save(); } } } }该场景把插件名从带 scope 前缀的nocobase/plugin-xxx改为短名依赖的是 afterSync 阶段表结构已经就绪这一前提。创建 Migration 文件CLI 命令与生成逻辑通过 CLI 命令创建yarn nocobase create-migration my-migration --pkg my-project/plugin-hello命令会在插件的src/server/migrations/目录下生成带时间戳的文件模板如下import { Migration } from nocobase/server; export default class extends Migration { on afterLoad; appVersion 当前版本; async up() { // coding } }命令参数参数说明namemigration 名称用于生成文件名--pkg pkg包名决定文件存放路径--on on执行时机默认afterLoadCLI 侧与 server 侧各有一层实现可以对照阅读CLI 包装层packages/core/cli/src/commands/scaffold/migration.ts 定义了nocobase create-migration命令--pkg为必填--on仅接受beforeLoad/afterSync/afterLoad三个枚举值它把参数拼装为[create-migration, name, --pkg, pkg]可选追加--on后转调 server 侧命令。Server 实现层packages/core/server/src/commands/create-migration.ts 真正负责落盘关键行为包括文件名使用dayjs().format(YYYYMMDDHHmmss)时间戳前缀即YYYYMMDDHHmmss-name.ts保证同目录下多个 migration 可按文件名排序确定执行先后落盘目录对nocobase/server本身是src/migrations/其余包统一为src/server/migrations/若目录不存在会mkdir -pappVersion的“当前版本”由app.getPackageVersion()推算若版本含alpha/beta后缀则保留原号${major}.${minor}.${patch}否则取下一个 minor 版${major}.${minor 1}.0并以前缀写成 semver 范围模板中的 import 路径为nocobase/server核心包生成时用相对路径../migration其余插件包使用nocobase/server。相关链接Migration 升级脚本插件开发 — 插件开发中 migration 的使用教程Collections 数据表 — defineCollection 和表结构同步Database 数据库操作 — Repository API 和数据库操作Plugin 插件 — 插件生命周期中 install() 和 migration 的关系【免费下载链接】nocobaseNocoBase is an open-source AI no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考