Help us shape the future of Go! Take the 2026 Go Developer Survey

plugin

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2026 License: AGPL-3.0 Imports: 57 Imported by: 0

Documentation

Overview

Package plugin 提供插件系统:插件包加载、生命周期、设置声明与后端代码的运行。

插件分两种:纯声明式的只有清单、设置与静态资源;带后端的另有一个 plugin.wasm, 在 wazero 沙箱里运行(internal/plugin/wasm),能做什么全看宿主开放了什么、 站长授予了什么(capabilities.go、host.go)。

Index

Constants

View Source
const (
	// FileManifest 是插件元信息,必需。
	FileManifest = "plugin.yaml"
	// FileSettings 是插件设置项声明,可缺省;格式与主题设置完全一致。
	FileSettings = "settings.yaml"
	FileLogo = "logo.png"
	// FileWasm 是插件的后端代码,spec.runtime 为 wasm 时必需。
	FileWasm = "plugin.wasm"
)

插件包内的固定文件名。

View Source
const (
	APIVersion = "io.github.feibaikin.lumo/v1alpha1"
	Kind       = "Plugin"
)

APIVersion 与 Kind 是 plugin.yaml 里的固定写法。

与 Extension 平面同一套形态:插件清单本身就是一份 GVK 资源, 将来要把它放进 extensions 表或经 API 暴露,都不必改格式。

View Source
const AssetsPath = "/plugin-assets"

AssetsPath 是插件静态文件的访问前缀:/plugin-assets/<插件>/<static 之下的路径>。

View Source
const (

	// MaxRecordPage 是一次列出的记录数上限。
	MaxRecordPage = 200
)

插件数据的配额。插件来自第三方,存多少由宿主说了算。

View Source
const Name = "plugin"

Name 是模块名,也是迁移版本表的后缀(goose_db_version_plugin)。

View Source
const PathPlugins = "/plugins"

PathPlugins 是插件管理页在 Console 中的路径。

与接口的 /plugins 恰好同名,但两者是不同的东西:这一条是前端路由。 写成一个常量是为了让「它们恰好一样」这件事是显式的,而不是靠人记得两边一起改。

View Source
const ResourceVersion = "v1"

ResourceVersion 是插件资源统一使用的版本。

View Source
const RoutesPrefix = "/api/v1/plugins"

RoutesPrefix 是插件接口的前缀:/api/v1/plugins/<插件>/<路径>。

公开接口与后台接口共用这一个前缀,由每条接口自己声明谁能调:后台的 /api/v1/console/plugins/<插件>/ 已经是插件管理接口(启停、设置、资源),插件的路径放进去迟早撞名。

View Source
const RuntimeWasm = "wasm"

RuntimeWasm 表示插件带 WebAssembly 后端(包根目录的 plugin.wasm)。

View Source
const StaticDir = "static"

StaticDir 是插件包里放前台与后台静态文件的目录。

Variables

View Source
var ErrAlreadyExists = errors.New("插件已安装")

ErrAlreadyExists 表示同名插件已安装。

View Source
var ErrBackend = errors.New("插件后端加载失败")

ErrBackend 表示插件的后端代码加载失败。

View Source
var ErrConsentRequired = errors.New("这个插件要用的能力还没有经过确认")

ErrConsentRequired 表示插件要用的能力还没征得站长同意。

View Source
var ErrIncompatible = errors.New("插件不支持本站的 Lumo 版本")

ErrIncompatible 表示插件要的 Lumo 版本(spec.requires)与本站运行的对不上。

View Source
var ErrInvalidPackage = errors.New("插件包不合法")

ErrInvalidPackage 表示插件包不合法。

View Source
var ErrMissingDeps = errors.New("依赖的插件没有就绪")

ErrMissingDeps 表示插件依赖的别的插件没就绪,因而不能启用。

View Source
var ErrNotFound = errors.New("插件不存在")

ErrNotFound 表示插件不存在。

View Source
var ErrQuota = errors.New("插件数据超出配额")

ErrQuota 表示插件的数据超出了配额。

View Source
var ErrRecordNotFound = errors.New("记录不存在")

ErrRecordNotFound 表示资源记录不存在。

Functions

func AllowedExtensions

func AllowedExtensions() []string

AllowedExtensions 返回允许的扩展名,供接口文档引用。

func ListInstalled

func ListInstalled(root string) ([]string, error)

ListInstalled 列出 root 下已安装的插件目录名。

目录不存在时返回空列表而不是报错:首次启动、或用不到插件的部署里, 这个目录压根不会被创建。

func Remove

func Remove(root, name string) error

Remove 删除一个已安装的插件目录。

func ResourceGroup added in v0.2.0

func ResourceGroup(plugin string) string

ResourceGroup 返回插件资源在 Extension 平面上的分组:每个插件一组,插件之间看不见彼此的数据。

Types

type Author

type Author struct {
	Name    string `yaml:"name" json:"name"`
	Website string `yaml:"website" json:"website"`
}

Author 是作者信息。

type Capabilities added in v0.2.0

type Capabilities struct {
	// Content 是对站点内容的读写。
	Content ContentAccess `yaml:"content" json:"content"`
	// HTTP 是允许访问的外部域名:精确域名,或 *.example.com 形式的一级通配。
	HTTP []string `yaml:"http" json:"http"`
	// Mail 表示要经站点的 SMTP 发邮件。
	Mail bool `yaml:"mail" json:"mail"`
	// Cron 表示要定时运行任务。
	Cron bool `yaml:"cron" json:"cron"`
	// Frontend 表示要往前台页面里放东西(插槽、小组件、短代码、静态脚本与样式)。
	Frontend bool `yaml:"frontend" json:"frontend"`
}

