plugin

package
v0.2.1 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