基本介绍
为提升框架整体的灵活性与可扩展性,主框架采用领域驱动的设计思路对核心能力进行建模,并以解耦方式组织各领域能力的实现与契约。apps/lina-core/pkg/plugin目录作为公开的Go契约边界,向插件暴露稳定的领域服务接口——源码插件通过pluginhost.Services消费完整的capability.Services目录,动态插件通过pluginbridge.Default()返回的pluginbridge.Services消费已经发布为hostServices的能力子集。
组件结构
| 路径 | 职责 |
|---|---|
capability/ | 聚合稳定宿主能力,包含Services、AdminServices、插件作用域服务绑定和各领域窄接口;源码插件直接使用完整目录,动态插件使用已发布的桥接子集 |
pluginhost/ | 源码插件宿主命名空间,提供编译期注册接口层和运行期回调契约 |
pluginbridge/ | 动态插件桥接命名空间,提供pluginbridge.Default()/pluginbridge.New()运行时能力目录、Wasm执行契约和hostServices编解码 |
声明期与运行期
插件能力分为声明期和运行期两个阶段。声明期是插件的静态注册和发现阶段,主框架在业务执行前使用声明输出构建治理状态;运行期是插件业务逻辑执行阶段,插件消费主框架提供的领域能力服务。
声明期能力的详细设计和使用方式请参阅声明期能力概览,包括资源声明、生命周期声明、路由声明、任务声明、钩子声明、提供方声明和访问控制声明。
声明期能力
声明期能力是插件的静态注册输出。源码插件在编译期通过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,直接暴露全部领域能力方法,并额外提供源码插件专属能力:
| 能力入口 | 功能说明 |
|---|---|
| 全部领域能力 | 包括AI、Auth、Cache、Storage等能力 |
Admin() | 可信管理命令,例如修改用户状态、替换角色权限、吊销会话、写入运行时配置等 |
TenantFilter() | 为插件自有表追加租户过滤条件的数据库查询构建器 |
动态插件运行期
动态插件通过pluginbridge.Default()访问已发布的运行期能力。所有调用经由WASI host call传输,由宿主按hostServices授权快照校验后分发执行。动态插件访问的是动态服务目录中的能力子集,并拥有三个专属能力:
| 能力入口 | 功能说明 |
|---|---|
| 已发布领域能力 | 例如AI、Auth、Cache、Storage等能力,通过host-call桥接访问;I18n()不发布为动态hostServices |
Runtime() | 专属能力:日志写入、插件状态读写、时间获取、UUID生成、节点身份读取 |
Network() | 专属能力:受治理的出站HTTP请求,需在plugin.yaml中声明授权目标地址 |
RecordStore() | 专属能力:data服务的类ORM封装层,只能访问声明的插件自有表 |
AdminServices边界
capability.AdminServices是可信源码插件的管理命令目录,只通过pluginhost.Services.Admin()暴露。源码插件可以在宿主进程内接收经过领域治理的管理能力,例如用户管理、权限管理、通知管理、会话强退和插件治理命令。
动态插件的pluginbridge.Services接口不提供Admin()入口,因此不能直接使用sessioncap.AdminService、notifycap.AdminService或其他领域AdminService接口。动态插件只能调用已经发布为动态hostServices、已经写入plugin.yaml声明、经过宿主授权并注册到WASM host-service分发器中的具体方法。
例如当前sessions动态服务只提供sessions.search和sessions.batch_get,不包含sessioncap.AdminService.RevokeSession对应的强退命令。如果未来确实需要让动态插件使用某个管理动作,再考虑开放管理接口。
领域能力概览
| 方法 | 领域文档 | 说明 |
|---|---|---|
AI() | AI能力 | 聚合文本、图片、向量、音频、视觉、文档、安全和视频子能力 |
APIDoc() | 接口文档能力 | 解析路由操作键、本地化模块标签和操作摘要 |
Auth() | 认证与授权能力 | 聚合Token()和Authz()子能力 |
Users() | 用户能力 | 用户视图、搜索和可见性校验 |
BizCtx() | 业务上下文能力 | 读取当前请求用户、租户、模拟登录和平台绕过状态 |
Cache() | 缓存能力 | 插件作用域运行时缓存 |
Dict() | 字典能力 | 字典标签解析和刷新视图 |
Files() | 文件能力 | 文件视图和可见性校验 |
HostConfig() | 配置管理能力 | 读取宿主配置值;动态插件读取时必须声明keys |
I18n() | 国际化能力 | 源码插件运行时翻译能力;动态插件不开放对应host service |
Infra() | 基础设施能力 | 基础设施组件状态视图 |
Jobs() | 任务与定时能力 | 定时任务视图读取 |
Manifest() | 清单资源能力 | 读取当前插件manifest/下的只读资源 |
Notifications() | 通知能力 | 通知消息视图读取 |
Org() | 组织能力 | 可选组织能力,读取用户部门和岗位视图 |
Plugins() | 插件治理能力 | 聚合插件注册表、插件配置、插件状态和生命周期子能力 |
Route() | 动态路由能力 | 读取当前动态路由元数据 |
Sessions() | 在线会话能力 | 在线会话搜索和批量读取 |
Storage() | 文件能力 | 插件作用域对象存储操作 |
Tenant() | 租户能力 | 可选租户能力,读取当前租户、可见性、切换校验和源码插件租户过滤 |
Lock() | 分布式锁能力 | 插件可见的分布式锁获取、续租和释放 |
插件作用域能力会由宿主绑定插件身份。例如Plugins().Config()只读取当前插件自己的config.yaml,Manifest()只读取当前插件的manifest/资源,AI()会把来源插件ID注入后续提供方请求。
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) {
// 实现部门信息查询
}
各能力的提供方接口定义位于对应的领域能力包中:
| 能力 | SPI接口 | SPI包 | 官方插件 |
|---|---|---|---|
AI | 各子能力独立接口 | aicap | linapro-ai-core |
Org | orgcap.Provider | orgcap | linapro-org-core |
Tenant | tenantcap.Provider + tenantcap.Resolver | tenantcap | linapro-tenant-core |
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
动态插件不能直接访问宿主实现包,也不能直接使用源码插件专属的AdminServices管理命令目录。它们通过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、authz、ai、users、bizctx、dict、files、infra、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_*这类宿主核心表,也不应把宿主表名作为插件数据能力的目标。
动态服务目录
| 服务 | 领域文档 | 资源类型 | 方法 |
|---|---|---|---|
runtime | 动态Runtime能力 | none | log.write、state.get、state.set、state.delete、info.now、info.uuid、info.node |
storage | 文件能力 | path | put、get、delete、list、stat |
network | 外部网络能力 | resource | request |
data | 数据记录能力 | table | list、get、create、update、delete、transaction |
cache | 缓存能力 | resource | get、set、delete、incr、expire |
lock | 分布式锁能力 | resource | acquire、renew、release |
hostconfig | 配置管理能力 | key | get |
manifest | 清单资源能力 | path | get |
apidoc | 接口文档能力 | none | route_text.resolve、route_texts.resolve、route_title_operation_keys.find |
auth | 认证与授权能力 | none | tenant.select、tenant.switch、impersonation_token.issue、impersonation_token.revoke |
authz | 认证与授权能力 | none | permissions.batch_get、permissions.has、users.platform_admin.check |
ai | AI能力 | none | text.generate、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 |
users | 用户能力 | none | users.batch_get、users.search、users.visible.ensure |
bizctx | 业务上下文能力 | none | current.get |
dict | 字典能力 | none | labels.resolve |
files | 文件能力 | none | files.batch_get、files.visible.ensure |
infra | 基础设施能力 | none | status.batch_get |
jobs | 任务与定时能力 | none | jobs.batch_get、jobs.register |
notifications | 通知能力 | 读取无资源;messages.send使用resources[].ref | messages.batch_get、messages.send |
plugins | 插件治理能力 | none | plugins.batch_get、plugins.tenant.list、plugins.enabled.check、plugins.provider_enabled.check、plugins.enabled_authoritative.check、config.get、lifecycle.tenant_plugin_disable.ensure、lifecycle.tenant_plugin_disabled.notify、lifecycle.tenant_delete.ensure、lifecycle.tenant_deleted.notify |
route | 动态路由能力 | none | metadata.get |
sessions | 在线会话能力 | none | sessions.search、sessions.batch_get |
org | 组织能力 | none | capability.available、capability.status、users.dept_assignments.list、users.dept_info.get、users.dept_name.get、users.dept_ids.get、users.post_ids.get |
tenant | 租户能力 | none | capability.available、capability.status、tenants.current、tenants.platform_bypass、tenants.visible.ensure、users.tenant_membership.validate、users.tenants.list、tenants.switch.validate |
secret | 预留 | resource | resolve |
event | 预留 | resource | publish |
queue | 预留 | resource | enqueue |
动态插件专属能力
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或提供方接缝 |