Rust 后端实战:高性能 Web 服务开发全链路

前言

💡 痛点: 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 多阶段构建 + 压测方案。

相关推荐
bubiyoushang8882 小时前
电力线信道“五类噪声”仿真MATLAB
开发语言·matlab
cici158742 小时前
彩色图像模糊增强(Fuzzy Enhancement)MATLAB 实现
开发语言·算法·matlab
kaikaile19952 小时前
图像稀疏化分解 + 压缩感知(CS)重建 MATLAB
开发语言·计算机视觉·matlab
yugi9878382 小时前
PNCC(Power-Normalized Cepstral Coefficients)— MATLAB 实现
开发语言·人工智能·matlab
甲维斯2 小时前
GLM5.2超过Opus4.8Think,全球第二了!
前端·人工智能·ai编程
大黄说说2 小时前
C++20 协程从入门到网络服务
开发语言
你是个什么橙2 小时前
Python入门学习2:Python 基础语法全解析——从代码结构到输入输出
开发语言·python·学习
by————组态2 小时前
Ricon组态系统 - 新一代Web可视化组态平台
前端·后端·物联网·架构·组态·组态软件
JieE2122 小时前
手把手带你用纯 CSS 实现一个 3D 旋转魔方,这些前端基础你能打几分?
前端·css·html
小白学大数据2 小时前
Python + 大模型行业资讯自动化摘要流水线完整工程实现方案
开发语言·python·自动化