rust语言待办事项小实例完整代码(axum+sqlx+sqlite+自定义错误)

1、Cargo.toml

toml 复制代码
[dependencies]
# Axum Web框架,用于构建HTTP服务
axum = "0.8"

# Tokio异步运行时,Axum的基础依赖
tokio = { version = "1.50", features = ["full"] }

# 数据库操作
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }

# 序列化和反序列化
serde = { version = "1.0", features = ["derive"] }

2、error.rs(错误处理)

rust 复制代码
use axum::http::StatusCode;
use axum::response::IntoResponse;

// 定义我们的错误枚举
pub enum AppError {
    // 数据库操作失败
    DatabaseError(sqlx::Error), 
    // 资源未找到,建议添加上下文(NotFound(String))记录缺失资源名,便于日志追踪
    NotFound, 
}

// 实现 IntoResponse 让错误可响应
// 这样 Axum 就能自动把我们的错误转成 HTTP 响应
impl IntoResponse for AppError {
    fn into_response(self) -> axum::response::Response {
        let (status, msg) = match self {
            AppError::DatabaseError(_) => (StatusCode::INTERNAL_SERVER_ERROR, "数据库出错,请稍后重试!"),
            AppError::NotFound => (StatusCode::NOT_FOUND, "数据不存在!"),
        };
        
        // 直接转为响应,自动设置 Content-Type: text/plain; charset=utf-8
        (status, msg).into_response()
    }
}

// 实现从 sqlx::Error 自动转到 AppError (可以使用 "?" 操作符)
// 可以实现其他错误类型的转换,做到错误处理统一
impl From<sqlx::Error> for AppError {
    fn from(err: sqlx::Error) -> Self {
        AppError::DatabaseError(err)
    }
}
  • StatusCode:Axum 提供的 HTTP 状态码枚举,如 StatusCode::NOT_FOUNDStatusCode::INTERNAL_SERVER_ERROR,用于标准化响应状态。
  • IntoResponse:Axum 的核心 trait,任何实现了它的类型都可以直接作为路由的返回值,Axum 会自动将其转换为 HTTP 响应。‌这是你自定义错误能"被框架识别"的关键‌。

3、models.rs(数据模型)

rust 复制代码
use serde::{Deserialize, Serialize};

// 数据库实体
#[derive(Serialize, sqlx::FromRow)]
pub struct Todo {
    pub id: i64,
    pub title: String,      // 待办事项
    pub completed: bool,    // 是否弯沉
}

// 新增待办事项使用
#[derive(Deserialize)]
pub struct CreateTodo {
    pub title: String,     // 待办事项
}

// 全局状态,数据库连接池
pub struct AppState {
    pub db: sqlx::SqlitePool,
}

4、method_routers.rs(路由方法)

rust 复制代码
use crate::error::AppError;
use crate::models::{AppState, CreateTodo, Todo};
use axum::Json;
use axum::extract::{Path, State};
use axum::http::StatusCode;
use sqlx::SqlitePool;
use std::sync::Arc;

// 初始化数据库连接,会在当前目录生成 todo.db
pub async fn init_db() -> Arc<AppState> {
    // 创建数据库连接,读写模式,数据库不存在时创建
    let pool = SqlitePool::connect("sqlite:todo.db?mode=rwc")
        .await
        .unwrap();

    // 创建表(如果不存在)
    sqlx::query(
        "CREATE TABLE IF NOT EXISTS todos (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    completed BOOL NOT NULL DEFAULT 0)",
    )
    .execute(&pool)
    .await
    .unwrap();

    Arc::new(AppState { db: pool })
}

// 添加待办
pub async fn create_todo(
    State(state): State<Arc<AppState>>,
    Json(payload): Json<CreateTodo>,
) -> Result<(StatusCode, Json<Todo>), AppError> {
    let id = sqlx::query("INSERT INTO todos (title,completed) VALUES (?,0)")
        .bind(&payload.title)
        .execute(&state.db)
        .await?
        .last_insert_rowid();        // 返回创建记录的id

    let new_todo = Todo {
        id,
        title: payload.title,
        completed: false,
    };
    Ok((StatusCode::CREATED, Json(new_todo)))
}

// 查询所有待办事项
pub async fn list_todos(
    State(state): State<Arc<AppState>>,
) -> Result<(StatusCode, Json<Vec<Todo>>), AppError> {
    let todos = sqlx::query_as("SELECT id,title,completed FROM todos")
        .fetch_all(&state.db)          // 查询全部数据
        .await?;
    Ok((StatusCode::OK, Json(todos)))
}

// 完成一项待办事项
pub async fn complete_todo(
    State(state): State<Arc<AppState>>,
    Path(id): Path<i64>,
) -> Result<StatusCode, AppError> {
    let result = sqlx::query("UPDATE todos SET completed = 1 WHERE id = ?")
        .bind(id)
        .execute(&state.db)
        .await?;
    
    // 修改数据影响的数据条数,0时为未修改,返回数据不存在
    if result.rows_affected() == 0 {
        return Err(AppError::NotFound);
    }
    Ok(StatusCode::OK)
}

// 删除一条待办事项
pub async fn delete_todo(
    State(state): State<Arc<AppState>>,
    Path(id): Path<i64>,
) -> Result<StatusCode, AppError> {
    let result = sqlx::query("DELETE FROM todos WHERE id = ?")
        .bind(id)
        .execute(&state.db)
        .await?;
    
    // 删除数据影响的数据条数,0时为未删除,返回数据不存在
    if result.rows_affected() == 0 {
        return Err(AppError::NotFound);
    }
    Ok(StatusCode::OK)
}

SQLite 数据库打开模式:

  • mode=ro:只读模式。

  • mode=rw:读写模式。

  • mode=rwc:读写模式,数据库不存在时创建。

  • mode=memory:内存数据库,只在程序运行期间存在。

5、main.rs(主文件)

rust 复制代码
mod method_routers;
mod error;
mod models;

use crate::method_routers::{complete_todo, create_todo, delete_todo, init_db, list_todos};
use axum::routing::{get, put};
use axum::{response::IntoResponse, Router};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() {
    // 初始化数据库连接
    let shared_state = init_db().await;

    // 创建路由
    let app = Router::new()
        .route("/todos", get(list_todos).post(create_todo))            // 查询,增加
        .route("/todos/{id}", put(complete_todo).delete(delete_todo))  // 修改,删除
        .with_state(shared_state);    // 将数据库连接,绑定到路由的全局状态

    // 绑定端口
    let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();

    println!("添加 post请求:  http://127.0.0.1:8080/todos");
    println!("    post请求体:{{\"title\":\"我的待办事项一\"}}");
    println!("查询所有待办事项 get请求:  http://127.0.0.1:8080/todos");
    println!("修改 put请求:  http://127.0.0.1:8080/todos/修改的id");
    println!("删除 delete请求:  http://127.0.0.1:8080/todos/删除的id");

    // 启动服务
    axum::serve(listener, app).await.unwrap();
}

Path 路径写法:

  • Axum 0.6及以 下写法,/users/:id

  • **Axum 0.7+**写法,/users/{id}

相关推荐
Rust研习社1 天前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
红尘散仙2 天前
想写一个像样的终端 App?试试把 React 的开发体验搬进 Rust TUI
前端·rust
vivo互联网技术2 天前
从 Web 到桌面:基于 Tauri 2.0 + Vue 3 打造 vivo 线下门店「大头贴」拍照体验系统
前端·rust
Rust研习社2 天前
这 8 个 Rust 学习资源值得每个新手收藏起来
后端·rust·编程语言
星栈3 天前
10 分钟跑起第一个 Dioxus 应用:`dx` CLI、`rsx!` 和热更新好不好用
前端·rust·前端框架
望眼欲穿的程序猿4 天前
读取芯片内部温度传感器
嵌入式硬件·rust
望眼欲穿的程序猿4 天前
ADC 模拟电压采集
嵌入式硬件·rust
codexu_4612291874 天前
NoteGen 里一条记录如何变成 Markdown
前端·笔记·rust·tauri
伶俜664 天前
鸿蒙原生应用实战(十八)ArkUI 记账本:SQLite 账单 + 图表统计 + 分类管理
jvm·sqlite·harmonyos
Rust研习社4 天前
Rust 错误处理的黄金搭档:一个定义错误,一个传播错误
后端·rust·编程语言