第二章:模块化项目结构与配置管理
博客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