87·实战项目进阶

实战:Todo API 服务

实战:Todo API 服务

学习目标

  1. 综合 Axum + Serde + SQLx
  2. 实现完整 CRUD API
  3. 理解项目结构

项目结构

todo-api/
├── Cargo.toml
├── src/
│   ├── main.rs
│   ├── models.rs
│   ├── handlers.rs
│   └── db.rs
└── migrations/
    └── 001_create_todos.sql

models.rs

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Todo {
    pub id: i64,
    pub title: String,
    pub completed: bool,
}

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

#[derive(Debug, Deserialize)]
pub struct UpdateTodo {
    pub title: Option<String>,
    pub completed: Option<bool>,
}

handlers.rs

use axum::{
    extract::{Path, State},
    Json,
    http::StatusCode,
};
use std::sync::{Arc, Mutex};
use crate::models::*;

pub type Db = Arc<Mutex<Vec<Todo>>>;

pub async fn list_todos(State(db): State<Db>) -> Json<Vec<Todo>> {
    let todos = db.lock().unwrap();
    Json(todos.clone())
}

pub async fn create_todo(
    State(db): State<Db>,
    Json(input): Json<CreateTodo>,
) -> (StatusCode, Json<Todo>) {
    let mut todos = db.lock().unwrap();
    let todo = Todo {
        id: todos.len() as i64 + 1,
        title: input.title,
        completed: false,
    };
    todos.push(todo.clone());
    (StatusCode::CREATED, Json(todo))
}

pub async fn toggle_todo(
    State(db): State<Db>,
    Path(id): Path<i64>,
) -> Result<Json<Todo>, StatusCode> {
    let mut todos = db.lock().unwrap();
    todos.iter_mut()
        .find(|t| t.id == id)
        .map(|t| { t.completed = !t.completed; t.clone() })
        .map(Json)
        .ok_or(StatusCode::NOT_FOUND)
}

main.rs

use axum::{Router, routing::{get, put}};
use std::sync::{Arc, Mutex};

mod models;
mod handlers;

#[tokio::main]
async fn main() {
    let db: handlers::Db = Arc::new(Mutex::new(vec![]));

    let app = Router::new()
        .route("/todos", get(handlers::list_todos).post(handlers::create_todo))
        .route("/todos/{id}/toggle", put(handlers::toggle_todo))
        .with_state(db);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();

    println!("服务运行在 http://localhost:3000");
    axum::serve(listener, app).await.unwrap();
}

小结

技能应用
AxumWeb 框架
SerdeJSON 序列化
共享状态Arc<Mutex<T>>
RESTfulCRUD 路由设计

练习编辑器

rust
Loading...