项目结构 #

良好的项目结构是开发可维护、可扩展应用的基础。本节将介绍Rocket项目的推荐目录结构和组织方式。

基础项目结构 #

一个典型的Rocket项目结构如下:

text
my_rocket_app/
├── Cargo.toml
├── Rocket.toml
├── .env
├── src/
│   ├── main.rs
│   ├── lib.rs
│   ├── routes/
│   │   ├── mod.rs
│   │   ├── user.rs
│   │   └── auth.rs
│   ├── models/
│   │   ├── mod.rs
│   │   └── user.rs
│   ├── handlers/
│   │   ├── mod.rs
│   │   └── error.rs
│   ├── db/
│   │   ├── mod.rs
│   │   └── pool.rs
│   ├── guards/
│   │   ├── mod.rs
│   │   └── auth.rs
│   ├── fairings/
│   │   ├── mod.rs
│   │   └── logging.rs
│   └── config.rs
├── tests/
│   └── integration_test.rs
├── migrations/
│   └── 20240101000000_create_users.sql
├── static/
│   ├── css/
│   ├── js/
│   └── images/
├── templates/
│   ├── base.html
│   └── user/
│       └── profile.html
└── .gitignore

目录说明 #

目录/文件 说明
Cargo.toml Cargo项目配置
Rocket.toml Rocket框架配置
.env 环境变量(不提交到git)
src/ 源代码目录
tests/ 集成测试
migrations/ 数据库迁移文件
static/ 静态资源文件
templates/ 模板文件

模块化组织 #

入口文件 (main.rs) #

rust
#[macro_use] extern crate rocket;

mod routes;
mod models;
mod handlers;
mod db;
mod guards;
mod fairings;
mod config;

use routes::{user, auth};

#[launch]
fn rocket() -> _ {
    rocket::build()
        .manage(db::init_pool())
        .mount("/api/users", routes![user::get_user, user::create_user])
        .mount("/api/auth", routes![auth::login, auth::logout])
        .attach(fairings::logging::LoggingFairing)
        .register("/", catchers![handlers::error::not_found])
}

路由模块 (routes/mod.rs) #

rust
pub mod user;
pub mod auth;

路由实现 (routes/user.rs) #

rust
use rocket::serde::json::Json;
use crate::models::user::User;
use crate::db::pool::Db;

#[get("/<id>")]
pub async fn get_user(id: u32, db: &Db) -> Option<Json<User>> {
    db.get_user(id).await.map(Json)
}

#[post("/", format = "json", data = "<user>")]
pub async fn create_user(user: Json<User>, db: &Db) -> Json<User> {
    let created = db.create_user(user.into_inner()).await;
    Json(created)
}

模型定义 (models/user.rs) #

rust
use rocket::serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct User {
    pub id: u32,
    pub name: String,
    pub email: String,
    pub created_at: String,
}

#[derive(Debug, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct CreateUser {
    pub name: String,
    pub email: String,
}

数据库模块 (db/pool.rs) #

rust
use std::sync::Arc;
use tokio::sync::Mutex;
use crate::models::user::User;

pub struct Db {
    users: Arc<Mutex<Vec<User>>>,
}

impl Db {
    pub fn new() -> Self {
        Self {
            users: Arc::new(Mutex::new(Vec::new())),
        }
    }

    pub async fn get_user(&self, id: u32) -> Option<User> {
        let users = self.users.lock().await;
        users.iter().find(|u| u.id == id).cloned()
    }

    pub async fn create_user(&self, user: User) -> User {
        let mut users = self.users.lock().await;
        users.push(user.clone());
        user
    }
}

pub fn init_pool() -> Db {
    Db::new()
}

Cargo.toml配置 #

toml
[package]
name = "my_rocket_app"
version = "0.1.0"
edition = "2021"

