当前发布与数据库规则以数据库版本规则为准:每个版本必备全量/增量 SQL,当前基线统一为 0.0.1,普通升级不支持降级。下文历史示例仅说明业务结构。
插件开发规范
新开发者从核心版本建分支、制作发行包及安装升级的完整流程,参见 插件开发、分发、安装与更新指南。
1. 适用范围
本文档适用于基于当前项目开发的源码级业务插件,例如商城、新闻资讯、内容管理和营销活动等插件。
插件必须遵循现有项目的代码分层、数据库、接口安全和管理端 UI 规范。插件不是独立于主项目的另一套框架,不得复制认证、响应、上传、配置等基础设施。
新插件开发基线统一为核心 0.0.1,采用编译期注册方式。插件清单中的 core_version 必须与实际使用的宿主 commonplugin.CoreVersion 一致:
插件脚手架
新插件必须优先使用核心命令生成,不再手工复制新闻插件或直接修改宿主目录:
go run . plugin create example \
--name "示例插件" \
--author "开发者名称" \
--homepage "https://example.com/plugin/example" \
--description "示例插件说明" \
--server-root . \
--admin-root ../admin_client \
--user-root ../user_clientplugin_id 必须使用小写字母、数字和下划线并以字母开头。默认输出到 server_api 同级的 go_basic_frame_plugin_{plugin_id}。命令生成:
- 标准
plugin.json、版本文档和数据库迁移目录; - 严格分层的后端最小可运行代码;
- 管理端独立组件和页面;
- 用户端 uni-app 子包、清单和入口页面;
scripts/dev.sh、scripts/sync-dev.sh与scripts/package.sh;- 被
.gitignore排除的.dev核心框架开发环境。
.dev 从当前核心工作区复制,自动排除 Git、node_modules、dist、既有插件和历史插件文档。插件仓库根目录是唯一发行源码。开发时只需启动一次 scripts/dev.sh,进程会先完成一次同步并持续监听三端插件源码,保存文件后自动同步到 .dev;scripts/sync-dev.sh 仅作为一次性同步或恢复手段。禁止直接在 .dev 中长期维护插件代码。
发行脚本只打包清单白名单中的插件目录、数据库和文档,不得把 .dev、核心框架、依赖目录或构建产物放入 ZIP。只需要生成发行源码、不需要本地核心副本时可以增加 --skip-dev。
- 不支持上传 Go 文件后动态加载;
- 不支持运行中热启停;
- 插件状态、版本或依赖变更后必须重启管理端 API 和用户端 API;
- 插件后端和管理端页面都必须进入主项目构建流程。
2. 强制原则
- 插件 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 枚举文件和前端枚举文件中集中定义,枚举优先查看核心框架是否有,没有的情况在自定义。 - 普通业务表中的文件字段只保存相对路径,通过统一文件地址补全方法返回绝对地址;
sys_upload_file按现有规则同时保存相对路径和绝对路径。 - SQL 不使用 GORM
AutoMigrate自动变更生产数据库,不在服务启动时执行 DDL。 - 前端必须使用 TypeScript、ES6 语法和箭头函数,不得使用
any绕过类型检查。 - 插件页面必须复用现有组件、主题变量、间距、按钮、表格和弹窗规范,不得覆盖全局样式。
- 时间展示精确到秒,不显示毫秒。
- 每个插件版本必须提供独立的
docs/api/v{version}.md接口文档,并随仓库、标签和发行 ZIP 一同发布;代码、菜单权限或接口字段变化时必须新增对应版本文档。 - 每个插件版本必须提供独立的
docs/database/v{version}.md数据库说明;有引用必须完整列出,没有引用也必须明确写“无”。缺少或空文档时安装器拒绝校验、安装和升级,Purge 缺少宿主归档文档时拒绝执行。 - 插件需要富文本编辑时必须复用宿主
src/components/RichTextEditor.vue,不得在插件内重复封装 WangEditor 或自行维护明暗主题。
3. 标准目录结构
3.1 后端目录
docs/
├── api/
│ └── v0.0.1.md
├── database/
│ └── v0.0.1.md
└── updates/
└── v0.0.1.md
server_api/internal/plugins/{plugin_id}/
├── plugin.go
├── enums/
│ └── *.go
├── model/
│ └── *.go
├── service/
│ └── *.go
├── admin/
│ ├── controller/
│ ├── logic/
│ ├── param/
│ └── resp/
└── api/
├── controller/
├── logic/
├── param/
└── resp/docs/api/v{version}.md 和 docs/database/v{version}.md 都是强制交付文件,版本必须与 plugin.json.version 一致。API 文档必须覆盖管理端接口、用户端接口以及第三方回调;数据库说明必须覆盖插件业务表、表内关系、核心表引用、其他插件表引用、外部表反向引用、文件归属和移除影响。没有某类接口或数据库引用时也要明确写明“无”。同一版本只保留一份完整接口文档和一份数据库说明,不再额外维护内容重复的顶层 API.md、DATABASE.md。
目录职责:
| 目录 | 职责 |
|---|---|
plugin.go | 插件清单、迁移声明、路由注册和生命周期 |
enums | 插件业务枚举,枚举值从 1 开始 |
model | 插件自身的数据库表模型,不定义关联对象 |
service | 管理端、用户端或其他插件共同使用的插件公开业务能力 |
admin | 管理端接口,必须使用现有管理端鉴权体系 |
api | 用户端接口,必须使用现有用户端鉴权体系 |
只有单端能力时可以省略对应的 admin 或 api 目录,但 Plugin 接口中的路由注册方法仍需实现并返回 nil。
Param 和 Resp 必须按业务功能拆分文件。例如:
admin/param/article_create.go
admin/param/article_list.go
admin/resp/article_detail.go
admin/resp/article_list.go禁止把整个插件的请求或响应结构全部堆放在一个 param.go、resp.go 中。
3.2 管理端目录
admin_client/src/plugins/{plugin_id}/
├── api/
├── components/
├── enums/
├── types/
└── views/
└── {view_path}/
└── index.vue数据库菜单路径:
/plugin/{plugin_id}/{view_path}页面文件:
src/plugins/{plugin_id}/views/{view_path}/index.vue例如 /plugin/news/article 对应 src/plugins/news/views/article/index.vue。
3.3 用户端目录
用户端插件使用 uni-app 独立分包:
user_client/src/plugins/{plugin_id}/
├── manifest.ts
├── api/
├── components/
├── composables/
├── constants/
├── pages/
├── types/
├── utils/
└── static/每个插件必须提供 UserPluginManifest 清单,subpackageRoot 使用 plugins/{plugin_id},页面路径使用 /plugins/{plugin_id}/pages/...。注册表和 Vite 分包配置在构建时自动扫描 src/plugins/*;禁止手工维护重复清单。
所有用户端插件必须是 src/plugins/{plugin_id} 下的 uni-app 独立子包。插件不得注册原生或自定义 TabBar、不得修改 TabBar 页面,也不得修改首页来硬编码入口;唯一业务入口由管理端配置的首页菜单等宿主入口跳转到插件子包。所有请求复用 src/utils/request.ts,交互反馈复用 appFeedback、AppFeedbackHost 和 AppConfirmDialog,禁止直接调用 uni-app 原生提示 API。页面必须同时适配亮色、暗色与跟随系统主题,布局使用 Flexbox,不使用 CSS Grid,并验证 H5、App 和声明支持的小程序平台。
涉及评论、点赞、收藏、分享等用户互动数据时,数据库自增主键 id 只能作为内部聚簇主键,不得作为公开 API 参数或跨表业务关联键。插件必须新增唯一 uid 并使用 pkg/sn.Generate() 生成;会员关系使用 sys_member.sn,且只能从鉴权上下文取得,禁止接受客户端提交的会员 SN。公开读取注册到 Public,写入、取消和个人状态查询注册到 Auth。写接口必须同时考虑幂等键、唯一索引、频率限制、事务计数和并发状态切换,用户端响应不得返回会员 SN、原始 IP、数据库主键或其他非展示字段。
用户端公共依赖安装在根工程,插件目录不维护独立 node_modules。新增依赖必须评估多插件重复依赖、多平台兼容性和分包体积。详细规则见 user_client/docs/plugin-development.md。
发行包包含用户端目录时,plugin install/upgrade --user-root ../user_client 会原子替换 user_client/src/plugins/{plugin_id};失败时与后端、管理端源码一起回滚。安装、升级、移除后必须重新构建用户端,各平台运行时代码不会热更新。
3.4 SQL 目录
发行包中的迁移使用独立目录:
database/
└── migrations/
├── v0.0.1.sql
└── v1.1.0.sql迁移只包含当前插件业务结构和数据。插件信息、迁移记录和菜单由安装器根据 plugin.json 写入,插件不得直接修改核心表。安装后迁移文件保存到 server_api/sql/plugins/{plugin_id}/{version}。
4. 插件清单和契约
插件必须实现 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: "0.0.1",
Logo: "plugins/news/logo.png",
Author: "项目维护者",
Homepage: "https://example.com/plugins/news",
Description: "提供新闻分类、文章和发布管理能力",
CoreVersion: commonplugin.CoreVersion,
Dependencies: []commonplugin.Dependency{},
}
}
func (p *Plugin) Migrations() []commonplugin.Migration {
return []commonplugin.Migration{
{
Version: "0.0.1",
Description: "创建新闻资讯基础表和菜单",
Checksum: "安装 SQL 文件的 SHA256 摘要",
},
}
}
func (p *Plugin) RegisterAdminRoutes(
ctx *commonplugin.Context,
groups commonplugin.AdminRouteGroups,
) error {
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
}清单要求:
PluginID必须符合命名规则且全局唯一;Name和Version不得为空;Logo只保存文件相对路径,查询展示时由统一文件地址方法补全;Author填写插件作者或维护团队,Homepage填写可公开访问的插件主页;Description应说明插件功能、适用场景和关键依赖;Version必须使用语义化版本(SemVer),格式为主版本.次版本.修订版本,例如1.2.3;- 每一次对外发布都必须提升版本号,禁止覆盖、重新打包或重复发布已经安装的相同版本;
- 不兼容的接口、配置或数据结构调整提升主版本,例如
1.2.3 → 2.0.0; - 向后兼容的新功能提升次版本,例如
1.2.3 → 1.3.0; - 向后兼容的问题修复、UI优化、文档或资源调整提升修订版本,例如
1.2.3 → 1.2.4; plugin.json.version、后端Manifest.Version、发行标签和 ZIP 文件名必须保持一致;CoreVersion必须填写当前commonplugin.CoreVersion;- 所有依赖必须在
Dependencies中声明; - 当前依赖版本使用精确匹配,不支持
>=0.0.1等范围表达式; - 禁止形成循环依赖。
插件必须导出 New()。安装器扫描插件目录并自动生成静态注册文件:
go run . plugin generate --server-root .生成结果为 internal/plugins/register_gen.go,禁止手工编辑。数据库启用但没有编译进程序的插件会导致服务拒绝启动。
4.1 订单插件接入财务中心
插件只要产生订单,就必须额外实现可选接口 commonplugin.FinanceOrderProvider,把订单投影到核心 sys_finance_order。核心财务列表只查询统一索引,不允许直接依赖或联表查询某个插件的订单表。完整订单、商品快照和业务操作仍由插件自身负责。
func (p *Plugin) SyncFinanceOrders(ctx *commonplugin.Context) error {
latest, err := financeorder.LatestSourceUpdatedAt(ctx.App.DB, "example")
if err != nil { return err }
// 查询 updated_at >= latest 的来源订单并映射为 []financeorder.Record。
return financeorder.UpsertBatch(ctx.App.DB, records)
}SourcePluginID + SourceOrderUID是统一索引的唯一关联键,禁止使用数据库自增id;- 插件订单、订单项及其关联仍使用插件自身的业务
uid; - 新订单与状态、金额变更必须更新来源订单的
updated_at,保证增量同步可发现; - 支付状态统一为 1 未支付、2 已支付、3 已退款,业务订单状态通过
OrderStatus + StatusText保留插件语义; - 商品图片和会员头像写入相对路径,财务接口查询时由核心存储工具补全;
- 同步必须幂等,并使用
financeorder.UpsertBatch处理并发冲突,禁止插件自行修改其他来源的统一订单记录。
5. 数据库规范
5.1 表和字段命名
插件业务表统一使用:
plg_{plugin_id}_{table_name}示例:
plg_news_category
plg_news_article
plg_shop_product
plg_shop_order禁止创建 sys_ 前缀的插件业务表。sys_ 只用于核心系统表。
5.2 建表要求
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) 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`),
KEY `idx_news_article_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='新闻文章表';必须满足:
- 表和每个字段都有明确注释;
- 主键、唯一约束、查询条件和排序字段根据访问方式建立索引;
- 业务唯一性必须由唯一索引兜底,不能只依赖代码先查询;
- 金额使用整数最小货币单位或明确精度的
decimal,禁止使用浮点数; - 并发更新使用事务、原子条件更新或锁,禁止“先查再无条件写”;
- 外键约束是否使用应与现有项目保持一致,但关联 ID 必须有索引;
- SQL 应具备可重复执行能力,或在文档中明确只能执行一次;
- 禁止在升级脚本中静默删除业务数据。
- MySQL DDL 会隐式提交,无法与插件元数据处于同一个物理事务;因此所有
CREATE TABLE、新增字段和新增索引迁移必须可安全重试。 - 安装器必须将迁移记录、系统菜单、菜单映射、插件信息和成功安装日志放在同一个事务中提交;任一步骤失败时不得留下孤立菜单或半套元数据。
- 同一插件的安装与升级必须使用数据库级互斥锁串行执行,防止多进程并发重复迁移或重复创建菜单。
5.3 Model 规范
// Article 新闻文章模型。
type Article struct {
ID uint64 `gorm:"column:id;primaryKey" json:"id" comment:"文章ID"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"-" comment:"删除时间"`
Title string `gorm:"column:title;size:200;not null" json:"title" comment:"文章标题"`
Status uint8 `gorm:"column:status;not null" json:"status" comment:"文章状态"`
}
// TableName 返回新闻文章表名。
func (Article) TableName() string {
return "plg_news_article"
}Model 只映射当前表字段,不定义分类、作者等关联对象。关联响应在 Resp 中定义。
5.4 迁移记录
- 计算迁移 SQL 的 SHA256 摘要。
- 在
plugin.json和Migrations()中填写相同版本与摘要。 - 通过
plugin install/upgrade --apply-database执行迁移。 - 安装器写入状态为
1的sys_plugin_migration记录。 - 安装器在迁移和菜单成功后写入或更新
sys_plugin.version。
迁移失败时必须记录状态 2 和错误原因,不得把未完成的迁移标记为成功。修改已经发布的 SQL 会造成摘要不一致;已发布迁移不得修改,只能新增更高版本迁移。每个插件版本都必须提供全量和增量SQL;无数据库变化时增量SQL用注释说明,插件版本仍必须提升。
5.5 数据库引用文档
插件每个版本必须提供 docs/database/v{version}.md,至少包含:
- 插件 ID、版本、核心版本和文档更新时间;
- 所有
plg_{plugin_id}_*业务表、用途和数据敏感级别; - 表内关联字段、引用方向、是否为物理外键、删除与更新规则;
- 对核心表的物理外键或逻辑读写引用,没有时明确写“无”;
- 对其他插件表的物理外键或逻辑读写引用,并与
Dependencies保持一致,没有时明确写“无”; - 其他模块可能反向引用本插件表的情况;
- 上传文件、缓存、队列、搜索索引等数据库之外的数据归属;
plugin remove保留和删除的内容;plugin purge删除的内容、不可恢复风险和备份要求;- 是否允许 Purge 的明确结论及限制条件。
插件每次发布还必须提供 docs/updates/v{version}.md,文件名版本必须与 plugin.json.version 完全一致,至少说明版本类型、主要变化、数据库变化、升级操作、兼容性和回滚方式。仅维护 CHANGELOG.md 不能替代独立版本更新说明。
安装器会把插件文档归档到 go_basic_frame_wiki/plugin/archives/{plugin_id}/{version}。Remove 保留归档文档供审查;Purge 必须先确认最新归档版本的 database/v{version}.md 存在且非空,再结合数据库真实表和外键执行。旧版本归档的顶层 DATABASE.md 仅用于兼容。文档是人工审查依据,不替代运行时外键扫描、插件依赖检查和表前缀校验。
为避免只创建空文档绕过检查,安装器还会校验数据库说明至少包含“插件业务表”“外部数据库引用”“是否引用核心业务表”“是否引用其他插件表”“是否允许 Purge”“是否自动删除文件”“Remove”和“Purge”等明确声明。缺少任一声明都不能安装、升级或彻底清理。
6. 后端分层规范
6.1 Param
// ArticleCreate 创建文章参数。
type ArticleCreate struct {
CategoryID uint64 `json:"category_id" binding:"required" validate:"分类ID" comment:"分类ID"`
Title string `json:"title" binding:"required,max=200" validate:"文章标题" comment:"文章标题"`
Content string `json:"content" binding:"required" validate:"文章内容" comment:"文章内容"`
}- 必须定义 JSON/form/uri、binding 校验、
validate:"字段语义"和 comment Tag; - 参数校验在进入 Logic 前完成;
- ID、状态、分页大小等必须校验边界;
- 不允许客户端提交创建人、租户、权限范围等应由服务端确定的字段。
6.2 Resp
// ArticleDetail 文章详情响应。
type ArticleDetail struct {
ID uint64 `json:"id" gorm:"column:id" comment:"文章ID"`
Title string `json:"title" gorm:"column:title" comment:"文章标题"`
CoverPath string `json:"cover_path" gorm:"column:cover_path" comment:"封面相对路径"`
CoverURL string `json:"cover_url" gorm:"-" comment:"封面完整地址"`
Category CategoryItem `json:"category" gorm:"foreignKey:CategoryID" comment:"所属分类"`
CreatedAt string `json:"created_at" gorm:"column:created_at" comment:"创建时间"`
}- Logic 返回 Resp,不直接暴露 Model;
- 关联对象和 GORM 预加载关系只定义在 Resp;
- 文件相对路径和完整访问地址应使用不同字段表达;
- 对外时间统一格式化到秒;
- 不返回内部错误、密码、密钥、完整存储凭据等敏感字段。
6.3 Logic 和事务
- Logic 负责权限范围、业务状态、事务、并发控制和 Resp 组装;
- 多表写入必须使用数据库事务;
- 事务内部所有查询和写入必须使用同一个事务对象;
- 外部网络调用不要长时间占用数据库事务;
- 重试操作必须设计幂等键或唯一约束;
- 列表查询必须分页并限制最大分页大小;
- 避免循环查询,关联数据优先批量查询或
Preload; - 用户可控排序字段必须使用白名单,禁止直接拼接 SQL;
- 数据库错误通过项目统一错误封装处理,不把底层错误直接返回客户端。
6.4 Controller
- 只通过
pkg/validate完成参数绑定、上下文信息获取、Logic 调用和统一响应,禁止直接调用 GinShouldBind*; - 参数不通过时返回统一验证器生成的字段语义错误,不得用“参数错误”覆盖;
- 不直接操作数据库;
- 不在 Controller 中编写复杂业务判断;
- 所有接口沿用项目现有响应结构和错误处理方式;
- 上传、文件地址补全、分页等能力复用公共封装。
7. 路由、鉴权和审计
7.1 管理端路由
func (p *Plugin) RegisterAdminRoutes(
ctx *commonplugin.Context,
groups commonplugin.AdminRouteGroups,
) error {
articleController := admincontroller.NewArticle(ctx.App)
groups.Permission.GET("/plugin/news/article/list", articleController.List)
groups.Permission.POST("/plugin/news/article/create", articleController.Create)
groups.Permission.PUT("/plugin/news/article/update", articleController.Update)
groups.Permission.DELETE("/plugin/news/article/delete", articleController.Delete)
return nil
}路由选择:
| 路由组 | 使用场景 | 要求 |
|---|---|---|
Permission | 管理端普通业务接口 | 默认选择,具备登录、接口权限和操作日志链路 |
Auth | 已登录即可访问的个人能力 | 必须说明不需要角色权限的原因 |
Public | 无法使用登录态的回调或公开能力 | 必须自行实现验签、防重放、限流和幂等 |
公开支付、短信、微信等回调必须验证第三方签名,并使用请求时间、随机数或事件 ID 防止重放。回调重复到达不能产生重复订单、重复退款或重复发放权益。
7.2 用户端路由
- 用户私有数据接口注册到
Auth; - 只有确实公开的列表、详情或第三方回调才放入
Public; - 即使已登录,也必须在 Logic 中校验资源归属;
- 不得只根据客户端传入的用户 ID 查询或修改数据。
7.3 菜单和权限
- 菜单、按钮和接口权限在
plugin.json.menus中交付,不编写sys_menuINSERT SQL; - 页面路径使用
/plugin/{plugin_id}/{view_path}; - API 权限标识必须与实际请求方法和路径一致;
- 普通角色默认不自动获得新插件权限;
- 超级管理员和普通管理员的行为沿用现有权限体系;
- 菜单只使用
key和parent_key描述层级,真实 ID 由数据库生成并记录在sys_plugin_menu; - 权限变更后按现有机制清理缓存或重新登录验证。
7.4 API 接口文档
每个插件仓库及发行 ZIP 必须包含:
docs/api/v0.0.1.md文档至少包含以下内容:
- 插件 ID、插件版本、适配的核心版本和文档更新时间;
- 接口基础前缀、统一响应结构、认证方式和权限规则;
- 接口方法、完整路由、接口用途及所属路由组(
Public、Auth或Permission); - Path、Query、Header、JSON Body 参数,包括类型、是否必填、约束、枚举值和说明;
- 成功响应字段、分页结构、关联对象、文件相对路径与完整地址字段;
- 业务失败场景、HTTP 状态码或统一业务错误码;
- 涉及写操作时说明幂等性、并发行为、事务边界或重复请求结果;
- 第三方回调说明验签、防重放、重试和幂等策略;
- 至少提供一组可直接使用的请求和响应示例,示例不得包含真实 Token、密钥或个人数据;
- 版本变更记录,标注新增、兼容变更和不兼容变更。
接口文档必须与实际注册路由、Param、Resp 和 plugin.json.menus[].api_path 一致。每次发布都必须新增 docs/api/v{version}.md,即使接口没有变化也要明确写明“无接口变化”;历史版本文件禁止覆盖。公开接口也必须记录访问频率、分页上限和内容安全注意事项。自动生成 OpenAPI/Swagger 文档可以作为补充,但不能替代发行包中的版本接口文档。
8. 前端开发规范
- 使用 Vue 3、
<script setup lang="ts">、ES6 和箭头函数。 - 网络请求统一使用现有
request<T>(),接口域名只从环境变量和统一请求封装读取。 - 插件 API、类型、枚举、组件和页面按目录拆分,禁止在页面中混写大量请求与数据转换逻辑。
- 列表页和新增/修改表单必须拆分:列表页只负责查询、筛选、分页、列表操作和打开组件;表单、弹窗、校验、详情回填及保存逻辑必须放在
components/*Form.vue等独立组件中,通过 Props、Emits 和defineExpose协作。禁止把完整表单或弹窗直接写在列表页index.vue中。 - 页面按钮必须使用现有
v-perm权限指令。 - 表格、表单、分页、上传、空状态和错误提示复用现有组件及交互规则。
- 页面不得定义影响其他模块的全局 CSS;样式优先使用
scoped和现有主题变量。 - 不硬编码后端域名、存储域名、权限值或中文状态映射。
- 文件上传只向业务接口提交相对路径;展示地址使用后端补全结果。
- 日期时间统一展示到秒。
- 新增页面后执行类型检查和生产构建,确认动态路由可以解析。
- 富文本字段统一使用宿主
@/components/RichTextEditor.vue,由宿主维护上传、亮色/暗色主题和编辑器销毁生命周期,插件只绑定业务字段和配置提示文本。
9. 生命周期和后台任务
Start 只用于启动确实需要的后台任务、订阅或连接。普通 CRUD 插件应直接返回 nil。
后台任务必须:
- 监听传入的
context.Context; - 在 Context 取消后及时退出;
- 避免重复启动;
- 对共享状态加锁或使用安全的并发结构;
- 设置外部请求超时;
- 对可重试任务设置退避和最大重试次数;
- 对消费任务设计幂等处理;
- 在
Stop中释放插件独占资源; - 不关闭宿主提供的数据库或 Redis 连接。
插件按依赖顺序启动,按相反顺序停止。任一插件启动失败时,服务启动失败,已启动插件会按相反顺序停止。
10. 配置和敏感信息
- 插件配置应使用独立配置键或独立业务表,避免无限扩展核心配置结构;
- 密钥、证书、支付私钥、短信凭证不得写入源码、日志或前端;
- 敏感配置应加密存储或通过安全环境变量注入;
- 返回配置详情时必须脱敏,更新接口使用“留空表示不修改”等明确语义;
- 配置变更需要考虑缓存失效和并发读取一致性;
- 多实例部署时不得依赖单机内存保存关键状态。
11. 依赖和插件间协作
- 插件不得导入其他插件的 Controller、Param、Resp 或 Model;
- 可复用能力应由被依赖插件在
service中提供稳定接口; - 必须在
Manifest.Dependencies中声明依赖插件和精确版本; - 禁止跨插件直接写表;确需读取时也应优先通过服务接口;
- 通用且不包含业务语义的能力放入
pkg; - 多业务模块共同依赖的业务能力放在合适的
internal/common子目录; - 不得为了单个插件把插件专属逻辑放入
common。
12. 安装、升级和卸载流程
12.1 安装
- 审查插件源码和依赖。
- 备份数据库及当前构建产物。
- 使用
plugin install --apply-database安装前后端源码、执行受限迁移并登记插件信息。 如果此前只安装了源码、数据库中还没有插件记录,必须允许使用同一个发行包重新执行plugin install --apply-database补齐数据库;不能要求删除插件目录,也不能错误地改用升级命令。 - 检查自动生成的
register_gen.go。 - 完成后端和管理端检查及构建。
- 检查插件、迁移、菜单映射和安装日志。
- 在测试环境启动并验证。
- 更新
sys_plugin.status = 1。 - 重启管理端 API 和用户端 API。
- 分配菜单权限并完成冒烟验证。
安装命令输出 数据库=false 代表只完成源码安装,不代表插件已经完成安装。完整安装必须确认业务表、sys_plugin、sys_plugin_migration、sys_plugin_menu 和 sys_plugin_install_log 均已正确写入;输出和部署文档必须清楚提示是否执行了数据库阶段。
12.2 升级
- 禁止修改历史迁移文件。
- 新增迁移文件并更新
plugin.json。 - 先执行数据库向前兼容变更,再发布可兼容新旧结构的代码。
- 写入迁移成功记录并更新插件版本。
- 同时发布后端和管理端构建产物。
- 重启服务并检查版本、依赖和迁移校验。
- 涉及删除旧字段时,至少延后一个发布周期处理。
12.3 停用和卸载
- 停用只修改插件状态并重启服务,不删除业务数据;插件必须先停用才能移除。
plugin remove <plugin_id>是可恢复移除:事务删除角色菜单授权、系统菜单、菜单映射和插件记录,然后移除前后端源码并重新生成注册文件;业务表、迁移记录、安装日志和上传文件继续保留。- 插件信息和插件菜单必须物理删除,避免软删除记录继续占用
plugin_id、菜单 ID 等唯一索引;业务数据仍由保留的插件业务表承载。 - 移除前必须检查没有已启用插件依赖当前插件。数据库元数据先删除,即使源码阶段意外中断,插件也不会重新运行;重复执行命令可以继续清理遗留源码。
- 移除后使用原发行包重新安装时,安装器复用已登记的迁移,不重复执行业务表迁移,并重新生成菜单和映射。
plugin purge <plugin_id> --confirm <plugin_id>是不可恢复的彻底清理:先执行可恢复移除,再删除plg_{plugin_id}_*业务表;所有业务表删除成功后,才事务删除迁移历史和安装日志。- Purge 执行前检查是否存在插件前缀以外的外键引用;存在时拒绝清理。插件内部外键只在当前数据库连接中临时关闭检查,并确保恢复。
- MySQL DDL 不能事务回滚,因此 Purge 使用“先使插件不可运行、DDL 可重复执行、最后删除历史记录”的可恢复状态机;中断后再次执行相同命令继续完成。
- 上传文件默认不自动删除,因为当前上传记录没有强制插件归属字段;文件清理由管理员根据业务归属单独审查。
- 删除数据、文件和表必须由管理员明确执行并提前备份;回滚代码前确认数据库结构和插件版本与旧代码兼容。
13. 发布前检查清单
后端
- [ ] 插件 ID、版本、核心版本和依赖声明正确。
- [ ] 插件导出
New(),安装器可以生成register_gen.go。 - [ ] Controller、Logic、Param、Resp、Model 分层符合规范。
- [ ] Logic 没有直接返回 Model。
- [ ] 关联关系只在 Resp 定义,并检查了 N+1 查询。
- [ ] 管理端业务接口默认使用
Permission路由组。 - [ ] Public 接口已完成验签、防重放、限流和幂等。
- [ ] 数据范围和资源归属在服务端校验。
- [ ] 并发写入有唯一索引、条件更新、事务或锁保护。
- [ ] 后台任务支持 Context 取消和安全停止。
- [ ] 日志中没有密码、Token、密钥和完整敏感配置。
- [ ]
docs/api/v{version}.md已覆盖全部实际路由,参数、响应、鉴权、权限和错误场景与代码一致。 - [ ] 接口变更已同步更新 API 文档中的版本变更记录和示例。
数据库
- [ ] 表名使用
plg_{plugin_id}_前缀。 - [ ] 表和字段都有注释。
- [ ] 字符集为
utf8mb4,排序规则为utf8mb4_general_ci。 - [ ] 枚举从 1 开始,并已同步后端及前端枚举。
- [ ] 唯一性、关联字段、筛选和排序字段索引合理。
- [ ] SQL 不会删除或覆盖现有业务数据。
- [ ] 迁移 SQL 可安全重试,元数据在同一事务提交,并发安装受插件级数据库锁保护。
- [ ] 迁移版本和 SHA256 摘要已写入迁移记录。
- [ ] 菜单业务键唯一、父级存在,清单中没有固定菜单 ID。
- [ ]
docs/database/v{version}.md已列出全部业务表、内外部引用、文件归属和 Remove/Purge 影响;没有引用的项目已明确写“无”。
管理端
- [ ] 页面路径和文件目录满足插件页面解析约定。
- [ ] 使用 TypeScript、ES6 和箭头函数。
- [ ] 新增/修改表单已拆成独立组件,列表页没有内嵌完整表单、详情回填或保存逻辑。
- [ ] 请求通过统一封装,未硬编码接口域名。
- [ ] 操作按钮使用
v-perm。 - [ ] UI、明暗主题和响应式布局符合现有页面风格。
- [ ] 时间显示到秒,文件地址使用后端补全结果。
- [ ] 页面无跨模块全局样式污染。
验证命令
后端:
gofmt -l .
go vet ./...
go test ./...
go build ./...管理端:
pnpm exec vue-tsc --noEmit
pnpm build所有检查通过后,还必须在测试环境验证插件安装、启用、权限分配、停用、升级和服务重启流程。
14. 禁止事项
- 禁止插件绕过注册中心直接修改核心路由初始化。
- 禁止在插件启动时自动建表或修改表结构。
- 禁止把 Model 直接作为接口响应。
- 禁止在 Model 中定义业务关联结构。
- 禁止跨插件直接修改数据表。
- 禁止使用未校验的字符串拼接 SQL、排序或表名。
- 禁止在 Public 路由中信任客户端身份字段。
- 禁止启动不能停止、没有超时或无限重试的 Goroutine。
- 禁止把第三方密钥、Token、证书或用户敏感信息写入日志。
- 禁止插件覆盖全局 CSS、全局组件或核心菜单行为。
- 禁止修改已经发布并登记摘要的迁移 SQL。
- 禁止未经备份直接执行插件卸载或破坏性回滚。