插件升级开发文档

本文面向插件二开开发者,重点回答:
插件什么时候需写 migration
插件什么时候需写Upgrade
插件Upgrade应该怎么写
有哪些命令可以生成 migration
为什么很多人写了升级代码却不生效
本文适用于:
extensions/<identifier>/

1. 先记住插件升级的基本模型

插件升级和主程序一样,也是:
migration -> Upgrade -> data/versions/<version>
但插件多一个前提:
你的升级代码必须先被构建到 build 目录,主程序启动时才看得见。
插件升级相关入口:
packages/api/src/core/database/extension-upgrade/extension-upgrade-orchestrator.service.ts
packages/api/src/core/database/extension-upgrade/extension-version-manager.service.ts

2. 插件 migration 和 Upgrade 怎么分

2.1 什么时候写插件 migration

只要你改的是插件自己的数据库结构,就写 migration。
典型场景:
插件新增表
插件新增字段
插件修改字段类型
插件新增索引
插件 schema 内的结构变更
一句话判断:
如果你的插件 schema 结构变了,就写 migration。

2.2 什么时候写插件 Upgrade

只要你改的是插件已有数据的迁移、修复、初始化,就写Upgrade
典型场景:
给插件旧数据补默认值
新功能发布后初始化一批默认记录
调整插件配置结构
把旧版本数据搬到新字段/新表
升级时根据已有数据做批量修复
一句话判断:
如果你需读取旧数据、判断旧状态、再写回去,就写 Upgrade。

2.3 什么场景两者都要写

比如插件给文章表新增status字段,同时要把旧文章统一补成draft
1.
migration:先新增字段
2.
Upgrade:再批量修复旧数据

2.4 什么场景通常都不用写

只改插件前端页面
只改接口返回格式,但不处理存量数据
只改组件样式、文案、交互

3. 插件 migration 写在哪里

代码目录:
extensions/<identifier>/src/api/db/migrations/
构建后目录:
extensions/<identifier>/build/db/migrations/
升级器识别的文件命名格式:
{timestamp}-{version}-{description}.js
比如:
1776000000000-0.0.3-add-article-status.ts
插件 migration 执行记录会写到统一表:
extensions_migrations_history
这张表会用extension_identifier区分不同插件。

4. 插件 migration 有哪些生成命令

这些命令在@chatbuddy-ai/db包里。
打开目录:

4.1 手工创建插件 migration 模板

比如:
这个命令会:
1.
在插件的src/api/db/migrations/下创建模板文件
2.
自动带上插件标识和版本信息
3.
需你自己补 SQL 或 TypeORM 逻辑

4.2 根据插件实体自动生成 migration

比如:
这个命令会:
1.
读取插件构建后的 entity
2.
和数据库当前 schema 做对比
3.
自动生成 migration
4.
输出到插件的src/api/db/migrations/
使用前提:
数据库必须是实体文件改动前的数据库
数据库必须可连接
插件实体已经改好
必须先执行过插件 API 构建,否则没有build/db/entities
也就是通常先跑:

5. 插件 Upgrade 写在哪里

代码目录:
extensions/<identifier>/src/api/upgrade/<version>/index.ts
构建后目录:
extensions/<identifier>/build/upgrade/<version>/index.js
比如:
extensions/simple-blog/src/api/upgrade/0.0.2/index.ts
模板和示例仓库里也已经给了参考:
extensions/simple-blog/src/api/upgrade/0.0.2/index.ts
templates/extension-starter/src/api/upgrade/0.0.2/index.ts

6. 插件 Upgrade 有没有生成命令

目前没有现成的“生成插件 Upgrade 文件”的脚本。
也就是说:
插件 migration:有生成命令
插件Upgrade:需你自己手工创建目录和index.ts

7. 插件 Upgrade 的最小写法

插件 Upgrade 的写法比主程序更轻量,一般直接导出Upgrade类就能。
示例:
import { DataSource } from "@chatbuddy-ai/db/typeorm";
import { Logger } from "@nestjs/common"; export class Upgrade { private readonly logger = new Logger(Upgrade.name); constructor(private readonly dataSource: DataSource) {} async execute: Promise<void> { this.logger.log("Start plugin upgrade 0.0.3"); await this.dataSource.query(` UPDATE "simple_blog"."article" SET "status" = 'draft' WHERE "status" IS NULL `); this.logger.log("Plugin upgrade 0.0.3 completed"); }
}
关键点:
1.
文件路径要和版本目录匹配
2.
必须导出Upgrade
3.
类里要有execute
4.
构造函数通常接收DataSource
插件升级器会在运行时执行:
new Upgrade(this.dataSource).execute

