当前位置: 首页 > news >正文

rust单体web项目模板搭建

代码仓库

gitee

创建项目

cargo new rust-web-starter

目录结构

/src/handlers- mod.rs- posts.rs- user.rs/utils- mod.rs- jwt.rsmain.rs
.env
dev.db

数据库

使用sqlx进行数据库操作, 为了方便测试,使用sqlite数据库

创建数据库表

在代码中实现


#[tokio::main]
async fn main() {// 加载环境变量let _ = dotenvy::dotenv();// 日志追踪器tracing_subscriber::fmt::init();info!("Starting server");// 数据库连接let database_url = std::env::var("SQLITE_DB_URL").expect("SQLITE_DB_URL not set");let pool = sqlx::SqlitePool::connect(&database_url).await.expect("Error with pool connection");// 建表sqlx::query(r#"create table users(id       integerprimary key autoincrement,username text,password text,email    text);"#,).execute(&pool).await;sqlx::query(r#"create table posts(id         integerprimary key autoincrement,created_at datetime,updated_at datetime,deleted_at datetime,title      text,body       text);create index idx_posts_deleted_aton posts (deleted_at);"#,).execute(&pool).await; }

密码加密

找不到合适的, 用明文

创建JWT令牌

use chrono::{DateTime, Duration, Utc};
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode, errors::Error};
use serde::{Deserialize, Serialize};
use tracing::{debug, error};#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {pub sub: String,pub exp: i64,pub iat: i64,
}impl Claims {pub fn new(sub: String, exp: DateTime<Utc>) -> Self {Self {sub,exp: exp.timestamp(),iat: Utc::now().timestamp(),}}
}// 32字节安全密钥
pub const SECRET_KEY: &[u8] = b"030c8d02eea6e5e5219096bd076c41e58e955632d59beb7d44fa18e3fbccb0bd12345678901234";// 生成JWT
pub fn generate_token(user_id: &str) -> Result<String, Error> {let claims = Claims::new(user_id.to_string(), Utc::now() + Duration::hours(1));let token = encode(&Header::default(),&claims,&EncodingKey::from_secret(SECRET_KEY),)?;debug!("Generated token: {}", token);Ok(token)
}// 验证JWT
pub fn validate_token(token: &str) -> Result<Claims, Error> {debug!("Received token: {}", token);let mut validation = Validation::new(jsonwebtoken::Algorithm::HS256);validation.validate_exp = true;match decode::<Claims>(token, &DecodingKey::from_secret(SECRET_KEY), &validation) {Ok(data) => {debug!("Decoded claims: {:?}", data.claims);Ok(data.claims)}Err(e) => {error!("Token error: {:?}", e);Err(e)}}
}

进行测试

/// jwt方法代码#[cfg(test)]
mod tests {use super::*;#[test]fn test_generate_and_validate_token() {// 生成 tokenlet user_id = "test_user";let token_result = generate_token(user_id);assert!(token_result.is_ok(), "Failed to generate token");let token = token_result.unwrap();// 验证 tokenlet claims_result = validate_token(&token);assert!(claims_result.is_ok(), "Failed to validate token");let claims = claims_result.unwrap();// 检查 claims 中的信息是否正确assert_eq!(claims.sub, user_id.to_string());}#[test]fn test_invalid_token() {// 提供一个无效的 tokenlet invalid_token = "invalid.token.here";let claims_result = validate_token(invalid_token);assert!(claims_result.is_err(), "Expected error for invalid token");}
}

帖子信息表

增删改查

posts.rs
提供获取、发布、更新和删除帖子信息的 API 接口。

use axum::{extract::{Path, State},http::StatusCode,Json,
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use sqlx_core::sqlite::SqlitePool;#[derive(Serialize, Deserialize)]
pub struct NewPost {title: String,body: String,
}#[derive(Serialize, Deserialize, sqlx::FromRow)]
pub struct Post {id: i32,title: String,body: String, 
}pub async fn create_post(// 从全局路由状态获取数据库连接池State(pool): State<SqlitePool>,Json(product): Json<NewPost>,
) -> Result<Json<Value>, (StatusCode, String)> {let resp = sqlx::query("INSERT INTO posts (title, body) values ($1, $2)")// 填充占位符.bind(&product.title).bind(&product.body).execute(&pool).await.map_err(|err| {(StatusCode::INTERNAL_SERVER_ERROR,format!("Error is: {}", err),)})?;Ok(Json(json!(product)))
}pub async fn get_posts(State(pool): State<SqlitePool>,
) -> Result<Json<Vec<Post>>, (StatusCode, String)> {let result = sqlx::query_as("SELECT * from posts")// 数据回填到结构体.fetch_all(&pool).await.map_err(|err| {(StatusCode::INTERNAL_SERVER_ERROR,format!("Error is: {}", err),)})?;Ok(Json(result))
}pub async fn get_one_post(State(pool): State<SqlitePool>,Path(id): Path<i32>,
) -> Result<Json<Post>, (StatusCode, String)> {let result = sqlx::query_as("SELECT * FROM posts WHERE id = $1").bind(id).fetch_one(&pool).await.map_err(|err| match err {sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, format!("Error is: {}", err)),_ => (StatusCode::INTERNAL_SERVER_ERROR,format!("Error is: {}", err),),})?;Ok(Json(result))
}pub async fn delete_post(State(pool): State<SqlitePool>,Path(id): Path<i32>,
) -> Result<Json<Value>, (StatusCode, String)> {let result = sqlx::query("DELETE FROM posts WHERE id = $1").bind(id).execute(&pool).await.map_err(|err| match err {sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, format!("Error is: {}", err)),_ => (StatusCode::INTERNAL_SERVER_ERROR,format!("Error is: {}", err),),})?;Ok(Json(json!({"msg": "Product deleted successfully"})))
}pub async fn update_post(State(pool): State<SqlitePool>,Path(id): Path<i32>,Json(product): Json<Post>,
) -> Result<Json<Value>, (StatusCode, String)> {let result = sqlx::query("UPDATE posts SET title=$1, body=$2 WHERE id=$3").bind(&product.title).bind(&product.body).bind(id).execute(&pool).await.map_err(|err| match err {sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, format!("Error is: {}", err)),_ => (StatusCode::INTERNAL_SERVER_ERROR,format!("Error is: {}", err),),})?;Ok(Json(json!({"msg": "Product updated successfully"})))
}

