Documentation
¶
Overview ¶
Package plugin 提供插件系统:插件包加载、生命周期、设置声明与后端代码的运行。
插件分两种:纯声明式的只有清单、设置与静态资源;带后端的另有一个 plugin.wasm, 在 wazero 沙箱里运行(internal/plugin/wasm),能做什么全看宿主开放了什么、 站长授予了什么(capabilities.go、host.go)。
Index ¶
- Constants
- Variables
- func AllowedExtensions() []string
- func ListInstalled(root string) ([]string, error)
- func Remove(root, name string) error
- func ResourceGroup(plugin string) string
- type Author
- type Capabilities
- type ContentAccess
- type CronJob
- type DataCounts
- type DataStore
- func (s *DataStore) Counts(ctx context.Context, plugin string) (DataCounts, error)
- func (s *DataStore) CreateRecord(ctx context.Context, plugin string, res *Resource, data map[string]any) (*ResourceRecord, error)
- func (s *DataStore) DeleteRecord(ctx context.Context, plugin string, res *Resource, id int64) error
- func (s *DataStore) KVDelete(ctx context.Context, plugin, key string) error
- func (s *DataStore) KVGet(ctx context.Context, plugin, key string) (value json.RawMessage, ok bool, err error)
- func (s *DataStore) KVIncr(ctx context.Context, plugin, key string, by int64, ttl time.Duration) (int64, error)
- func (s *DataStore) KVList(ctx context.Context, plugin, prefix string, limit int) ([]KVItem, error)
- func (s *DataStore) KVSet(ctx context.Context, plugin, key string, value json.RawMessage, ...) error
- func (s *DataStore) Purge(ctx context.Context, plugin string) error
- func (s *DataStore) Reclaim(ctx context.Context, name string) error
- func (s *DataStore) Record(ctx context.Context, plugin string, res *Resource, id int64) (*ResourceRecord, error)
- func (s *DataStore) Records(ctx context.Context, plugin string, res *Resource, q *RecordQuery) ([]ResourceRecord, int, error)
- func (s *DataStore) Retain(ctx context.Context, m *Manifest) error
- func (s *DataStore) RetainedList(ctx context.Context) ([]Retained, error)
- func (s *DataStore) SweepKV(ctx context.Context) (int64, error)
- func (s *DataStore) UpdateRecord(ctx context.Context, plugin string, res *Resource, id int64, ...) (*ResourceRecord, error)
- type DepStatus
- type Dependency
- type FrontendDecl
- type Handler
- type Hooks
- type KVItem
- type Loaded
- func (l *Loaded) Dependencies() []Dependency
- func (l *Loaded) ID() string
- func (l *Loaded) NeedsConsent() bool
- func (l *Loaded) PageByPath(path string) (*PageDecl, bool)
- func (l *Loaded) ResourceByKind(kind string) (*Resource, bool)
- func (l *Loaded) ResourceByPath(path string) (*Resource, bool)
- func (l *Loaded) SettingGroup(name string) (*SettingsGroup, bool)
- type Manifest
- type Meta
- type Module
- func (m *Module) AssetsHandler() http.Handler
- func (m *Module) Close(ctx context.Context) error
- func (m *Module) Emit(_ context.Context, action string, data any)
- func (m *Module) Filter(ctx context.Context, filter string, value json.RawMessage) json.RawMessage
- func (m *Module) Invoke(ctx context.Context, name string, req wasm.Request, timeout time.Duration) (json.RawMessage, error)
- func (m *Module) Migrations() fs.FS
- func (m *Module) MountRoutes(r chi.Router, resolve func(http.Handler) http.Handler)
- func (m *Module) Name() string
- func (m *Module) Navigation() app.Navigation
- func (m *Module) Permissions() []app.Permission
- func (m *Module) Register(a *app.App) error
- func (m *Module) Registry() *Registry
- func (m *Module) Routes(r app.Router)
- func (m *Module) RunDueJobs(ctx context.Context) int
- func (m *Module) Shortcodes(ctx context.Context, src string, page *hooks.Page) (string, bool)
- func (m *Module) Slot(ctx context.Context, slot string, page *hooks.Page) template.HTML
- func (m *Module) Start(ctx context.Context) error
- func (m *Module) StripShortcodes(text string) string
- func (m *Module) Subscribed(filter string) bool
- func (m *Module) UseFetchTransport(rt http.RoundTripper)
- func (m *Module) Widget(ctx context.Context, id string, page *hooks.Page) (string, template.HTML, bool)
- type PageDecl
- type Record
- type RecordQuery
- type Registry
- func (r *Registry) Backend(name string) (*wasm.Plugin, bool)
- func (r *Registry) Broken() map[string]string
- func (r *Registry) Close()
- func (r *Registry) DepStatuses(deps []Dependency) []DepStatus
- func (r *Registry) Enabled() []*Loaded
- func (r *Registry) EnabledDependents(name string) []string
- func (r *Registry) Get(name string) (*Loaded, bool)
- func (r *Registry) Install(ctx context.Context, reader io.ReaderAt, size int64) (*Manifest, error)
- func (r *Registry) List() []*Loaded
- func (r *Registry) Load(ctx context.Context) error
- func (r *Registry) Root() string
- func (r *Registry) SetEnabled(ctx context.Context, name string, enabled, accept bool) error
- func (r *Registry) SetEngine(e *wasm.Engine)
- func (r *Registry) Suspend(ctx context.Context, name, reason string)
- func (r *Registry) Uninstall(ctx context.Context, name string, keepData bool) error
- type RegistryOptions
- type Resource
- type ResourceDecl
- type ResourceRecord
- type Retained
- type Route
- type SettingsGroup
- type ShortcodeDecl
- type Spec
- type State
- type Store
- func (s *Store) Delete(ctx context.Context, name string) error
- func (s *Store) Get(ctx context.Context, name string) (*Record, error)
- func (s *Store) List(ctx context.Context) ([]Record, error)
- func (s *Store) LoadSettings(ctx context.Context, name string) (map[string]map[string]any, error)
- func (s *Store) Put(ctx context.Context, manifest *Manifest, raw []byte) error
- func (s *Store) SaveSettings(ctx context.Context, name, group string, values map[string]any) error
- func (s *Store) SetState(ctx context.Context, name string, st State) error
- type WidgetDecl
Constants ¶
const ( // FileManifest 是插件元信息,必需。 FileManifest = "plugin.yaml" // FileSettings 是插件设置项声明,可缺省;格式与主题设置完全一致。 FileSettings = "settings.yaml" // FileLogo 是后台展示用的图标,可缺省。 FileLogo = "logo.png" // FileWasm 是插件的后端代码,spec.runtime 为 wasm 时必需。 FileWasm = "plugin.wasm" )
插件包内的固定文件名。
const ( APIVersion = "io.github.feibaikin.lumo/v1alpha1" Kind = "Plugin" )
APIVersion 与 Kind 是 plugin.yaml 里的固定写法。
与 Extension 平面同一套形态:插件清单本身就是一份 GVK 资源, 将来要把它放进 extensions 表或经 API 暴露,都不必改格式。
const AssetsPath = "/plugin-assets"
AssetsPath 是插件静态文件的访问前缀:/plugin-assets/<插件>/<static 之下的路径>。
const (
// MaxRecordPage 是一次列出的记录数上限。
MaxRecordPage = 200
)
插件数据的配额。插件来自第三方,存多少由宿主说了算。
const Name = "plugin"
Name 是模块名,也是迁移版本表的后缀(goose_db_version_plugin)。
const PathPlugins = "/plugins"
PathPlugins 是插件管理页在 Console 中的路径。
与接口的 /plugins 恰好同名,但两者是不同的东西:这一条是前端路由。 写成一个常量是为了让「它们恰好一样」这件事是显式的,而不是靠人记得两边一起改。
const ResourceVersion = "v1"
ResourceVersion 是插件资源统一使用的版本。
const RoutesPrefix = "/api/v1/plugins"
RoutesPrefix 是插件接口的前缀:/api/v1/plugins/<插件>/<路径>。
公开接口与后台接口共用这一个前缀,由每条接口自己声明谁能调:后台的 /api/v1/console/plugins/<插件>/ 已经是插件管理接口(启停、设置、资源),插件的路径放进去迟早撞名。
const RuntimeWasm = "wasm"
RuntimeWasm 表示插件带 WebAssembly 后端(包根目录的 plugin.wasm)。
const StaticDir = "static"
StaticDir 是插件包里放前台与后台静态文件的目录。
Variables ¶
var ErrAlreadyExists = errors.New("插件已安装")
ErrAlreadyExists 表示同名插件已安装。
var ErrBackend = errors.New("插件后端加载失败")
ErrBackend 表示插件的后端代码加载失败。
var ErrConsentRequired = errors.New("这个插件要用的能力还没有经过确认")
ErrConsentRequired 表示插件要用的能力还没征得站长同意。
var ErrIncompatible = errors.New("插件不支持本站的 Lumo 版本")
ErrIncompatible 表示插件要的 Lumo 版本(spec.requires)与本站运行的对不上。
var ErrInvalidPackage = errors.New("插件包不合法")
ErrInvalidPackage 表示插件包不合法。
var ErrMissingDeps = errors.New("依赖的插件没有就绪")
ErrMissingDeps 表示插件依赖的别的插件没就绪,因而不能启用。
var ErrNotFound = errors.New("插件不存在")
ErrNotFound 表示插件不存在。
var ErrQuota = errors.New("插件数据超出配额")
ErrQuota 表示插件的数据超出了配额。
var ErrRecordNotFound = errors.New("记录不存在")
ErrRecordNotFound 表示资源记录不存在。
Functions ¶
func ListInstalled ¶
ListInstalled 列出 root 下已安装的插件目录名。
目录不存在时返回空列表而不是报错:首次启动、或用不到插件的部署里, 这个目录压根不会被创建。
func ResourceGroup ¶ added in v0.2.0
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 的一项:按固定间隔在后台运行的任务。
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
NewDataStore 构造 DataStore。
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
DeleteRecord 删一条记录。
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) 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) 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) RetainedList ¶ added in v0.2.0
RetainedList 列出保留着数据的插件,附上数据量。
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:插件能往后台加页面、改设置,门槛与主题同级。
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) NeedsConsent ¶ added in v0.2.0
NeedsConsent 判断启用之前要不要先征得站长同意:声明了能力、而授予过的覆盖不了。
func (*Loaded) PageByPath ¶ added in v0.2.0
PageByPath 按 path 取后台页面。
func (*Loaded) ResourceByKind ¶ added in v0.2.0
ResourceByKind 按种类取插件的资源声明。
func (*Loaded) ResourceByPath ¶ added in v0.2.0
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 ¶
Validate 校验一个**已安装**的插件目录,返回其元信息。
比 validateContents 多一条:目录名必须等于插件标识。 启停与卸载都按名字定位目录,两者不一致时那些操作会作用到别的东西上—— 而这类错只有在这里能拦下,因为它是文件系统与清单之间的关系,不是包自身的性质。
func (*Manifest) HasBackend ¶ added in v0.2.0
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 (*Module) AssetsHandler ¶ added in v0.2.0
AssetsHandler 提供启用中插件的 static 目录,挂在 AssetsPath 下。
插件的文件与站点同源:HTML 与 SVG 带上 CSP sandbox,直接打开时落在一个不透明的源里, 碰不到站点的 Cookie 与后台接口;后台的自定义页也靠这一条与管理员会话隔开。
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) MountRoutes ¶ added in v0.2.0
MountRoutes 把插件的接口与静态文件挂到根路由上,由 serve 在模块启动之后调用。
插件接口不经 huma:路径由插件在运行时声明,进不了启动期生成的接口规范。resolve 解析凭据但不强制 (失效的会话按匿名处理,不回 401);CSRF 由 serveRoute 按接口是否公开分别处置。 静态文件不套会话解析,理由同主题静态资源。
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) RunDueJobs ¶ added in v0.2.0
RunDueJobs 立即跑一轮到期的定时任务并等它们跑完,返回跑了几个。供测试与运维排查用。
func (*Module) Shortcodes ¶ added in v0.2.0
Shortcodes 实现 app.Frontend:把正文里认得的短代码交给提供它的插件渲染。
同名短代码由标识排在前面的插件提供。一页最多展开 maxShortcodesPerPage 个,其余原样保留。
func (*Module) Slot ¶ added in v0.2.0
Slot 实现 app.Frontend。
页头放各插件的样式表,页尾放各插件的脚本(脚本只能是插件包里的文件,不收内联脚本); 登记了这个插槽的插件再各渲染一段,净化后包在带插件名的容器里,主题与插件脚本都能按它定位。
func (*Module) Start ¶
Start 实现 app.Starter:备好运行时,扫描已安装插件、与库里的状态对齐,启动启用中插件的后端。
这是模块第一次被允许访问数据库的时机。运行时起不来不算启动失败: 纯声明式插件照常可用,带后端的插件启用时会得到明确的错误。
func (*Module) StripShortcodes ¶ added in v0.2.0
StripShortcodes 实现 app.Frontend:摘要是从正文取的纯文字,里面的短代码只会是一串方括号, 去掉认得的那些,[[名字]] 还原成 [名字];不认得的原样保留。
func (*Module) Subscribed ¶ added in v0.2.0
Subscribed 实现 app.Events。
func (*Module) UseFetchTransport ¶ added in v0.2.0
func (m *Module) UseFetchTransport(rt http.RoundTripper)
UseFetchTransport 替换插件对外请求的传输层。测试用:把请求路由到本地的假服务器, 域名白名单照常核对(那一步在传输层之前)。
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) 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) DepStatuses ¶ added in v0.2.0
func (r *Registry) DepStatuses(deps []Dependency) []DepStatus
DepStatuses 返回各条依赖的现状,按声明顺序。
func (*Registry) EnabledDependents ¶ added in v0.2.0
EnabledDependents 返回依赖 name 且当前启用中的插件标识,按标识排序。
停用或卸载 name 时,这些正是会被连带停用的插件;后台拿它在确认框里列出后果。 没有时返回空切片而不是 nil:接口那边 JSON 给 [] 比 null 好用。
func (*Registry) Install ¶
Install 从 zip 包安装或升级一个插件,新装的登记为未启用。
升级**保持原来的启用状态**——升级一个正在用的插件不该把它停掉; 除非新版本多要了能力,那就先停用、等站长确认。
func (*Registry) Load ¶
Load 扫描已安装的插件目录并与库里的状态对齐,再启动启用中插件的后端。
这是模块第一次被允许访问数据库的时机。
两边不一致时的处置:
- 目录在、库里没有 —— 手工拷进来的插件。照收,登记为「已安装但未启用」, 与主题的做法一致;不这样,用户把目录拷进去后会找不到它。
- 库里有、目录不在 —— 文件被手工删了。标记为损坏并在列表里标出来, 而不是静默丢掉那一行:用户需要知道发生了什么。
func (*Registry) SetEnabled ¶
SetEnabled 启用或停用一个插件。
启用一个声明了能力的插件时,accept 表示站长已经看过并同意了这些能力; 没同意而又需要同意时返回 ErrConsentRequired。带后端的插件在启用时编译加载, 加载失败则保持停用并返回 ErrBackend。
声明了依赖的插件,依赖没就绪时返回 ErrMissingDeps——装得上但启用不了, 依赖可以在这之后安装,故这道理到用时才查。停用一个插件会连带停用依赖它的插件。 Lumo 版本不满足 spec.requires 时返回 ErrIncompatible(装的时候已经挡过,站点回滚后才会遇到)。
func (*Registry) Suspend ¶ added in v0.2.0
Suspend 由系统停用一个插件并记下原因,如连续崩溃。原因会显示在后台。 依赖它的插件跟着停用,与站长手动停用时一样。
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 是编译好的资源声明。
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 (*Store) LoadSettings ¶
LoadSettings 读取某个插件全部分组的已保存值。
func (*Store) SaveSettings ¶
SaveSettings 覆盖式写入某个插件某个分组的值。
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 是一个侧栏小组件。