RaisFastRaisFast
Getting Started

插件开发入门

几分钟写出你的第一个 RaisFast 插件 — JS、Lua 或 Rhai。

一个插件就是一个文件夹,包含 manifest.toml 和一个入口文件。就这么简单。

你的第一个插件

raisfast plugin new my-plugin --runtime js
extensions/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 = 10
import { 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 = 10
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
  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 = 10
fn 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-creatingfilter内容创建前(可修改数据)
on-content-createdaction内容创建后
on-content-updatingfilter内容更新前
on-content-updatedaction内容更新后
on-content-deletedaction内容删除后
on-post-creatingfilter文章创建前
on-post-createdaction文章创建后
render-markdownfilter覆盖 Markdown 渲染
on-loginaction用户登录后
on-cron-tickaction定时任务触发

自定义路由

[[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)发射事件

下一步

On this page