Plugins
Manifest 参考
完整的 manifest.toml Schema — 插件信息、权限、Hook、路由、定时任务、依赖。
每个插件都有一个 manifest.toml,定义插件的身份、权限、Hook、路由和定时任务。
[plugin] — 插件身份
[plugin]
id = "com.example.my-plugin"
name = "My Plugin"
version = "0.1.0"
description = "Does something useful"
author = "Alice"
license = "MIT"
runtime = "js"
entry = "main.js"| 字段 | 必填 | 默认值 | 说明 |
|---|---|---|---|
id | 是 | — | 唯一插件 ID(建议反向域名格式) |
name | 是 | — | 显示名称 |
version | 是 | — | 语义版本号 |
description | 否 | "" | 描述 |
author | 否 | — | 作者 |
license | 否 | — | 许可证 |
runtime | 否 | "wasm" | 运行时:js / lua / rhai / wasm |
language | 否 | "rust" | 语言标识 |
entry | 否 | "index.js" | 入口文件名 |
wasm | 否 | "plugin.wasm" | WASM 文件路径(仅 runtime = wasm) |
sdk_version | 否 | "v1" | SDK 版本 |
自动检测:runtime = "lua" 且未设置 entry 时,加载器查找 init.lua。runtime = "rhai" 时查找 init.rhai。
[permissions] — 安全控制
除非显式声明,否则一律拒绝访问。
[permissions]
max_memory_mb = 16
timeout_ms = 5000
database = ["read:products", "write:orders", "categories"]
http = ["api.example.com", "*.github.com"]
config = ["app.*", "jwt.*"]
filesystem = ["read-write"]| 字段 | 默认值 | 说明 |
|---|---|---|
max_memory_mb | 32(配置默认) | 单实例内存上限 |
timeout_ms | 配置默认值 | Hook 执行超时 |
database | [](禁止) | 数据库表访问权限 |
http | [](禁止) | HTTP 白名单 |
config | [](禁止) | 配置键白名单 |
filesystem | [](禁止) | VFS 访问级别 |
数据库权限
| 格式 | 权限 |
|---|---|
"read:TABLE" | 只读 |
"write:TABLE" | 只写 |
"TABLE" | 读写 |
"*" | 所有表(受保护表除外) |
受保护表即使声明 "*" 也不可访问:users、roles、permissions、audit_log、plugin_storage、options、rbac_roles、rbac_permissions、rbac_role_permissions、tenants。
SQL 安全:DDL 被阻止(CREATE/DROP/ALTER/TRUNCATE)。UNION、JOIN 和子查询在权限检查中被检测并阻止。
HTTP 白名单
- 精确域名:
api.example.com - 通配符子域:
*.github.com - 路径通配:
api.example.com/v1/*
内置 SSRF 防护:阻止 localhost、127.x、10.x、172.16-31.x、192.168.x、169.254.x、::1。
文件系统权限
| 值 | 启用 |
|---|---|
"read" | vfsRead、vfsExists、vfsList、vfsStat |
"write" | vfsWrite、vfsDelete |
"read-write" | 所有 VFS 操作 |
"*" | 所有 VFS 操作 |
[hooks.<name>] — Hook 注册
[hooks.on-content-created]
priority = 50
[hooks.on-content-updating]
priority = 100
match = "product"
[hooks.on-content-updating]
priority = 100
content_types = ["product", "course"]
[hooks.render-markdown]
priority = 10| 字段 | 默认值 | 说明 |
|---|---|---|
priority | 100 | 执行顺序(数字越小越先执行) |
match | — | 按 Content Type 名称匹配 |
content_types | [](全部) | 仅对指定的 Content Type 触发 |
Hook 名使用连字符(on-content-created),系统自动转换为下划线(on_content_created)。
[[routes]] — 自定义 API 端点
[[routes]]
method = "GET"
path = "/api/v1/plugins/my-plugin/stats"
handler = "getStats"
auth = "public"
[[routes]]
method = "POST"
path = "/api/v1/plugins/my-plugin/contacts/:contactId"
handler = "updateContact"
auth = "admin"
description = "Update a contact"| 字段 | 必填 | 默认 | 说明 |
|---|---|---|---|
method | 是 | — | HTTP 方法 |
path | 是 | — | 路由路径,支持 :param 占位符 |
handler | 是 | — | 插件中的函数名 |
auth | 否 | default | public / member / admin |
description | 否 | — | 描述 |
permission | 否 | — | 额外权限要求 |
处理函数接收 input,包含 { path, method, body, headers, params }。直接返回数据,框架自动包装为 { code: 0, data: ... }。
路由输入参数
[[routes]]
method = "POST"
path = "/api/v1/plugins/my-plugin/deals"
handler = "createDeal"
[[routes.input]]
name = "title"
type = "string"
in = "body"
required = true
description = "Deal title"
[[routes.input]]
name = "page"
type = "integer"
in = "query"
default = 1
description = "Page number"| 字段 | 必填 | 默认 | 说明 |
|---|---|---|---|
name | 是 | — | 参数名 |
in | 否 | "query" | 位置:query / body / path / header |
type | 否 | "" | 类型提示 |
required | 否 | false | 是否必填 |
description | 否 | — | 描述 |
default | 否 | — | 默认值 |
路由输出
[routes.output]
description = "Deal list"
[[routes.output.fields]]
name = "id"
type = "string"
description = "Deal ID"
[[routes.output.fields]]
name = "title"
type = "string"
description = "Deal title"[[cron]] — 定时任务
[[cron]]
label = "Daily Cleanup"
job_type = "daily_cleanup"
cron_expr = "0 0 * * *"
payload = """{"type": "full"}"""
enabled = true| 字段 | 必填 | 默认 | 说明 |
|---|---|---|---|
label | 是 | — | 任务名称 |
job_type | 是 | — | 任务类型(传给 on_cron_tick) |
cron_expr | 是 | — | Cron 表达式(七段,含秒) |
payload | 否 | — | JSON 负载字符串 |
enabled | 否 | true | 是否启用 |
Cron 条目在插件加载时同步到数据库,卸载时移除。
[[content_types]] — 捆绑 Content Type
[[content_types]]
file = "content_types/contact.toml"插件可以自带 Content Type TOML 文件,安装时自动加载。
[[admin_pages]] — 管理后台页面
[[admin_pages]]
path = "/admin/plugins/crm"
label = "CRM"
icon = "users"
component = "CrmDashboard"| 字段 | 必填 | 说明 |
|---|---|---|
path | 是 | 页面路径 |
label | 是 | 显示名称 |
icon | 否 | 图标名 |
component | 否 | 前端组件名 |
[dependencies] — 插件依赖
[dependencies]
"com.example.base" = "1.0.0"插件按拓扑排序加载,确保依赖先初始化。
完整示例
[plugin]
id = "com.example.crm"
name = "CRM API"
version = "0.1.0"
description = "Sales pipeline and contact management"
author = "Alice"
license = "MIT"
runtime = "js"
entry = "main.js"
[permissions]
max_memory_mb = 16
timeout_ms = 5000
database = ["crm_contacts", "crm_companies", "crm_deals"]
http = ["api.example.com"]
config = ["app.*"]
filesystem = ["read-write"]
[hooks.on-content-created]
priority = 50
[hooks.on-content-updating]
priority = 100
content_types = ["contact"]
[hooks.render-markdown]
priority = 10
[[routes]]
method = "GET"
path = "/api/v1/plugins/crm/pipeline"
handler = "getPipeline"
auth = "public"
[[routes]]
method = "GET"
path = "/api/v1/plugins/crm/pipeline/:dealId"
handler = "getDealDetail"
auth = "member"
[[cron]]
label = "Daily Stats"
job_type = "daily_stats"
cron_expr = "0 0 * * *"
payload = """{"type": "full"}"""
[dependencies]
"com.example.base" = "1.0.0"