基本介绍
为提升框架整体的灵活性与可扩展性,主框架采用领域驱动的设计思路对核心能力进行建模,并以解耦方式组织各领域能力的实现与契约。
apps/lina-core/pkg/plugin目录作为公开的领域契约边界,向插件暴露稳定的领域服务接口。- 源码插件通过
pluginhost.Services消费完整的capability.Services目录。 - 动态插件通过
pluginbridge.Default()返回的pluginbridge.Services消费已经发布为hostServices的能力子集。
组件结构
| 路径 | 职责 |
|---|---|
capability/ | 聚合稳定宿主能力,包含Services、插件作用域服务绑定和各领域窄接口;源码插件直接使用完整目录,动态插件使用已发布的桥接子集 |
pluginhost/ | 源码插件宿主命名空间,提供编译期注册接口层和运行期回调契约 |
pluginbridge/ | 动态插件桥接命名空间,提供pluginbridge.Default()/pluginbridge.New()运行时能力目录、Wasm执行契约和hostServices编解码 |
领域能力概览
| 方法 | 领域文档 | SPI扩展 | 说明 |
|---|---|---|---|
AI() | AI能力 | 支持 | 聚合文本、图片、向量、音频、视觉、文档、安全和视频子能力 |
APIDoc() | 接口文档 | - | 解析路由操作键、本地化模块标签和操作摘要 |
Auth() | 认证与授权 | - | 认证令牌签发、租户切换、模拟令牌和授权权限视图读取与判断 |
Users() | 用户管理 | - | 用户视图、搜索和可见性校验 |
BizCtx() | 业务上下文 | - | 读取当前请求用户、租户、模拟登录和平台绕过状态 |
Cache() | 缓存能力 | - | 插件作用域运行时缓存 |
Dict() | 字典能力 | - | 字典类型和值的生命周期管理、标签解析和缓存刷新 |
Files() | 文件能力 | - | 文件视图、上传、流式打开和可见性校验 |
HostConfig() | 配置管理 | - | 读取宿主配置值;动态插件读取时必须声明keys |
I18n() | 国际化能力 | - | 源码插件运行时翻译能力;动态插件不开放对应host service |
Jobs() | 定时任务 | - | 定时任务生命周期管理 |
Manifest() | 清单资源 | - | 读取当前已打包的插件manifest/下的只读资源 |
Notifications() | 通知能力 | - | 通知消息的生命周期管理 |
Org() | 组织管理 | 支持 | 可选组织能力,部门和岗位的生命周期管理 |
Plugins() | 插件治理 | - | 聚合插件注册表、插件配置、插件状态和生命周期子能力 |
Route() | 动态路由 | - | 读取当前动态路由元数据 |
Sessions() | 在线会话 | - | 在线会话搜索、批量读取和吊销 |
Storage() | 文件能力 | 支持 | 插件作用域对象存储操作 |
Tenant() | 租户能力 | 支持 | 可选租户能力,读取当前租户、可见性、切换校验和源码插件租户过滤 |
Lock() | 分布式锁 | - | 插件可见的分布式锁获取、续租和释放 |
插件作用域能力会由宿主绑定插件身份。例如Plugins().Config()只读取当前插件自己的config.yaml,Manifest()只读取当前插件的manifest/资源,AI()会把来源插件ID注入后续提供方请求。
能力生命周期
插件能力分为声明期和运行期两个阶段。
- 声明期是插件的静态注册和发现阶段,主框架在业务执行前使用声明输出构建治理状态。
- 运行期是插件业务逻辑执行阶段,插件消费主框架提供的领域能力服务。
声明期能力
声明期能力是插件的静态注册输出。源码插件在编译期通过pluginhost.Declarations注册,动态插件通过plugin.yaml清单和pluginbridge.Declarations声明。
声明期能力的详细设计和使用方式请参阅声明期能力概览。
源码插件声明期
源码插件通过pluginhost.Declarations在init()中注册以下声明:
| 声明入口 | 功能说明 |
|---|---|
ID() | 返回与plugin.yaml一致的稳定插件标识 |
Assets() | 绑定插件嵌入文件系统,包含清单、前端页面、SQL和i18n资源 |
Lifecycle() | 注册安装、升级、禁用、卸载、租户禁用、租户删除和安装模式变更等16个生命周期回调 |
Hooks() | 订阅主框架扩展点事件,例如auth.login.succeeded、plugin.enabled、system.started等 |
HTTP() | 注册插件HTTP路由贡献回调,由主框架启动时统一触发 |
Jobs() | 注册定时任务贡献回调,由主框架调度时统一触发 |
Providers() | 声明领域能力提供方工厂,例如ProvideTenant、ProvideOrg和ProvideAIText |
Access() | 注册菜单过滤和权限过滤回调,用于运行时动态调整工作台导航和权限 |
动态插件声明期
动态插件通过plugin.yaml清单和构建期契约表达声明:
| 声明来源 | 功能说明 |
|---|---|
plugin.yaml | 声明插件身份、版本、依赖、菜单、权限、多租户策略、公开静态资源和hostServices授权申请 |
Routes() | 声明路由组绑定,指定API前缀和路由包 |
Jobs() | 通过host-service调用注册定时任务契约 |
WASM自定义段 | 在.wasm产物中嵌入ABI版本、运行时类型、编解码器和导出函数名等元数据 |
protocol.BridgeSpec | 定义桥接ABI契约,包括版本号、运行时类型、编解码方式和alloc/execute导出名称 |
运行期能力
运行期能力是插件业务逻辑执行时可用的服务。源码插件和动态插件共享宿主领域能力模型,但公开入口不同。
源码插件运行期
源码插件通过pluginhost.Services访问运行期能力。该接口内嵌capability.Services,直接暴露全部领域能力方法。
动态插件运行期
动态插件通过pluginbridge.Default()访问已发布的运行期能力。所有调用经由WASI host call传输,由宿主按hostServices授权快照校验后分发执行。动态插件访问的是动态服务目录中的能力子集,并拥有三个专属能力:
| 能力入口 | 功能说明 |
|---|---|
| 已发布领域能力 | 例如AI、Auth、Cache、Storage等能力,通过host-call桥接访问;I18n()不发布为动态hostServices |
Runtime() | 专属能力:日志写入、插件状态读写、时间获取、UUID生成、节点身份读取 |
Network() | 专属能力:受治理的出站HTTP请求,需在plugin.yaml中声明授权目标地址 |
RecordStore() | 专属能力:data服务的类ORM封装层,只能访问声明的插件自有表 |
源码插件与动态插件的能力差异
源码插件通过capability.Services访问完整的领域能力,包括读取和写入操作(例如Jobs().Run()、Sessions().Revoke()、Notifications().Send()等)。这些写操作经过状态、目标、租户、审计等治理校验后执行。
动态插件的pluginbridge.Services接口只暴露已发布为hostServices的能力子集。动态插件只能调用已经写入plugin.yaml声明、经过宿主授权并注册到WASM host-service分发器中的具体方法。几乎所有领域能力都已对动态插件开放了读写接口——例如sessions动态服务包含sessions.revoke强退命令,jobs动态服务包含jobs.run和jobs.status.set等写操作,dict、files、hostconfig、org、users等服务也同时提供了Create、Update、Delete等完整的生命周期管理方法。插件作者应参考下方动态服务目录表格确认各服务的具体方法列表。
SPI架构设计
部分领域能力属于可选框架能力——它们不是主框架内置的,而是由提供方插件实现具体逻辑后注入到宿主运行时。当前采用SPI模式的能力比如AI、Org和Tenant等,对应的官方提供方插件分别为linapro-ai-core、linapro-org-core和linapro-tenant-core。
架构设计
SPI(Service Provider Interface)模式的核心思想是将能力契约与能力实现分离。宿主定义领域能力的公开接口(即SPI契约),提供方插件负责实现具体业务逻辑。宿主通过延迟构造避免启动阶段对可选插件的强依赖——只有当能力首次被消费时,宿主才会实例化提供方。
| 设计要点 | 说明 |
|---|---|
| 延迟构造 | 宿主只在能力首次被消费时构造提供方实例,避免启动阶段强依赖可选插件 |
| 安全降级 | 没有可用提供方时,能力返回空结果或不可用状态,而非nil或错误 |
| 来源注入 | 宿主通过ProviderEnv向提供方注入请求上下文、插件身份和辅助能力 |
| 启用状态隔离 | 提供方状态独立于业务入口可见性——插件的业务入口可能对当前租户不可见,但仍可作为平台能力提供方可用 |
SPI服务注册
源码插件通过pluginhost.Declarations.Providers()声明SPI工厂。每个工厂是一个构造函数,接收ProviderEnv参数并返回提供方实例:
ProviderEnv是宿主向提供方注入的运行期上下文,通常包含:
| 注入项 | 说明 |
|---|---|
| 插件身份 | 当前提供方插件的ID,用于审计和隔离 |
| 请求上下文 | 当前请求的租户、用户等业务上下文 |
| 辅助能力 | 提供方实现所需的宿主能力,例如TenantFilter、用户视图等 |
SPI提供方状态检查
宿主通过Plugins().State().IsProviderEnabled()判断提供方是否可用。该检查与IsEnabled语义不同:
| 检查方法 | 语义 | 适用场景 |
|---|---|---|
IsEnabled | 插件的业务入口对当前租户是否可见 | 菜单过滤、路由可见性、权限过滤 |
IsProviderEnabled | 插件是否平台启用且可承接框架能力提供方调用 | AI、Org、Tenant等能力调用前检查 |
提供方检查确保即使业务入口被租户级禁用,平台级能力仍可正常服务。
插件实现SPI提供方
源码插件实现SPI提供方分为注册和实现两个步骤。以Org能力为例:
注册SPI工厂:
提供方插件在init()中通过Providers()声明入口注册工厂函数:
func init() {
plugin := pluginhost.NewDeclarations("my-author-my-org-provider")
if err := plugin.Providers().ProvideOrg(func(ctx context.Context, env orgspi.ProviderEnv) (orgspi.Provider, error) {
return &myOrgProvider{env: env}, nil
}); err != nil {
panic(err)
}
if err := pluginhost.RegisterSourcePlugin(plugin); err != nil {
panic(err)
}
}
实现SPI契约:
提供方需要实现能力领域包中定义的Provider接口。以orgcap.Provider为例,提供方需要实现部门视图、岗位视图等完整组织能力:
type myOrgProvider struct {
env orgcap.ProviderEnv
}
func (p *myOrgProvider) ListUserDeptAssignments(ctx context.Context, userIDs []string) ([]DeptAssignment, error) {
// 查询提供方自己的组织数据
// 可通过 p.env 访问宿主注入的辅助能力
}
func (p *myOrgProvider) GetUserDeptInfo(ctx context.Context, userID string) (*DeptInfo, error) {
// 实现部门信息查询
}
Tenant能力额外提供tenantcap.Resolver接口,负责从HTTP请求中解析租户身份,可按请求头、域名、路径、令牌或其他策略组成责任链。
动态插件与SPI
动态插件不能直接注册SPI工厂,因为提供方需要实现Go接口并运行在宿主进程中。动态插件通过以下方式与SPI能力交互:
| 交互方式 | 说明 |
|---|---|
消费SPI能力 | 通过hostServices声明调用已发布的SPI能力方法,例如service: ai、service: org、service: tenant |
检查SPI提供方状态 | 通过plugins.provider_enabled.check动态方法判断提供方是否可用 |
| 状态查询 | 通过capability.available和capability.status动态方法查询能力可用性和活跃提供方 |
动态插件在plugin.yaml中声明对SPI能力的消费:
hostServices:
- service: ai
methods:
- text.generate
- service: org
methods:
- users.dept_name.get
- service: tenant
methods:
- tenants.current
动态hostServices
动态插件不能直接访问宿主实现包,只能通过plugin.yaml中的hostServices声明需要调用的宿主服务。一个典型的plugin.yaml中的hostServices声明如下:
hostServices:
- service: runtime
methods:
- log.write
- state.get
- state.set
- service: storage
methods:
- put
- get
- list
resources:
paths:
- exports/
- service: data
methods:
- list
- get
- create
resources:
tables:
- plugin_demo_reports
- service: network
methods:
- request
resources:
- url: https://api.example.com/v1/*
- service: hostconfig
methods: [get]
resources:
keys:
- workspace.basePath
- service: manifest
methods: [get]
resources:
paths:
- profile.yaml
- service: ai
methods:
- text.generate
资源声明形态
| 资源类型 | 声明字段 | 服务 |
|---|---|---|
none | 不声明resources | runtime、apidoc、auth、ai、users、bizctx、dict、files、jobs、notifications、plugins、route、sessions、org、tenant |
path | resources.paths | storage、manifest |
table | resources.tables | data |
key | resources.keys | hostconfig |
resource | resources[].url或resources[].ref及服务专属属性 | network、cache、lock、notifications(仅messages.send) |
生产校验会要求data服务表属于插件自有命名空间。动态插件不得声明sys_*这类宿主核心表,也不应把宿主表名作为插件数据能力的目标。
动态服务目录
| 服务 | 领域文档 | 资源类型 | 方法 |
|---|---|---|---|
ai | AI能力 | none | text.generate、text.method_status.get、ai.methods.status.batch_get、image.generate、image.edit、embedding.create、audio.transcribe、audio.synthesize、vision.analyze、document.analyze、document.cite、safety.moderate、video.generate、video.edit、video.extend、video.operation.get、video.operation.cancel |
apidoc | 接口文档 | none | route_text.resolve、route_texts.resolve、route_title_operation_keys.find |
auth | 认证与授权 | none | token.tenant.select、token.tenant.switch、token.impersonation_token.issue、token.impersonation_token.revoke、authz.permissions.batch_get、authz.permissions.batch_has、authz.permissions.has、authz.users.platform_admin.check、authz.role_permissions.replace |
bizctx | 业务上下文 | none | current.get |
cache | 缓存能力 | resource | get、get_many、set、set_many、delete、delete_many、incr、expire |
data | 数据库操作 | table | list、get、batch_get、create、update、delete、transaction |
dict | 字典能力 | none | dict.refresh、dict.type.get、dict.type.batch_get、dict.type.list、dict.type.visible.ensure、dict.type.keys.visible.ensure、dict.type.create、dict.type.update、dict.type.delete、dict.value.get、dict.value.batch_get、dict.value.labels.resolve、dict.value.list、dict.value.visible.ensure、dict.value.values.visible.ensure、dict.value.create、dict.value.update、dict.value.delete、dict.value.by_type.delete |
files | 文件能力 | none | files.batch_get、files.list、files.visible.ensure、files.upload、files.create_from_storage、files.metadata.update、files.delete、files.delete_many |
hostconfig | 配置管理 | key | get、sys_config.get、sys_config.value.set、sys_config.reset |
jobs | 定时任务 | none | jobs.batch_get、jobs.list、jobs.visible.ensure、jobs.create、jobs.update、jobs.delete、jobs.run、jobs.status.set、jobs.register |
lock | 分布式锁 | resource | acquire、renew、release |
manifest | 清单资源 | path | get、get_many、list |
network | 网络访问 | resource | request |
notifications | 通知能力 | 读取无资源;messages.send使用resources[].ref | messages.batch_get、messages.list、messages.by_source.batch_get、messages.visible.ensure、messages.send、messages.delete、messages.by_source.delete、messages.mark_read、messages.mark_unread |
org | 组织管理 | none | capability.available、capability.status、org.assignment.user_profiles.batch_get、org.department.tree.list、org.department.batch_get、org.department.list、org.department.create、org.department.update、org.department.delete、org.post.batch_get、org.post.options.list、org.post.create、org.post.update、org.post.delete、org.department.visible.ensure_many、org.post.visible.ensure_many、org.assignment.by_user.replace、org.assignment.by_user.cleanup |
plugins | 插件治理 | none | plugins.current.get、plugins.batch_get、plugins.registry.list、plugins.tenant.list、config.get、plugins.state.enabled.check、plugins.state.provider_enabled.check、plugins.state.enabled_authoritative.check、plugins.lifecycle.tenant_plugin_disable.ensure、plugins.lifecycle.tenant_plugin_disabled.notify、plugins.lifecycle.tenant_delete.ensure、plugins.lifecycle.tenant_deleted.notify |
route | 动态路由 | none | metadata.get |
runtime | 运行时能力 | none | log.write、state.get、state.get_many、state.set、state.set_many、state.delete、state.delete_many、info.now、info.uuid、info.node |
sessions | 会话管理 | none | sessions.current.get、sessions.list、sessions.batch_get、sessions.users.online.batch_get、sessions.visible.ensure、sessions.revoke、sessions.revoke_many |
storage | 文件能力 | path | put、put.init、put.chunk、put.commit、put.abort、get、delete、delete.batch、list、list.cursor、stat、stat.batch |
tenant | 多租户能力 | none | capability.available、capability.status、tenant.context.current、tenant.context.info、tenant.context.platform_bypass、tenant.directory.batch_get、tenant.directory.list、tenant.membership.validate、tenant.membership.list_by_user、tenant.directory.visible.ensure_many、tenant.plugins.enabled.set、tenant.plugins.defaults.provision、tenant.filter.context |
users | 用户管理 | none | users.current.get、users.batch_get、users.resolve.batch、users.list、users.visible.ensure、users.create、users.update、users.delete、users.status.set、users.password.reset、users.assignment.roles.replace |
动态插件专属能力
Runtime()、Network()和RecordStore()是pluginbridge.Default()返回目录上的动态插件专属能力。它们不属于capability.Services,因为源码插件已经运行在宿主进程内,可以使用宿主原生等价能力。
| 能力 | 公开入口 | 说明 |
|---|---|---|
Runtime() | pluginbridge.Default().Runtime() | 动态插件通过WASI host-service客户端写日志、读写状态、读取时间、生成UUID和读取节点身份;源码插件直接使用宿主原生日志和运行期上下文 |
Network() | pluginbridge.Default().Network() | 动态插件通过host-service授权访问受治理的出站HTTP;源码插件使用宿主原生HTTP client或注入的领域服务 |
RecordStore() | pluginbridge.Default().RecordStore() | 动态插件使用pluginbridge侧facade封装data host-service协议和类型化查询计划;源码插件使用自有DAO或提供方接缝 |