博客系统 #

本节将使用Rocket构建一个完整的博客系统,包括文章管理、评论系统和用户交互功能。

项目结构 #

text
blog_system/
├── Cargo.toml
├── Rocket.toml
├── src/
│   ├── main.rs
│   ├── models/
│   │   ├── mod.rs
│   │   ├── user.rs
│   │   ├── post.rs
│   │   └── comment.rs
│   ├── routes/
│   │   ├── mod.rs
│   │   ├── auth.rs
│   │   ├── post.rs
│   │   └── comment.rs
│   └── db.rs
├── templates/
│   ├── base.html
│   ├── index.html
│   ├── post.html
│   └── login.html
└── static/
    └── css/
        └── style.css

数据模型 #

用户模型 #

rust
use serde::{Serialize, Deserialize};
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    pub id: Uuid,
    pub username: String,
    pub email: String,
    pub password_hash: String,
    pub bio: Option<String>,
    pub created_at: String,
}

#[derive(Debug, Deserialize)]
pub struct RegisterUser {
    pub username: String,
    pub email: String,
    pub password: String,
}

文章模型 #

rust
use serde::{Serialize, Deserialize};
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Post {
    pub id: Uuid,
    pub title: String,
    pub content: String,
    pub author_id: Uuid,
    pub author_name: String,
    pub created_at: String,
    pub updated_at: String,
    pub views: u32,
    pub likes: u32,
}

#[derive(Debug, Deserialize)]
pub struct CreatePost {
    pub title: String,
    pub content: String,
}

#[derive(Debug, Deserialize)]
pub struct UpdatePost {
    pub title: Option<String>,
    pub content: Option<String>,
}

评论模型 #

rust
use serde::{Serialize, Deserialize};
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Comment {
    pub id: Uuid,
    pub post_id: Uuid,
    pub author_id: Uuid,
    pub author_name: String,
    pub content: String,
    pub created_at: String,
}

#[derive(Debug, Deserialize)]
pub struct CreateComment {
    pub content: String,
}

数据库 #

rust
use std::collections::HashMap;
use std::sync::Mutex;
use uuid::Uuid;

pub struct Db {
    pub users: Mutex<HashMap<Uuid, crate::models::user::User>>,
    pub posts: Mutex<HashMap<Uuid, crate::models::post::Post>>,
    pub comments: Mutex<HashMap<Uuid, crate::models::comment::Comment>>,
}

impl Db {
    pub fn new() -> Self {
        Self {
            users: Mutex::new(HashMap::new()),
            posts: Mutex::new(HashMap::new()),
            comments: Mutex::new(HashMap::new()),
        }
    }
}

路由实现 #

文章路由 #

rust
use rocket::serde::json::Json;
use rocket::State;
use rocket_dyn_templates::Template;
use uuid::Uuid;
use crate::models::post::{Post, CreatePost, UpdatePost};
use crate::db::Db;
use crate::guards::AuthUser;

#[get("/posts")]
pub fn list(db: &State<Db>) -> Json<Vec<Post>> {
    let posts = db.posts.lock().unwrap();
    Json(posts.values().cloned().collect())
}

#[get("/posts/<id>")]
pub fn get(id: Uuid, db: &State<Db>) -> Option<Json<Post>> {
    let posts = db.posts.lock().unwrap();
    posts.get(&id).cloned().map(Json)
}

#[post("/posts", format = "json", data = "<post>")]
pub fn create(post: Json<CreatePost>, user: AuthUser, db: &State<Db>) -> Json<Post> {
    let id = Uuid::new_v4();
    let now = chrono::Utc::now().to_rfc3339();
    
    let new_post = Post {
        id,
        title: post.title.clone(),
        content: post.content.clone(),
        author_id: Uuid::parse_str(&user.id).unwrap(),
        author_name: user.username.clone(),
        created_at: now.clone(),
        updated_at: now,
        views: 0,
        likes: 0,
    };
    
    db.posts.lock().unwrap().insert(id, new_post.clone());
    Json(new_post)
}

#[put("/posts/<id>", format = "json", data = "<post>")]
pub fn update(
    id: Uuid,
    post: Json<UpdatePost>,
    user: AuthUser,
    db: &State<Db>,
) -> Option<Json<Post>> {
    let mut posts = db.posts.lock().unwrap();
    
    if let Some(existing) = posts.get_mut(&id) {
        if existing.author_id.to_string() == user.id {
            if let Some(title) = &post.title {
                existing.title = title.clone();
            }
            if let Some(content) = &post.content {
                existing.content = content.clone();
            }
            existing.updated_at = chrono::Utc::now().to_rfc3339();
            return Some(Json(existing.clone()));
        }
    }
    None
}

