前言
💡 痛点: Go/Java 服务内存占用高?高并发下延迟不稳定?想用内存安全语言替代 C/C++ 但又不想放弃性能?
🎯 解决方案: 用 Rust + Axum 构建生产级 Web 服务,借助零成本抽象实现接近 C 的性能,同时拥有编译时内存安全保证。2026 年 Rust 后端生态已完全成熟。
#mermaid-svg-z5LbgpFD5jqiJxFm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-z5LbgpFD5jqiJxFm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-z5LbgpFD5jqiJxFm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-z5LbgpFD5jqiJxFm .error-icon{fill:#552222;}#mermaid-svg-z5LbgpFD5jqiJxFm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-z5LbgpFD5jqiJxFm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-z5LbgpFD5jqiJxFm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-z5LbgpFD5jqiJxFm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-z5LbgpFD5jqiJxFm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-z5LbgpFD5jqiJxFm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-z5LbgpFD5jqiJxFm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-z5LbgpFD5jqiJxFm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-z5LbgpFD5jqiJxFm .marker.cross{stroke:#333333;}#mermaid-svg-z5LbgpFD5jqiJxFm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-z5LbgpFD5jqiJxFm p{margin:0;}#mermaid-svg-z5LbgpFD5jqiJxFm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-z5LbgpFD5jqiJxFm .cluster-label text{fill:#333;}#mermaid-svg-z5LbgpFD5jqiJxFm .cluster-label span{color:#333;}#mermaid-svg-z5LbgpFD5jqiJxFm .cluster-label span p{background-color:transparent;}#mermaid-svg-z5LbgpFD5jqiJxFm .label text,#mermaid-svg-z5LbgpFD5jqiJxFm span{fill:#333;color:#333;}#mermaid-svg-z5LbgpFD5jqiJxFm .node rect,#mermaid-svg-z5LbgpFD5jqiJxFm .node circle,#mermaid-svg-z5LbgpFD5jqiJxFm .node ellipse,#mermaid-svg-z5LbgpFD5jqiJxFm .node polygon,#mermaid-svg-z5LbgpFD5jqiJxFm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-z5LbgpFD5jqiJxFm .rough-node .label text,#mermaid-svg-z5LbgpFD5jqiJxFm .node .label text,#mermaid-svg-z5LbgpFD5jqiJxFm .image-shape .label,#mermaid-svg-z5LbgpFD5jqiJxFm .icon-shape .label{text-anchor:middle;}#mermaid-svg-z5LbgpFD5jqiJxFm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-z5LbgpFD5jqiJxFm .rough-node .label,#mermaid-svg-z5LbgpFD5jqiJxFm .node .label,#mermaid-svg-z5LbgpFD5jqiJxFm .image-shape .label,#mermaid-svg-z5LbgpFD5jqiJxFm .icon-shape .label{text-align:center;}#mermaid-svg-z5LbgpFD5jqiJxFm .node.clickable{cursor:pointer;}#mermaid-svg-z5LbgpFD5jqiJxFm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-z5LbgpFD5jqiJxFm .arrowheadPath{fill:#333333;}#mermaid-svg-z5LbgpFD5jqiJxFm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-z5LbgpFD5jqiJxFm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-z5LbgpFD5jqiJxFm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z5LbgpFD5jqiJxFm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-z5LbgpFD5jqiJxFm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z5LbgpFD5jqiJxFm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-z5LbgpFD5jqiJxFm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-z5LbgpFD5jqiJxFm .cluster text{fill:#333;}#mermaid-svg-z5LbgpFD5jqiJxFm .cluster span{color:#333;}#mermaid-svg-z5LbgpFD5jqiJxFm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-z5LbgpFD5jqiJxFm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-z5LbgpFD5jqiJxFm rect.text{fill:none;stroke-width:0;}#mermaid-svg-z5LbgpFD5jqiJxFm .icon-shape,#mermaid-svg-z5LbgpFD5jqiJxFm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z5LbgpFD5jqiJxFm .icon-shape p,#mermaid-svg-z5LbgpFD5jqiJxFm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-z5LbgpFD5jqiJxFm .icon-shape .label rect,#mermaid-svg-z5LbgpFD5jqiJxFm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z5LbgpFD5jqiJxFm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-z5LbgpFD5jqiJxFm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-z5LbgpFD5jqiJxFm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数据层
业务层
中间件
入口
Axum
HTTP 框架
认证中间件
限流中间件
日志追踪
CORS
Handler
路由处理
Service
业务逻辑
Repository
数据访问
SQLx
编译时 SQL 检查
Redis
缓存/会话
2026 Rust 后端技术栈:
| 组件 | 推荐方案 | 替代 |
|---|---|---|
| HTTP 框架 | Axum 0.8 | Actix-web / Warp |
| 异步运行时 | Tokio | async-std |
| ORM | SQLx(编译时检查) | SeaORM / Diesel |
| 序列化 | serde + serde_json | |
| 认证 | axum-extra + JWT | |
| Redis | redis-rs | |
| 日志 | tracing | log + env_logger |
| 配置 | config-rs | dotenvy |
| 部署 | Docker + scratch | AWS Lambda / Shuttle |
一、项目脚手架
1.1 Cargo 配置
toml
# Cargo.toml
[package]
name = "rust-backend"
version = "0.1.0"
edition = "2024"
[dependencies]
# Web 框架
axum = { version = "0.8", features = ["macros", "multipart"] }
axum-extra = { version = "0.10", features = ["typed-header", "cookie"] }
tower = { version = "0.5", features = ["full"] }
tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip", "limit"] }
# 异步
tokio = { version = "1", features = ["full"] }
futures = "0.3"
# 数据库
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "uuid", "chrono", "json", "migrate"] }
# 序列化
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# 认证
jsonwebtoken = "9"
argon2 = "0.5"
# Redis
redis = { version = "0.27", features = ["tokio-comp", "connection-manager"] }
# 工具
uuid = { version = "1", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
validator = { version = "0.19", features = ["derive"] }
thiserror = "2"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
config = "0.14"
dotenvy = "0.15"
[dev-dependencies]
reqwest = { version = "0.12", features = ["json"] }
fake = { version = "3", features = ["derive", "chrono", "uuid"] }
1.2 项目结构
rust-backend/
├── Cargo.toml
├── .env
├── migrations/
│ └── 001_initial.sql
├── src/
│ ├── main.rs
│ ├── config.rs
│ ├── error.rs
│ ├── middleware/
│ │ ├── mod.rs
│ │ ├── auth.rs
│ │ └── rate_limit.rs
│ ├── model/
│ │ ├── mod.rs
│ │ ├── user.rs
│ │ └── post.rs
│ ├── repository/
│ │ ├── mod.rs
│ │ ├── user_repo.rs
│ │ └── post_repo.rs
│ ├── service/
│ │ ├── mod.rs
│ │ ├── auth_service.rs
│ │ └── post_service.rs
│ ├── handler/
│ │ ├── mod.rs
│ │ ├── auth_handler.rs
│ │ └── post_handler.rs
│ ├── router.rs
│ ├── db.rs
│ └── AppState.rs
├── tests/
│ └── api_tests.rs
└── Dockerfile
1.3 配置管理
rust
// src/config.rs
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
pub struct AppConfig {
pub server: ServerConfig,
pub database: DatabaseConfig,
pub redis: RedisConfig,
pub jwt: JwtConfig,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
}
#[derive(Debug, Deserialize, Clone)]
pub struct DatabaseConfig {
pub url: String,
pub max_connections: u32,
pub min_connections: u32,
}
#[derive(Debug, Deserialize, Clone)]
pub struct RedisConfig {
pub url: String,
pub max_connections: u32,
}
#[derive(Debug, Deserialize, Clone)]
pub struct JwtConfig {
pub secret: String,
pub expiration_hours: i64,
}
impl AppConfig {
pub fn load() -> Result<Self, config::ConfigError> {
dotenvy::dotenv().ok();
let config = config::Config::builder()
.add_source(config::File::with_name("config/default"))
.add_source(config::Environment::with_prefix("APP").separator("__"))
.build()?;
config.try_deserialize()
}
}
// config/default.toml
/*
[server]
host = "0.0.0.0"
port = 8080
[database]
url = "postgres://user:pass@localhost:5432/rustdb"
max_connections = 20
min_connections = 5
[redis]
url = "redis://127.0.0.1:6379"
max_connections = 10
[jwt]
secret = "your-secret-key"
expiration_hours = 24
*/
二、错误处理
rust
// src/error.rs
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AppError {
#[error("未找到资源: {0}")]
NotFound(String),
#[error("认证失败: {0}")]
Unauthorized(String),
#[error("权限不足")]
Forbidden,
#[error("参数验证失败: {0}")]
ValidationError(String),
#[error("数据库错误: {0}")]
DatabaseError(#[from] sqlx::Error),
#[error("Redis 错误: {0}")]
RedisError(#[from] redis::RedisError),
#[error("JWT 错误: {0}")]
JwtError(#[from] jsonwebtoken::errors::Error),
#[error("请求过大")]
PayloadTooLarge,
#[error("请求过于频繁")]
TooManyRequests,
#[error("内部错误")]
InternalError,
}
// 🔥 自动映射 HTTP 状态码
impl AppError {
pub fn status_code(&self) -> StatusCode {
match self {
Self::NotFound(_) => StatusCode::NOT_FOUND,
Self::Unauthorized(_) => StatusCode::UNAUTHORIZED,
Self::Forbidden => StatusCode::FORBIDDEN,
Self::ValidationError(_) => StatusCode::BAD_REQUEST,
Self::DatabaseError(_) | Self::RedisError(_) | Self::InternalError => {
StatusCode::INTERNAL_SERVER_ERROR
}
Self::JwtError(_) => StatusCode::UNAUTHORIZED,
Self::PayloadTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
}
}
}
// 实现 IntoResponse ------ 所有 handler 返回 Result<T, AppError> 自动处理
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let status = self.status_code();
let body = Json(json!({
"error": {
"code": status.as_u16(),
"message": self.to_string(),
}
}));
(status, body).into_response()
}
}
// 为 Handler Result 添加类型别名
pub type AppResult<T> = Result<T, AppError>;
三、数据模型
3.1 用户模型
rust
// src/model/user.rs
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use sqlx::FromRow;
use validator::Validate;
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct User {
pub id: Uuid,
pub email: String,
#[serde(skip_serializing)]
pub password_hash: String,
pub name: String,
pub avatar: Option<String>,
pub role: UserRole,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type, PartialEq)]
#[sqlx(type_name = "user_role", rename_all = "lowercase")]
pub enum UserRole {
User,
Admin,
}
// 创建请求(带验证)
#[derive(Debug, Deserialize, Validate)]
pub struct CreateUserRequest {
#[validate(email(message = "邮箱格式不正确"))]
pub email: String,
#[validate(length(min = 8, message = "密码至少 8 位"))]
pub password: String,
#[validate(length(min = 2, max = 50, message = "名字长度 2-50"))]
pub name: String,
}
// 登录请求
#[derive(Debug, Deserialize, Validate)]
pub struct LoginRequest {
#[validate(email(message = "邮箱格式不正确"))]
pub email: String,
#[validate(length(min = 1, message = "密码不能为空"))]
pub password: String,
}
// JWT Claims
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: Uuid, // user id
pub email: String,
pub role: UserRole,
pub exp: usize, // 过期时间
pub iat: usize, // 签发时间
}
// 用户响应(隐藏敏感字段)
#[derive(Debug, Serialize)]
pub struct UserResponse {
pub id: Uuid,
pub email: String,
pub name: String,
pub avatar: Option<String>,
pub role: UserRole,
pub created_at: DateTime<Utc>,
}
impl From<User> for UserResponse {
fn from(u: User) -> Self {
Self {
id: u.id,
email: u.email,
name: u.name,
avatar: u.avatar,
role: u.role,
created_at: u.created_at,
}
}
}
3.2 文章模型
rust
// src/model/post.rs
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use sqlx::FromRow;
use validator::Validate;
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct Post {
pub id: Uuid,
pub title: String,
pub slug: String,
pub content: String,
pub excerpt: Option<String>,
pub cover_image: Option<String>,
pub status: PostStatus,
pub author_id: Uuid,
pub category_id: Option<Uuid>,
pub tags: Vec<String>,
pub view_count: i64,
pub like_count: i64,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type, PartialEq)]
#[sqlx(type_name = "post_status", rename_all = "lowercase")]
pub enum PostStatus {
Draft,
Published,
Archived,
}
// 创建请求
#[derive(Debug, Deserialize, Validate)]
pub struct CreatePostRequest {
#[validate(length(min = 5, max = 200, message = "标题 5-200 字"))]
pub title: String,
#[validate(regex(path = "RE_SLUG", message = "Slug 格式不正确"))]
pub slug: String,
#[validate(length(min = 10, message = "内容至少 10 字"))]
pub content: String,
pub excerpt: Option<String>,
pub cover_image: Option<String>,
pub category_id: Option<Uuid>,
#[validate(length(max = 10, message = "最多 10 个标签"))]
pub tags: Option<Vec<String>>,
}
// 列表查询参数
#[derive(Debug, Deserialize)]
pub struct ListPostsQuery {
pub page: Option<u32>,
pub limit: Option<u32>,
pub sort_by: Option<String>,
pub sort_order: Option<String>,
pub status: Option<PostStatus>,
pub category_id: Option<Uuid>,
pub search: Option<String>,
}
impl ListPostsQuery {
pub fn page(&self) -> u32 { self.page.unwrap_or(1).max(1) }
pub fn limit(&self) -> u32 { self.limit.unwrap_or(20).min(100).max(1) }
}
// 文章列表响应
#[derive(Debug, Serialize)]
pub struct PostListResponse {
pub items: Vec<PostWithAuthor>,
pub pagination: Pagination,
}
#[derive(Debug, Serialize, FromRow)]
pub struct PostWithAuthor {
pub id: Uuid,
pub title: String,
pub slug: String,
pub excerpt: Option<String>,
pub cover_image: Option<String>,
pub status: PostStatus,
pub tags: Vec<String>,
pub view_count: i64,
pub like_count: i64,
pub created_at: DateTime<Utc>,
// 关联
pub author_name: String,
pub author_avatar: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct Pagination {
pub page: u32,
pub limit: u32,
pub total: i64,
pub total_pages: u32,
}
四、Repository(数据访问层)
4.1 用户 Repository
rust
// src/repository/user_repo.rs
use sqlx::PgPool;
use uuid::Uuid;
use crate::model::user::{User, UserRole, CreateUserRequest};
use crate::error::AppResult;
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier, password_hash::SaltString};
use rand::rngs::OsRng;
pub struct UserRepository;
impl UserRepository {
// 创建用户
pub async fn create(
pool: &PgPool,
req: CreateUserRequest,
) -> AppResult<User> {
// 检查邮箱是否已存在
let exists = sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM users WHERE email = $1)",
req.email
)
.fetch_one(pool)
.await?;
if exists {
return Err(crate::error::AppError::ValidationError("邮箱已注册".into()));
}
// Argon2 密码哈希
let salt = SaltString::generate(&mut OsRng);
let hash = Argon2::default()
.hash_password(req.password.as_bytes(), &salt)?
.to_string();
let user = sqlx::query_as!(
User,
r#"INSERT INTO users (email, password_hash, name, role)
VALUES ($1, $2, $3, $4)
RETURNING id, email, password_hash, name, avatar, role as "role: UserRole",
created_at, updated_at"#,
req.email,
hash,
req.name,
UserRole::User as _,
)
.fetch_one(pool)
.await?;
Ok(user)
}
// 按 ID 查找
pub async fn find_by_id(pool: &PgPool, id: Uuid) -> AppResult<Option<User>> {
let user = sqlx::query_as!(
User,
r#"SELECT id, email, password_hash, name, avatar, role as "role: UserRole",
created_at, updated_at
FROM users WHERE id = $1"#,
id
)
.fetch_optional(pool)
.await?;
Ok(user)
}
// 按邮箱查找
pub async fn find_by_email(pool: &PgPool, email: &str) -> AppResult<Option<User>> {
let user = sqlx::query_as!(
User,
r#"SELECT id, email, password_hash, name, avatar, role as "role: UserRole",
created_at, updated_at
FROM users WHERE email = $1"#,
email
)
.fetch_optional(pool)
.await?;
Ok(user)
}
// 验证密码
pub async fn verify_password(
pool: &PgPool,
email: &str,
password: &str,
) -> AppResult<User> {
let user = Self::find_by_email(pool, email)
.await?
.ok_or_else(|| AppError::Unauthorized("邮箱或密码错误".into()))?;
let hash = PasswordHash::new(&user.password_hash)
.map_err(|_| AppError::InternalError)?;
Argon2::default()
.verify_password(password.as_bytes(), &hash)
.map_err(|_| AppError::Unauthorized("邮箱或密码错误".into()))?;
Ok(user)
}
}
4.2 文章 Repository
rust
// src/repository/post_repo.rs
use sqlx::PgPool;
use uuid::Uuid;
use crate::model::post::*;
use crate::error::AppResult;
pub struct PostRepository;
impl PostRepository {
// 创建文章
pub async fn create(pool: &PgPool, author_id: Uuid, req: CreatePostRequest) -> AppResult<Post> {
let post = sqlx::query_as!(
Post,
r#"INSERT INTO posts (title, slug, content, excerpt, cover_image, status,
author_id, category_id, tags)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id, title, slug, content, excerpt, cover_image,
status as "status: PostStatus", author_id, category_id,
tags, view_count, like_count, created_at, updated_at"#,
req.title, req.slug, req.content, req.excerpt, req.cover_image,
PostStatus::Draft as _, author_id, req.category_id, req.tags,
)
.fetch_one(pool)
.await?;
Ok(post)
}
// 带分页的列表查询
pub async fn list(pool: &PgPool, query: ListPostsQuery) -> AppResult<PostListResponse> {
let page = query.page();
let limit = query.limit();
let offset = (page - 1) * limit;
let sort_col = query.sort_by.as_deref().unwrap_or("created_at");
let sort_order = query.sort_order.as_deref().unwrap_or("desc");
// 安全的排序列(白名单)
let valid_sort = match sort_col {
"title" | "view_count" | "like_count" | "created_at" => sort_col,
_ => "created_at",
};
let order = if sort_order == "asc" { "ASC" } else { "DESC" };
let sql = format!(
r#"SELECT p.id, p.title, p.slug, p.excerpt, p.cover_image,
p.status as "status: PostStatus", p.tags, p.view_count, p.like_count,
p.created_at, u.name as author_name, u.avatar as author_avatar
FROM posts p
JOIN users u ON p.author_id = u.id
WHERE p.status = 'published'
ORDER BY p.{valid_sort} {order}
LIMIT $1 OFFSET $2"#
);
let items = sqlx::query_as::<_, PostWithAuthor>(&sql)
.bind(limit as i64)
.bind(offset as i64)
.fetch_all(pool)
.await?;
let total: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM posts WHERE status = 'published'"
)
.fetch_one(pool)
.await?;
Ok(PostListResponse {
items,
pagination: Pagination {
page,
limit,
total,
total_pages: ((total as u32 + limit - 1) / limit),
},
})
}
// 获取单篇
pub async fn find_by_slug(pool: &PgPool, slug: &str) -> AppResult<Option<Post>> {
let post = sqlx::query_as!(
Post,
r#"SELECT id, title, slug, content, excerpt, cover_image,
status as "status: PostStatus", author_id, category_id,
tags, view_count, like_count, created_at, updated_at
FROM posts WHERE slug = $1"#,
slug
)
.fetch_optional(pool)
.await?;
Ok(post)
}
// 增加浏览量
pub async fn increment_views(pool: &PgPool, id: Uuid) -> AppResult<()> {
sqlx::query("UPDATE posts SET view_count = view_count + 1 WHERE id = $1")
.bind(id)
.execute(pool)
.await?;
Ok(())
}
// 切换点赞
pub async fn toggle_like(pool: &PgPool, user_id: Uuid, post_id: Uuid) -> AppResult<bool> {
let liked = sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM likes WHERE user_id = $1 AND post_id = $2)",
user_id, post_id
)
.fetch_one(pool)
.await?;
if liked {
sqlx::query("DELETE FROM likes WHERE user_id = $1 AND post_id = $2")
.bind(user_id).bind(post_id).execute(pool).await?;
sqlx::query("UPDATE posts SET like_count = like_count - 1 WHERE id = $1")
.bind(post_id).execute(pool).await?;
Ok(false)
} else {
sqlx::query("INSERT INTO likes (user_id, post_id) VALUES ($1, $2)")
.bind(user_id).bind(post_id).execute(pool).await?;
sqlx::query("UPDATE posts SET like_count = like_count + 1 WHERE id = $1")
.bind(post_id).execute(pool).await?;
Ok(true)
}
}
}
五、Service(业务逻辑层)
5.1 认证服务
rust
// src/service/auth_service.rs
use jsonwebtoken::{encode, EncodingKey, Header, DecodingKey, Validation};
use chrono::Utc;
use uuid::Uuid;
use crate::model::user::*;
use crate::error::AppResult;
use crate::config::JwtConfig;
use crate::repository::user_repo::UserRepository;
pub struct AuthService;
impl AuthService {
// 注册
pub async fn register(
pool: &sqlx::PgPool,
req: CreateUserRequest,
) -> AppResult<(UserResponse, String)> {
// 验证输入
req.validate()
.map_err(|e| AppError::ValidationError(e.to_string()))?;
let user = UserRepository::create(pool, req).await?;
let token = Self::generate_token(&user, &JwtConfig::default())?;
Ok((user.into(), token))
}
// 登录
pub async fn login(
pool: &sqlx::PgPool,
req: LoginRequest,
jwt_config: &JwtConfig,
) -> AppResult<(UserResponse, String)> {
req.validate()
.map_err(|e| AppError::ValidationError(e.to_string()))?;
let user = UserRepository::verify_password(pool, &req.email, &req.password).await?;
let token = Self::generate_token(&user, jwt_config)?;
Ok((user.into(), token))
}
// 生成 JWT
pub fn generate_token(user: &User, config: &JwtConfig) -> AppResult<String> {
let now = Utc::now();
let exp = now + chrono::Duration::hours(config.expiration_hours);
let claims = Claims {
sub: user.id,
email: user.email.clone(),
role: user.role.clone(),
exp: exp.timestamp() as usize,
iat: now.timestamp() as usize,
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(config.secret.as_bytes()),
)?;
Ok(token)
}
// 验证 JWT
pub fn verify_token(token: &str, config: &JwtConfig) -> AppResult<Claims> {
let token_data = jsonwebtoken::decode::<Claims>(
token,
&DecodingKey::from_secret(config.secret.as_bytes()),
&Validation::default(),
)?;
Ok(token_data.claims)
}
}
六、Handler 与路由
6.1 Auth Handler
rust
// src/handler/auth_handler.rs
use axum::{Json, Extension};
use crate::model::user::*;
use crate::error::AppResult;
use crate::service::auth_service::AuthService;
pub async fn register(
Extension(pool): Extension<sqlx::PgPool>,
Json(req): Json<CreateUserRequest>,
) -> AppResult<Json<AuthResponse>> {
let (user, token) = AuthService::register(&pool, req).await?;
Ok(Json(AuthResponse { user, token }))
}
pub async fn login(
Extension(pool): Extension<sqlx::PgPool>,
Json(req): Json<LoginRequest>,
) -> AppResult<Json<AuthResponse>> {
let config = /* 从 state 获取 */;
let (user, token) = AuthService::login(&pool, req, &config).await?;
Ok(Json(AuthResponse { user, token }))
}
#[derive(Debug, Serialize)]
pub struct AuthResponse {
pub user: UserResponse,
pub token: String,
}
6.2 Post Handler
rust
// src/handler/post_handler.rs
use axum::{Json, Extension, extract::{Path, Query, State}};
use crate::model::post::*;
use crate::error::AppResult;
use crate::repository::post_repo::PostRepository;
use uuid::Uuid;
pub async fn list_posts(
State(pool): State<sqlx::PgPool>,
Query(query): Query<ListPostsQuery>,
) -> AppResult<Json<PostListResponse>> {
let result = PostRepository::list(&pool, query).await?;
Ok(Json(result))
}
pub async fn get_post(
State(pool): State<sqlx::PgPool>,
Path(slug): Path<String>,
) -> AppResult<Json<Post>> {
let post = PostRepository::find_by_slug(&pool, &slug)
.await?
.ok_or_else(|| AppError::NotFound("文章不存在".into()))?;
// 异步增加浏览量(spawn 不阻塞响应)
let pool_clone = pool.clone();
let slug_clone = slug.clone();
tokio::spawn(async move {
if let Ok(Some(p)) = PostRepository::find_by_slug(&pool_clone, &slug_clone).await {
let _ = PostRepository::increment_views(&pool_clone, p.id).await;
}
});
Ok(Json(post))
}
pub async fn create_post(
State(pool): State<sqlx::PgPool>,
Extension(current_user): Extension<Claims>,
Json(req): Json<CreatePostRequest>,
) -> AppResult<Json<Post>> {
req.validate()
.map_err(|e| AppError::ValidationError(e.to_string()))?;
let post = PostRepository::create(&pool, current_user.sub, req).await?;
Ok(Json(post))
}
pub async fn toggle_like(
State(pool): State<sqlx::PgPool>,
Extension(current_user): Extension<Claims>,
Path(post_id): Path<Uuid>,
) -> AppResult<Json<serde_json::Value>> {
let liked = PostRepository::toggle_like(&pool, current_user.sub, post_id).await?;
Ok(Json(serde_json::json!({ "liked": liked })))
}
6.3 路由配置
rust
// src/router.rs
use axum::{
routing::{get, post, delete},
Router,
middleware,
};
use sqlx::PgPool;
use crate::handler::{auth_handler, post_handler};
use crate::middleware::auth::auth_middleware;
pub fn create_router(pool: PgPool, jwt_secret: String) -> Router {
let protected_routes = Router::new()
.route("/posts", post(post_handler::create_post))
.route("/posts/:id/like", post(post_handler::toggle_like))
.route_layer(middleware::from_fn(move |req, next| {
let secret = jwt_secret.clone();
auth_middleware(secret)(req, next)
}));
Router::new()
.route("/health", get(|| async { "OK" }))
.route("/auth/register", post(auth_handler::register))
.route("/auth/login", post(auth_handler::login))
.route("/posts", get(post_handler::list_posts))
.route("/posts/:slug", get(post_handler::get_post))
.merge(protected_routes)
.with_state(pool)
}
七、中间件
7.1 认证中间件
rust
// src/middleware/auth.rs
use axum::{
extract::FromRequestParts,
http::request::Parts,
middleware::Next,
response::Response,
};
use crate::model::user::Claims;
use crate::service::auth_service::AuthService;
use crate::error::AppError;
// JWT 认证中间件
pub async fn auth_middleware(
secret: String,
) -> impl Fn(
axum::extract::Request,
Next,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Response, AppError>> + Send + '_>> {
move |req, next| {
let secret = secret.clone();
Box::pin(async move {
// 从 Authorization header 提取 token
let auth_header = req
.headers()
.get("authorization")
.and_then(|v| v.to_str().ok())
.ok_or(AppError::Unauthorized("缺少认证头".into()))?;
let token = auth_header
.strip_prefix("Bearer ")
.ok_or(AppError::Unauthorized("认证格式错误".into()))?;
let jwt_config = crate::config::JwtConfig {
secret,
expiration_hours: 24,
};
let claims = AuthService::verify_token(token, &jwt_config)?;
let mut req = req;
req.extensions_mut().insert(claims);
Ok(next.run(req).await)
})
}
}
7.2 限流中间件
rust
// src/middleware/rate_limit.rs
use std::sync::Arc;
use tokio::sync::Mutex;
use std::collections::HashMap;
use std::time::{Instant, Duration};
pub struct RateLimiter {
// client_id -> (count, window_start)
windows: Arc<Mutex<HashMap<String, (u32, Instant)>>>,
max_requests: u32,
window_duration: Duration,
}
impl RateLimiter {
pub fn new(max_requests: u32, per_seconds: u64) -> Self {
Self {
windows: Arc::new(Mutex::new(HashMap::new())),
max_requests,
window_duration: Duration::from_secs(per_seconds),
}
}
pub async fn check(&self, client_id: &str) -> bool {
let mut windows = self.windows.lock().await;
let now = Instant::now();
let entry = windows.entry(client_id.to_string())
.or_insert((0, now));
// 滑动窗口
if now.duration_since(entry.1) > self.window_duration {
*entry = (1, now);
} else {
entry.0 += 1;
}
entry.0 <= self.max_requests
}
}
八、应用启动
rust
// src/main.rs
mod config;
mod error;
mod model;
mod repository;
mod service;
mod handler;
mod middleware;
mod router;
use axum::http::HeaderValue;
use sqlx::postgres::PgPoolOptions;
use tower_http::{
cors::{CorsLayer, Any},
trace::TraceLayer,
compression::CompressionLayer,
};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use std::net::SocketAddr;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// 初始化日志
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new(
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()),
))
.with(tracing_subscriber::fmt::layer().json())
.init();
// 加载配置
let config = config::AppConfig::load()?;
// 数据库连接池
let pool = PgPoolOptions::new()
.max_connections(config.database.max_connections)
.min_connections(config.database.min_connections)
.acquire_timeout(std::time::Duration::from_secs(5))
.connect(&config.database.url)
.await?;
tracing::info!("✅ 数据库连接成功");
// 🔥 编译时 SQL 检查(嵌入式迁移)
sqlx::migrate!("./migrations")
.run(&pool)
.await?;
tracing::info!("✅ 数据库迁移完成");
// Redis 连接
let redis_client = redis::Client::open(config.redis.url.as_str())?;
let redis_manager = redis::aio::ConnectionManager::new(redis_client);
redis_manager.get().await?;
tracing::info!("✅ Redis 连接成功");
// CORS
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any);
// 构建路由
let app = router::create_router(pool, config.jwt.secret)
.layer(cors)
.layer(CompressionLayer::new())
.layer(TraceLayer::new_for_http());
// 启动服务
let addr = SocketAddr::from(([0, 0, 0, 0], config.server.port));
tracing::info!("🚀 服务启动: http://{}", addr);
let listener = tokio::net::TcpListener::bind(addr).await?;
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await?;
tracing::info!("👋 服务已停止");
Ok(())
}
async fn shutdown_signal() {
tokio::signal::ctrl_c()
.await
.expect("Failed to install CTRL+C handler");
}
九、Docker 部署
dockerfile
# 多阶段构建
FROM rust:1.84 AS builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
COPY src ./src
COPY migrations ./migrations
# 编译
RUN cargo build --release --locked
# 最小运行镜像
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates libssl3 && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/rust-backend /usr/local/bin/
EXPOSE 8080
CMD ["rust-backend"]
yaml
# docker-compose.yml
version: "3.9"
services:
app:
build: .
ports:
- "8080:8080"
environment:
- APP__SERVER__PORT=8080
- APP__DATABASE__URL=postgres://user:pass@db:5432/rustdb
- APP__REDIS__URL=redis://redis:6379
- APP__JWT__SECRET=${JWT_SECRET}
- RUST_LOG=info
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: rustdb
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
redis:
image: redis:7-alpine
# 压测
k6:
image: grafana/k6
volumes:
- ./tests:/tests
command: run --vus 100 --duration 30s /tests/load.js
profiles:
- benchmark
总结
Rust 后端技术选型 Checklist
□ 框架
□ Axum(Tower 生态、tokio 异步)
□ SQLx(编译时 SQL 检查)
□ serde(序列化)
□ 安全
□ Argon2 密码哈希
□ JWT 认证
□ validator 输入校验
□ 基础设施
□ tracing 结构化日志
□ thiserror 错误处理
□ Docker 多阶段构建
□ 优雅关停
Rust vs Go/Java 性能对比
| 指标 | Rust (Axum) | Go (Gin) | Java (Spring Boot) |
|---|---|---|---|
| QPS | ~120K | ~80K | ~30K |
| P99 延迟 | ~0.8ms | ~1.5ms | ~5ms |
| 内存占用 | ~15MB | ~50MB | ~300MB |
| 启动时间 | ~10ms | ~50ms | ~3s |
| 编译时检查 | SQL + 类型 | 无 | 无 |
本文覆盖 Rust 后端开发完整链路:Axum 框架 + SQLx 编译时 SQL 检查 + Argon2 密码哈希 + JWT 认证 + Repository/Service/Handler 分层 + 限流中间件 + 结构化日志 + 优雅关停 + Docker 多阶段构建 + 压测方案。