# EasyPostman 插件开发、更新与发布指南
这份文档面向两类人:
- 想看懂当前插件机制是怎么工作的维护者
- 正在开发、更新、发布插件的开发者
如果你只关心一句话,可以先记住这 4 点:
1. 插件是单独的 JAR,不和宿主一起绑死发版
2. 插件元数据的单一真源是各插件自己的 `pom.xml`
3. GitHub Actions 只负责读取产物、校验一致性、发布和回写 catalog
4. 本地验证优先走“构建插件 JAR -> 插件管理安装 -> 重启验证”这条路径
如果你当前更想看 runtime 内核实现,而不是开发/发布流程,可以直接先读:
- [PLUGIN_RUNTIME_ARCHITECTURE_zh.md](./PLUGIN_RUNTIME_ARCHITECTURE_zh.md)
## 1. 组件分层和原理
当前插件体系分成 7 层:
```text
easy-postman-parent
├── easy-postman-plugin-api
├── easy-postman-foundation
├── easy-postman-platform
├── easy-postman-ui
├── easy-postman-plugin-runtime
├── easy-postman-app
└── easy-postman-plugins
├── plugin-manager
├── plugin-client-cert
├── plugin-capture
├── plugin-redis
├── plugin-kafka
└── plugin-decompiler
```
### 1.1 每层负责什么
- `easy-postman-plugin-api`
- 插件对宿主暴露的稳定 SPI
- 包含 `EasyPostmanPlugin`、`PluginContext`、`PluginDescriptor`
- 插件服务契约也放在这里,例如 `GitPluginService`、`ClientCertificatePluginService`、`RequestCollectionImportService`
- 新插件的入口类必须实现这里的接口
- `easy-postman-foundation`
- 宿主和插件共享的基础工具、消息 key、公共模型
- 插件需要和宿主共享非 UI DTO、enum、常量、路径、JSON、系统、设置工具或通用解析/格式化工具时,优先放这里
- `easy-postman-platform`
- 宿主平台框架能力,例如 IOC 和更新发现核心
- 不是普通插件 SPI;插件需要扩展宿主时仍应优先依赖 `easy-postman-plugin-api`
- `easy-postman-ui`
- 插件和宿主公用的 UI 基础组件和视觉常量
- 插件面板如果需要复用统一 UI 风格、按钮、搜索框、表格或基础输入控件,优先依赖这一层
- `easy-postman-plugin-runtime`
- 负责扫描、读取 descriptor、版本兼容校验、类加载、生命周期调用、扩展点注册和 registry
- 可以把它理解成插件运行时内核
- `easy-postman-app`
- 宿主应用本身
- 从 runtime 和 registry 里消费插件注册出来的能力
- `easy-postman-plugins`
- 通常是官方插件 JAR 集合
- `plugin-client-cert`、`plugin-capture`、`plugin-redis`、`plugin-kafka`、`plugin-decompiler` 这类 `plugin-*` 是普通运行时插件
- `plugin-manager` 是例外:它是宿主 app 直接依赖的宿主侧插件管理辅助模块,负责 catalog 解析、在线/离线安装门面和安装来源记录,不是 runtime 扫描加载的普通插件
### 1.2 插件是怎么被宿主加载的
真实运行链路大致是:
```text
本地 JAR / 远程 catalog
-> plugin-manager 负责安装和下载
-> 安装版插件 JAR 进入数据目录下的 plugins/installed 和 plugins/packages
-> 便携版默认写入程序根目录下的 plugins 和 plugins/packages,方便离线拷贝
-> runtime 扫描插件目录
-> 读取 META-INF/easy-postman/*.properties
-> 解析 PluginDescriptor
-> 校验 app 版本 + platform 版本兼容性
-> 为每个插件创建独立 URLClassLoader
-> 反射创建 entryClass
-> 调用 onLoad(context)
-> 插件向 registry 注册脚本 API / Toolbox / 补全 / Snippet / Service
-> 宿主消费这些能力
```
关键点:
- 插件不是直接依赖宿主内部实现,而是通过 `PluginContext` 声明能力
- 插件之间不共享类加载器,避免依赖冲突污染宿主
- `easy-postman-plugin-runtime` 负责插件扫描、加载、生命周期和 registry;`plugin-manager` 只负责安装/catalog 侧的宿主辅助能力
- 普通插件不得依赖 `easy-postman-app`;宿主 app 可以依赖 `easy-postman-plugins/plugin-manager` 这个目录特例
- 同一 `plugin.id` 存在多个版本时,运行时只会选择最高版本加载
- 安装阶段就会做兼容性拦截,避免出现“安装成功但运行时跳过”的假成功状态
### 1.3 插件能扩展哪些能力
当前 `PluginContext` 支持这几类扩展点:
- `registerScriptApi`
- 给脚本环境暴露 `pm.plugin("xxx")`
- `registerService`
- 给宿主侧访问层按类型取服务
- `registerToolboxContribution`
- 往 Toolbox 增加面板
- `registerScriptCompletionContributor`
- 给脚本编辑器增加自动补全
- `registerSnippet`
- 增加脚本片段
以 `plugin-kafka` 为例,`KafkaPlugin` 在 `onLoad` 里一次性注册了:
- `pm.plugin("kafka")`
- Kafka Toolbox 面板
- Kafka 相关脚本补全
- Kafka 示例片段
也就是说,插件入口类的职责不是“自己跑业务逻辑”,而是“在加载时把能力挂到宿主上”。
## 2. 版本模型和兼容性
### 2.1 为什么要区分 app 和 platform
当前体系里有两种版本边界:
- `app version`
- 宿主 EasyPostman 的发行版版本
- 例如 `5.3.16`
- `plugin platform version`
- 插件 SPI / 运行时装配边界版本
- 当前由根 `pom.xml` 里的 `plugin.platform.version` 管理
简单理解:
- `platform` 管“这套插件接口还能不能接上”
- `app` 管“当前宿主有没有插件依赖的具体功能”
再细一点说:
- 根 `pom.xml` 里的 `revision`
- 是宿主发行版版本
- 会随着宿主正常发版、补丁、非插件改动持续递增
- 插件 `pom.xml` 里的 `host.version`
- 默认跟随 `${revision}`
- 作用只是“编译时依赖哪一版宿主平台包”
- 根 `pom.xml` 里的 `plugin.platform.version`
- 是运行时插件平台版本
- 作用是表达“当前 runtime / SPI 装配边界有没有发生不兼容变化”
为什么 `plugin.min/maxPlatformVersion` 不能直接写 `${revision}`:
- `revision` 变化太频繁,很多变化和插件 SPI 无关
- 如果把 platform 兼容范围直接绑到 `revision`,会把很多其实还能加载的老插件误判成“不兼容”
- 所以必须把“宿主发版节奏”和“插件平台兼容边界”拆成两个版本概念分别维护
### 2.2 什么时候改 platform version
只有插件装配边界或插件可依赖的公共平台 API 发生不兼容变化时,才应该提升 `plugin.platform.version`,例如:
- `PluginContext` 有不兼容修改
- `PluginDescriptor` 语义发生破坏性变化
- runtime 的装配方式变了,老插件会失效
- 某类扩展点契约被替换
- 插件以 `provided` 方式依赖的平台模块(例如 `easy-postman-plugin-api`、`easy-postman-foundation`、`easy-postman-ui`)删除或重命名公开类/方法,旧插件会出现 `NoClassDefFoundError`、`NoSuchMethodError` 或其他 linkage 错误
如果只是:
- 宿主修了一个普通 bug
- 宿主发了一个补丁版本
- 某个插件自己新增功能
通常都不应该 bump `plugin.platform.version`。
### 2.3 什么时候加 min/maxAppVersion
只有插件确实依赖某个宿主能力时,才建议加:
- 宿主新增了一个插件要调用的 API/service
- 宿主某个 UI 容器、入口或事件只在新版本存在
- 插件明确不支持某个老宿主版本
如果插件只是普通扩展,通常只写 `min/maxPlatformVersion` 就够了。
### 2.4 当前 descriptor 和 catalog 的关系
当前规则是:
- 插件自己的 `pom.xml` 维护元数据
- Maven 过滤生成 `META-INF/easy-postman/*.properties`
- GitHub Actions 从构建好的 JAR 里读取 descriptor
- workflow 再把 descriptor 内容同步回 `catalog.json`
所以真正的顺序是:
```text
pom.xml -> descriptor -> release asset -> catalog
```
不是:
```text
workflow 手写字段 -> catalog -> 再反推插件
```
这能避免多处手填导致漂移。
## 3. 开发一个新插件应该怎么做
下面按当前仓库的真实做法给出最短路径。
### 3.1 新建模块
普通官方插件建议在 `easy-postman-plugins/` 下新建:
```text
easy-postman-plugins/plugin-xxx
├── pom.xml
├── src/main/java
└── src/main/resources/META-INF/easy-postman/plugin-xxx.properties
```
然后把模块加入聚合构建。
不要把 `easy-postman-plugins/plugin-manager` 当作普通插件模板。它留在 `easy-postman-plugins` 下是历史路径和发布组织选择;新的宿主侧插件管理库不应默认继续放到 `easy-postman-plugins/*`,后续可迁到更清晰的模块名或目录。
### 3.2 写插件 `pom.xml`
推荐直接参考现有官方插件,例如:
- `easy-postman-plugins/plugin-capture/pom.xml`
- `easy-postman-plugins/plugin-kafka/pom.xml`
- `easy-postman-plugins/plugin-redis/pom.xml`
至少要有这些字段:
```xml
5.3.16
easy-postman-plugin-xxx
${revision}
plugin-xxx
XXX Plugin
com.laker.postman.plugin.xxx.XxxPlugin
...
${plugin.platform.version}
${plugin.platform.version}
```
说明:
- `host.version`
- 编译时依赖哪一版宿主平台包
- 官方插件默认跟随 `${revision}`,这样能直接对齐当前 `api / foundation / ui` 的编译输入
- 如果只是宿主正常发版,这里跟随即可;它不负责表达运行兼容范围
- `plugin.id`
- 插件唯一 ID
- 发布、安装、升级都靠它识别
- `plugin.entryClass`
- 插件入口类
- `plugin.min/maxPlatformVersion`
- 当前官方插件默认都绑定到根 `pom.xml` 的 `plugin.platform.version`
- 它表达的是运行时插件平台兼容范围,不是宿主发行版号
- 只有插件机制本身发生破坏性变化时才应该调整
如果你发现“版本不合适”,按下面判断:
- 编译不过,因为插件要依赖新的宿主 `api / foundation / ui`
- 调整或跟随 `host.version`
- 运行时老宿主缺少插件依赖的新功能
- 增加或调整 `plugin.minAppVersion`
- runtime / SPI 契约变了,老插件会装配失败
- 先提升根 `pom.xml` 的 `plugin.platform.version`
- 再调整插件的 `plugin.min/maxPlatformVersion`
- 只是插件自己改了功能或修了 bug
- 通常只改插件自己的 ``
如果插件确实依赖宿主特定功能,可以额外加:
```xml
5.3.16
5.4.0
```
但要注意,当前官方 descriptor 模板默认没有把这两个字段写进去;你需要同时更新 descriptor 模板。
### 3.3 写 descriptor 模板
示例:
```properties
plugin.id=${plugin.id}
plugin.name=${plugin.name}
plugin.version=${project.version}
plugin.entryClass=${plugin.entryClass}
plugin.description=${plugin.description}
plugin.homepage=${plugin.homepage}
plugin.minPlatformVersion=${plugin.minPlatformVersion}
plugin.maxPlatformVersion=${plugin.maxPlatformVersion}
```
如果你要使用 `minAppVersion` / `maxAppVersion`,记得把它们也加进模板。
当前设计原则是:
- descriptor 是构建产物的一部分
- descriptor 的真实值来自 `pom.xml`
- 不要在 descriptor 里手工写死版本和兼容范围
### 3.4 写入口类
入口类必须实现 `EasyPostmanPlugin`:
```java
public class XxxPlugin implements EasyPostmanPlugin {
@Override
public void onLoad(PluginContext context) {
// 注册扩展点
}
}
```
常见模式:
```java
context.registerScriptApi("xxx", ScriptXxxApi::new);
context.registerToolboxContribution(...);
context.registerScriptCompletionContributor(...);
context.registerSnippet(...);
```
建议:
- `onLoad`
- 只做注册,不做重量级初始化
- `onStart`
- 放真正需要启动时执行的逻辑
- `onStop`
- 做资源释放
### 3.5 依赖应该怎么选
优先规则:
- 插件契约依赖 `easy-postman-plugin-api`
- 共享非 UI 基础类型和工具依赖 `easy-postman-foundation`
- 共享 Swing 组件、字体、图标、颜色和通知 UI 依赖 `easy-postman-ui`
- 不要直接反向依赖 `easy-postman-app` 内部类
- 第三方库如果插件自身需要,跟随插件一起打进插件 JAR
- 宿主已经提供、且插件只需要编译期引用的依赖,优先用 `provided`
### 3.6 新插件开发 checklist
建议按这个清单走:
1. 新建 `plugin-xxx` 模块
2. 配好 `pom.xml`
3. 写 descriptor 模板
4. 写入口类并实现 `EasyPostmanPlugin`
5. 注册需要的扩展点
6. 本地打包生成 JAR
7. 本地安装验证
8. 补测试
9. 再走 GitHub 发布
## 4. 更新一个已有插件应该怎么做
更新已有插件,优先回答这 3 个问题:
1. 这次是插件自己的功能更新,还是平台兼容边界变化
2. 需要不需要提升插件版本
3. 需不需要调整 app/platform 兼容范围
### 4.1 最常见的更新:插件功能更新
如果只是:
- 新增一个脚本 API 方法
- 新增一个 Toolbox 功能
- 修插件自己的 bug
- 调整插件自己的 UI
通常只需要:
1. 修改 `plugin-xxx` 代码
2. 提升该插件自己的 ``
3. 重新构建和验证
一般不需要改:
- 宿主 `revision`
- `plugin.platform.version`
### 4.2 需要调整兼容范围的更新
如果插件依赖了新的宿主能力:
1. 在插件 `pom.xml` 里新增或调整 `plugin.minAppVersion`
2. 在 descriptor 模板里同步加上 `plugin.minAppVersion`
3. 本地验证老版本宿主确实不再适配
如果是插件平台边界变了(SPI、runtime 装配方式,或插件可依赖平台模块的公开 API 被破坏):
1. 先提升根 `pom.xml` 里的 `plugin.platform.version`
2. 再更新受影响插件的 `plugin.min/maxPlatformVersion`
3. 验证旧插件被正确判为不兼容
不要因为下面这些情况去改 `plugin.platform.version`:
- 宿主 `revision` 从 `5.3.16` 升到 `5.3.17`
- 宿主修了普通 bug,但插件 SPI 没变
- 某个插件自己新增了一个工具面板或脚本 API
### 4.3 更新已有插件的推荐顺序
```text
改代码
-> 决定是否 bump 插件版本
-> 决定是否修改 min/maxAppVersion
-> 决定是否修改 min/maxPlatformVersion
-> 本地构建
-> 本地安装验证
-> dry run 发布
-> 正式发布
```
## 5. 在 GitHub 发布插件应该怎么做
当前官方插件独立发版走:
- `.github/workflows/plugin-release.yml`
### 5.1 正式发布前你要先改什么
至少确认下面几件事:
1. 目标插件的 `pom.xml` 版本已经提升
2. descriptor 模板字段齐全
3. 本地构建通过
4. 本地验证通过
5. 如果涉及兼容性变化,`min/maxAppVersion` 或 `min/maxPlatformVersion` 已正确更新
### 5.2 workflow 会做什么
这个 workflow 当前会做:
1. 只构建你选中的插件模块
2. 产出单插件 JAR
3. 计算 JAR 的 `sha256`
4. 从 JAR 内的 descriptor 读取插件元数据
5. 校验 `plugin id / version / tag` 一致性
6. 发布 GitHub Release
7. 可选发布 Gitee Release
8. 可选更新 GitHub / Gitee catalog
### 5.3 workflow 的关键约束
当前正式发版有这几个规则:
- 正式发版只能从默认分支触发
- 非 `dry_run` 情况下,官方 GitHub catalog 必须一起更新
- workflow 不再自己维护兼容字段,而是从 JAR descriptor 读取
### 5.4 推荐发布步骤
建议每次都按这个顺序:
1. 本地改完代码和版本
2. 本地构建并验证
3. 在 GitHub Actions 手动触发 `plugin-release.yml`
4. 先跑一次 `dry_run=true`
5. 确认产物、descriptor、版本都对
6. 再跑正式发布
### 5.5 workflow_dispatch 参数怎么选
你通常只需要关心这些输入:
- `plugin`
- 选择要发布的插件,例如 `capture`、`kafka`
- `dry_run`
- 建议先 `true`
- `publish_gitee_release`
- 是否同步 Gitee Release
- `update_github_catalog`
- 是否回写 GitHub catalog
- `update_gitee_catalog`
- 是否回写 Gitee catalog
- `release_notes`
- 可选
### 5.6 发布后会得到什么
正式发布后,通常会得到:
- 一个单插件 JAR 附件
- 一个 `.sha256.txt` 文件
- 一条 GitHub Release
- 更新后的 `plugin-catalog/catalog-github.json`
- 如开启 Gitee,同步得到对应 Gitee Release 和 catalog 更新
## 6. 本地怎么验证
本地验证建议分成 4 层,不要只做编译通过。
### 6.1 第一层:构建通过
构建单个插件:
```bash
mvn -pl easy-postman-plugins/plugin-kafka -am clean package -DskipTests
```
构建多个插件:
```bash
mvn -pl easy-postman-plugins/plugin-kafka,easy-postman-plugins/plugin-redis -am clean package -DskipTests
```
### 6.2 第二层:检查 descriptor 是否正确
至少检查两处:
1. 构建输出目录
```text
easy-postman-plugins/plugin-kafka/target/classes/META-INF/easy-postman/plugin-kafka.properties
```
2. 最终 JAR 内的 descriptor
你需要确认:
- `plugin.id`
- `plugin.version`
- `plugin.entryClass`
- `plugin.description`
- `plugin.min/maxPlatformVersion`
- 如有需要,`plugin.min/maxAppVersion`
这一层的目标是确认:
- `pom.xml` -> descriptor 模板 -> 构建产物
没有漂移
### 6.3 第三层:安装验证
最推荐的本地验证方式:
1. 打开 EasyPostman
2. 进入插件管理
3. 选择“安装本地 JAR”
4. 选中刚打出来的插件 JAR
5. 重启应用
6. 验证插件能力是否真的出现
按插件类型,至少要检查:
- Toolbox 面板是否出现
- `pm.xxx` 是否可用
- 脚本补全是否出现
- Snippet 是否出现
- 禁用 / 启用 / 卸载后状态是否正确
### 6.4 第四层:兼容性验证
如果这次改动涉及兼容范围,建议专门验证:
- 不兼容插件在安装阶段是否被拦截
- 兼容插件是否能正常安装和加载
- 插件管理界面的兼容文案是否符合预期
- 市场列表里的安装按钮是否被正确禁用或允许
### 6.5 可选:开发期扫描目录
当前还支持一个开发期附加扫描目录:
- `-DeasyPostman.plugins.dir=/your/path`
它适合:
- 调试额外插件目录
- 企业预置插件目录验证
但对单插件开发来说,最稳定的验证路径仍然是“构建 JAR 后通过插件管理安装”。
## 7. 用户安装和分发方式
虽然这份文档更偏开发者,但用户安装方式最好也统一理解。
当前对用户只保留 2 条路径:
1. 本地安装
- 直接安装单个插件 JAR
2. 在线安装
- 通过远程 `catalog.json` 加载并安装
当前官方 catalog:
- GitHub
- `https://raw.githubusercontent.com/lakernote/easy-postman/master/plugin-catalog/catalog-github.json`
- Gitee
- `https://gitee.com/lakernote/easy-postman/raw/master/plugin-catalog/catalog-gitee.json`
### 7.1 为什么本地和在线都保留
因为两者解决的是不同问题:
- 本地 JAR
- 适合开发联调、手工分发、最低心智成本安装
- 在线 catalog
- 适合长期维护、官方分发、团队内网插件源
### 7.2 当前目录结构的意义
用户机器上默认会看到两个目录:
- `EasyPostman/plugins/installed/`
- `EasyPostman/plugins/packages/`
含义:
- `installed/`
- 当前用于实际加载的副本
- `packages/`
- 保留包副本
- 为重装、升级和某些文件锁场景做缓冲
## 8. 常见问题
### Q1:开发一个插件,最少要写哪些文件
至少这些:
1. `plugin-xxx/pom.xml`
2. `src/main/java/.../XxxPlugin.java`
3. `src/main/resources/META-INF/easy-postman/plugin-xxx.properties`
如果插件有 UI 或脚本 API,再加对应类。
### Q2:更新插件一定要改宿主版本吗
不一定,通常不用。
大多数情况下:
- 只改插件代码
- 只提升插件自己的 ``
只有插件机制本身发生不兼容变化时,才需要改 `plugin.platform.version`。
### Q3:为什么 GitHub Actions 不直接维护兼容性字段
因为兼容性定义应该属于插件自己,而不是 workflow。
当前正确的数据流是:
- 插件 `pom.xml` 定义元数据
- Maven 生成 descriptor
- workflow 从 JAR descriptor 读取并校验
- workflow 更新 catalog
这样不容易出现多处手填漂移。
### Q4:本地验证为什么不推荐直接手拷文件
因为插件管理这条路径会顺带验证:
- JAR 是否有效
- descriptor 是否可读
- 兼容性是否满足
- 安装来源是否被记录
这比手工复制更接近真实用户路径。
### Q5:我应该如何描述这套插件体系
推荐统一成一句话:
> EasyPostman 的插件以单 JAR 为交付单元,通过稳定的插件 API、独立的插件运行时和远程 catalog 机制,实现独立开发、独立升级和独立发布。