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}

相关推荐
柯南466828 分钟前
【AI开发之Rust】第 8 课:泛型、trait 与 trait 对象
rust·编程语言
yume_sibai2 小时前
12-Rust 性能优化指南(性能分析 + 内存优化 + SIMD + 并发优化 + 编译器优化 + 基准测试)
开发语言·性能优化·rust
geovindu2 小时前
rust:Builder Pattern
开发语言·设计模式·rust·建造者模式
传奇开心果编程6 小时前
【Rust入门知识点学与练】第33课:异步编程入门(async/await)强化
开发语言·学习·rust
传奇开心果编程6 小时前
【Rust入门知识点学与练】第34课:所有权与借用
开发语言·学习·rust
柒儿吖7 小时前
SSCom 重构全记录:用 Rust 把串口调试助手送上鸿蒙 PC、macOS、Windows和Linux
测试工具·rust·harmonyos
小静AI工程实验室7 小时前
Python 爬虫翻页为何重复、漏数据?SQLite 复现 OFFSET、复合游标与快照的 8 项检查
爬虫·python·sqlite
Kapaseker8 小时前
Rust 为什么越来越受欢迎?开发者角度谈
rust
孙启超8 小时前
【AI开发之Rust】第 6 课:结构体、枚举与方法 —— 开始定义自己的类型
开发语言·人工智能·后端·ai·rust
foolishlee8 小时前
Rust 下载依赖包
rust