第二章:模块化项目结构与配置管理

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

2.1 项目模块结构

src/
├── main.rs              # 入口:服务器启动 + SSR 渲染
├── content.rs           # Markdown 文章加载与解析
├── common/              # 共享基础设施
│   ├── mod.rs
│   ├── config.rs        # 环境变量配置
│   ├── db.rs            # SQLx 连接池
│   ├── error.rs         # 统一错误处理
│   └── auth.rs          # JWT 认证中间件
├── models/              # 数据模型
│   ├── mod.rs
│   ├── entity.rs        # 数据库实体(FromRow)
│   └── dto.rs           # 数据传输对象
├── api/                 # REST API 路由
│   ├── mod.rs
│   ├── article.rs
│   ├── category.rs
│   ├── comment.rs
│   ├── user.rs
│   └── ...
├── services/            # 业务逻辑层
│   ├── mod.rs
│   ├── article_service.rs
│   └── ...
├── components/          # Dioxus UI 组件
│   ├── mod.rs
│   ├── layout/          # App 框架
│   ├── ui/              # 原子组件
│   └── blog/            # 博客组件
└── pages/               # 页面组件
    ├── mod.rs
    ├── home.rs
    ├── blog_list.rs
    ├── blog_post.rs
    └── login.rs

2.2 模块声明

// src/main.rs
mod api;
mod common;
mod content;
mod models;
mod services;
mod components;
mod pages;
// src/common/mod.rs
pub mod auth;
pub mod config;
pub mod db;
pub mod error;

2.3 环境变量配置

使用 dotenvy 和 serde 实现类型安全的配置管理:

// src/common/config.rs
use serde::Deserialize;

#[derive(Debug, Clone, Deserialize)]
pub struct AppConfig {
    pub database_url: String,
    pub server_host: String,
    pub server_port: u16,
    pub jwt_secret: String,
}

impl AppConfig {
    pub fn from_env() -> Self {
        dotenvy::dotenv().ok();
        AppConfig {
            database_url: std::env::var("DATABASE_URL")
                .expect("DATABASE_URL must be set"),
            server_host: std::env::var("SERVER_HOST")
                .unwrap_or_else(|_| "0.0.0.0".into()),
            server_port: std::env::var("SERVER_PORT")
                .unwrap_or_else(|_| "5051".into())
                .parse()
                .expect("SERVER_PORT must be a number"),
            jwt_secret: std::env::var("JWT_SECRET")
                .expect("JWT_SECRET must be set"),
        }
    }
}

.env 文件

DATABASE_URL=postgres://user:password@localhost:5432/blog_ssr
SERVER_HOST=0.0.0.0
SERVER_PORT=5051
JWT_SECRET=your-256-bit-secret

2.4 数据库连接池

// src/common/db.rs
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;

pub async fn init_pool(database_url: &str) -> Result<PgPool, sqlx::Error> {
    let pool = PgPoolOptions::new()
        .max_connections(20)
        .min_connections(5)
        .connect(database_url)
        .await?;

    // 执行迁移
    sqlx::migrate!("./migrations").run(&pool).await?;

    tracing::info!("Database connected and migrations applied");
    Ok(pool)
}

2.5 统一错误处理

// src/common/error.rs
use axum::response::{IntoResponse, Response};
use axum::http::StatusCode;

#[derive(Debug, thiserror::Error)]
pub enum AppError {
    #[error("Not found: {0}")]
    NotFound(String),

    #[error("Unauthorized: {0}")]
    Unauthorized(String),

    #[error("Bad request: {0}")]
    BadRequest(String),

    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),

    #[error("Internal error: {0}")]
    Internal(String),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match &self {
            AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
            AppError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg.clone()),
            AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
            AppError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Database error".into()),
            AppError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.clone()),
        };

        (status, axum::Json(serde_json::json!({
            "error": message
        }))).into_response()
    }
}

下一章将深入 SQLx 数据模型与迁移系统。

rustmodulesconfigstructure