项目结构 #
良好的项目结构是开发可维护、可扩展应用的基础。本节将介绍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