第十章:测试、优化与生产部署

博客v1.0系列教程(Rust)博客 v1.0 系列教程 (Rust)

10.1 集成测试

使用 SQLx 的测试数据库进行集成测试:

// tests/api_test.rs
use sqlx::PgPool;

#[sqlx::test]
async fn test_create_article(pool: PgPool) {
    let service = ArticleService::new(pool);

    let dto = CreateArticleDto {
        title: "Test Article".into(),
        content: "# Hello\nWorld".into(),
        category: Some("Test".into()),
        tags: Some(vec!["rust".into(), "test".into()]),
    };

    let article = service.create_article(dto).await.unwrap();

    assert_eq!(article.title, Some("Test Article".into()));
    assert_eq!(article.category, Some("Test".into()));
    assert!(article.id > 0);
}

#[sqlx::test]
async fn test_article_not_found(pool: PgPool) {
    let service = ArticleService::new(pool);
    let result = service.get_article(99999).await;
    assert!(result.is_err());
    assert!(matches!(
        result.unwrap_err(),
        AppError::NotFound(_)
    ));
}

10.2 API 测试

use axum::http::StatusCode;

#[sqlx::test]
async fn test_article_list_api(pool: PgPool) {
    let app = create_test_app(pool).await;

    let response = app
        .get("/api/article")
        .await;

    assert_eq!(response.status_code(), StatusCode::OK);

    let body: PageResult<Article> = response.json();
    assert!(body.items.len() >= 0);
}

async fn create_test_app(pool: PgPool) -> Router {
    Router::new()
        .route("/api/article", get(list_articles))
        .with_state(pool)
}

10.3 性能优化

连接池优化

pub async fn init_pool(database_url: &str) -> Result<PgPool, sqlx::Error> {
    PgPoolOptions::new()
        .max_connections(50)
        .min_connections(10)
        .max_lifetime(Duration::from_secs(1800))
        .idle_timeout(Duration::from_secs(600))
        .acquire_timeout(Duration::from_secs(3))
        .connect(database_url)
        .await
}

查询优化

// 使用索引覆盖查询
#[derive(Debug, FromRow)]
pub struct ArticleSummary {
    pub id: i64,
    pub title: Option<String>,
    pub category: Option<String>,
    pub create_time: Option<NaiveDateTime>,
}

// 只查询需要的字段
let summaries = sqlx::query_as::<_, ArticleSummary>(
    "SELECT id, title, category, create_time FROM article_models
     ORDER BY create_time DESC LIMIT $1"
)
.bind(20)
.fetch_all(&pool)
.await?;

响应缓存

use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

pub struct Cache<T> {
    data: Arc<RwLock<HashMap<String, (T, Instant)>>>,
    ttl: Duration,
}

impl<T: Clone + Send + Sync + 'static> Cache<T> {
    pub fn new(ttl: Duration) -> Self {
        Cache {
            data: Arc::new(RwLock::new(HashMap::new())),
            ttl,
        }
    }

    pub async fn get(&self, key: &str) -> Option<T> {
        let map = self.data.read().await;
        if let Some((value, expires)) = map.get(key) {
            if expires.elapsed() < self.ttl {
                return Some(value.clone());
            }
        }
        None
    }

    pub async fn set(&self, key: String, value: T) {
        let mut map = self.data.write().await;
        map.insert(key, (value, Instant::now()));
    }
}

10.4 Release 构建优化

# Cargo.toml
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
strip = true
# 构建 release 版
cargo build --release

# 查看二进制大小
ls -lh target/release/rabitlogic-blog

10.5 Docker 部署

# Dockerfile
FROM rust:1.81-slim-bookworm AS builder
WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /app/target/release/rabitlogic-blog .
COPY --from=builder /app/templates ./templates
COPY --from=builder /app/articles ./articles
COPY --from=builder /app/migrations ./migrations
COPY --from=builder /app/assets ./assets
COPY --from=builder /app/.env.example .env
EXPOSE 5051
CMD ["./rabitlogic-blog"]

Docker Compose

version: '3.8'

services:
  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: blog_ssr
      POSTGRES_USER: blog
      POSTGRES_PASSWORD: blog123
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U blog"]
      interval: 5s
      timeout: 5s
      retries: 5

  app:
    build: .
    ports:
      - "5051:5051"
    environment:
      DATABASE_URL: postgres://blog:blog123@postgres:5432/blog_ssr
      SERVER_HOST: 0.0.0.0
      SERVER_PORT: 5051
      JWT_SECRET: change-this-in-production
      RUST_LOG: info
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  pgdata:

10.6 .env 示例

DATABASE_URL=postgres://blog:blog123@localhost:5432/blog_ssr
SERVER_HOST=0.0.0.0
SERVER_PORT=5051
JWT_SECRET=your-production-secret-key-change-me
RUST_LOG=info

10.7 一键部署脚本

# deploy.ps1
Write-Host "Building Rust backend..." -ForegroundColor Green
cargo build --release

Write-Host "Deploying with Docker..." -ForegroundColor Green
docker-compose up -d --build

Write-Host "Done! Server running on http://localhost:5051" -ForegroundColor Green

至此,博客 v1.0 (Rust 后端) 系列教程完成!我们深入学习了 Rust 异步编程、Axum 框架、SQLx 数据库操作、JWT 认证、RESTful API 设计、错误处理、文件上传、中间件、日志系统以及生产部署的完整知识体系。

rusttestingoptimizationdeploydocker