Files
RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/plugins/examples/rust/rust_demo/src/lib.rs
T
insistence 4bd83bc7cd feat: 支持 v2 插件、独立前端和签名制品发布 (#130)
* feat: 支持 v2 插件、原生扩展与签名发布

* fix: 修复测试

* feat: 增强宿主 SDK、运行观测与制品维护能力

* feat: 增强文件传输、配置观测与本地开发能力

* feat: 支持 SSE 实时事件流并完善开发脚手架

* feat: 完善长连接鉴权复核与关闭回收

* feat: 增强生命周期与任务关闭可靠性

* feat: 收紧健康检查契约并补齐前端与真实网络验收

* feat: 增加SDK溯源与离线更新工具并验证跨版本兼容

* feat: 增加持久化业务示例与受控 Python 交付

* feat: 增加统一运行诊断与多worker状态观测

* feat: 增加固定目标发布等待与自动验收

* fix: 修复多环境插件部署前缀与代理配置不一致

* fix: 修复测试
2026-10-09 23:49:45 +08:00

195 lines
7.5 KiB
Rust

//! 交付 wheel 包含编译后的原生扩展,Rust 源码保留在开发工程中。
use pyo3::prelude::*;
use pyo3::types::PyDict;
/// 声明 ASGI 子应用工厂,资源初始化由宿主驱动的生命周期负责。
#[pyfunction]
fn create_plugin<'py>(py: Python<'py>, _host: Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
let kwargs = PyDict::new(py);
kwargs.set_item("app_factory", wrap_pyfunction!(create_app, py)?)?;
py.import("plugins.core.sdk")?
.getattr("PluginDefinition")?
.call((), Some(&kwargs))
}
/// 演示以显式路由声明方式接入宿主,并使用独立权限保护接口。
#[pyfunction]
fn create_router_plugin<'py>(
py: Python<'py>,
_host: Bound<'py, PyAny>,
) -> PyResult<Bound<'py, PyAny>> {
let router = py.import("fastapi")?.getattr("APIRouter")?.call0()?;
add_routes(py, &router, "/rust_demo")?;
let kwargs = PyDict::new(py);
kwargs.set_item("routers", vec![router])?;
py.import("plugins.core.sdk")?
.getattr("PluginDefinition")?
.call((), Some(&kwargs))
}
/// 为当前 worker 创建子应用,并绑定原生启动、关闭回调和业务路由。
#[pyfunction]
fn create_app<'py>(py: Python<'py>, host: Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
let sdk = py.import("plugins.core.sdk")?;
let lifespan = sdk.getattr("plugin_lifespan")?.call1((
host,
wrap_pyfunction!(startup, py)?,
wrap_pyfunction!(shutdown, py)?,
))?;
let kwargs = PyDict::new(py);
kwargs.set_item("title", "Rust native plugin")?;
kwargs.set_item("lifespan", lifespan)?;
kwargs.set_item("docs_url", py.None())?;
kwargs.set_item("redoc_url", py.None())?;
let app = py
.import("fastapi")?
.getattr("FastAPI")?
.call((), Some(&kwargs))?;
add_routes(py, &app, "")?;
Ok(app)
}
/// 通过宿主 SDK 包装接口权限,供子应用和显式路由两种模式复用。
fn add_routes(py: Python<'_>, app: &Bound<'_, PyAny>, prefix: &str) -> PyResult<()> {
let sdk = py.import("plugins.core.sdk")?;
for (path, permission, callback, summary, description, method, json_body) in [
(
"/api/info",
"rust_demo:view",
wrap_pyfunction!(info, py)?,
"获取原生插件信息接口",
"用于获取当前插件标识、宿主API版本、运行引擎和服务器时间",
"GET",
false,
),
(
"/api/profile",
"rust_demo:profile",
wrap_pyfunction!(profile, py)?,
"获取原生插件当前用户信息接口",
"用于通过宿主服务获取当前登录用户的基本信息",
"GET",
false,
),
(
"/api/echo/{item_id}",
"rust_demo:view",
wrap_pyfunction!(echo, py)?,
"验证原生插件请求上下文接口",
"返回路径参数、多值查询参数、JSON请求体和宿主追踪标识",
"POST",
true,
),
] {
let options = PyDict::new(py);
options.set_item("permission", permission)?;
options.set_item("json_body", json_body)?;
let endpoint = sdk
.getattr("plugin_endpoint")?
.call((callback,), Some(&options))?;
let route_options = PyDict::new(py);
route_options.set_item("summary", summary)?;
route_options.set_item("description", description)?;
route_options.set_item("response_model", py.get_type::<PyDict>())?;
route_options.set_item("methods", vec![method])?;
app.call_method(
"add_api_route",
(format!("{prefix}{path}"), endpoint),
Some(&route_options),
)?;
}
Ok(())
}
/// 记录插件启动,并通过 Python 可等待对象返回就绪状态。
#[pyfunction]
fn startup<'py>(py: Python<'py>, host: Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
host.getattr("logger")?
.call_method1("info", ("Rust plugin started",))?;
pyo3_async_runtimes::tokio::future_into_py(py, async {
Python::attach(|py| {
let state = PyDict::new(py);
state.set_item("nativeReady", true)?;
Ok(state.unbind())
})
})
}
/// 演示插件关闭回调;插件只负责释放自己创建的资源。
#[pyfunction]
fn shutdown<'py>(py: Python<'py>, host: Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
host.getattr("logger")?
.call_method1("info", ("Rust plugin stopped",))?;
pyo3_async_runtimes::tokio::future_into_py(py, async { Ok(()) })
}
/// 通过可等待对象返回健康结果,供宿主统一处理原生异步回调。
#[pyfunction]
fn health<'py>(py: Python<'py>, _context: Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
pyo3_async_runtimes::tokio::future_into_py(py, async { Ok(true) })
}
#[pyfunction]
fn info<'py>(py: Python<'py>, context: Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
let host = context.getattr("host")?;
let plugin_id: String = host.getattr("plugin_id")?.extract()?;
let api_version: String = host.getattr("api_version")?.extract()?;
// 在当前 Python 调用线程中复用宿主时间工具,再将普通字符串交给异步任务。
let timestamp: String = py
.import("utils.time_util")?
.getattr("TimezoneUtil")?
.call_method0("utc_now")?
.call_method0("isoformat")?
.extract()?;
pyo3_async_runtimes::tokio::future_into_py(py, async move {
Python::attach(|py| {
let result = PyDict::new(py);
result.set_item("pluginId", plugin_id)?;
result.set_item("hostApiVersion", api_version)?;
result.set_item("engine", "rust")?;
result.set_item("serverTime", timestamp)?;
Ok(result.unbind())
})
})
}
#[pyfunction]
fn profile<'py>(_py: Python<'py>, context: Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
// 请求取消和数据库会话生命周期继续由宿主事件循环管理,不能阻塞等待或调用 asyncio.run。
context
.getattr("host")?
.call_method1("service", ("users.current_profile.v1",))?
.call1((context,))
}
#[pyfunction]
fn echo<'py>(py: Python<'py>, context: Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
let result = context.getattr("request")?.call_method0("to_payload")?;
result.set_item("requestId", context.getattr("request_id")?)?;
let payload = result.unbind();
pyo3_async_runtimes::tokio::future_into_py(py, async move { Ok(payload) })
}
/// 演示原生后台任务读取宿主任务上下文并返回执行结果。
#[pyfunction]
fn heartbeat<'py>(py: Python<'py>, context: Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
let job_id: String = context.getattr("job_id")?.extract()?;
context
.getattr("host")?
.getattr("logger")?
.call_method1("info", (format!("Rust background job: {job_id}"),))?;
pyo3_async_runtimes::tokio::future_into_py(py, async move { Ok(job_id) })
}
/// 导出清单和宿主运行时需要的 Python 扩展入口。
#[pymodule]
fn _native(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_function(wrap_pyfunction!(create_plugin, module)?)?;
module.add_function(wrap_pyfunction!(create_router_plugin, module)?)?;
module.add_function(wrap_pyfunction!(create_app, module)?)?;
module.add_function(wrap_pyfunction!(health, module)?)?;
module.add_function(wrap_pyfunction!(profile, module)?)?;
module.add_function(wrap_pyfunction!(heartbeat, module)?)?;
Ok(())
}