Capabilities 是插件要用的宿主能力,写在 plugin.yaml 的 spec.capabilities 里。

日志、读自己的设置、存自己的数据是每个带后端的插件都有的,不在此列;这里只放 需要站长点头的那些。启用时逐项列给站长看,确认后记下「授予了什么」; 升级后声明多出来的部分没被授予过,插件就先停用、等再次确认。

func (*Capabilities) AllowsHost added in v0.2.0

func (c *Capabilities) AllowsHost(host string) bool

AllowsHost 判断插件能否访问某个主机名。

func (*Capabilities) AllowsWrite added in v0.2.0

func (c *Capabilities) AllowsWrite(p perm.Permission) bool

AllowsWrite 判断插件被授予了某个写权限。

func (*Capabilities) Covers added in v0.2.0

func (c *Capabilities) Covers(want *Capabilities) bool

Covers 判断已授予的能力是否覆盖 want:升级后的插件是否不需要再征得同意。

func (*Capabilities) IsZero added in v0.2.0

func (c *Capabilities) IsZero() bool

IsZero 判断插件是否没有申请任何需要确认的能力。

func (*Capabilities) NeedsBackend added in v0.2.0

func (c *Capabilities) NeedsBackend() bool

NeedsBackend 判断声明的能力里有没有只能由后端代码使用的。

type ContentAccess added in v0.2.0

type ContentAccess struct {
	// Read 表示读取文章、页面、分类、标签、评论与用户公开资料。
	Read bool `yaml:"read" json:"read"`
	// Write 是写操作要用的权限串,写入时按它判定;声明了写就隐含可读。
	Write []string `yaml:"write" json:"write"`
}

ContentAccess 是对站点内容的读写权限。

type CronJob added in v0.2.0

type CronJob struct {
	// Name 是任务名,对应 SDK 里 OnCron 登记的名字。
	Name string `yaml:"name" json:"name"`
	// Every 是运行间隔,Go 时长写法,如 10m、1h、24h。
	Every       string `yaml:"every" json:"every"`
	Description string `yaml:"description" json:"description"`
	// contains filtered or unexported fields
}

CronJob 是 plugin.yaml 里 spec.cron 的一项:按固定间隔在后台运行的任务。

func (*CronJob) Interval added in v0.2.0

func (j *CronJob) Interval() time.Duration

Interval 返回解析后的运行间隔。

type DataCounts added in v0.2.0

type DataCounts struct {
	Settings int `json:"settings" doc:"保存过的设置分组数"`
	KV       int `json:"kv" doc:"键值条数"`
	Records  int `json:"records" doc:"资源记录条数"`
}

DataCounts 是一个插件名下的数据量,卸载时给站长看。

type DataStore added in v0.2.0

type DataStore struct {
	// contains filtered or unexported fields
}

DataStore 存插件自己的数据:键值与资源记录。所有查询都带着插件名,插件之间互相看不见。

func NewDataStore added in v0.2.0

func NewDataStore(db *bun.DB) *DataStore

NewDataStore 构造 DataStore。

func (*DataStore) Counts added in v0.2.0

func (s *DataStore) Counts(ctx context.Context, plugin string) (DataCounts, error)

Counts 统计一个插件名下的数据。

func (*DataStore) CreateRecord added in v0.2.0

func (s *DataStore) CreateRecord(ctx context.Context, plugin string, res *Resource, data map[string]any) (*ResourceRecord, error)

CreateRecord 新建一条记录。记录名由宿主随机生成:插件与站长都按 ID 寻址。

func (*DataStore) DeleteRecord added in v0.2.0

func (s *DataStore) DeleteRecord(ctx context.Context, plugin string, res *Resource, id int64) error

DeleteRecord 删一条记录。

func (*DataStore) KVDelete added in v0.2.0

func (s *DataStore) KVDelete(ctx context.Context, plugin, key string) error

KVDelete 删一个键;不存在也不报错。

func (*DataStore) KVGet added in v0.2.0

func (s *DataStore) KVGet(ctx context.Context, plugin, key string) (value json.RawMessage, ok bool, err error)

KVGet 读一个键;不存在或已过期时 ok 为 false。

func (*DataStore) KVIncr added in v0.2.0

func (s *DataStore) KVIncr(ctx context.Context, plugin, key string, by int64, ttl time.Duration) (int64, error)

KVIncr 把一个整数键原子地加上 by,返回加完的值。键不存在或已过期时从零开始计, ttl 大于零时只在这种「从零开始」的时候设置过期——按天计数的键因此到点自动归零。

func (*DataStore) KVList added in v0.2.0

func (s *DataStore) KVList(ctx context.Context, plugin, prefix string, limit int) ([]KVItem, error)

KVList 按前缀列出键值,按键排序。

func (*DataStore) KVSet added in v0.2.0

func (s *DataStore) KVSet(ctx context.Context, plugin, key string, value json.RawMessage, ttl time.Duration) error

KVSet 写一个键。新键受数量配额约束,值受大小配额约束。

func (*DataStore) Purge added in v0.2.0

func (s *DataStore) Purge(ctx context.Context, plugin string) error

