Rust Web 开发实战:从 Actix-web 框架到 RESTful API 完整构建
Rust Web 开发实战教程,深入对比 Actix-web、Rocket、Axum 三大框架,重点讲解 Actix-web 的项目初始化、路由定义、参数解析及中间件配置。内容涵盖 SQLx 类型安全数据库操作、JWT 认证中间件实现、任务管理系统 CRUD 实战,以及 Docker 容器化部署与 Prometheus+Grafana 监控方案。适合希望构建高性能 Rust Web 应用的开发者参考。

Rust Web 开发实战教程,深入对比 Actix-web、Rocket、Axum 三大框架,重点讲解 Actix-web 的项目初始化、路由定义、参数解析及中间件配置。内容涵盖 SQLx 类型安全数据库操作、JWT 认证中间件实现、任务管理系统 CRUD 实战,以及 Docker 容器化部署与 Prometheus+Grafana 监控方案。适合希望构建高性能 Rust Web 应用的开发者参考。

💡 三大核心难点:
⚠️ 三大高频错误点:
Rust 生态中有很多优秀的 Web 框架,其中最主流的是Actix-web、Rocket、Axum。
| 框架 | 架构 | 性能 | 文档 | 易用性 | 生态 | 适用场景 |
|---|---|---|---|---|---|---|
| Actix-web | Actor 模型(Actix actor system)+ 异步运行时(Tokio) | 🌟🌟🌟🌟🌟(最高) | 🌟🌟🌟🌟 | 🌟🌟🌟 | 🌟🌟🌟🌟 | 高并发、低延迟的 Web 应用(API 网关、实时通信服务器) |
| Rocket | 宏驱动(属性路由)+ 同步/异步运行时(Rocket runtime/Tokio) | 🌟🌟🌟🌟 | 🌟🌟🌟🌟🌟(最完善) | 🌟🌟🌟🌟🌟(最易用) | 🌟🌟🌟 | 快速开发原型、RESTful API |
| Axum | Tokio 的 Service 模型 + 异步运行时(Tokio)+ 宏驱动(属性/函数路由) | 🌟🌟🌟🌟 | 🌟🌟🌟 | 🌟🌟🌟 | 🌟🌟🌟 | 中等并发的 Web 应用、API |
① 安装 Cargo(Rust 的包管理工具)。 ② 创建新的 Rust 项目:
cargo new task-management-api
cd task-management-api
③ 在 Cargo.toml 中添加 Actix-web 的依赖:
[dependencies]
actix-web = "4"
actix-rt = "2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sqlx = { version = "0.7", features = ["postgres", "runtime-tokio-native-tls"] }
tokio = { version = "1", features = ["full"] }
juniper = "0.15" # GraphQL 支持(可选)
Actix-web 支持属性路由(使用 #[get]、#[post] 等宏)和函数路由(使用 web::get() 等方法)。
⌨️ 属性路由示例:
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
// 定义健康检查响应结构
#[derive(Serialize)]
struct HealthCheckResponse {
status: String,
version: String,
}
// 健康检查路由
#[get("/health")]
async fn health_check() -> impl Responder {
HttpResponse::Ok().json(HealthCheckResponse {
status: "ok".to_string(),
version: "1.0.0".to_string(),
})
}
// 定义创建任务的请求结构
#[derive(Deserialize)]
struct CreateTaskRequest {
title: String,
description: Option<String>,
}
// 定义创建任务的响应结构
#[derive(Serialize)]
struct CreateTaskResponse {
id: u32,
title: String,
description: Option<String>,
completed: bool,
}
// 创建任务路由
#[post("/tasks")]
async fn create_task(req: web::Json<CreateTaskRequest>) -> impl Responder {
HttpResponse::Ok().json(CreateTaskResponse {
id: 1,
title: req.title.clone(),
description: req.description.clone(),
completed: false,
})
}
// 获取任务列表路由(使用 Query 参数)
#[derive(Deserialize)]
struct GetTasksQuery {
page: Option<u32>,
per_page: Option<u32>,
}
#[get("/tasks")]
async fn get_tasks(query: web::Query<GetTasksQuery>) -> impl Responder {
let page = query.page.unwrap_or(1);
let per_page = query.per_page.unwrap_or(10);
HttpResponse::Ok().json(serde_json::json!({
"page": page,
"per_page": per_page,
"total": 100,
"data": []
}))
}
// 获取单个任务路由(使用 Path 参数)
#[get("/tasks/{id}")]
async fn get_task(path: web::Path<u32>) -> impl Responder {
let id = path.into_inner();
HttpResponse::Ok().json(serde_json::json!({
"id": id,
"title": "学习 Rust Web 开发",
"description": "深入学习 Actix-web 框架",
"completed": false
}))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(health_check)
.service(create_task)
.service(get_tasks)
.service(get_task)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Actix-web 提供了多种请求参数解析器:
?page=1&per_page=10)。/tasks/{id})。Actix-web 提供了多种响应构建方法:
Actix-web 的内置日志中间件可以记录 HTTP 请求的详细信息(如请求方法、URL、响应状态码、请求耗时)。
⌨️ 配置日志中间件:
use actix_web::{App, HttpServer, middleware::Logger};
use env_logger::Env;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// 初始化 env_logger
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
HttpServer::new(|| {
App::new()
.wrap(Logger::default()) // 使用默认格式的日志中间件
// .wrap(Logger::new("%a %r %s %b %Dms")) // 使用自定义格式的日志中间件
.service(health_check)
.service(create_task)
.service(get_tasks)
.service(get_task)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
CORS 中间件用于允许跨域请求(Cross-Origin Resource Sharing)。
⌨️ 配置 CORS 中间件:
use actix_web::{App, HttpServer, middleware::Logger};
use actix_cors::Cors;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(Logger::default())
.wrap(
Cors::default()
.allow_any_origin()
.allow_any_method()
.allow_any_header()
.max_age(3600), // 1 小时的预检请求缓存时间
)
.service(health_check)
.service(create_task)
.service(get_tasks)
.service(get_task)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
⚠️ 生产环境注意事项:在生产环境中,应该配置 allow_origin 为允许的域名,而不是 allow_any_origin(),以提高安全性。
自定义中间件需要实现 actix_web::dev::Service trait。
⌨️ 自定义 JWT 认证中间件:
use actix_web::{
dev::{ServiceRequest, ServiceResponse},
Error, HttpResponse,
};
use actix_web::middleware::Logger;
use actix_web::web;
use actix_web::http::header::AUTHORIZATION;
use jsonwebtoken::{decode, encode, Header, Validation};
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
// 定义 JWT 的 Claims 结构
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String, // 用户名或用户 ID
exp: usize, // 过期时间(Unix 时间戳)
iat: usize, // 签发时间(Unix 时间戳)
}
// 定义 JWT 的配置
struct JwtConfig {
secret_key: String,
expiration: Duration,
}
impl Default for JwtConfig {
fn default() -> Self {
JwtConfig {
secret_key: "your-secret-key".to_string(), // 生产环境应该使用环境变量
expiration: Duration::from_secs(3600), // 1 小时
}
}
}
impl JwtConfig {
// 生成 JWT 令牌
fn encode(&self, sub: &str) -> Result<String, jsonwebtoken::errors::Error> {
let now = Instant::now();
let claims = Claims {
sub: sub.to_string(),
exp: (now + self.expiration).as_secs() as usize,
iat: now.as_secs() as usize,
};
encode(&Header::default(), &claims, self.secret_key.as_bytes())
}
// 验证 JWT 令牌
fn decode(&self, token: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
decode(token, self.secret_key.as_bytes(), &Validation::default()).map(|data| data.claims)
}
}
// 自定义 JWT 认证中间件
struct JwtMiddleware {
config: JwtConfig,
}
impl JwtMiddleware {
fn new(config: JwtConfig) -> Self {
JwtMiddleware { config }
}
// 验证请求中的 JWT 令牌
async fn authenticate(&self, req: &ServiceRequest) -> Result<(), Error> {
let auth_header = req.headers().get(AUTHORIZATION);
if auth_header.is_none() {
return Err(actix_web::error::ErrorUnauthorized("缺少 Authorization 头"));
}
let auth_value = auth_header
.unwrap()
.to_str()
.map_err(|_| actix_web::error::ErrorUnauthorized("无效的 Authorization 头"))?;
if !auth_value.starts_with("Bearer ") {
return Err(actix_web::error::ErrorUnauthorized("无效的授权类型"));
}
let token = auth_value
.strip_prefix("Bearer ")
.ok_or(actix_web::error::ErrorUnauthorized("无效的 Bearer 令牌"))?;
match self.config.decode(token) {
Ok(claims) => {
// 将 Claims 存入请求的 Extensions 中,以便后续路由使用
req.extensions_mut().insert(claims);
Ok(())
}
Err(e) => {
match e.kind() {
jsonwebtoken::errors::ErrorKind::ExpiredSignature => {
Err(actix_web::error::ErrorUnauthorized("令牌已过期"))
}
_ => Err(actix_web::error::ErrorUnauthorized(format!("无效的令牌:{}", e))),
}
}
}
}
}
// 实现 Service trait
impl<S, B> actix_web::dev::Service<ServiceRequest> for JwtMiddleware
where
S: actix_web::dev::Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = std::pin::Pin<Box<dyn futures::Future<Output = Result<Self::Response, Self::Error>>>>;
actix_web::dev::forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let svc = self.service.clone();
let config = self.config.clone();
Box::pin(async move {
match config.authenticate(&req).await {
Ok(()) => svc.call(req).await,
Err(e) => Ok(ServiceResponse::new(
req.into_parts().0,
HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()})),
)),
}
})
}
}
// 中间件工厂
fn jwt_middleware(config: JwtConfig) -> impl actix_web::dev::Transform<
actix_web::dev::Service<ServiceRequest>,
Response = ServiceResponse,
Error = Error,
> {
actix_web::middleware::from_fn(move |req, srv| {
let config = config.clone();
async move {
match config.authenticate(&req).await {
Ok(()) => srv.call(req).await,
Err(e) => Ok(ServiceResponse::new(
req.into_parts().0,
HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()})),
)),
}
}
})
}
使用 SQLx 的连接池可以避免频繁地创建和销毁数据库连接,提高性能。
⌨️ 配置 SQLx 连接池:
use actix_web::{App, HttpServer};
use sqlx::postgres::PgPoolOptions;
use std::env;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// 从环境变量获取数据库连接 URL
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL 环境变量未设置");
// 创建 PostgreSQL 连接池
let pool = PgPoolOptions::new()
.max_connections(5) // 连接池大小为 5
.connect(&database_url)
.await
.expect("无法连接到数据库");
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(pool.clone())) // 将连接池存入 App 的 Data 中,以便所有路由使用
.wrap(jwt_middleware(JwtConfig::default()))
.service(health_check)
.service(create_task)
.service(get_tasks)
.service(get_task)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
使用 SQLx 的查询宏可以实现类型安全的 SQL 查询,避免 SQL 注入。
⌨️ 类型安全的 SQL 查询示例:
use actix_web::{web, HttpResponse, Responder};
use sqlx::PgPool;
use serde::{Deserialize, Serialize};
// 定义任务的数据库模型
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
struct Task {
id: i32,
title: String,
description: Option<String>,
completed: bool,
created_at: chrono::DateTime<chrono::Utc>,
updated_at: chrono::DateTime<chrono::Utc>,
}
// 创建任务的请求结构
#[derive(Debug, Deserialize)]
struct CreateTaskRequest {
title: String,
description: Option<String>,
}
// 创建任务的路由
#[post("/tasks")]
async fn create_task(
req: web::Json<CreateTaskRequest>,
pool: web::Data<PgPool>,
) -> impl Responder {
let task = sqlx::query_as!(
Task,
r#" INSERT INTO tasks (title, description) VALUES ($1, $2) RETURNING id, title, description, completed, created_at, updated_at "#,
req.title,
req.description
)
.fetch_one(pool.get_ref())
.await;
match task {
Ok(task) => HttpResponse::Ok().json(task),
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()})),
}
}
// 获取任务列表的查询参数
#[derive(Debug, Deserialize)]
struct GetTasksQuery {
page: Option<i32>,
per_page: Option<i32>,
}
// 获取任务列表的路由
#[get("/tasks")]
async fn get_tasks(
query: web::Query<GetTasksQuery>,
pool: web::Data<PgPool>,
) -> impl Responder {
let page = query.page.unwrap_or(1);
let per_page = query.per_page.unwrap_or(10);
let offset = (page - 1) * per_page;
let tasks = sqlx::query_as!(
Task,
r#" SELECT id, title, description, completed, created_at, updated_at FROM tasks ORDER BY created_at DESC LIMIT $1 OFFSET $2 "#,
per_page as i64,
offset as i64
)
.fetch_all(pool.get_ref())
.await;
let total = sqlx::query_scalar!(r#" SELECT COUNT(*) FROM tasks "#)
.fetch_one(pool.get_ref())
.await;
match (tasks, total) {
(Ok(tasks), Ok(total)) => HttpResponse::Ok().json(serde_json::json!({
"page": page,
"per_page": per_page,
"total": total,
"data": tasks
})),
(Err(e), _) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()})),
(_, Err(e)) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()})),
}
}
首先,我们需要设计任务管理系统的数据库表结构。我们将使用 PostgreSQL 数据库,创建以下两个表:
⌨️ SQL 表结构定义:
-- 创建用户表
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 创建任务表
CREATE TABLE IF NOT EXISTS tasks (
id SERIAL PRIMARY KEY,
title VARCHAR(100) NOT NULL,
description TEXT,
completed BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE
);
-- 创建索引
CREATE INDEX IF NOT EXISTS idx_tasks_user_id ON tasks(user_id);
CREATE INDEX IF NOT EXISTS idx_tasks_completed ON tasks(completed);
任务管理系统 API 的项目目录结构如下:
task-management-api/
├── Cargo.toml
├── Cargo.lock
├── .env
├── .env.example
├── src/
│ ├── main.rs
│ ├── models.rs
│ ├── routes/
│ │ ├── mod.rs
│ │ ├── auth.rs
│ │ └── tasks.rs
│ ├── middleware/
│ │ ├── mod.rs
│ │ └── jwt.rs
│ ├── utils/
│ │ ├── mod.rs
│ │ ├── hash.rs
│ │ └── jwt.rs
│ └── database.rs
└── migrations/
└── 0001_initial.sql
⌨️ main.rs(项目入口):
use actix_web::{App, HttpServer};
use dotenv::dotenv;
use std::env;
mod models;
mod routes;
mod middleware;
mod utils;
mod database;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL 环境变量未设置");
let pool = database::connect(&database_url).await;
HttpServer::new(move || {
App::new()
.app_data(pool.clone())
.configure(routes::config)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
⌨️ routes/auth.rs(用户认证路由):
use actix_web::{web, HttpResponse, Responder};
use argon2::password_hash::SaltString;
use argon2::Argon2;
use rand::thread_rng;
use rand::distributions::Alphanumeric;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use crate::models::User;
use crate::utils::jwt;
#[derive(Debug, Deserialize)]
struct RegisterRequest {
username: String,
password: String,
}
#[derive(Debug, Serialize)]
struct RegisterResponse {
id: i32,
username: String,
created_at: chrono::DateTime<chrono::Utc>,
}
#[post("/auth/register")]
async fn register(
req: web::Json<RegisterRequest>,
pool: web::Data<PgPool>,
) -> impl Responder {
let salt = SaltString::generate(&mut thread_rng());
let password_hash = Argon2::default()
.hash_password(req.password.as_bytes(), &salt)
.expect("密码哈希失败")
.to_string();
let user = sqlx::query_as!(
User,
r#" INSERT INTO users (username, password_hash) VALUES ($1, $2) RETURNING id, username, password_hash, created_at, updated_at "#,
req.username,
password_hash
)
.fetch_one(pool.get_ref())
.await;
match user {
Ok(user) => HttpResponse::Ok().json(RegisterResponse {
id: user.id,
username: user.username,
created_at: user.created_at,
}),
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()})),
}
}
#[derive(Debug, Deserialize)]
struct LoginRequest {
username: String,
password: String,
}
#[derive(Debug, Serialize)]
struct LoginResponse {
access_token: String,
token_type: String,
expires_in: u32,
}
#[post("/auth/login")]
async fn login(
req: web::Json<LoginRequest>,
pool: web::Data<PgPool>,
) -> impl Responder {
let user = sqlx::query_as!(
User,
r#" SELECT id, username, password_hash, created_at, updated_at FROM users WHERE username = $1 "#,
req.username
)
.fetch_one(pool.get_ref())
.await;
match user {
Ok(user) => {
let password_hash = argon2::PasswordHash::new(&user.password_hash).expect("密码哈希格式错误");
if argon2::verify_password(req.password.as_bytes(), &password_hash).is_ok() {
let access_token = jwt::encode(&user.username);
HttpResponse::Ok().json(LoginResponse {
access_token,
token_type: "Bearer".to_string(),
expires_in: 3600,
})
} else {
HttpResponse::Unauthorized().json(serde_json::json!({"error": "用户名或密码错误"}))
}
}
Err(e) => HttpResponse::Unauthorized().json(serde_json::json!({"error": "用户名或密码错误"})),
}
}
pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(register).service(login);
}
⌨️ routes/tasks.rs(任务管理路由):
use actix_web::{web, HttpResponse, Responder};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use crate::models::Task;
use crate::utils::jwt;
#[derive(Debug, Deserialize)]
struct CreateTaskRequest {
title: String,
description: Option<String>,
}
#[derive(Debug, Deserialize)]
struct UpdateTaskRequest {
title: Option<String>,
description: Option<String>,
completed: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct GetTasksQuery {
page: Option<i32>,
per_page: Option<i32>,
}
#[post("/tasks")]
async fn create_task(
req: web::Json<CreateTaskRequest>,
pool: web::Data<PgPool>,
claims: web::ReqData<jwt::Claims>,
) -> impl Responder {
let task = sqlx::query_as!(
Task,
r#" INSERT INTO tasks (title, description, user_id) VALUES ($1, $2, (SELECT id FROM users WHERE username = $3)) RETURNING id, title, description, completed, created_at, updated_at, user_id "#,
req.title,
req.description,
claims.sub
)
.fetch_one(pool.get_ref())
.await;
match task {
Ok(task) => HttpResponse::Ok().json(task),
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()})),
}
}
#[get("/tasks")]
async fn get_tasks(
query: web::Query<GetTasksQuery>,
pool: web::Data<PgPool>,
claims: web::ReqData<jwt::Claims>,
) -> impl Responder {
let page = query.page.unwrap_or(1);
let per_page = query.per_page.unwrap_or(10);
let offset = (page - 1) * per_page;
let tasks = sqlx::query_as!(
Task,
r#" SELECT id, title, description, completed, created_at, updated_at, user_id FROM tasks WHERE user_id = (SELECT id FROM users WHERE username = $1) ORDER BY created_at DESC LIMIT $2 OFFSET $3 "#,
claims.sub,
per_page as i64,
offset as i64
)
.fetch_all(pool.get_ref())
.await;
let total = sqlx::query_scalar!(
r#" SELECT COUNT(*) FROM tasks WHERE user_id = (SELECT id FROM users WHERE username = $1) "#,
claims.sub
)
.fetch_one(pool.get_ref())
.await;
match (tasks, total) {
(Ok(tasks), Ok(total)) => HttpResponse::Ok().json(serde_json::json!({
"page": page,
"per_page": per_page,
"total": total,
"data": tasks
})),
(Err(e), _) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()})),
(_, Err(e)) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e.to_string()})),
}
}
#[get("/tasks/{id}")]
async fn get_task(
path: web::Path<i32>,
pool: web::Data<PgPool>,
claims: web::ReqData<jwt::Claims>,
) -> impl Responder {
let id = path.into_inner();
let task = sqlx::query_as!(
Task,
r#" SELECT id, title, description, completed, created_at, updated_at, user_id FROM tasks WHERE id = $1 AND user_id = (SELECT id FROM users WHERE username = $2) "#,
id,
claims.sub
)
.fetch_one(pool.get_ref())
.await;
match task {
Ok(task) => HttpResponse::Ok().json(task),
Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": "任务不存在"})),
}
}
#[put("/tasks/{id}")]
async fn update_task(
path: web::Path<i32>,
req: web::Json<UpdateTaskRequest>,
pool: web::Data<PgPool>,
claims: web::ReqData<jwt::Claims>,
) -> impl Responder {
let id = path.into_inner();
let task = sqlx::query_as!(
Task,
r#" UPDATE tasks SET title = COALESCE($1, title), description = COALESCE($2, description), completed = COALESCE($3, completed), updated_at = NOW() WHERE id = $4 AND user_id = (SELECT id FROM users WHERE username = $5) RETURNING id, title, description, completed, created_at, updated_at, user_id "#,
req.title,
req.description,
req.completed,
id,
claims.sub
)
.fetch_one(pool.get_ref())
.await;
match task {
Ok(task) => HttpResponse::Ok().json(task),
Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": "任务不存在"})),
}
}
#[delete("/tasks/{id}")]
async fn delete_task(
path: web::Path<i32>,
pool: web::Data<PgPool>,
claims: web::ReqData<jwt::Claims>,
) -> impl Responder {
let id = path.into_inner();
let task = sqlx::query_as!(
Task,
r#" DELETE FROM tasks WHERE id = $1 AND user_id = (SELECT id FROM users WHERE username = $2) RETURNING id, title, description, completed, created_at, updated_at, user_id "#,
id,
claims.sub
)
.fetch_one(pool.get_ref())
.await;
match task {
Ok(task) => HttpResponse::Ok().json(task),
Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": "任务不存在"})),
}
}
pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(create_task)
.service(get_tasks)
.service(get_task)
.service(update_task)
.service(delete_task);
}
① 编写 Dockerfile:
# 使用官方的 Rust 镜像作为基础镜像
FROM rust:1.75-slim-bullseye AS builder
# 安装构建依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
libssl-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /app
# 复制 Cargo.toml 和 Cargo.lock
COPY Cargo.toml Cargo.lock ./
# 创建一个虚拟库来缓存依赖
RUN mkdir -p src && echo 'fn main() {}' > src/main.rs
RUN cargo build --release
# 复制源代码
COPY . .
# 重新构建项目
RUN cargo build --release
# 使用 Debian Bullseye 作为基础镜像
FROM debian:bullseye-slim
# 安装运行依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
libssl1.1 \
&& rm -rf /var/lib/apt/lists/*
# 复制二进制文件
COPY --from=builder /app/target/release/task-management-api /usr/local/bin/
# 暴露端口
EXPOSE 8080
# 入口点
CMD ["task-management-api"]
② 编写 docker-compose.yml:
version: "3.8"
services:
task-management-api:
build: .
ports:
- "8080:8080"
environment:
DATABASE_URL: postgres://postgres:password@postgres:5432/task_management
depends_on:
- postgres
restart: always
postgres:
image: postgres:15-alpine
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: task_management
volumes:
- postgres_data:/var/lib/postgresql/data
- ./migrations:/docker-entrypoint-initdb.d
restart: always
volumes:
postgres_data:
③ 启动容器:
docker-compose up -d
① 编写 prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: "task-management-api"
static_configs:
- targets: ["localhost:8080"]
② 编写 docker-compose.yml(包含 Prometheus 和 Grafana):
version: "3.8"
services:
task-management-api:
build: .
ports:
- "8080:8080"
environment:
DATABASE_URL: postgres://postgres:password@postgres:5432/task_management
depends_on:
- postgres
restart: always
postgres:
image: postgres:15-alpine
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: task_management
volumes:
- postgres_data:/var/lib/postgresql/data
- ./migrations:/docker-entrypoint-initdb.d
restart: always
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
restart: always
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
restart: always
volumes:
postgres_data:
grafana_data:
③ 启动容器:
docker-compose up -d
问题现象:前端无法发送请求,浏览器控制台提示'Access-Control-Allow-Origin' header 缺失。
解决方案:
allow_origin 配置为允许的域名。allow_any_origin()。问题现象:高并发下服务器响应时间变慢,甚至返回 500 Internal Server Error。
解决方案:
问题现象:用户频繁重新登录。
解决方案:
✅ 框架选型对比:深入了解了 Actix-web、Rocket、Axum 三大主流 Rust Web 框架的架构差异、性能表现与适用场景。 ✅ Actix-web 核心用法:熟练掌握了项目初始化、路由定义、请求参数解析、响应构建。 ✅ 中间件开发与使用:学习了内置中间件(日志、CORS)的配置,以及自定义 JWT 认证中间件的实现方法。 ✅ 数据库操作与集成:使用 SQLx 异步连接 PostgreSQL,实现了类型安全的 SQL 查询、事务管理。 ✅ 完整 API 构建实战:结合真实场景编写了任务管理系统 API,包含用户认证(JWT)、任务 CRUD、分页查询等功能。 ✅ 部署与监控:掌握了 Docker 容器化部署,以及 Prometheus+Grafana 监控的配置。
下一篇文章,我们将深入学习 Rust 的微服务架构,包括使用 gRPC 通信、使用 Consul 进行服务发现、使用 Docker Swarm 或 Kubernetes 进行容器编排,通过这些知识我们将能够构建可伸缩、高可用的微服务系统。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online