Migration 升级脚本
在 NocoBase 插件的开发与更新过程中,插件的数据库结构或配置可能会发生不兼容的变化。为了保证升级的平滑执行,NocoBase 提供了 Migration 机制,通过编写 migration 文件来处理这些变更。本文将带你系统了解 Migration 的使用方法和开发流程。
Migration 的概念
Migration 是插件升级时自动执行的脚本,用于解决以下问题:
- 数据表结构调整(新增字段、修改字段类型等)
- 数据迁移(如字段值的批量更新)
- 插件配置或内部逻辑更新
Migration 的执行时机分为三类:
| 类型 | 触发时机 | 执行场景 |
|---|
beforeLoad | 所有插件配置加载前 | |
afterSync | 数据表配置与数据库同步之后(表结构已变更) | |
afterLoad | 所有插件配置加载后 | |
创建 Migration 文件
Migration 文件应放在插件目录下的 src/server/migrations/*.ts 中。NocoBase 提供 create-migration 命令快速生成 migration 文件。
yarn nocobase create-migration [options] <name>
可选参数
| 参数 | 说明 |
|---|
--pkg <pkg> | 指定插件包名 |
--on [on] | 指定执行时机,可选 beforeLoad、afterSync、afterLoad |
示例
$ yarn nocobase create-migration update-ui --pkg=@nocobase/plugin-client
生成的 migration 文件路径如下:
/nocobase/packages/plugins/@nocobase/plugin-client/src/server/migrations/20240107173313-update-ui.ts
文件初始内容:
import { Migration } from '@nocobase/server';
export default class extends Migration {
on = 'afterLoad'; // 'beforeLoad' | 'afterSync' | 'afterLoad'
appVersion = '<0.19.0-alpha.3';
async up() {
// 在这里编写升级逻辑
}
}
⚠️ appVersion 用于标识升级所针对的版本,小于指定版本的环境会执行该 migration。
编写 Migration
在 Migration 文件中,你可以通过 this 访问以下常用属性和 API,方便操作数据库、插件及应用实例:
常用属性
-
this.app
当前 NocoBase 应用实例。可用于访问全局服务、插件或配置。
const config = this.app.config.get('database');
-
this.db
数据库服务实例,提供对模型(Tables)操作的接口。
const users = await this.db.getRepository('users').findAll();
-
this.plugin
当前插件实例,可用于访问插件的自定义方法。
const settings = this.plugin.customMethod();
-
this.sequelize
Sequelize 实例,可直接执行原生 SQL 或事务操作。
await this.sequelize.transaction(async (transaction) => {
await this.sequelize.query('UPDATE users SET active = 1', { transaction });
});
-
this.queryInterface
Sequelize 的 QueryInterface,常用于修改表结构,例如新增字段、删除表等。
await this.queryInterface.addColumn('users', 'age', {
type: this.sequelize.Sequelize.INTEGER,
allowNull: true,
});
编写 Migration 示例
import { Migration } from '@nocobase/server';
export default class extends Migration {
on = 'afterSync';
appVersion = '<0.19.0-alpha.3';
async up() {
// 使用 queryInterface 添加字段
await this.queryInterface.addColumn('users', 'nickname', {
type: this.sequelize.Sequelize.STRING,
allowNull: true,
});
// 使用 db 访问数据模型
const users = await this.db.getRepository('users').findAll();
for (const user of users) {
user.nickname = user.username;
await user.save();
}
// 执行 plugin 的自定义方法
await this.plugin.customMethod();
}
}
除了上面列出的常用属性,Migration 还提供丰富的 API,详细文档请参考 Migration API。
触发 Migration
Migration 的执行由 nocobase upgrade 命令触发:
升级时,系统会根据 Migration 的类型和 appVersion 判断执行顺序。
测试 Migration
在插件开发中,建议使用 Mock Server 测试 migration 是否正确执行,避免破坏真实数据。
import { createMockServer, MockServer } from '@nocobase/test';
describe('Migration Test', () => {
let app: MockServer;
beforeEach(async () => {
app = await createMockServer({
plugins: ['my-plugin'], // 插件名称
version: '0.18.0-alpha.5', // 升级前版本
});
});
afterEach(async () => {
await app.destroy();
});
test('run upgrade migration', async () => {
await app.runCommand('upgrade');
// 编写验证逻辑,例如检查字段是否存在、数据是否迁移成功
});
});
Tip: 使用 Mock Server 可以快速模拟升级场景,并对 Migration 执行顺序和数据变更进行验证。
开发实践建议
- 拆分 Migration
每次升级尽量生成一个 migration 文件,保持原子性,便于排查问题。
- 指定执行时机
根据操作对象选择 beforeLoad、afterSync 或 afterLoad,避免依赖未加载的模块。
- 注意版本控制
使用 appVersion 明确 migration 适用的版本,防止重复执行。
- 测试覆盖
在 Mock Server 上验证 migration 后,再在真实环境中执行升级。