Purge 删掉一个插件名下的全部数据:设置、键值、资源记录,以及「保留数据」的登记。

func (*DataStore) Reclaim added in v0.2.0

func (s *DataStore) Reclaim(ctx context.Context, name string) error

Reclaim 同名插件重装时移除登记:数据原样接回去。

func (*DataStore) Record added in v0.2.0

func (s *DataStore) Record(ctx context.Context, plugin string, res *Resource, id int64) (*ResourceRecord, error)

Record 读一条记录。

func (*DataStore) Records added in v0.2.0

func (s *DataStore) Records(ctx context.Context, plugin string, res *Resource, q *RecordQuery) ([]ResourceRecord, int, error)

Records 按条件列出记录并给出总数。

func (*DataStore) Retain added in v0.2.0

func (s *DataStore) Retain(ctx context.Context, m *Manifest) error

Retain 登记一个保留了数据的插件。

func (*DataStore) RetainedList added in v0.2.0

func (s *DataStore) RetainedList(ctx context.Context) ([]Retained, error)

RetainedList 列出保留着数据的插件,附上数据量。

func (*DataStore) SweepKV added in v0.2.0

func (s *DataStore) SweepKV(ctx context.Context) (int64, error)

SweepKV 删掉全部已过期的键值,返回删了多少。

func (*DataStore) UpdateRecord added in v0.2.0

func (s *DataStore) UpdateRecord(ctx context.Context, plugin string, res *Resource, id int64, data map[string]any) (*ResourceRecord, error)

UpdateRecord 整体替换一条记录的数据。

type DepStatus added in v0.2.0

type DepStatus struct {
	Dep Dependency
	// Installed 为真表示被依赖的插件已安装。
	Installed bool
	// InstalledVersion 是已安装那个插件的版本;没装时为空串。
	InstalledVersion string
	// Enabled 为真表示已安装且已启用。
	Enabled bool
	// Satisfied 为真表示这条依赖已就绪:装了、启用了、版本也够。
	Satisfied bool
}

DepStatus 是一条依赖的现状。

type Dependency added in v0.2.0

type Dependency struct {
	// Name 是被依赖插件的标识。
	Name string `yaml:"name" json:"name"`
	// Version 是版本范围,如 >=1.0.0、^1.2、~1.2.3、1.2.*;留空表示不限。写法见 version.go。
	Version string `yaml:"version" json:"version"`
	// contains filtered or unexported fields
}

Dependency 是 plugin.yaml 里 spec.dependencies 的一项:本插件要用到的另一个插件。

与 spec.requires 分工不同:requires 说的是「要哪个版本的 Lumo」,这里说的是「要哪个插件」。

type FrontendDecl added in v0.2.0

type FrontendDecl struct {
	// Styles 是放进页头的样式表,包里 static/ 下的 .css。
	Styles []string `yaml:"styles" json:"styles"`
	// Scripts 是放在页尾的脚本,包里 static/ 下的 .js,defer 加载。
	Scripts []string `yaml:"scripts" json:"scripts"`
	// Slots 是由后端代码渲染内容的插槽,取值见 hooks.Slots。
	Slots []string `yaml:"slots" json:"slots"`
	// Widgets 是插件提供的侧栏小组件,站长在主题的侧边栏设置里选用。
	Widgets []WidgetDecl `yaml:"widgets" json:"widgets"`
	// Shortcodes 是插件提供的短代码:作者在正文里写 [名字 参数="值"],前台展开成插件渲染的内容。
	Shortcodes []ShortcodeDecl `yaml:"shortcodes" json:"shortcodes"`
}

FrontendDecl 是 plugin.yaml 里的 spec.frontend:插件往前台页面里放的东西。都要声明 capabilities.frontend。

func (*FrontendDecl) IsZero added in v0.2.0

func (f *FrontendDecl) IsZero() bool

IsZero 判断插件有没有往前台放任何东西。

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler 提供插件的 Console 接口。

全部操作都要求 plugins:manage:插件能往后台加页面、改设置,门槛与主题同级。

func NewHandler

func NewHandler(module *Module) *Handler

NewHandler 构造 Handler。

func (*Handler) Register

func (h *Handler) Register(console huma.API)

Register 挂载接口。

type Hooks added in v0.2.0

type Hooks struct {
	// Actions 是订阅的动作名,如 comment.created。
	Actions []string `yaml:"actions" json:"actions"`
	// Filters 是订阅的过滤器名,如 comment.judge。
	Filters []string `yaml:"filters" json:"filters"`
}

Hooks 是插件订阅的钩子,写在 plugin.yaml 的 spec.hooks 里。

订阅要先声明:站长在启用前能看到插件会在什么时候被叫起来, 宿主也据此只把事情派给真正订阅了的插件,而不是挨个问一遍。

type KVItem added in v0.2.0

type KVItem struct {
	Key       string          `json:"key"`
	Value     json.RawMessage `json:"value"`
	ExpiresAt *time.Time      `json:"expiresAt,omitempty"`
}

KVItem 是一条键值。

type Loaded

type Loaded struct {
	Manifest *Manifest
	// Dir 是插件目录的绝对路径。
	Dir string
	// FS 是插件包的根文件系统。
	FS fs.FS
	// Groups 是插件声明的设置分组。
	Groups []SettingsGroup
	// Resources 是插件声明的资源。
	Resources []Resource
	// Enabled 是当前的启用状态。
	Enabled bool
	// Granted 是站长授予过的能力;nil 表示从没授予过。
	Granted *Capabilities
	// DisabledReason 是系统停用它的原因;站长手动停用时为空串。
	DisabledReason string
	// contains filtered or unexported fields
}

