插件开发规范
新开发者从核心版本建立插件仓库、制作发行包与安装升级的完整流程,参见插件安装与更新。
适用范围
本文档适用于基于当前项目开发的源码级业务插件,例如商城、新闻资讯、内容管理和营销活动等插件。
插件基线为核心 v0.0.4(最低要求,推荐使用最新的版本开发),采用源码级、编译期注册模式:
- 不支持上传 Go 文件后动态加载,不支持运行中热启停;
- 插件安装、升级、启停后必须重新构建并重启管理端 API 和用户端 API;
- 插件后端和管理端页面都进入主项目构建流程。
强制原则
- 插件 ID 发布后不得修改:小写字母开头,仅含小写字母、数字和下划线,2~64 位;
- 插件间只能通过公开服务接口或明确声明的依赖协作,禁止直接修改其他插件的业务表;
- Controller 只负责参数绑定、调用 Logic 和统一响应,业务规则及事务放在 Logic;
- Logic 不直接返回 Model,接口响应必须在
resp中重新定义; - Model、Param、Resp 的字段必须添加 JSON、GORM 或校验 Tag,并写明字段注释;
- 表关联关系在 Resp 中定义,不在 Model 中定义;优先使用 GORM
Preload; - 管理端业务接口默认注册到
Permission路由组,禁止绕过登录、权限和操作日志; - 数据库表和字段必须有注释,字符集
utf8mb4,排序规则utf8mb4_general_ci; - 枚举值从
1开始,并在 Go 枚举文件和前端枚举文件中集中定义; - 业务表中的文件字段只保存相对路径,展示时统一补全访问地址;
- 不使用 GORM
AutoMigrate,结构变化通过增量 SQL 交付; - 前端使用 TypeScript、ES6 与箭头函数,不得用
any绕过类型检查; - 插件页面复用现有组件、主题变量与交互规范,不得覆盖全局样式;
- 富文本编辑必须复用宿主
src/components/RichTextEditor.vue。
目录结构
后端
server_api/internal/plugins/{plugin_id}/
├── plugin.go # 清单、迁移声明、路由注册与生命周期
├── enums/ # 插件业务枚举(从 1 开始)
├── model/ # 插件自身的数据库表模型
├── service/ # 管理端、用户端共用的插件公开能力
├── admin/ # 管理端接口:controller / logic / param / resp
└── api/ # 用户端接口:controller / logic / param / respParam 与 Resp 按业务功能拆分文件(如 admin/param/article_create.go),禁止整个插件堆放在单一 param.go / resp.go。只有单端能力时可省略 admin 或 api 目录,但路由注册方法仍需实现并返回 nil。
管理端
admin_client/src/plugins/{plugin_id}/
├── api/ # 接口封装
├── types/ # 请求与响应类型
├── enums/ # 前端枚举
├── components/ # 表单等组件(与列表页拆分)
└── views/{view_path}/index.vue页面路径约定:数据库菜单路径 /plugin/{plugin_id}/{view_path} 对应 src/plugins/{plugin_id}/views/{view_path}/index.vue。
迁移 SQL
database/migrations/
├── v1.0.0.sql
└── v1.1.0.sql插件契约
插件必须导出 New() 并实现 internal/common/plugin.Plugin:
package news
import (
"context"
commonplugin "server_api/internal/common/plugin"
)
type Plugin struct{}
func New() *Plugin { return &Plugin{} }
func (p *Plugin) Manifest() commonplugin.Manifest {
return commonplugin.Manifest{
PluginID: "news",
Name: "新闻资讯",
Version: "1.0.0",
CoreVersion: commonplugin.CoreVersion,
Dependencies: []commonplugin.Dependency{},
}
}
func (p *Plugin) Migrations() []commonplugin.Migration {
return []commonplugin.Migration{
{Version: "1.0.0", Description: "创建新闻资讯基础表", Checksum: "SQL 文件 SHA256"},
}
}
func (p *Plugin) RegisterAdminRoutes(ctx *commonplugin.Context, groups commonplugin.AdminRouteGroups) error {
groups.Permission.GET("/plugin/news/article/list", controller.List)
return nil
}
func (p *Plugin) RegisterAPIRoutes(ctx *commonplugin.Context, groups commonplugin.APIRouteGroups) error {
return nil
}
func (p *Plugin) Start(ctx context.Context, service commonplugin.ServiceType) error { return nil }
func (p *Plugin) Stop(ctx context.Context) error { return nil }清单要求:
plugin.json.version、后端Manifest.Version、Git 标签与 ZIP 文件名保持一致,遵循 SemVer;CoreVersion必须与实际宿主commonplugin.CoreVersion精确一致,不解析版本范围;- 所有依赖在
Dependencies中声明,精确匹配版本,禁止循环依赖; Version每次对外发布必须提升,禁止同版本重新打包。
安装器通过以下命令扫描插件并生成注册文件 internal/plugins/register_gen.go(禁止手工编辑):
go run . plugin generate --server-root .数据库启用但未编译进程序的插件会导致服务拒绝启动。
数据库规范
建表要求
CREATE TABLE IF NOT EXISTS `plg_news_article` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '文章ID',
`created_at` datetime(3) NOT NULL COMMENT '创建时间',
`updated_at` datetime(3) NOT NULL COMMENT '更新时间',
`deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
`title` varchar(200) COLLATE utf8mb4_general_ci NOT NULL COMMENT '文章标题',
`status` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '状态:1草稿,2已发布,3已下线',
PRIMARY KEY (`id`),
KEY `idx_news_article_deleted_at` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='新闻文章表';- 插件业务表统一
plg_{plugin_id}_*前缀,禁止使用sys_前缀; - 业务唯一性由唯一索引兜底;金额用整数最小货币单位或明确精度
decimal,禁止浮点数; - 迁移必须可安全重试(MySQL DDL 隐式提交,无法与元数据同事务);
- 已发布迁移不可修改,只能新增更高版本;无数据库变化的版本不需要空迁移,但插件版本仍必须提升;
- 每个版本必须提供
docs/database/v{version}.md:业务表、表内关系、对核心表与其他插件表的引用、文件归属、Remove/Purge 影响;没有引用也要明确写「无」——缺少声明时安装器拒绝校验、安装、升级和 Purge。
迁移允许与禁止
允许:CREATE TABLE、ALTER TABLE、CREATE INDEX、对当前插件表的 INSERT(显式字段、不含自增 ID)。
拒绝:DROP、TRUNCATE、DELETE、RENAME、业务数据 UPDATE、破坏性 ALTER、存储过程/触发器、操作 sys_ 核心表或其他插件表、写入菜单与插件元数据。
路由与鉴权
| 路由组 | 使用场景 | 要求 |
|---|---|---|
Permission | 管理端普通业务接口 | 默认选择,登录 + 接口权限 + 操作日志完整链路 |
Auth | 登录即可访问的个人能力 | 必须说明不需要角色权限的原因 |
Public | 无登录态的回调或公开能力 | 必须自行实现验签、防重放、限流和幂等 |
用户端私有数据接口注册到 Auth,公开列表/详情或第三方回调注册到 Public;即使用户已登录,也必须在 Logic 中校验资源归属。
菜单与权限
- 菜单、按钮和接口权限在
plugin.json.menus中交付,不编写sys_menuINSERT SQL; - 菜单只用
key与parent_key描述层级,真实 ID 由数据库生成并记录到sys_plugin_menu; - 页面菜单路径必须位于
/plugin/{plugin_id}/下; - 普通角色默认不自动获得插件权限,由管理员明确授权。
版本文档
每个插件版本必须提供(文件名版本与 plugin.json.version 完全一致):
docs/api/v{version}.md:覆盖管理端、用户端与回调接口,含参数、响应、鉴权、错误场景与版本变更记录;docs/database/v{version}.md:数据库引用说明(内容要求见上);docs/updates/v{version}.md:版本类型、主要变化、数据库变化、升级操作、兼容性与回滚方式。
自动生成的 OpenAPI/Swagger 可作补充,不能替代上述文档。
前端开发要点
- 请求统一走
request<T>(),接口域名只从环境变量读取; - 列表页与表单组件拆分,表单交互不写在列表页
index.vue; - 按钮使用
v-perm;时间展示到秒;文件字段只提交相对路径; - 明暗主题都要适配;不得定义跨模块的全局 CSS;
- 新增页面后执行
pnpm exec vue-tsc --noEmit与pnpm build确认可解析。
生命周期与后台任务
Start 只用于确实需要的后台任务、订阅或连接,普通 CRUD 插件返回 nil 即可。后台任务必须:
- 监听传入的
context.Context,取消后及时退出; - 避免重复启动,共享状态加锁或使用并发安全结构;
- 设置外部请求超时,可重试任务带退避与最大重试次数,消费任务设计幂等;
- 在
Stop中释放插件独占资源,不关闭宿主的数据库或 Redis 连接。
发布前检查清单
后端:插件 ID / 版本 / 依赖正确;分层合规;Logic 未返回 Model;关联无 N+1;管理端接口走 Permission;Public 接口有验签防重放限流;并发写入有唯一索引或锁;日志无敏感信息;gofmt / go vet / go build / go test 通过。
数据库:表前缀正确;注释齐全;utf8mb4_general_ci;枚举从 1 开始且前后端同步;索引合理;迁移可重试;版本与 SHA256 已登记;数据库引用文档完整。
管理端:页面路径符合解析约定;TS/ES6/箭头函数;表单拆分独立组件;v-perm 生效;明暗主题正常;时间到秒;无全局样式污染。
验证通过后仍需在测试环境完成安装、启用、权限分配、停用、升级与服务重启流程。
禁止事项
- 禁止绕过注册中心直接修改核心路由初始化;
- 禁止在插件启动时自动建表或修改表结构;
- 禁止把 Model 直接作为接口响应,禁止在 Model 中定义业务关联结构;
- 禁止跨插件直接改表、使用未校验的字符串拼接 SQL / 排序 / 表名;
- 禁止在 Public 路由中信任客户端身份字段;
- 禁止启动不能停止、没有超时或无限重试的 Goroutine;
- 禁止把密钥、Token、证书或用户敏感信息写入日志;
- 禁止覆盖全局 CSS、全局组件或核心菜单行为;
- 禁止修改已发布并登记摘要的迁移 SQL。