8. 插件 Upgrade 里常用的方法怎么写

插件 Upgrade 里最常用的是构造函数注入进来的dataSource

8.1 直接执行 SQL

适合:
批量修复插件旧数据
初始化默认记录
清洗历史配置
示例:
await this.dataSource.query( ` UPDATE "simple_blog"."article" SET "status" = $1 WHERE "status" IS NULL `, ["draft"],
);

8.2 用 Repository

适合:
插件已经有实体类
想走实体读写
示例:
const repo = this.dataSource.getRepository("Article");
const rows = await repo.find; for (const row of rows) { row.status = row.status || "draft";
} await repo.save(rows);

8.3 用事务

如果插件升级逻辑包含多步写操作,建议显式开事务。
示例:
const queryRunner = this.dataSource.createQueryRunner;
await queryRunner.connect;
await queryRunner.startTransaction; try { await queryRunner.query(` UPDATE "simple_blog"."article" SET "status" = 'draft' WHERE "status" IS NULL `); await queryRunner.query(` INSERT INTO "simple_blog"."setting" ("key", "value") VALUES ('default_status', 'draft') `); await queryRunner.commitTransaction;
} catch (error) { await queryRunner.rollbackTransaction; throw error;
} finally { await queryRunner.release;
}

9. 插件开发里最常见的判断题

场景应该写什么
插件表新增字段migration
插件旧数据补默认值Upgrade
新字段 + 旧数据回填migration + Upgrade
新版本初始化默认配置Upgrade
只改插件页面都不用

10. 插件 Upgrade 设计时的硬规则

10.1 尽量幂等

升级失败后主程序重启可能再次执行,所以要尽量做到重复运行不写坏数据。
建议:
UPDATE ... WHERE xxx IS NULL
插入前先查重
已迁移过的数据不要重复迁移

10.2 不要把表结构修改塞进 Upgrade

表结构变化应该打开 migration。
插件升级器本身就是先跑 migration,再跑Upgrade

10.3 升级代码要和插件版本绑定

插件版本来自:
extensions/<identifier>/package.json
如果你写了0.0.3的升级代码,但插件版本还停留在0.0.2,升级器不会按你预期打开这个版本。

11. 写完以后怎么让插件升级器识别

插件升级器识别的是构建产物,不是代码目录。
所以写完后至少要执行构建。
常见命令:
如果插件同时有前后端要一起发布,更常用:
之后重启主程序,主程序启动时会:
1.
读取extensions/extensions.json
2.
找出已启用插件
3.
检查插件build/是否存在
4.
再执行插件 migration 和Upgrade
所以插件升级不生效时,优先检查这三件事:
1.
插件是否启用
2.
插件是否构建
3.
插件版本是否已提升

12. 插件开发者最容易踩的坑

12.1 只写了src/api/upgrade,没重新构建

主程序只认:
build/upgrade/<version>/index.js

12.2 写了 Upgrade,但没升级插件版本号

升级器以package.json.version为当前目标版本。

12.3 用migration:generate:extension前没先构建 API

这个命令依赖:
build/db/entities
没有构建产物就生成不了。

12.4 把旧数据修复写在 service 正常运行逻辑里

这会导致:
每次求都重复修复
数据修复和业务逻辑耦合
后续很难维护
这类逻辑应该放进Upgrade

13. 推荐开发流程

给插件发一个新版本时,推荐顺序:
1.
先判断这次改动是结构变化、数据变化,还是两者都有
2.
结构变化先创建 migration
3.
数据变化再手工创建src/api/upgrade/<version>/index.ts
4.
提升插件package.json.version
5.
构建插件
6.
重启主程序验证
7.
确认extensions/<identifier>/data/versions/<version>已写入

14. 你能够直接照抄的结论

改插件表结构:写 migration
改插件历史数据:写Upgrade
两者都有:两个都写,先 migration 后Upgrade
插件 migration 有命令生成
插件Upgrade没有命令生成,需手工创建
写完后一定要构建,否则主程序启动时看不到

319 篇文档 · 内容同步自官方帮助中心