引言
作为一个长期从事java的程序员,进行web服务开发第一件事就是寻找成熟的web及orm框架。在rust生态中成熟的web框架有很多,列如Actix-web、Rocket、Warp、Axum 等。
关于数据库访问有diesel这个非常成熟的ORM,还有sea-orm这样的后起之秀。diesel 不支持异步,而 sea-orm 支持异步,所以,有理由相信,随着 sea-orm 的不断成熟,会有越来越多的应用在 sea-orm 上构建。
如果你觉得 ORM 太过笨重,繁文缛节太多,但又不想直接使用某个数据库的驱动来访问数据库,那么你还可以用 sqlx。
sqlx 提供了对多种数据库(Postgres、MySQL、SQLite、MSSQL)的异步访问支持,并且不使用 DSL 就可以对 SQL query 做编译时检查,非常轻便;它可以从数据库中直接查询出来一行数据,也可以通过派生宏自动把行数据转换成对应的结构。
Rust以其卓越的内存安全性和高效的性能在编程社区中备受瞩目。结合ORM和成熟Web框架,我们可以快速开发出既安全又高效的Web服务。
本案例将使用Actix-web和sea-orm进行实现。
hello word
依赖包:
1 2 3 4
| [dependencies] actix-web = "4" serde_json = "1.0.116" serde = { version = "1.0.200", features = ["derive"] }
|
现代web应用通常使用前后端分离开发模式,前后端交互使用json进行数据传递,根据官方文档可使用以下代码快速实现一个返回json数据的接口。
此处绑定IP为0.0.0.0,任何ip都可以访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| use actix_web::{get, web, Responder, Result}; use serde::Serialize;
#[derive(Serialize)] struct MyObj { name: String, }
#[get("/a/{name}")] async fn index(name: web::Path<String>) -> Result<impl Responder> { let obj = MyObj { name: name.to_string(), }; Ok(web::Json(obj)) }
#[actix_web::main] async fn main() -> std::io::Result<()> { use actix_web::{App, HttpServer};
HttpServer::new(|| App::new().service(index)) .bind(("0.0.0.0", 8080))? .run() .await }
|
封装统一响应格式
为了一致性、可读性、可扩展性及错误处理,通常会封装json的统一响应格式,列如返回 {“data”: {”userId”:1,”username”:张三},”code”: 200,”msg”: “查询成功”}。
其中code为状态码,msg为提示消息,data承载具体数据,以下是java代码大致实现。
1 2 3 4 5 6 7 8
| public class R<T> { private int code; private String msg; private T data; }
|
其中data使用了泛型T,因为各个接口返回的数据结构及类型会不一样。转换为rust实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #[derive(Serialize)] struct MyObj<T> { data: Option<T>, code: i32, msg:String } impl <T> MyObj<T>{ fn success(data: Option<T>) -> web::Json<MyObj<T>> { web::Json(MyObj {data,code: 200,msg: String::from("操作成功"),}) } fn failed(msg: String) -> web::Json<MyObj<String>> { web::Json(MyObj { data:None, code: 200, msg}) } }
#[get("/a/{name}")] async fn index(name: web::Path<String>) -> Result<impl Responder> { Ok(MyObj::<String>::success(None)) }
#[get("/b/{name}")] async fn index1(name: web::Path<String>) -> Result<impl Responder> { Ok(MyObj::<String>::failed(String::from("查询失败"))) }
|
由于直接使用泛型T无法设置空值,即无法得到{“data”: null,”code”: 200,”msg”: “查询失败”} 这样的数据格式,所以使用Option包裹泛型T,至此接口开发准备结束。
引入ORM
引入依赖
1
| sea-orm = {version = "0.12.15",features = ["sqlx-mysql","runtime-actix-native-tls","macros"],default-features = false}
|
运行命令安装cli
1
| cargo install sea-orm-cli
|
根据数据库生成实体文件代码
1
| sea-orm-cli generate entity -u mysql://账号:密码@数据库地址:端口/数据库名 -o src/entity
|
执行以后会得到如下目录结构

在main方法中修改启动代码配置数据库链接,并将数据库连接放在actix的提取器中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #[actix_web::main] async fn main() -> std::io::Result<()> { let db: DatabaseConnection = Database::connect("mysql://root:xxx@x.x.x.x:3306/xxx").await.unwrap(); let state = AppState { conn:db }; HttpServer::new(move || { App::new() .service(index) .app_data(web::Data::new(state.clone())) .wrap(middleware::Logger::default()) .configure(init) }).bind(("0.0.0.0", 8080))? .run() .await } #[derive(Debug, Clone)] struct AppState { conn: DatabaseConnection, }
#[get("/app")] async fn index(data: web::Data<AppState>) -> Result<impl Responder>{ let conn = &data.conn; let option = PostsDao::find_by_id(1).one(conn).await.unwrap(); Ok(MyObj::<Model>::success(option)) }
|
至此完结
访问浏览器 http://localhost:8080/app 可得到数据库中id为1的数据
