跳到主要内容
版本:0.1.x

基本介绍

源码插件是LinaPro推荐的默认扩展方式。它以Go源码形式与主框架一起编译部署,使用pluginhost注册路由、钩子、定时任务、生命周期回调和治理逻辑,适合长期维护、性能要求高、需要完整工程体验的业务模块。

官方源码插件位于apps/lina-plugins/。主仓库通过该目录挂载官方插件工作区;用户项目也在该目录下维护自己的业务插件。

适用场景

场景是否推荐源码插件原因
长期业务模块推荐可测试、可审查、性能最好
组织、内容、监控等后台能力推荐与主框架权限、菜单、调度和多租户协作紧密
运行时热加载不优先源码插件需要重新构建和部署主框架
商业二进制分发不优先源码插件通常暴露源码

标准目录结构

apps/lina-plugins/<plugin-id>/
├── plugin.yaml
├── plugin_embed.go
├── backend/
│ ├── api/ # API DTO与路由契约
│ ├── internal/
│ │ ├── controller/ # HTTP控制器
│ │ ├── service/ # 业务服务层
│ │ ├── dao/ # gf gen dao生成
│ │ └── model/ # do/entity模型
│ └── plugin.go # 插件注册入口
├── frontend/
│ └── pages/ # 插件页面
├── manifest/
│ ├── sql/ # 安装与升级SQL
│ │ ├── mock-data/ # 演示数据,可选
│ │ └── uninstall/ # 卸载SQL
│ └── i18n/ # 插件语言包
└── README.md

backend/internal/service/是插件服务逻辑的固定位置,不要在插件根目录或backend/根目录另建service/包。

插件清单

plugin.yaml声明插件身份、运行形态、多租户边界、菜单和权限:

id: content-article
name: 文章管理
version: v0.1.0
type: source
scope_nature: tenant_aware
supports_multi_tenant: true
default_install_mode: tenant_scoped
description: 提供文章内容的增删改查管理功能
author: linapro
license: Apache-2.0
menus:
- key: plugin:content-article:list
name: 文章管理
path: content-article-list
component: system/plugin/dynamic-page
perms: content-article:article:view
icon: ant-design:file-text-outlined
type: M
sort: 1
- key: plugin:content-article:create
parent_key: plugin:content-article:list
name: 创建文章
perms: content-article:article:create
type: B

菜单key必须全局唯一,推荐使用plugin:<plugin-id>:<menu-key>格式。按钮权限通过type: B挂在菜单下,不直接出现在侧边栏中。

数据库与SQL

插件安装SQL位于manifest/sql/,卸载SQL位于manifest/sql/uninstall/。安装和升级脚本必须幂等,常用CREATE TABLE IF NOT EXISTSCREATE INDEX IF NOT EXISTS等写法。

CREATE TABLE IF NOT EXISTS content_article_record (
"id" BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
"tenant_id" INT NOT NULL DEFAULT 0,
"title" VARCHAR(255) NOT NULL DEFAULT '',
"content" TEXT NOT NULL DEFAULT '',
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_content_article_record_tenant
ON content_article_record ("tenant_id");

需要支持多租户的插件表应包含tenant_id列。未启用multi-tenant插件时,tenant_id = 0表示平台上下文。

API与服务层

插件API定义同样使用g.Meta声明路径、方法、权限和文档说明:

type ArticleListReq struct {
g.Meta `path:"/plugins/content-article/article" method:"get" tags:"Article" summary:"List articles" permission:"content-article:article:view"`
Page int `json:"page" v:"min:1"`
PageSize int `json:"pageSize" v:"min:1,max:100"`
}

服务层通过插件自有DAO访问数据库。需要租户隔离时,应使用主框架发布的TenantFilterService追加租户条件,而不是手写不一致的过滤规则。

注册入口

源码插件在backend/plugin.go中通过init()注册:

func init() {
plugin := pluginhost.NewSourcePlugin("content-article")

plugin.Assets().UseEmbeddedFiles(contentarticle.EmbeddedFiles)

plugin.HTTP().RegisterRoutes(
pluginhost.ExtensionPointHTTPRouteRegister,
pluginhost.CallbackExecutionModeBlocking,
registerRoutes,
)

plugin.Cron().RegisterCron(
pluginhost.ExtensionPointCronRegister,
pluginhost.CallbackExecutionModeBlocking,
registerCronJobs,
)

plugin.Lifecycle().RegisterBeforeUpgradeHandler(beforeUpgrade)
plugin.Lifecycle().RegisterAfterUpgradeHandler(afterUpgrade)

pluginhost.RegisterSourcePlugin(plugin)
}

主框架在插件完整模式下生成聚合入口,空白导入已配置插件,使这些init()注册逻辑进入主框架进程。

前端页面

源码插件的前端页面位于frontend/pages/,由主框架工作台动态页壳加载。插件菜单中的component通常使用:

component: system/plugin/dynamic-page

页面可以复用默认工作台的前端生态和设计规范。插件禁用后,主框架菜单接口不再返回该插件入口,工作台侧边栏会自动隐藏。

事件钩子与定时任务

源码插件可以订阅主框架事件,例如登录成功、插件启用、系统启动等。钩子可以同步阻断,也可以异步执行,取决于注册时选择的执行模式。

插件也可以注册自己的定时任务处理器,供管理工作台创建任务时选择:

plugin.Cron().RegisterCron(
pluginhost.ExtensionPointCronRegister,
pluginhost.CallbackExecutionModeBlocking,
func(registry pluginhost.CronRegistry) error {
registry.Register("content-article:cleanup", cleanupExpiredArticles)
return nil
},
)

运行时升级

源码插件文件更新后,主框架会比较数据库中的有效版本和当前发现版本。发现更高版本时,插件进入pending_upgrade运行时状态,主框架基础治理能力保持可用,插件业务入口进入受控状态。

管理员在插件管理页执行显式运行时升级。升级流程会重新读取有效清单和目标清单,执行依赖检查、BeforeUpgrade回调、插件自定义升级逻辑、升级SQL、治理资源同步、有效版本切换和缓存失效。失败后进入upgrade_failed,可以查看诊断信息并重试。

这种模型避免把文件覆盖误认为数据和治理资源已经完成升级。

最佳实践

  • 插件ID使用kebab-case,数据库表前缀使用对应的snake_case
  • 安装和升级SQL必须幂等,避免保留数据后重新安装失败。
  • 服务逻辑放在backend/internal/service/
  • 插件只使用pluginhostpluginservice等稳定契约,不直接依赖主框架internal/包。
  • 多租户插件表预留tenant_id列,并使用主框架租户过滤服务。
  • 菜单和按钮权限一并声明,避免页面可见但操作权限缺失。
  • 卸载时区分治理记录、数据库数据和文件数据,避免误删。