Loaded 是一个已安装的插件。

func (*Loaded) Dependencies added in v0.2.0

func (l *Loaded) Dependencies() []Dependency

Dependencies 返回插件声明的依赖。

func (*Loaded) ID

func (l *Loaded) ID() string

ID 返回插件标识。

func (*Loaded) NeedsConsent added in v0.2.0

func (l *Loaded) NeedsConsent() bool

NeedsConsent 判断启用之前要不要先征得站长同意:声明了能力、而授予过的覆盖不了。

func (*Loaded) PageByPath added in v0.2.0

func (l *Loaded) PageByPath(path string) (*PageDecl, bool)

PageByPath 按 path 取后台页面。

func (*Loaded) ResourceByKind added in v0.2.0

func (l *Loaded) ResourceByKind(kind string) (*Resource, bool)

ResourceByKind 按种类取插件的资源声明。

func (*Loaded) ResourceByPath added in v0.2.0

func (l *Loaded) ResourceByPath(path string) (*Resource, bool)

ResourceByPath 按 URL 复数段取插件的资源声明。

func (*Loaded) SettingGroup

func (l *Loaded) SettingGroup(name string) (*SettingsGroup, bool)

SettingGroup 按名字取设置分组。

type Manifest

type Manifest struct {
	APIVersion string `yaml:"apiVersion" json:"apiVersion"`
	Kind       string `yaml:"kind" json:"kind"`
	Metadata   Meta   `yaml:"metadata" json:"metadata"`
	Spec       Spec   `yaml:"spec" json:"spec"`
}

Manifest 是 plugin.yaml 的内容。

func Install

func Install(root string, r io.ReaderAt, size int64, overwrite bool, accept func(*Manifest) error) (*Manifest, error)

Install 把一个 zip 插件包解压安装到 root 下,返回其元信息。

与主题同样的两段式:先解到临时目录、校验通过再改名就位。 半个插件留在 plugins/ 下比装不上更糟——它会被当成一个已安装的插件加载, 而缺少的清单或设置声明要到启用时才暴露。accept 非 nil 时在就位之前再核对一次清单, 不通过就原样返回它的错误,旧版本不受影响。

func Validate

func Validate(dir string) (*Manifest, error)

Validate 校验一个**已安装**的插件目录,返回其元信息。

比 validateContents 多一条:目录名必须等于插件标识。 启停与卸载都按名字定位目录,两者不一致时那些操作会作用到别的东西上—— 而这类错只有在这里能拦下,因为它是文件系统与清单之间的关系,不是包自身的性质。

func (*Manifest) HasBackend added in v0.2.0

func (m *Manifest) HasBackend() bool

HasBackend 判断插件带不带后端代码。

type Meta

type Meta struct {
	// Name 是插件标识(DNS-1123),同时是安装目录名,必须与目录名一致。
	Name string `yaml:"name" json:"name"`
}

Meta 是清单的元信息。

type Module

type Module struct {
	// contains filtered or unexported fields
}

Module 是插件模块。

func From

func From(a *app.App) *Module

From 取回插件模块;未装配时返回 nil。

调用方须能在 nil 时退化——插件是可选能力,核心功能不该因为它而不可用。

func New

func New() *Module

New 构造模块。

func (*Module) AssetsHandler added in v0.2.0

func (m *Module) AssetsHandler() http.Handler

AssetsHandler 提供启用中插件的 static 目录,挂在 AssetsPath 下。

插件的文件与站点同源:HTML 与 SVG 带上 CSP sandbox,直接打开时落在一个不透明的源里, 碰不到站点的 Cookie 与后台接口;后台的自定义页也靠这一条与管理员会话隔开。

func (*Module) Close added in v0.2.0

func (m *Module) Close(ctx context.Context) error

Close 实现 app.Closer:停掉全部后端并关闭运行时。

func (*Module) Emit added in v0.2.0

func (m *Module) Emit(_ context.Context, action string, data any)

Emit 实现 app.Events:把动作放进队列,由后台协程派给订阅的插件。

func (*Module) Filter added in v0.2.0

func (m *Module) Filter(ctx context.Context, filter string, value json.RawMessage) json.RawMessage

Filter 实现 app.Events:把值依次交给订阅的插件,每一环失败都跳过、沿用上一环的值。

func (*Module) Invoke added in v0.2.0

func (m *Module) Invoke(ctx context.Context, name string, req wasm.Request, timeout time.Duration) (json.RawMessage, error)

Invoke 调用一个运行中插件的后端,并累计连续崩溃:到了上限就自动停用它。

func (*Module) Migrations

func (m *Module) Migrations() fs.FS

Migrations 实现 app.Migrator。

func (*Module) MountRoutes added in v0.2.0

func (m *Module) MountRoutes(r chi.Router, resolve func(http.Handler) http.Handler)

MountRoutes 把插件的接口与静态文件挂到根路由上,由 serve 在模块启动之后调用。

插件接口不经 huma:路径由插件在运行时声明,进不了启动期生成的接口规范。resolve 解析凭据但不强制 (失效的会话按匿名处理,不回 401);CSRF 由 serveRoute 按接口是否公开分别处置。 静态文件不套会话解析,理由同主题静态资源。

