引言

作为一个长期从事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();//从数据库从查询id为1的数据
Ok(MyObj::<Model>::success(option))
}

至此完结
访问浏览器 http://localhost:8080/app 可得到数据库中id为1的数据