使用 Rust 开发 Web 服务

引言

作为一个长期从事 Java 的程序员,做 Web 服务开发时第一件事就是寻找成熟的 Web 和 ORM 框架。在 Rust 生态中,成熟的 Web 框架选择不少:

框架 特点 适用场景
Actix-web 性能极高(基于 actor 模型),生态丰富 高性能 API 服务、微服务
Rocket 开箱体验好,宏驱动 快速原型、全栈 Web
Warp 基于过滤器链,类型安全 需要强路由编译期检查的场景
Axum Tokio 官方出品,异步原生 新项目首选

数据库访问层同样有多种选择:

方案 特点
Diesel 成熟稳定、同步阻塞,Rust 社区最老牌的 ORM
Sea-ORM 后起之秀,支持异步,基于 sqlx 构建 —— 本案例选用
sqlx 轻量级,无 DSL,编译时 SQL 检查

为什么选 Sea-ORM?Diesel 不支持异步,而 Sea-ORM 天然适配 Rust 的异步生态。随着它不断成熟,越来越多新项目选择在其上构建。

Rust 以卓越的内存安全性和极致性能在编程社区中备受瞩目。结合成熟框架和 ORM,我们可以快速构建既安全又高效的 Web 服务。

一、Hello World

添加依赖

1
2
3
4
[dependencies]
actix-web = "4"
serde_json = "1.0.116"
serde = { version = "1.0.200", features = ["derive"] }

返回 JSON 接口

现代 Web 应用通常采用前后端分离架构,通过 JSON 进行数据交互。下面实现一个返回 JSON 的接口——绑定 IP 为 0.0.0.0,允许任意地址访问:

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
}

运行后访问 http://localhost:8080/a/world 即可看到 JSON 响应。

二、封装统一响应格式

为了保持接口响应的一致性、可读性和可扩展性,通常需要封装统一的 JSON 响应格式。例如:

1
2
3
4
5
{
"data": { "userId": 1, "username": "张三" },
"code": 200,
"msg": "查询成功"
}

其中 code 为状态码,msg 为提示消息,data 承载具体业务数据。Java 中通常这样写:

1
2
3
4
5
public class R<T> {
private int code;
private String msg;
private T data;
}

Rust 实现

由于直接用泛型 T 无法表达”空值”语义(即无法生成 {"data": null, ...} 格式),我们用 Option\<T\> 包裹泛型来解决这个问题:

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
26
#[derive(Serialize)]
struct MyObj<T> {
data: Option<T>,
code: i32,
msg: String,
}

impl<T: Serialize> 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: 500, 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("查询失败")))
}

三、引入 ORM(Sea-ORM)

3.1 安装依赖

1
sea-orm = { version = "0.12.15", features = ["sqlx-mysql", "runtime-actix-native-tls", "macros"], default-features = false }

3.2 安装 CLI 工具并生成实体代码

1
2
3
4
5
6
7
# 安装 sea-orm-cli 命令行工具
cargo install sea-orm-cli

# 根据现有数据库自动生成实体文件
sea-orm-cli generate entity \
-u mysql://账号:密码@数据库地址:端口/数据库名 \
-o src/entity

执行后会自动生成如下目录结构:

3.3 整合到 Actix-web

将数据库连接放入 Actix 的 App Data(应用状态)中,通过提取器注入到 Handler:

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
26
27
28
29
30
31
32
33
34
35
36
37
use actix_web::{get, web, App, HttpServer, middleware};
use sea_orm::{Database, DatabaseConnection};

// 应用状态:持有数据库连接池
#[derive(Debug, Clone)]
struct AppState {
conn: DatabaseConnection,
}

#[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
}

#[get("/app")]
async fn index(data: web::Data<AppState>) -> Result<impl Responder> {
let conn = &data.conn;
// 通过 DAO 查询 id=1 的数据
let option = PostsDao::find_by_id(1).one(conn).await.unwrap();
Ok(MyObj::<Model>::success(option))
}

访问 http://localhost:8080/app 即可从数据库获取 id=1 的数据并以统一格式返回:

四、小结

本文演示了从零搭建一个 Rust Web 服务的完整流程:

步骤 内容
Hello World Actix-web 基础路由 + JSON 响应
统一响应 Option\<T\> 包裹泛型,支持成功/失败两种格式
数据库集成 Sea-ORM 自动生成实体 + Actix App Data 状态注入

这只是入门引导。实际项目中还需要补充:

  • CRUD 完整操作(Create / Read / Update / Delete)
  • 中间件配置(认证、CORS、限流)
  • 错误处理统一(自定义 Error Response)
  • 配置管理.env 文件读取数据库连接串等)

Sea-ORM 的完整 CRUD 用法可以参考其官方文档