func (*Module) Name

func (m *Module) Name() string

Name 实现 app.Module。

func (*Module) Navigation

func (m *Module) Navigation() app.Navigation

Navigation 实现 app.NavigationProvider。

插件能改后台的页面与设置,门槛与主题同级,故整项按 plugins:manage 收起。

启用中的插件各自的资源页也在这里:每个插件一组,排在「系统」之前, 每次取菜单时现算,启停插件后侧栏立即跟着变。

func (*Module) Permissions

func (m *Module) Permissions() []app.Permission

Permissions 实现 app.PermissionProvider。

func (*Module) Register

func (m *Module) Register(a *app.App) error

Register 实现 app.Module:只做装配,不访问数据库。

func (*Module) Registry

func (m *Module) Registry() *Registry

Registry 返回插件注册表,供其他模块与测试取用。

func (*Module) Routes

func (m *Module) Routes(r app.Router)

Routes 实现 app.RouteProvider。

func (*Module) RunDueJobs added in v0.2.0

func (m *Module) RunDueJobs(ctx context.Context) int

RunDueJobs 立即跑一轮到期的定时任务并等它们跑完,返回跑了几个。供测试与运维排查用。

func (*Module) Shortcodes added in v0.2.0

func (m *Module) Shortcodes(ctx context.Context, src string, page *hooks.Page) (string, bool)

Shortcodes 实现 app.Frontend:把正文里认得的短代码交给提供它的插件渲染。

同名短代码由标识排在前面的插件提供。一页最多展开 maxShortcodesPerPage 个,其余原样保留。

func (*Module) Slot added in v0.2.0

func (m *Module) Slot(ctx context.Context, slot string, page *hooks.Page) template.HTML

Slot 实现 app.Frontend。

页头放各插件的样式表,页尾放各插件的脚本(脚本只能是插件包里的文件,不收内联脚本); 登记了这个插槽的插件再各渲染一段,净化后包在带插件名的容器里,主题与插件脚本都能按它定位。

func (*Module) Start

func (m *Module) Start(ctx context.Context) error

Start 实现 app.Starter:备好运行时,扫描已安装插件、与库里的状态对齐,启动启用中插件的后端。

这是模块第一次被允许访问数据库的时机。运行时起不来不算启动失败: 纯声明式插件照常可用,带后端的插件启用时会得到明确的错误。

func (*Module) StripShortcodes added in v0.2.0

func (m *Module) StripShortcodes(text string) string

StripShortcodes 实现 app.Frontend:摘要是从正文取的纯文字,里面的短代码只会是一串方括号, 去掉认得的那些,[[名字]] 还原成 [名字];不认得的原样保留。

func (*Module) Subscribed added in v0.2.0

func (m *Module) Subscribed(filter string) bool

Subscribed 实现 app.Events。

func (*Module) UseFetchTransport added in v0.2.0

func (m *Module) UseFetchTransport(rt http.RoundTripper)

UseFetchTransport 替换插件对外请求的传输层。测试用:把请求路由到本地的假服务器, 域名白名单照常核对(那一步在传输层之前)。

func (*Module) Widget added in v0.2.0

func (m *Module) Widget(ctx context.Context, id string, page *hooks.Page) (string, template.HTML, bool)

Widget 实现 app.Frontend。

type PageDecl added in v0.2.0

type PageDecl struct {
	// Path 是地址里的一段,后台地址为 /plugins/<插件>/p/<path>。
	Path        string `yaml:"path" json:"path"`
	Label       string `yaml:"label" json:"label"`
	Description string `yaml:"description" json:"description"`
	// Icon 是侧栏图标名,留空用拼图。
	Icon string `yaml:"icon" json:"icon"`
	// File 是包里 static/ 下的 .html 文件。
	File string `yaml:"file" json:"file"`
	// Menu 为 false 时不在侧栏出入口。缺省为 true。
	Menu *bool `yaml:"menu" json:"menu"`
	// Permission 是打开它要的权限串,缺省 plugins:manage。
	Permission string `yaml:"permission" json:"permission"`
	// contains filtered or unexported fields
}

PageDecl 是 plugin.yaml 里 spec.pages 的一项:后台的一张自定义页面。

页面是插件包里的一个 HTML 文件,放在隔离的 iframe 里:它跑在一个不透明的源上, 拿不到管理员的会话,只能经后台转发调用本插件自己的接口(见 docs/plugin-development.md 的「后台页面」)。

func (*PageDecl) InMenu added in v0.2.0

func (p *PageDecl) InMenu() bool

InMenu 判断页面要不要在侧栏出入口。

func (*PageDecl) RequiredPermission added in v0.2.0

func (p *PageDecl) RequiredPermission() perm.Permission

RequiredPermission 返回打开页面要的权限。

type Record

type Record struct {
	bun.BaseModel `bun:"table:plugins,alias:p"`

	Name        string `bun:"name,pk"`
	Version     string `bun:"version,notnull"`
	DisplayName string `bun:"display_name,notnull"`
	Description string `bun:"description,notnull"`
	Author      string `bun:"author,notnull"`
	Enabled     bool   `bun:"enabled,notnull"`
	Manifest    []byte `bun:"manifest,type:jsonb,notnull"`
	// Granted 是站长授予过的能力;nil 表示从没授予过。
	Granted *Capabilities `bun:"granted,type:jsonb,nullzero"`
	// DisabledReason 是系统停用插件的原因,手动停用时为空串。
	DisabledReason string    `bun:"disabled_reason,notnull"`
	InstalledAt    time.Time `bun:"installed_at,nullzero,notnull,default:now()"`
	UpdatedAt      time.Time `bun:"updated_at,nullzero,notnull,default:now()"`
}

