2024年在线市场平台的11大最佳支付解决方案
深入探索 Rust Salvo:从简单博客系统到完整 RESTful API 的实战项目
2024-11-27
摘要
本文通过实战项目深入探讨 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