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_FOUND、StatusCode::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}