Record 是 plugins 表的一行。

type RecordQuery added in v0.2.0

type RecordQuery struct {
	// Match 是字段相等条件(jsonb 包含匹配,走 GIN 索引)。
	Match map[string]any
	// Search 在标题字段里模糊查找。
	Search string
	// Sort 是排序字段,须是资源的顶层字段;留空按创建先后倒序。
	Sort string
	// Desc 为真时倒序。
	Desc   bool
	Limit  int
	Offset int
}

RecordQuery 是资源记录的查询条件。

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry 持有全部已安装插件、它们的启用状态与运行中的后端。

它是插件系统的唯一运行时状态:设置分组、菜单项、列表接口与钩子分发都经它取数据。 读远多于写,故用 RWMutex。编译后端要一两秒,一律在锁外做。

func NewRegistry

func NewRegistry(opts *RegistryOptions) *Registry

NewRegistry 构造 Registry。此时不读盘也不读库——那是 Load 的事。

func (*Registry) Backend added in v0.2.0

func (r *Registry) Backend(name string) (*wasm.Plugin, bool)

Backend 返回运行中插件的后端。

func (*Registry) Broken

func (r *Registry) Broken() map[string]string

Broken 返回加载失败或被手工删除的插件及原因。

func (*Registry) Close added in v0.2.0

func (r *Registry) Close()

Close 停掉全部运行中的后端。

func (*Registry) DepStatuses added in v0.2.0

func (r *Registry) DepStatuses(deps []Dependency) []DepStatus

DepStatuses 返回各条依赖的现状,按声明顺序。

func (*Registry) Enabled

func (r *Registry) Enabled() []*Loaded

Enabled 返回全部已启用的插件,按标识排序。

每次调用都现算:设置分组与菜单项都靠它,而启用状态随时会变。

func (*Registry) EnabledDependents added in v0.2.0

func (r *Registry) EnabledDependents(name string) []string

EnabledDependents 返回依赖 name 且当前启用中的插件标识,按标识排序。

停用或卸载 name 时,这些正是会被连带停用的插件;后台拿它在确认框里列出后果。 没有时返回空切片而不是 nil:接口那边 JSON 给 [] 比 null 好用。

func (*Registry) Get

func (r *Registry) Get(name string) (*Loaded, bool)

Get 按名字取插件。

func (*Registry) Install

func (r *Registry) Install(ctx context.Context, reader io.ReaderAt, size int64) (*Manifest, error)

Install 从 zip 包安装或升级一个插件,新装的登记为未启用。

升级**保持原来的启用状态**——升级一个正在用的插件不该把它停掉; 除非新版本多要了能力,那就先停用、等站长确认。

func (*Registry) List

func (r *Registry) List() []*Loaded

List 返回全部已安装插件,按标识排序。

func (*Registry) Load

func (r *Registry) Load(ctx context.Context) error

Load 扫描已安装的插件目录并与库里的状态对齐,再启动启用中插件的后端。

这是模块第一次被允许访问数据库的时机。

两边不一致时的处置:

  • 目录在、库里没有 —— 手工拷进来的插件。照收,登记为「已安装但未启用」, 与主题的做法一致;不这样,用户把目录拷进去后会找不到它。
  • 库里有、目录不在 —— 文件被手工删了。标记为损坏并在列表里标出来, 而不是静默丢掉那一行:用户需要知道发生了什么。

func (*Registry) Root

func (r *Registry) Root() string

Root 返回插件安装目录。

func (*Registry) SetEnabled

func (r *Registry) SetEnabled(ctx context.Context, name string, enabled, accept bool) error

SetEnabled 启用或停用一个插件。

启用一个声明了能力的插件时,accept 表示站长已经看过并同意了这些能力; 没同意而又需要同意时返回 ErrConsentRequired。带后端的插件在启用时编译加载, 加载失败则保持停用并返回 ErrBackend。

声明了依赖的插件,依赖没就绪时返回 ErrMissingDeps——装得上但启用不了, 依赖可以在这之后安装,故这道理到用时才查。停用一个插件会连带停用依赖它的插件。 Lumo 版本不满足 spec.requires 时返回 ErrIncompatible(装的时候已经挡过,站点回滚后才会遇到)。

func (*Registry) SetEngine added in v0.2.0

func (r *Registry) SetEngine(e *wasm.Engine)

SetEngine 设置运行后端代码的引擎。没有引擎时,带后端的插件启用不了。

func (*Registry) Suspend added in v0.2.0

func (r *Registry) Suspend(ctx context.Context, name, reason string)

Suspend 由系统停用一个插件并记下原因,如连续崩溃。原因会显示在后台。 依赖它的插件跟着停用,与站长手动停用时一样。

func (*Registry) Uninstall

func (r *Registry) Uninstall(ctx context.Context, name string, keepData bool) error

Uninstall 卸载一个插件:处置数据、停后端、删状态、删目录。

keepData 为真时设置、键值与资源记录都留在库里,并登记为「保留的数据」, 同名插件重装后原样接回;否则一并删掉。

