Getting Started
插件开发入门
几分钟写出你的第一个 RaisFast 插件 — JS、Lua 或 Rhai。
一个插件就是一个文件夹,包含 manifest.toml 和一个入口文件。就这么简单。
你的第一个插件
raisfast plugin new my-plugin --runtime jsextensions/plugins/my-plugin/
├── manifest.toml
└── main.js在 main.js 中写一个 Hook:
import { logInfo } from 'sdk';
export function on_content_created(input) {
const data = JSON.parse(input);
logInfo("new content: " + data.id);
}启动服务 — 插件自动加载。每次创建内容时,你都会看到日志。
三种语言,同一个插件
JavaScript
import { dbQuery, ok, logInfo } from 'sdk';
export function on_post_creating(input) {
const data = JSON.parse(input);
if (!data.slug) {
data.slug = data.title.toLowerCase().replace(/ /g, '-');
}
return JSON.stringify(data);
}
export function getProducts() {
return ok(dbQuery("SELECT * FROM products"));
}- QuickJS(ES2024,支持 async/await)
import { ... } from 'sdk'
Lua
local sdk = require("sdk")
Plugin = {}
Plugin.on_post_creating = function(input)
local data = sdk.extractJson(input, "body")
if not data.slug then
data.slug = string.lower(data.title):gsub(" ", "-")
end
return sdk.ok(data)
end
Plugin.getProducts = function(input)
return sdk.ok(sdk.dbQuery("SELECT * FROM products"))
end- Lua 5.4(沙箱化)
local sdk = require("sdk")
Rhai
fn on_post_creating(input) {
if input.slug == "" {
input.slug = to_lower(input.title);
input.slug = replace(input.slug, " ", "-");
}
input
}
fn get_products(input) {
let rows = db_query("SELECT * FROM products", "");
rows
}- Rhai(Rust 原生脚本,无需导入 SDK)
- 宿主函数直接注入全局作用域
实战示例:SEO 优化插件
自动生成 slug 并将文章缓存到虚拟文件系统。
[plugin]
id = "com.example.seo-js"
name = "SEO Optimizer"
version = "1.0.0"
runtime = "js"
entry = "main.js"
[permissions]
database = ["read:posts"]
filesystem = ["read-write"]
[hooks.on-post-creating]
priority = 10
[hooks.on-post-created]
priority = 10import { ok, logInfo, vfsWrite, vfsExists, vfsRead } from 'sdk';
export function on_post_creating(input) {
const data = JSON.parse(input);
if (!data.slug) {
data.slug = data.title.toLowerCase().replace(/ /g, '-');
}
logInfo("[seo] slug: " + data.slug);
return JSON.stringify(data);
}
export function on_post_created(input) {
const data = JSON.parse(input);
vfsWrite("cache/" + data.slug + ".json", JSON.stringify(data));
}
export function seoStats() {
const count = vfsExists("stats.json") ? vfsRead("stats.json") : "0";
return ok({ total: parseInt(count) });
}[plugin]
id = "com.example.seo-lua"
name = "SEO Optimizer (Lua)"
version = "1.0.0"
runtime = "lua"
entry = "init.lua"
[permissions]
database = ["read:posts"]
filesystem = ["read-write"]
[hooks.on-post-creating]
priority = 10
[hooks.on-post-created]
priority = 10local sdk = require("sdk")
Plugin = {}
Plugin.on_post_creating = function(input)
local data = sdk.extractJson(input, "body")
if not data.slug then
data.slug = string.lower(data.title):gsub(" ", "-")
end
sdk.logInfo("[seo] slug: " .. data.slug)
return sdk.ok(data)
end
Plugin.on_post_created = function(input)
local data = sdk.extractJson(input, "body")
sdk.vfsWrite("cache/" .. data.slug .. ".json", Host.jsonEncode(data))
end[plugin]
id = "com.example.seo-rhai"
name = "SEO Optimizer (Rhai)"
version = "1.0.0"
runtime = "rhai"
entry = "init.rhai"
[permissions]
database = ["read:posts"]
filesystem = ["read-write"]
[hooks.on-post-creating]
priority = 10
[hooks.on-post-created]
priority = 10fn on_post_creating(input) {
if input.slug == "" {
input.slug = to_lower(input.title);
input.slug = replace(input.slug, " ", "-");
}
log("info", "[seo] slug: " + input.slug);
input
}
fn on_post_created(data) {
let key = "cache/" + data.slug + ".json";
vfsWrite(key, to_json(data));
}Hook
[hooks.on-post-creating] # 创建前(可修改数据)
priority = 10
[hooks.on-post-created] # 创建后(副作用)
priority = 50| Hook | 类型 | 时机 |
|---|---|---|
on-content-creating | filter | 内容创建前(可修改数据) |
on-content-created | action | 内容创建后 |
on-content-updating | filter | 内容更新前 |
on-content-updated | action | 内容更新后 |
on-content-deleted | action | 内容删除后 |
on-post-creating | filter | 文章创建前 |
on-post-created | action | 文章创建后 |
render-markdown | filter | 覆盖 Markdown 渲染 |
on-login | action | 用户登录后 |
on-cron-tick | action | 定时任务触发 |
自定义路由
[[routes]]
method = "GET"
path = "/api/v1/plugins/my-plugin/stats"
handler = "getStats"
[[routes]]
method = "POST"
path = "/api/v1/plugins/my-plugin/orders"
handler = "createOrder"
auth = "admin"处理函数接收 input,包含 { path, method, body, headers, params }。直接返回数据,框架自动包装为 { code: 0, data: ... }。
定时任务
[[cron]]
label = "每日清理"
job_type = "daily_cleanup"
cron_expr = "0 0 * * *"由 on_cron_tick Hook 处理,通过 job_type 过滤。
权限
除非显式声明,否则一律拒绝访问:
[permissions]
database = ["products", "write:orders"]
http = ["api.example.com"]
filesystem = ["read-write"]Host API
| 函数 | 功能 |
|---|---|
dbQuery(sql, params) | SELECT |
dbExec(sql, params) | INSERT / UPDATE / DELETE |
dbBegin/Commit/Rollback() | 事务 |
httpGet(url) / httpPost(url, body) | HTTP 请求 |
getConfig(key) | 读取配置 |
getData/setData(key, value) | 插件 KV 存储 |
vfsRead/Write/Delete/Exists | 虚拟文件系统 |
getPost(slug) | 按 slug 获取文章 |
log(level, msg) | 日志 |
emitEvent(type, data) | 发射事件 |