网络接口

定义路由

restful风格API:

HTTP 方法|操作类型|示例
GET|查询资源|获取用户列表 /api/users
POST|创建资源|创建新用户 /api/users
PUT|更新资源|更新指定用户 /api/users/{id}
DELETE|删除资源|删除指定用户 /api/users/{id}


#[tokio::main]
async fn main() {/// 前文中的数据库生成代码// 网络接口代码let postRouter = Router::new().route("/posts", get(handlers::posts::get_posts)).route("/posts/:id", get(handlers::posts::get_one_post)).route("/posts", post(handlers::posts::create_post)).route("/posts/:id", patch(handlers::posts::update_post)).route("/posts/:id", delete(handlers::posts::delete_post)).route_layer(middleware::from_fn(auth));let userRouter = Router::new().route("/users", post(handlers::uesr::register)).route("/auth/login", post(handlers::uesr::login));let userProfileRouter = Router::new().route("/auth/profile", get(handlers::uesr::validateUser)).route_layer(middleware::from_fn(auth));// 跨域中间件let cors = CorsLayer::new().allow_origin(Any);let app = Router::new()// 合并router.merge(userRouter).merge(postRouter).merge(userProfileRouter)// 状态, 全路由可用的数据, 这里是数据连接池.with_state(pool)// 跨域.layer(cors)// 代码压缩层.layer(CompressionLayer::new())// http跟踪器.layer(TraceLayer::new_for_http());let listener = tokio::net::TcpListener::bind("127.0.0.1:4000").await.unwrap();println!("listening on {}", listener.local_addr().unwrap());axum::serve(listener, app).await.unwrap();
}

鉴权中间件

/// main函数#[derive(Clone)]
struct AuthHeader {id: String,
}async fn auth(headers: HeaderMap,mut req: Request,next: Next,
) -> Result<impl IntoResponse, (StatusCode, String)> {// 提取 Authorization headerlet header = headers.get("Authorization").ok_or((StatusCode::UNAUTHORIZED,"missing authorization header".to_string(),))?;let header_str = header.to_str().map_err(|_| (StatusCode::BAD_REQUEST, "invalid authorization header")).unwrap();let token = header_str.replace("Bearer ", "").trim().to_string();// 验证 tokenlet claims = validate_token(token.as_str()).map_err(|e| {tracing::warn!("token validation failed: {:?}", e);(StatusCode::UNAUTHORIZED,"invalid or expired token".to_string(),)})?;// 将用户信息注入请求上下文req.extensions_mut().insert(AuthHeader { id: claims.sub });Ok(next.run(req).await)
}

使用APIfox进行测试

APIfox是一个接口测试工具
apifox
本案例的接口我已经共享了:
接口文档

目前较成熟的二开框架

未知

社群

你可以在这些平台联系我:

  • bili: 刚子哥forever
  • 企鹅群: 940263820
  • gitee: gitee
  • 博客: malcode-site
  • 邮箱: malguy2022@163.com
  • 知乎: 乐妙善哉居士
  • csdn: 飞鸟malred
http://www.xdnf.cn/news/1065187.html

相关文章:

  • JAVA集合篇--深入理解ConcurrentHashMap图解版
  • Dalvik和ART的区别
  • 华为云Flexus+DeepSeek征文|开启DeepSeek-V3+R1商用服务之旅
  • 顶顶通AI呼叫软件(大模型电话机器人)介绍
  • Flink源码阅读环境准备全攻略:搭建高效探索的基石
  • [论文阅读] 软件工程 + 教学 | 软件工程项目管理课程改革:从传统教学到以学生为中心的混合式学习实践
  • Spark教程6:Spark 底层执行原理详解
  • C++法则8:对于有引用成员的类,合成拷贝赋值运算符被定义为删除的。
  • Java八股文——系统场景设计
  • 多设备Obsidian笔记同步:WebDAV与内网穿透技术高效实现教程
  • 从【人工智能】到【计算机视觉】,【深度学习】引领的未来科技创新与变革
  • Linux->进程概念(精讲)
  • GPU机器安装docker
  • Python下构建毫秒级低延迟RTSP/RTMP播放器并实现AI视觉处理
  • 数据库(1)-SQL
  • EXPLAIN优化 SQL示例
  • Oracle 数据库查询:单表查询
  • 统计用户本月的连续登录天数
  • 62-Oracle ADR(Automatic Diagnostic Repository)
  • 量化-因子处理
  • 【递归,搜索与回溯算法】记忆化搜索(二)
  • Vue.js数据代理与事件处理全解析:从原理到实践
  • 【DDD】——带你领略领域驱动设计的独特魅力
  • React基础
  • MakeItTalk: Speaker-Aware Talking-Head Animation——说话者感知的说话头动画
  • 【笔记】Windows 系统迁移 Ubuntu(Preview)应用到其他磁盘
  • Element表格表头合并技巧
  • 第八章 目录一致性协议 A Primer on Memory Consistency and Cache Coherence - 2nd Edition
  • Bytemd@Bytemd/react详解(编辑器实现基础AST、插件、跨框架)
  • 分库分表下的 ID 冲突问题与雪花算法讲解