顺序是刻意的:**先处置数据、再删状态、最后删文件**。数据删了而状态没删,最坏是一个没有数据的插件; 反过来则会留下一堆没人认领、也没有登记的数据。删文件放最后,理由同前: 库里留一条指向不存在目录的记录,会让列表里一直显示一个打不开的插件。

type RegistryOptions

type RegistryOptions struct {
	// Root 是插件安装目录,通常是 data/plugins。
	Root  string
	Store *Store
	// Data 是插件自己的数据(设置、键值、资源记录);卸载时按站长的选择清除或保留。
	Data   *DataStore
	Logger *slog.Logger
	// LumoVersion 是本站 Lumo 的版本号,用来核对 spec.requires;空串表示不核对。
	LumoVersion string
}

RegistryOptions 是构造 Registry 的参数。

type Resource added in v0.2.0

type Resource struct {
	Kind string
	// Path 是 URL 里的复数段,由 Kind 推导,如 visits。
	Path        string
	Label       string
	Description string
	Icon        string
	Columns     []string
	Title       string
	Editable    bool
	Menu        bool
	Permission  perm.Permission
	Form        *form.Form
	// contains filtered or unexported fields
}

Resource 是编译好的资源声明。

func (*Resource) HasField added in v0.2.0

func (r *Resource) HasField(name string) bool

HasField 判断资源有没有某个顶层字段。

func (*Resource) Normalize added in v0.2.0

func (r *Resource) Normalize(data map[string]any) (map[string]any, error)

Normalize 用缺省值补齐一条记录并按字段声明校验,返回要存下来的完整数据。

插件经宿主写入与站长在后台编辑走同一道校验:两边写进去的东西长得一样,列表页才画得出来。

func (*Resource) Validator added in v0.2.0

func (r *Resource) Validator() *settings.Validator

Validator 返回字段校验器。

type ResourceDecl added in v0.2.0

type ResourceDecl struct {
	// Kind 是资源种类,单数 PascalCase,如 Visit。
	Kind string `yaml:"kind" json:"kind"`
	// Label 是显示名,如「访问记录」。
	Label       string `yaml:"label" json:"label"`
	Description string `yaml:"description" json:"description"`
	// Icon 是侧栏图标名(Console 的图标登记表),留空用拼图。
	Icon string `yaml:"icon" json:"icon"`
	// Schema 是字段声明,JSON Schema 子集,与设置分组相同。
	Schema map[string]any `yaml:"schema" json:"schema"`
	// Defaults 是新建记录时的缺省值。
	Defaults map[string]any `yaml:"defaults" json:"defaults"`
	// Columns 是列表页显示的字段,留空取前三个。
	Columns []string `yaml:"columns" json:"columns"`
	// Title 是用作记录标题的字段,留空取第一个文本字段。
	Title string `yaml:"title" json:"title"`
	// Editable 为 false 时后台只能查看,数据只由插件自己写。缺省为 true。
	Editable *bool `yaml:"editable" json:"editable"`
	// Menu 为 false 时不在侧栏出入口。缺省为 true。
	Menu *bool `yaml:"menu" json:"menu"`
	// Permission 是后台查看与编辑它要的权限串,缺省 plugins:manage。
	Permission string `yaml:"permission" json:"permission"`
}

ResourceDecl 是 plugin.yaml 里 spec.resources 的一项:插件自己的一类数据。

声明之后宿主替插件存数据(进 Extension 平面,按插件分组隔离),后台自动生成列表页与编辑页, 插件经宿主调用增删改查。字段用与设置同一套 Schema 描述,站长在后台看到的表单也是同一套控件。

type ResourceRecord added in v0.2.0

type ResourceRecord struct {
	ID        int64          `json:"id"`
	Data      map[string]any `json:"data"`
	CreatedAt time.Time      `json:"createdAt"`
	UpdatedAt time.Time      `json:"updatedAt"`
}

ResourceRecord 是插件资源的一条记录。

type Retained added in v0.2.0

type Retained struct {
	Name        string     `bun:"name" json:"name"`
	DisplayName string     `bun:"display_name" json:"displayName"`
	Version     string     `bun:"version" json:"version"`
	RetainedAt  time.Time  `bun:"retained_at" json:"retainedAt"`
	Counts      DataCounts `bun:"-" json:"counts"`
}

Retained 是卸载了但保留着数据的插件。

type Route added in v0.2.0

type Route struct {
	// Name 是处理函数名,对应 SDK 里 OnRoute 登记的名字。
	Name string `yaml:"name" json:"name"`
	// Method 是请求方法,缺省 GET。
	Method string `yaml:"method" json:"method"`
	// Path 是插件前缀之后的路径,如 /hit、/items/{id}。
	Path string `yaml:"path" json:"path"`
	// Public 为真时谁都能调;否则要登录并持有 Permission。
	Public bool `yaml:"public" json:"public"`
	// Permission 是非公开接口要的权限串,缺省 plugins:manage。
	Permission  string `yaml:"permission" json:"permission"`
	Description string `yaml:"description" json:"description"`
	// contains filtered or unexported fields
}

Route 是 plugin.yaml 里 spec.routes 的一项:插件自己的一个 HTTP 接口。

type SettingsGroup

type SettingsGroup struct {
	Name        string
	Label       string
	Description string
	Order       int
	Icon        string
	Form        *form.Form
	// contains filtered or unexported fields
}

SettingsGroup 是一个插件的设置分组。

