majorsilence-reporting
从 Rust 生成 PDF、Excel、CSV 等格式的 RDL 报表——通过 libloading 实现进程内原生 FFI。无需 .NET 运行时,无需子进程。线程安全、基于 Arc 的库句柄。仅有一个依赖项。
安装
该 crate 尚未发布到 crates.io。请将其作为路径依赖添加:
# Cargo.toml [dependencies] majorsilence-reporting = { path = "/path/to/reporting-rust" } # 发布到 crates.io 后: # majorsilence-reporting = "1.0"
唯一的运行时依赖是 libloading = "0.8"。Rust 2021 版,最低支持 Rust 1.65。
快速开始
use majorsilence_reporting::RdlLibrary; fn main() -> Result<(), Box<dyn std::error::Error>> { // 每个进程只加载一次原生库。 let lib = RdlLibrary::load("/path/to/rdlnative/librdlnative.so")?; // 构建报表配置。 let mut rpt = lib.report("/path/to/report.rdl"); rpt.set_connection_string("Data Source=/path/to/northwindEF.db") .set_parameter("Country", "Germany"); // 导出到文件。 rpt.export("pdf", "/tmp/output.pdf")?; // 或者导出到内存——不写入任何临时文件。 let pdf_bytes: Vec<u8> = rpt.export_to_memory("pdf")?; println!("获得 {} 字节", pdf_bytes.len()); Ok(()) }
加载库
RdlLibrary::load 会完成所有必要的准备工作:设置 RDLNATIVE_LIB_DIR,以 RTLD_GLOBAL 方式预加载相关的 .so / .dylib 文件,以便 .NET 的 P/Invoke 解析器能找到它们,解析所有 FFI 符号,并调用 rdl_init()。
use majorsilence_reporting::RdlLibrary; // 在进程启动时调用一次。RdlLibrary 基于 Arc——克隆成本低,且满足 Send + Sync。 let lib = RdlLibrary::load("/path/to/librdlnative.so")?; // 跨线程克隆(例如放入 Axum 的 state 或 Actix 的 Data 中) let lib2 = lib.clone(); std::thread::spawn(move || { let mut rpt = lib2.report("/path/to/report.rdl"); rpt.export("pdf", "/tmp/thread.pdf").unwrap(); });
LD_LIBRARY_PATH(Linux)或 DYLD_LIBRARY_PATH(macOS)必须包含原生库所在目录。RdlLibrary::load 会处理 RDLNATIVE_LIB_DIR 及相关库的预加载,但无法为正在运行的进程追溯性地修改动态链接器的搜索路径。
核心 API
构建器方法返回 &mut Self 以支持链式调用。每次调用 export 或 export_to_memory 都会打开并关闭一个新的原生报表句柄,因此一个 Report 值可以在多次渲染中重复使用。
| 方法 | 说明 |
|---|---|
| RdlLibrary::load(path) | 加载原生库并初始化 FFI 符号表。返回 Result<RdlLibrary, Error>。每个进程调用一次。 |
| lib.report(rdl_path) | 创建一个绑定到 .rdl 文件的 Report 值。成本很低——此时尚未打开任何原生句柄。 |
| rpt.set_connection_string(cs) | 设置数据库连接字符串。返回 &mut Self。 |
| rpt.set_parameter(name, value) | 设置一个具名报表参数。返回 &mut Self。可根据需要多次调用。 |
| rpt.add_data(name, rows) | 提供一个内存数据集。rows 类型为 Vec<HashMap<String, String>>。返回 &mut Self。参见内存数据集。 |
| rpt.export(format, path) | 渲染并写入文件路径。返回 Result<(), Error>。 |
| rpt.export_to_memory(format) | 渲染并返回字节。返回 Result<Vec<u8>, Error>。不写入任何临时文件。 |
内存数据集
无需数据库即可将结构化数据直接传递给报表。每一行是一个 HashMap<String, String>,键为列名,值为以字符串编码的字段值。
use std::collections::HashMap; use majorsilence_reporting::RdlLibrary; let lib = RdlLibrary::load("/path/to/librdlnative.so")?; let mut rpt = lib.report("/path/to/report.rdl"); let rows: Vec<HashMap<String, String>> = vec![ [("Product", "PDF Library Pro"), ("Revenue", "1200.00"), ("Units", "3")] .iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(), [("Product", "Report Designer"), ("Revenue", "250.00"), ("Units", "1")] .iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(), ]; rpt.add_data("SalesData", rows); let pdf = rpt.export_to_memory("pdf")?;
.rdl 文件中定义的数据集匹配。所有字段值都必须是字符串——在插入映射前请先转换数字、日期和布尔值。可以通过多次调用 add_data 提供多个数据集。
导出到内存
export_to_memory 调用原生 FFI 函数 rdl_report_render_buffer,该函数填充一个原生分配的缓冲区,并返回指针和长度。Rust 封装层会将字节复制到 Vec<u8> 中,然后调用 rdl_free。整个过程都不会写入临时文件——非常适合用于 HTTP 响应体或流水线集成。
// Axum 处理函数示例 async fn report_handler(State(lib): State<RdlLibrary>) -> impl IntoResponse { let mut rpt = lib.report("/reports/sales.rdl"); rpt.set_connection_string("Data Source=/data/northwindEF.db"); match rpt.export_to_memory("pdf") { Ok(bytes) => ( StatusCode::OK, [("content-type", "application/pdf")], bytes, ).into_response(), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), } }
错误处理
所有可能失败的操作都返回 Result<T, majorsilence_reporting::Error>。Error 实现了 std::error::Error,并封装了原生函数 rdl_last_error() 返回的消息。
match rpt.export_to_memory("pdf") { Ok(bytes) => { /* 使用 bytes */ } Err(e) => eprintln!("渲染失败:{e}"), } // 或使用 ? 进行传播 let bytes = rpt.export_to_memory("pdf")?;
"pdf"。如需在编译期枚举所有有效格式,请使用公共常量 majorsilence_reporting::VALID_FORMATS: &[&str]。
导出格式
支持全部十种格式。与 Python/PHP/Ruby 的子进程模式不同,Rust 封装层始终使用原生 FFI 路径,因此所有格式均可用。
| 格式字符串 | 输出 |
|---|---|
pdf | PDF(同时也是任何无法识别字符串的回退格式) |
csv | 逗号分隔值 |
xlsx | Excel 工作簿 |
xlsx_table | Excel 工作簿(表格样式) |
xml | XML 数据 |
rtf | Rich Text Format |
tif | TIFF 图像(彩色) |
tifb | TIFF 图像(黑白) |
html | HTML |
mht | MHTML 网页存档 |