[dependencies]
rocket = { version = "0.5", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

[dependencies.rocket_sync_db_pools]
version = "0.1"
features = ["diesel_postgres_pool"]

[dev-dependencies]
rocket = { version = "0.5", features = ["json"] }

[[bin]]
name = "my_rocket_app"
path = "src/main.rs"

配置管理 #

环境配置 (config.rs) #

rust
use std::env;

pub struct Config {
    pub database_url: String,
    pub jwt_secret: String,
    pub server_port: u16,
}

impl Config {
    pub fn from_env() -> Self {
        Self {
            database_url: env::var("DATABASE_URL")
                .expect("DATABASE_URL must be set"),
            jwt_secret: env::var("JWT_SECRET")
                .unwrap_or_else(|_| "secret".to_string()),
            server_port: env::var("PORT")
                .map(|p| p.parse().unwrap_or(8000))
                .unwrap_or(8000),
        }
    }
}

Rocket.toml配置 #

toml
[default]
address = "127.0.0.1"
port = 8000
workers = 4
keep_alive = 5
log_level = "normal"
secret_key = "your-secret-key-here"

[default.databases]
my_db = { url = "postgres://user:pass@localhost/mydb" }

[debug]
address = "127.0.0.1"
port = 8000
log_level = "debug"

[release]
address = "0.0.0.0"
port = 80
workers = 12
log_level = "critical"

错误处理模块 #

handlers/error.rs #

rust
use rocket::http::Status;
use rocket::response::status::Custom;
use rocket::serde::json::Json;
use serde::Serialize;

#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct ErrorResponse {
    pub error: String,
    pub code: u16,
}

pub type ApiResult<T> = Result<Json<T>, Custom<Json<ErrorResponse>>>;

pub fn not_found(msg: &str) -> Custom<Json<ErrorResponse>> {
    Custom(Status::NotFound, Json(ErrorResponse {
        error: msg.to_string(),
        code: 404,
    }))
}

pub fn bad_request(msg: &str) -> Custom<Json<ErrorResponse>> {
    Custom(Status::BadRequest, Json(ErrorResponse {
        error: msg.to_string(),
        code: 400,
    }))
}

pub fn internal_error(msg: &str) -> Custom<Json<ErrorResponse>> {
    Custom(Status::InternalServerError, Json(ErrorResponse {
        error: msg.to_string(),
        code: 500,
    }))
}

#[catch(404)]
pub fn not_found_catcher() -> Json<ErrorResponse> {
    Json(ErrorResponse {
        error: "Resource not found".to_string(),
        code: 404,
    })
}

#[catch(500)]
pub fn internal_error_catcher() -> Json<ErrorResponse> {
    Json(ErrorResponse {
        error: "Internal server error".to_string(),
        code: 500,
    })
}

守卫模块 #

guards/auth.rs #

rust
use rocket::request::{self, Request, FromRequest, Outcome};
use rocket::http::Status;

pub struct AuthUser {
    pub id: u32,
    pub name: String,
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for AuthUser {
    type Error = AuthError;

    async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
        let token = req.headers().get_one("Authorization");
        
        match token {
            Some(t) if t.starts_with("Bearer ") => {
                let token = t.trim_start_matches("Bearer ");
                match validate_token(token) {
                    Ok(user) => Outcome::Success(user),
                    Err(_) => Outcome::Error((Status::Unauthorized, AuthError::InvalidToken)),
                }
            }
            _ => Outcome::Error((Status::Unauthorized, AuthError::MissingToken)),
        }
    }
}

#[derive(Debug)]
pub enum AuthError {
    MissingToken,
    InvalidToken,
}

fn validate_token(token: &str) -> Result<AuthUser, ()> {
    Ok(AuthUser {
        id: 1,
        name: "test".to_string(),
    })
}

静态文件服务 #

rust
use rocket::fs::{FileServer, relative};

#[launch]
fn rocket() -> _ {
    rocket::build()
        .mount("/", routes![index])
        .mount("/static", FileServer::from(relative!("static")))
}

模板配置 #

rust
use rocket::fs::{FileServer, relative};
use rocket_dyn_templates::Template;

#[launch]
fn rocket() -> _ {
    rocket::build()
        .attach(Template::fairing())
        .mount("/", routes![index])
        .mount("/static", FileServer::from(relative!("static")))
}

#[get("/")]
fn index() -> Template {
    Template::render("index", &Context {
        title: "Home",
        items: vec!["Item 1", "Item 2"],
    })
}

测试目录结构 #

text
tests/
├── common/
│   ├── mod.rs
│   └── fixtures.rs
├── user_test.rs
└── auth_test.rs

.gitignore配置 #

gitignore
/target
**/*.rs.bk
Cargo.lock
.env
Rocket.toml
*.db
*.sqlite
.DS_Store

最佳实践总结 #

实践 说明
模块化 按功能划分模块,保持代码组织清晰
配置分离 使用环境变量和配置文件管理配置
错误统一 统一的错误处理和响应格式
类型安全 充分利用Rust类型系统
测试覆盖 编写单元测试和集成测试
文档注释 为公共API添加文档注释

下一步 #

了解了项目结构后,让我们深入学习 路由基础,掌握Rocket路由系统的核心概念。

最后更新:2026-03-28