形态与 app.SettingGroup 同构,但**不注册进全局设置服务**: 插件的设置随插件走,停用时要能连同它一起收起,而全局设置分组的生命周期 钉在进程启动那一刻。主题设置也是同样的处置(见 internal/theme/settings.go)。

func (*SettingsGroup) Validator

func (g *SettingsGroup) Validator() *settings.Validator

Validator 返回分组的校验器。

type ShortcodeDecl added in v0.2.0

type ShortcodeDecl struct {
	Name        string `yaml:"name" json:"name"`
	Description string `yaml:"description" json:"description"`
}

ShortcodeDecl 是一个短代码。

type Spec

type Spec struct {
	// DisplayName 是显示名。
	DisplayName string `yaml:"displayName" json:"displayName"`
	// Version 是插件版本。
	Version string `yaml:"version" json:"version"`
	// Description 是一句话说明。
	Description string `yaml:"description" json:"description"`
	// Author 是作者信息。
	Author Author `yaml:"author" json:"author"`
	// Homepage 是插件主页。
	Homepage string `yaml:"homepage" json:"homepage"`
	// Repo 是源码仓库地址。
	Repo string `yaml:"repo" json:"repo"`
	// License 是许可证标识。
	License string `yaml:"license" json:"license"`
	// Requires 声明所需的 Lumo 版本范围,写法同依赖的 version;留空表示不限制。
	// 不满足时装不上,也启用不了(站点回滚到旧版之后);源码构建的本站版本不比,见 runningVersion。
	Requires string `yaml:"requires" json:"requires"`
	// Dependencies 声明本插件要用到的别的插件与版本范围,如 name: comment-guard、version: ">=1.0.0"。
	// 依赖没就绪(没装、没启用、版本不够)时插件启用不了;被依赖的插件停用或卸载会连带停用本插件。
	Dependencies []Dependency `yaml:"dependencies" json:"dependencies"`
	// Runtime 是后端的运行方式:留空表示纯声明式插件,wasm 表示包里带 plugin.wasm。
	Runtime string `yaml:"runtime" json:"runtime"`
	// Capabilities 是插件要用的宿主能力,启用时由站长逐项确认。
	Capabilities Capabilities `yaml:"capabilities" json:"capabilities"`
	// Hooks 是插件订阅的动作与过滤器。
	Hooks Hooks `yaml:"hooks" json:"hooks"`
	// Resources 是插件自己的数据类型:宿主替它存,后台替它出列表页与编辑页。
	Resources []ResourceDecl `yaml:"resources" json:"resources"`
	// Cron 是按固定间隔在后台运行的任务。
	Cron []CronJob `yaml:"cron" json:"cron"`
	// Routes 是插件自己的 HTTP 接口,挂在 /api/v1/plugins/<插件>/ 之下。
	Routes []Route `yaml:"routes" json:"routes"`
	// Frontend 是插件往前台页面里放的东西:样式、脚本、插槽、小组件与短代码。
	Frontend FrontendDecl `yaml:"frontend" json:"frontend"`
	// Pages 是后台的自定义页面,放在隔离的 iframe 里。
	Pages []PageDecl `yaml:"pages" json:"pages"`
}

Spec 是清单的规格。

type State added in v0.2.0

type State struct {
	Enabled bool
	// Reason 是系统停用的原因,手动启停时为空串。
	Reason string
	// Granted 是授予过的能力;nil 表示不改动库里已有的值。
	Granted *Capabilities
}

State 是插件的启用状态。

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store 持久化插件状态与设置值。

func NewStore

func NewStore(db *bun.DB) *Store

NewStore 构造 Store。

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, name string) error

Delete 删除一个插件的状态;它的设置值由外键级联一并清除。

func (*Store) Get

func (s *Store) Get(ctx context.Context, name string) (*Record, error)

Get 读取一个插件;不存在时返回 ErrNotFound。

func (*Store) List

func (s *Store) List(ctx context.Context) ([]Record, error)

List 返回全部已安装插件,按名称排序。

func (*Store) LoadSettings

func (s *Store) LoadSettings(ctx context.Context, name string) (map[string]map[string]any, error)

LoadSettings 读取某个插件全部分组的已保存值。

func (*Store) Put

func (s *Store) Put(ctx context.Context, manifest *Manifest, raw []byte) error

Put 写入或整体覆盖一个插件的元信息。

安装与升级共用这一条:两者的差别只在 overwrite,而那是文件系统层面的事。

func (*Store) SaveSettings

func (s *Store) SaveSettings(ctx context.Context, name, group string, values map[string]any) error

SaveSettings 覆盖式写入某个插件某个分组的值。

func (*Store) SetState added in v0.2.0

func (s *Store) SetState(ctx context.Context, name string, st State) error

SetState 写入插件的启用状态。

type WidgetDecl added in v0.2.0

type WidgetDecl struct {
	Name        string `yaml:"name" json:"name"`
	Label       string `yaml:"label" json:"label"`
	Description string `yaml:"description" json:"description"`
}

WidgetDecl 是一个侧栏小组件。

Directories

Path Synopsis
Package wasm 在进程内运行插件的 WebAssembly 后端(wazero,纯 Go、无 CGO)。
Package wasm 在进程内运行插件的 WebAssembly 后端(wazero,纯 Go、无 CGO)。
wasmtest
Package wasmtest 给测试编译插件:用 Go SDK 现场编一个 wasip1 模块。
Package wasmtest 给测试编译插件:用 Go SDK 现场编一个 wasip1 模块。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL