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研习社2 小时前
深入 Rust 引用计数智能指针:Rc 与 Arc 从入门到实战
开发语言·后端·rust
@atweiwei3 小时前
LangChainRust:用 Rust 构建高性能 LLM 应用的完整指南
开发语言·人工智能·ai·rust·大模型·llm·agent
专注VB编程开发20年4 小时前
2000条25列数据导出到Sqlite只用40毫秒,ACCESS用时800-1300毫秒
数据库·sqlite·access
浪客川4 小时前
【百例RUST - 006】一文理解所有权和切片
开发语言·后端·rust
Rust研习社4 小时前
Rust 是如何判断对象是否相等的?一起来聊一聊 PartialEq 与 Eq
后端·rust·编程语言
古城小栈5 小时前
Rust在当下AI领域的用武之地:从底层加速到上层应用全解析
开发语言·人工智能·rust
卜夋5 小时前
Rust 所有权概念
后端·rust
freewlt6 小时前
Rust在前端工具链的崛起:2026年生态全景
开发语言·前端·rust
浪客川6 小时前
【百例RUST - 007】结构体
java·前端·rust