#[delete("/posts/<id>")]
pub fn delete(id: Uuid, user: AuthUser, db: &State<Db>) -> bool {
    let mut posts = db.posts.lock().unwrap();
    
    if let Some(post) = posts.get(&id) {
        if post.author_id.to_string() == user.id {
            posts.remove(&id);
            return true;
        }
    }
    false
}

#[post("/posts/<id>/like")]
pub fn like(id: Uuid, db: &State<Db>) -> Option<Json<Post>> {
    let mut posts = db.posts.lock().unwrap();
    
    if let Some(post) = posts.get_mut(&id) {
        post.likes += 1;
        return Some(Json(post.clone()));
    }
    None
}

评论路由 #

rust
use rocket::serde::json::Json;
use rocket::State;
use uuid::Uuid;
use crate::models::comment::{Comment, CreateComment};
use crate::db::Db;
use crate::guards::AuthUser;

#[get("/posts/<post_id>/comments")]
pub fn list(post_id: Uuid, db: &State<Db>) -> Json<Vec<Comment>> {
    let comments = db.comments.lock().unwrap();
    let post_comments: Vec<Comment> = comments
        .values()
        .filter(|c| c.post_id == post_id)
        .cloned()
        .collect();
    Json(post_comments)
}

#[post("/posts/<post_id>/comments", format = "json", data = "<comment>")]
pub fn create(
    post_id: Uuid,
    comment: Json<CreateComment>,
    user: AuthUser,
    db: &State<Db>,
) -> Json<Comment> {
    let id = Uuid::new_v4();
    
    let new_comment = Comment {
        id,
        post_id,
        author_id: Uuid::parse_str(&user.id).unwrap(),
        author_name: user.username.clone(),
        content: comment.content.clone(),
        created_at: chrono::Utc::now().to_rfc3339(),
    };
    
    db.comments.lock().unwrap().insert(id, new_comment.clone());
    Json(new_comment)
}

#[delete("/comments/<id>")]
pub fn delete(id: Uuid, user: AuthUser, db: &State<Db>) -> bool {
    let mut comments = db.comments.lock().unwrap();
    
    if let Some(comment) = comments.get(&id) {
        if comment.author_id.to_string() == user.id {
            comments.remove(&id);
            return true;
        }
    }
    false
}

模板渲染 #

基础模板 #

html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Blog{% endblock %}</title>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <header>
        <nav>
            <a href="/">Home</a>
            {% if user %}
                <a href="/new-post">New Post</a>
                <a href="/logout">Logout ({{ user.username }})</a>
            {% else %}
                <a href="/login">Login</a>
                <a href="/register">Register</a>
            {% endif %}
        </nav>
    </header>
    
    <main>
        {% block content %}{% endblock %}
    </main>
    
    <footer>
        <p>&copy; 2024 My Blog</p>
    </footer>
</body>
</html>

首页模板 #

html
{% extends "base.html" %}

{% block title %}Blog - Home{% endblock %}

{% block content %}
<h1>Latest Posts</h1>

{% for post in posts %}
<article class="post-card">
    <h2><a href="/posts/{{ post.id }}">{{ post.title }}</a></h2>
    <p class="meta">
        By {{ post.author_name }} | {{ post.created_at }}
        | Views: {{ post.views }} | Likes: {{ post.likes }}
    </p>
    <p>{{ post.content | truncate(length=200) }}</p>
</article>
{% endfor %}
{% endblock %}

主程序 #

rust
#[macro_use] extern crate rocket;

mod models;
mod routes;
mod db;
mod guards;

use db::Db;
use rocket_dyn_templates::Template;
use rocket::fs::FileServer;

#[launch]
fn rocket() -> _ {
    rocket::build()
        .manage(Db::new())
        .attach(Template::fairing())
        .mount("/static", FileServer::from("static/"))
        .mount("/api", routes![
            routes::post::list,
            routes::post::get,
            routes::post::create,
            routes::post::update,
            routes::post::delete,
            routes::post::like,
            routes::comment::list,
            routes::comment::create,
            routes::comment::delete,
        ])
}

API测试 #

bash
# 创建文章
curl -X POST http://localhost:8000/api/posts \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {token}" \
  -d '{"title":"My First Post","content":"Hello World!"}'

# 获取文章列表
curl http://localhost:8000/api/posts

# 获取单篇文章
curl http://localhost:8000/api/posts/{id}

# 添加评论
curl -X POST http://localhost:8000/api/posts/{id}/comments \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {token}" \
  -d '{"content":"Great post!"}'

# 点赞文章
curl -X POST http://localhost:8000/api/posts/{id}/like

下一步 #

掌握了博客系统后,让我们继续学习 部署上线,了解如何将应用部署到生产环境。

最后更新:2026-03-28