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研习社6 小时前
为什么 Rust 没有空指针?
开发语言·后端·rust
星河耀银海12 小时前
C语言与数据库交互:SQLite实战与数据持久化
c语言·数据库·sqlite·交互
xcLeigh14 小时前
IoTDB Rust 原生接口开发指南:从零生成 + 完整 RPC 调用
数据库·rpc·rust·接口·api·时序数据库·iotdb
万事大吉CC16 小时前
【3】深入剖析 Django 之 MTV:路径引用与资源加载机制
数据库·django·sqlite
十 一 丶16 小时前
如何在客户端实现ssh的免密登录?
运维·rust·ssh
kyriewen16 小时前
你等的Babel编译,够喝三杯咖啡了——用Rust重写的SWC,只需眨个眼
前端·javascript·rust
辞山18 小时前
Coordinate SDK 技术解析
rust
TheRouter1 天前
Agent Harness系列(三):记忆层的3种持久化架构——从SQLite到向量库
人工智能·架构·sqlite·llm·ai-native
skilllite作者2 天前
SkillLite 原生系统级沙箱功能代码导览
人工智能·chrome·后端·架构·rust