深入探索 Rust Salvo:从简单博客系统到完整 RESTful API 的实战项目
作者:weixin02 · 2024-11-27 · 阅读时间:15分钟
摘要 本文通过实战项目深入探讨 Rust Salvo 框架的应用,详细介绍如何实现一个简单的博客系统,包括用户 […]
摘要
本文通过实战项目深入探讨 Rust Salvo 框架的应用,详细介绍如何实现一个简单的博客系统,包括用户注册、登录和发布文章功能,以及如何构建一个完整的 RESTful API 并生成 API 文档(如 OpenAPI)。通过详细的理论知识和完整的示例代码,帮助您全面理解并应用这些功能,从而提升您的 Rust Web 开发技能。
8. 实战项目
8.1. 博客系统
实现一个简单的博客系统
在这个项目中,我们将实现一个简单的博客系统,包括用户注册、登录和发布文章。
- 在
Cargo.toml文件中添加必要的依赖:
[dependencies]
salvo = "0.68"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sqlx = { version = "0.5", features = ["sqlite", "async-std"] }
bcrypt = "0.10"
jsonwebtoken = "8.0"
- 实现用户注册、登录和发布文章:
use salvo::prelude::*;
use salvo::Server;
use sqlx::{sqlite::SqlitePool, Pool, Sqlite};
use bcrypt::{hash, verify};
use serde::{Serialize, Deserialize};
use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Serialize, Deserialize)]
struct User {
id: i32,
username: String,
password: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
exp: usize,
}
#[derive(Debug, Serialize, Deserialize)]
struct BlogPost {
id: i32,
title: String,
content: String,
author: String,
}
#[handler]
async fn register(req: &mut Request, res: &mut Response, db: &SqlitePool) {
let user: User = req.parse_json().await.unwrap();
let hashed_password = hash(&user.password, 4).unwrap();
sqlx::query("INSERT INTO users (username, password) VALUES (?, ?)")
.bind(&user.username)
.bind(&hashed_password)
.execute(db)
.await
.unwrap();
res.render_plain_text("User registered");
}
#[handler]
async fn login(req: &mut Request, res: &mut Response, db: &SqlitePool) {
let user: User = req.parse_json().await.unwrap();
let row = sqlx::query_as!(User, "SELECT * FROM users WHERE username = ?", user.username)
.fetch_one(db)
.await
.unwrap();
if verify(&user.password, &row.password).unwrap() {
let expiration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as usize + 3600;
let claims = Claims {
sub: row.username,
exp: expiration,
};
let token = encode(&Header::default(), &claims, &EncodingKey::from_secret("secret".as_ref())).unwrap();
res.render_plain_text(&token);
} else {
res.set_status_code(StatusCode::UNAUTHORIZED);
res.render_plain_text("Invalid credentials");
}
}
#[handler]
async fn create_post(req: &mut Request, res: &mut Response, db: &SqlitePool) {
let post: BlogPost = req.parse_json().await.unwrap();
sqlx::query("INSERT INTO posts (title, content, author) VALUES (?, ?, ?)")
.bind(&post.title)
.bind(&post.content)
.bind(&post.author)
.execute(db)
.await
.unwrap();
res.render_plain_text("Post created");
}
#[tokio::main]
async fn main() {
let db = SqlitePool::connect("sqlite::memory:").await.unwrap();
sqlx::query("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, password TEXT)")
.execute(&db)
.await
.unwrap();
sqlx::query("CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT, content TEXT, author TEXT)")
.execute(&db)
.await
.unwrap();
let router = Router::new()
.post("/register", register)
.post("/login", login)
.post("/posts", create_post);
Server::new(router.with_shared_data(db))
.bind(([0, 0, 0, 0], 3030))
.await
.unwrap();
}
这个示例展示了如何实现用户注册、登录和创建博客文章的基本功能。
8.2. RESTful API
构建一个完整的 RESTful API
在这个项目中,我们将构建一个完整的 RESTful API,包括 CRUD 操作和 API 文档生成。
- 在
Cargo.toml文件中添加必要的依赖:
[dependencies]
salvo = "0.68"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sqlx = { version = "0.5", features = ["sqlite", "async-std"] }
utoipa = "0.3"
utoipa-swagger-ui = "0.3"
- 实现 RESTful API:
use salvo::prelude::*;
use salvo::Server;
use serde::{Serialize, Deserialize};
use sqlx::{sqlite::SqlitePool, Pool, Sqlite};
use utoipa::openapi::{OpenApi, Info};
use utoipa::OpenApi;
#[derive(Debug, Serialize, Deserialize)]
struct Item {
id: i32,
name: String,
description: String,
}
#[handler]
async fn create_item(req: &mut Request, res: &mut Response, db: &SqlitePool) {
let item: Item = req.parse_json().await.unwrap();
sqlx::query("INSERT INTO items (name, description) VALUES (?, ?)")
.bind(&item.name)
.bind(&item.description)
.execute(db)
.await
.unwrap();
res.render_plain_text("Item created");
}
#[handler]
async fn get_items(req: &mut Request, res: &mut Response, db: &SqlitePool) {
let items: Vec<Item> = sqlx::query_as!(Item, "SELECT id, name, description FROM items")
.fetch_all(db)
.await
.unwrap();
res.render_json(&items);
}
#[handler]
async fn update_item(req: &mut Request, res: &mut Response, db: &SqlitePool) {
let item: Item = req.parse_json().await.unwrap();
sqlx::query("UPDATE items SET name = ?, description = ? WHERE id = ?")
.bind(&item.name)
.bind(&item.description)
.bind(item.id)
.execute(db)
.await
.unwrap();
res.render_plain_text("Item updated");
}
#[handler]
async fn delete_item(req: &mut Request, res: &mut Response, db: &SqlitePool) {
let id: i32 = req.parse_json().await.unwrap();
sqlx::query("DELETE FROM items WHERE id = ?")
.bind(id)
.execute(db)
.await
.unwrap();
res.render_plain_text("Item deleted");
}
#[tokio::main]
async fn main() {
let db = SqlitePool::connect("sqlite::memory:").await.unwrap();
sqlx::query("CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT, description TEXT)")
.execute(&db)
.await
.unwrap();
let router = Router::new()
.post("/items", create_item)
.get("/items", get_items)
.put("/items", update_item)
.delete("/items", delete_item);
Server::new(router.with_shared_data(db))
.bind(([0, 0, 0, 0], 3030))
.await
.unwrap();
}
API 文档生成(如 OpenAPI)
- 使用
utoipa生成 OpenAPI 文档:
#[utoipa::path(
post,
path = "/items",
request_body = Item,
responses(
(status = 201, description = "Item created"),
(status = 400, description = "Bad request")
)
)]
#[handler]
async fn create_item(req: &mut Request, res: &mut Response, db: &SqlitePool) {
let item: Item = req.parse_json().await.unwrap();
sqlx::query("INSERT INTO items (name, description) VALUES (?, ?)")
.bind(&item.name)
.bind(&item.description)
.execute(db)
.await
.unwrap();
res.set_status_code(StatusCode::CREATED);
res.render_plain_text("Item created");
}
#[tokio::main]
async fn main() {
let db = SqlitePool::connect("sqlite::memory:").await.unwrap();
sqlx::query("CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT, description TEXT)")
.execute(&db)
.await
.unwrap();
let api = utoipa::openapi::OpenApi::new(
Info {
title: "My API",
description: Some("This is a sample API"),
version: "1.0.0",
},
vec![utoipa::openapi::paths::PathItem::new("create_item", create_item)]
);
let router = Router::new()
.post("/items", create_item)
.get("/items", get_items)
.put("/items", update_item)
.delete("/items", delete_item)
.get("/openapi.json", |_, res| {
res.render_json(&api);
})
.get("/swagger-ui", |_, res| {
res.render_html(utoipa_swagger_ui::SwaggerUi::new("/openapi.json"));
});
Server::new(router.with_shared_data(db))
.bind(([0, 0, 0, 0], 3030))
.await
.unwrap();
}
通过这些示例,我们可以构建一个完整的 RESTful API,并生成 OpenAPI 文档。
通过这个详细的指南,您将能够全面掌握 Rust Salvo 框架的实战应用,并能够构建功能丰富的 Web 应用和 RESTful API。
文章转自微信公众号@育儿之家 YEZJ
热门推荐
一个账号试用1000+ API
助力AI无缝链接物理世界 · 无需多次注册
3000+提示词助力AI大模型
和专业工程师共享工作效率翻倍的秘密
热门API
- 1. AI文本生成
- 2. AI图片生成_文生图
- 3. AI图片生成_图生图
- 4. AI图像编辑
- 5. AI视频生成_文生视频
- 6. AI视频生成_图生视频
- 7. AI语音合成_文生语音
- 8. AI文本生成(中国)
最新文章
- Duolingo API 使用指南:语言学习与智能应用的融合实践
- 超级英雄尽在掌握:超级英雄数据API的超能力
- 了解API端点:初学者指南
- API版本控制:URL、标头、媒体类型版本控制
- Python 查询专利信息:轻松获取最新技术专利数据
- IOT语义互操作性之API接口
- 地图API服务商百度的竞争对手和替代品
- 强化 API 访问控制:基于属性的授权(ABAC)安全实践指南
- SIGN×Bithumb 永续行情 API:边缘缓存 3 天优化策略
- 百度地图批量算路api服务介绍及应用场景
- Express + TypeScript + OpenFGA 权限控制实践指南
- 细粒度授权修复关键API安全风险 – Auth0
热门推荐
一个账号试用1000+ API
助力AI无缝链接物理世界 · 无需多次注册