Repository navigation
Core Framework
StackPanel 采用 微内核(Microkernel)架构。内核提供六项基础设施职责,其余一切功能均为插件。除这六项外,内核还拥有少量平台服务(支付编排、钱包、汇率、站内通知、共享状态),供任意插件通过 ctx.* 复用——这些是横向能力,而非业务功能。
| 职责 | 实现位置 | 说明 |
|---|---|---|
| 用户与 RBAC |
plugins/auth.ts、lib/rbac.ts、auth/session-store.ts
|
本地认证、服务端会话(Redis/进程内)、角色权限 |
| 插件运行时 |
plugins/runtime.ts、plugins/dispatcher.ts
|
生命周期管理、路由分发、扩展点 |
| 主题引擎 |
lib/themes.ts、routes/theme.ts
|
ZIP 主题包、CSS Token 校验、激活/预览 |
| 事件总线 | plugins/events.ts |
DB Outbox + Redis Pub/Sub,本地即时投递 |
| 设置与密钥 |
lib/platform-info.ts、lib/crypto.ts
|
JSON 设置存储、AES-256-GCM 加密密钥 |
| 审计日志 |
plugins/audit.ts、routes/admin/audit.ts
|
只追加审计,覆盖全部管理操作 |
插件定义的 execution 字段决定插件运行在哪个进程边界,取值为 isolated 或 trusted。第三方包默认 isolated,内置包始终 trusted;未声明该字段的旧包在生产环境按 isolated 加载,在开发环境按 trusted 加载。
包的入口在独立 Node 子进程中运行,API 进程只保留清单、路由代理与 RPC 桥。ctx.* 通过 IPC RPC 暴露给 worker,协议带版本与身份校验:
- 每条消息携带
protocol: 1与已认证的pluginId,协议版本或插件 id 不匹配的消息被丢弃。 - 双向 10 秒超时、单条消息 1 MiB 上限、每个 worker 64 个在途请求上限;超时拒绝调用方并发尽力发送
cancel。 -
Date、Buffer、bigint、Error以{ "__spType": ... }标记编码,循环引用被拒绝;类实例、函数、流、socket 不跨边界。 - worker 退出会把插件置为
failed,清理其路由/模型/任务并拒绝在途请求;下次激活启动新 worker;退出累计达到 3 次后运行时熔断,必须重新注册修复后的包才能恢复。 - 原始流式路由与无法表示为 JSON Schema 的自定义模型 schema 在 worker 启动时被拒绝。
-
ctx.events.intercept()与ctx.events.waterfall()依赖同步next()与同步返回值语义,无法跨异步 IPC 保真,在 isolated 模式下调用会得到明确错误。
内置插件在所有环境下都是 trusted;非内置插件只能在非生产环境请求 execution: "trusted",生产环境校验会拒绝该模式。
permissions 与路由上的 permission 是路由准入与前端可见性控制,不是 OS 沙盒;isolated 也不是完整容器。PluginContext 不提供数据库句柄,插件只能通过 Extension 引擎读写自己声明的模型。
所有功能先有 REST API,后台(Next.js Web)只是 API 的客户端(BFF 模式)。Web 不含业务逻辑,仅负责渲染和 API 转发。
内核路由和插件路由物理隔离:
- 内核路由:注册为 Fastify 原生路由,由 radix router 匹配,优先级最高
-
插件路由:存在内存路由表(
PluginDispatcher),由 catch-all/*处理器分发
因为 catch-all 最后注册,内核路由永远优先匹配。
插件路由表可运行时变更。安装、升级、启停插件无需重启服务,路由即时生效。
TypeScript strict 模式(当前 typescript@6)。@stackpanel/sdk 提供完整的类型契约,插件开发者和 API 消费者共享同一套类型定义;@stackpanel/spec 是内核 API 版本与 OpenAPI 投影的单一来源。
HTTP 请求
→ Fastify radix router 匹配内核路由
→ preHandler 链(requireAuth → requireRole)
→ 路由 handler
→ onSend 钩子(安全头注入)
→ 响应
HTTP 请求
→ Fastify radix router 无匹配(落到 catch-all)
→ PluginDispatcher.match(method, path)
→ runGuards(isActive → requireAuth → admin 时 requirePermission('platform.admin') → permission 校验)
→ 构造 HttpRequest(框架无关视图)
→ 插件 handler(request, reply)
→ reply.send(result)
→ 响应
统一错误模型为 RFC 7807 application/problem+json。全局 onSend 钩子把所有 >=400 的 JSON 响应归一化,setErrorHandler 处理异常:
-
业务错误(SDK
PluginError):按插件给定的code/status输出 problem+json,info 级日志 -
4xx:
{ type, title, status, detail, instance, requestId },响应头回传x-request-id -
5xx:
internal.unexpected+服务器内部错误,详情只入日志 -
校验失败(Fastify validation):
422 validation.invalid
initInfra() ← 校验基础设施、连接 Redis、确保对象存储桶(server.ts 中先于 buildApp)
buildApp({ redis })
├── 注册 Fastify 插件(cookie/cors/multipart/rateLimit/swagger/swagger-ui)
├── 全局 onSend 安全头 + RFC 7807 归一化 + setErrorHandler / setNotFoundHandler
├── 创建 PluginDispatcher → app.decorate
├── createPluginHost() → 内核服务 + PluginRuntime → app.decorate(payments/wallet/fx/auth/notifications/state)
├── registerKernelJobs() ← 内核 sweep 注册为 BullMQ 周期任务
├── installExtensionRoutes() ← Extension 引擎自定义资源路由
├── 注册内核路由(health/media/auth/nav/notifications/seo/open-api-v1/sp-v1/mcp/plugin/theme/admin/*)
├── registerApiVersioning() + installObservability()
├── onReady: initThemes() + seedDefaultBrand() ← 种子默认主题与品牌
├── onReady: host.registerPlugins() ← seed 内置插件 + 扫描 data/plugins 注册
└── installBodyRouting() + dispatcher.install() ← catch-all(最后注册)
start()
├── ensureBootstrapAdmin() ← 首启创建管理员
├── app.ready() ← 触发 onReady hooks
├── app.pluginHost.syncEnabledPlugins() ← 按 DB enabled 标志激活插件
├── startRuntimeCoherence() ← 订阅跨副本运行时失效
└── app.listen()
领域事件走事务性 Outbox + Redis Pub/Sub:
-
publish先把事件写入 outbox(至少一次投递,进程崩溃不丢),由 relay 循环认领并本地投递 + 跨副本经 Redis Pub/Sub 分发。 - 未绑定 outbox 时(开发/测试/早期启动)退化为进程内
EventEmitter,同步投递。 -
EventBus接口是集群接缝:publish/subscribe广播事实,intercept/waterfall是同步环绕管线。这两者在 isolated 插件中不可用。
interface EventBus {
publish(topic: string, payload?: unknown): void;
subscribe(topic: string, handler: (payload: unknown) => void): () => void;
}存储引擎唯一为 PostgreSQL(Prisma provider=postgresql + @prisma/adapter-pg)。连接串非 postgres:// / postgresql:// 时启动即失败。迁移一律手写 migration.sql 并用 migrate deploy,禁用 migrate dev。应用层无状态,集群一致性依赖 Redis 与对象存储。
核心开发
插件开发
主题开发
前端包开发
RESTful API